diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index faa7e138d8..0917cc5a0d 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1166,6 +1166,9 @@ fn (t Tree) expr(expr ast.Expr) &Node { ast.GoExpr { return t.go_expr(expr) } + ast.SpawnExpr { + return t.spawn_expr(expr) + } ast.OffsetOf { return t.offset_of(expr) } @@ -1862,6 +1865,15 @@ fn (t Tree) go_expr(expr ast.GoExpr) &Node { return obj } +fn (t Tree) spawn_expr(expr ast.SpawnExpr) &Node { + mut obj := new_object() + obj.add_terse('ast_type', t.string_node('SpawnExpr')) + obj.add_terse('call_expr', t.call_expr(expr.call_expr)) + obj.add_terse('is_expr', t.bool_node(expr.is_expr)) + obj.add('pos', t.pos(expr.pos)) + return obj +} + fn (t Tree) offset_of(expr ast.OffsetOf) &Node { mut obj := new_object() obj.add_terse('ast_type', t.string_node('OffsetOf')) diff --git a/thirdparty/photon/photonwrapper.h b/thirdparty/photon/photonwrapper.h new file mode 100644 index 0000000000..f3241a0073 --- /dev/null +++ b/thirdparty/photon/photonwrapper.h @@ -0,0 +1,44 @@ + + + +#ifndef C_PHOTONWRAPPER_H_ +#define C_PHOTONWRAPPER_H_ + + +#ifdef __cplusplus + +#include +//#include + +#include +#include +#include +#include +#include + +#include + +extern "C" { +#else + typedef struct CppClass CppClass; +#endif + +//CppClass *cpp_class_create(); +//void cpp_class_destroy(CppClass *c); +//void cpp_class_do_work(CppClass *c); + +int photon_init_default(); +void photon_thread_create11(void* (* f)(void*)); + +void photon_sleep_s(int n); + +void photon_sleep_ms(int n); + + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/vlib/coroutines/coroutines.v b/vlib/coroutines/coroutines.v new file mode 100644 index 0000000000..a217b2b29c --- /dev/null +++ b/vlib/coroutines/coroutines.v @@ -0,0 +1,21 @@ +// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module coroutines + +import time + +#flag -I @VEXEROOT/thirdparty/photon +#flag @VEXEROOT/thirdparty/photon/photonwrapper.so + +#include "photonwrapper.h" + +fn C.photon_init_default() int +fn C.photon_thread_create11(f voidptr) +fn C.photon_sleep_s(n int) +fn C.photon_sleep_ms(n int) + +// sleep is coroutine-safe version of time.sleep() +pub fn sleep(duration time.Duration) { + C.photon_sleep_ms(duration.milliseconds()) +} diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index f4491cdd4a..bd4c387b5b 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -54,6 +54,7 @@ pub type Expr = AnonFn | SelectExpr | SelectorExpr | SizeOf + | SpawnExpr | SqlExpr | StringInterLiteral | StringLiteral @@ -1386,6 +1387,15 @@ pub mut: is_expr bool } +[minify] +pub struct SpawnExpr { +pub: + pos token.Pos +pub mut: + call_expr CallExpr + is_expr bool +} + pub struct GotoLabel { pub: name string @@ -1948,10 +1958,11 @@ pub fn (expr Expr) pos() token.Pos { } NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, - EnumVal, DumpExpr, FloatLiteral, GoExpr, Ident, IfExpr, IntegerLiteral, IsRefType, Likely, - LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, - RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, - StructInit, TypeNode, TypeOf, UnsafeExpr, ComptimeType, Nil { + EnumVal, DumpExpr, FloatLiteral, GoExpr, SpawnExpr, Ident, IfExpr, IntegerLiteral, + IsRefType, Likely, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, + PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, + StringInterLiteral, StringLiteral, StructInit, TypeNode, TypeOf, UnsafeExpr, ComptimeType, + Nil { return expr.pos } IndexExpr { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 32d9621dca..39f48da004 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -403,6 +403,9 @@ pub fn (x Expr) str() string { GoExpr { return 'go ${x.call_expr}' } + SpawnExpr { + return 'spawn ${x.call_expr}' + } Ident { return x.name.clone() } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index fbf1aa9347..41261e5156 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2548,6 +2548,9 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { ast.GoExpr { return c.go_expr(mut node) } + ast.SpawnExpr { + return c.spawn_expr(mut node) + } ast.Ident { return c.ident(mut node) } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 99a6ed5eca..c479bf0362 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2100,7 +2100,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { return ast.void_type } -fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { +fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { ret_type := c.call_expr(mut node.call_expr) if node.call_expr.or_block.kind != .absent { c.error('option handling cannot be done in `spawn` call. Do it when calling `.wait()`', @@ -2126,6 +2126,33 @@ fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { } } +fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { + // TODO copypasta from spawn_expr + ret_type := c.call_expr(mut node.call_expr) + if node.call_expr.or_block.kind != .absent { + c.error('option handling cannot be done in `go` call. Do it when calling `.wait()`', + node.call_expr.or_block.pos) + } + // Make sure there are no mutable arguments + for arg in node.call_expr.args { + if arg.is_mut && !arg.typ.is_ptr() { + c.error('function in `go` statement cannot contain mutable non-reference arguments', + arg.expr.pos()) + } + } + if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr() + && !node.call_expr.left_type.is_ptr() { + c.error('method in `go` statement cannot have non-reference mutable receiver', + node.call_expr.left.pos()) + } + + if c.pref.backend.is_js() { + return c.table.find_or_register_promise(c.unwrap_generic(ret_type)) + } else { + return c.table.find_or_register_thread(c.unwrap_generic(ret_type)) + } +} + fn (mut c Checker) set_node_expected_arg_types(mut node ast.CallExpr, func &ast.Fn) { if node.expected_arg_types.len == 0 { start_idx := if func.is_method { 1 } else { 0 } diff --git a/vlib/v/eval/expr.v b/vlib/v/eval/expr.v index fde1fe2604..76796e46c7 100644 --- a/vlib/v/eval/expr.v +++ b/vlib/v/eval/expr.v @@ -534,10 +534,10 @@ pub fn (mut e Eval) expr(expr ast.Expr, expecting ast.Type) Object { } ast.AnonFn, ast.ArrayDecompose, ast.AsCast, ast.Assoc, ast.AtExpr, ast.CTempVar, ast.ChanInit, ast.Comment, ast.ComptimeCall, ast.ComptimeSelector, ast.ComptimeType, - ast.ConcatExpr, ast.DumpExpr, ast.EmptyExpr, ast.EnumVal, ast.GoExpr, ast.IfGuardExpr, - ast.IndexExpr, ast.IsRefType, ast.Likely, ast.LockExpr, ast.MapInit, ast.MatchExpr, - ast.Nil, ast.NodeError, ast.None, ast.OffsetOf, ast.OrExpr, ast.RangeExpr, ast.SelectExpr, - ast.SqlExpr, ast.TypeNode, ast.TypeOf { + ast.ConcatExpr, ast.DumpExpr, ast.EmptyExpr, ast.EnumVal, ast.GoExpr, ast.SpawnExpr, + ast.IfGuardExpr, ast.IndexExpr, ast.IsRefType, ast.Likely, ast.LockExpr, ast.MapInit, + ast.MatchExpr, ast.Nil, ast.NodeError, ast.None, ast.OffsetOf, ast.OrExpr, ast.RangeExpr, + ast.SelectExpr, ast.SqlExpr, ast.TypeNode, ast.TypeOf { e.error('unhandled expression ${typeof(expr).name}') } } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 78a412cff7..9f89efe464 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -640,6 +640,9 @@ pub fn (mut f Fmt) expr(node_ ast.Expr) { ast.GoExpr { f.go_expr(node) } + ast.SpawnExpr { + f.spawn_expr(node) + } ast.Ident { f.ident(node) } @@ -1206,11 +1209,16 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) { } } -pub fn (mut f Fmt) go_expr(node ast.GoExpr) { +pub fn (mut f Fmt) spawn_expr(node ast.SpawnExpr) { f.write('spawn ') f.call_expr(node.call_expr) } +pub fn (mut f Fmt) go_expr(node ast.GoExpr) { + f.write('go ') + f.call_expr(node.call_expr) +} + pub fn (mut f Fmt) goto_label(node ast.GotoLabel) { f.writeln('${node.name}:') } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ab23af6447..441872904c 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3160,6 +3160,9 @@ fn (mut g Gen) expr(node_ ast.Expr) { g.write(node.val) } } + ast.SpawnExpr { + g.spawn_expr(node) + } ast.GoExpr { g.go_expr(node) } @@ -3260,7 +3263,7 @@ fn (mut g Gen) expr(node_ ast.Expr) { mut expr_str := '' if mut node.expr is ast.ComptimeSelector && (node.expr as ast.ComptimeSelector).left is ast.Ident { - // val.$(field.name)? + // val.$(field.name)? expr_str = '${node.expr.left.str()}.${g.comptime_for_field_value.name}' } else if mut node.expr is ast.Ident && g.is_comptime_var(node.expr) { // val? diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 58a726a5ee..d482ff283d 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2090,6 +2090,11 @@ fn (mut g Gen) call_args(node ast.CallExpr) { } fn (mut g Gen) go_expr(node ast.GoExpr) { + g.writeln('/*go (coroutine) */') +} + +fn (mut g Gen) spawn_expr(node ast.SpawnExpr) { + g.writeln('/*spawn (thread) */') line := g.go_before_stmt(0) mut handle := '' tmp := g.new_tmp_var() diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 491a84bd93..6656dd64f2 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -935,6 +935,9 @@ fn (mut g JsGen) expr(node_ ast.Expr) { ast.GoExpr { g.gen_go_expr(node) } + ast.SpawnExpr { + g.gen_spawn_expr(node) + } ast.Ident { g.gen_ident(node) } @@ -1826,6 +1829,20 @@ fn (mut g JsGen) gen_go_expr(node ast.GoExpr) { g.writeln('})});') } +fn (mut g JsGen) gen_spawn_expr(node ast.SpawnExpr) { + if g.pref.output_es5 { + verror('No support for goroutines on ES5 output') + return + } + g.writeln('new _v_Promise({promise: new Promise(function(resolve){') + g.inc_indent() + g.write('resolve(') + g.expr(node.call_expr) + g.write(');') + g.dec_indent() + g.writeln('})});') +} + fn (mut g JsGen) gen_import_stmt(it ast.Import) { g.ns.imports[it.mod] = it.alias } diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 22363bccb2..672cdf77b2 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -273,7 +273,7 @@ fn (mut w Walker) expr(node_ ast.Expr) { w.fn_by_name('eprint') w.fn_by_name('eprintln') } - ast.GoExpr { + ast.SpawnExpr { w.expr(node.call_expr) if w.pref.os == .windows { w.fn_by_name('panic_lasterr') @@ -283,6 +283,9 @@ fn (mut w Walker) expr(node_ ast.Expr) { w.fn_by_name('panic_error_number') } } + ast.GoExpr { + w.expr(node.call_expr) + } ast.IndexExpr { w.expr(node.left) w.expr(node.index) diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 51f906bc4d..0238d53a53 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -112,9 +112,15 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { } } .key_go, .key_spawn { - mut go_expr := p.go_expr() - go_expr.is_expr = true - node = go_expr + if p.pref.use_coroutines && p.tok.kind == .key_go { + mut go_expr := p.go_expr() + go_expr.is_expr = true + node = go_expr + } else { + mut spawn_expr := p.spawn_expr() + spawn_expr.is_expr = true + node = spawn_expr + } } .key_true, .key_false { node = ast.BoolLiteral{ diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 99397bd0f0..04b61f5d7c 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -1038,7 +1038,7 @@ fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) { return args, types_only, is_variadic } -fn (mut p Parser) go_expr() ast.GoExpr { +fn (mut p Parser) spawn_expr() ast.SpawnExpr { p.next() spos := p.tok.pos() expr := p.expr(0) @@ -1053,6 +1053,27 @@ fn (mut p Parser) go_expr() ast.GoExpr { pos := spos.extend(p.prev_tok.pos()) p.register_auto_import('sync.threads') p.table.gostmts++ + return ast.SpawnExpr{ + call_expr: call_expr + pos: pos + } +} + +fn (mut p Parser) go_expr() ast.GoExpr { + p.next() + spos := p.tok.pos() + expr := p.expr(0) + call_expr := if expr is ast.CallExpr { + expr + } else { + p.error_with_pos('expression in `go` must be a function call', expr.pos()) + ast.CallExpr{ + scope: p.scope + } + } + pos := spos.extend(p.prev_tok.pos()) + // p.register_auto_import('coroutines') + p.table.gostmts++ return ast.GoExpr{ call_expr: call_expr pos: pos diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 29b4d844ec..25520c0c0d 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1078,10 +1078,18 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } } .key_go, .key_spawn { - go_expr := p.go_expr() - return ast.ExprStmt{ - expr: go_expr - pos: go_expr.pos + if p.pref.use_coroutines && p.tok.kind == .key_go { + go_expr := p.go_expr() + return ast.ExprStmt{ + expr: go_expr + pos: go_expr.pos + } + } else { + spawn_expr := p.spawn_expr() + return ast.ExprStmt{ + expr: spawn_expr + pos: spawn_expr.pos + } } } .key_goto { diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index a68561e417..6707d37212 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -218,10 +218,6 @@ pub fn default_tcc_compiler() string { } pub fn (mut p Preferences) default_c_compiler() { - // fast_clang := '/usr/local/Cellar/llvm/8.0.0/bin/clang' - // if os.exists(fast_clang) { - // return fast_clang - // } // TODO fix $if after 'string' $if windows { p.ccompiler = 'gcc' diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 19c1aab834..45de296f33 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -223,6 +223,7 @@ pub mut: assert_failure_mode AssertFailureMode // whether to call abort() or print_backtrace() after an assertion failure message_limit int = 150 // the maximum amount of warnings/errors/notices that will be accumulated nofloat bool // for low level code, like kernels: replaces f32 with u32 and f64 with u64 + use_coroutines bool // experimental coroutines // checker settings: checker_match_exhaustive_cutoff_limit int = 12 thread_stack_size int = 8388608 // Change with `-thread-stack-size 4194304`. Note: on macos it was 524288, which is too small for more complex programs with many nested callexprs. @@ -798,6 +799,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.cmain = cmdline.option(current_args, '-cmain', '') i++ } + '-use-coroutines' { + res.use_coroutines = true + } else { if command == 'build' && is_source_file(arg) { eprintln('Use `v ${arg}` instead.')