sync: allow `go` routine join with return value (#8125)

pull/8132/head
Uwe Krüger 2021-01-15 13:45:26 +01:00 committed by GitHub
parent 995f27a7c0
commit 8d014d4646
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 323 additions and 65 deletions

View File

@ -11,10 +11,10 @@ pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl
pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | BoolLiteral |
CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector |
ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr |
IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr |
PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral |
StringLiteral | StructInit | Type | TypeOf | UnsafeExpr
ConcatExpr | EnumVal | FloatLiteral | GoExpr | Ident | IfExpr | IfGuardExpr | IndexExpr |
InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr |
ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf |
SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf | UnsafeExpr
pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt |
EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt |
@ -875,8 +875,18 @@ pub:
pub struct GoStmt {
pub:
call_expr Expr
pos token.Position
pos token.Position
pub mut:
call_expr CallExpr
}
pub struct GoExpr {
pub:
pos token.Position
pub mut:
go_stmt GoStmt
mut:
return_type table.Type
}
pub struct GotoLabel {
@ -1172,7 +1182,7 @@ pub fn (expr Expr) position() token.Position {
AnonFn {
return expr.decl.pos
}
ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, EnumVal, FloatLiteral, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr {
ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, EnumVal, FloatLiteral, GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral, Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr {
return expr.pos
}
ArrayDecompose {
@ -1261,7 +1271,7 @@ pub fn (stmt Stmt) position() token.Position {
return stmt.pos
}
GoStmt {
return stmt.call_expr.position()
return stmt.call_expr.pos
}
TypeDecl {
match stmt {

View File

@ -1338,6 +1338,12 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
if !c.check_types(arg_type, info.elem_type) && !c.check_types(left_type, arg_type) {
c.error('cannot $method_name `$arg_sym.name` to `$left_type_sym.name`', arg_expr.position())
}
} else if left_type_sym.kind == .gohandle && method_name == 'wait' {
info := left_type_sym.info as table.GoHandle
if call_expr.args.len > 0 {
c.error('wait() does not have any arguments', call_expr.args[0].pos)
}
return info.return_type
}
mut method := table.Fn{}
mut has_method := false
@ -2978,22 +2984,17 @@ fn (mut c Checker) stmt(node ast.Stmt) {
}
}
ast.GoStmt {
if node.call_expr !is ast.CallExpr {
c.error('expression in `go` must be a function call', node.call_expr.position())
c.call_expr(mut node.call_expr)
// 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.position())
}
}
c.expr(node.call_expr)
if mut node.call_expr is ast.CallExpr {
// 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.position())
}
}
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.position())
}
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.position())
}
}
ast.GotoLabel {}
@ -3241,6 +3242,10 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
ast.CallExpr {
return c.call_expr(mut node)
}
ast.GoExpr {
ret_type := c.call_expr(mut node.go_stmt.call_expr)
return c.table.find_or_register_gohandle(ret_type)
}
ast.ChanInit {
return c.chan_init(mut node)
}

View File

@ -892,6 +892,9 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
ast.FloatLiteral {
f.write(node.val)
}
ast.GoExpr {
f.stmt(node.go_stmt)
}
ast.IfExpr {
f.if_expr(node)
}

View File

@ -89,6 +89,7 @@ mut:
defer_profile_code string
str_types []string // types that need automatic str() generation
threaded_fns []string // for generating unique wrapper types and fns for `go xxx()`
waiter_fns []string // functions that wait for `go xxx()` to finish
array_fn_definitions []string // array equality functions that have been defined
map_fn_definitions []string // map equality functions that have been defined
struct_fn_definitions []string // struct equality functions that have been defined
@ -1107,7 +1108,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
g.global_decl(node)
}
ast.GoStmt {
g.go_stmt(node)
g.go_stmt(node, false)
}
ast.GotoLabel {
g.writeln('$node.name: {}')
@ -2577,6 +2578,9 @@ fn (mut g Gen) expr(node ast.Expr) {
ast.FloatLiteral {
g.write(node.val)
}
ast.GoExpr {
g.go_expr(node)
}
ast.Ident {
g.ident(node)
}
@ -4883,6 +4887,22 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) {
table.Alias {
// table.Alias { TODO
}
table.GoHandle {
if g.pref.os == .windows {
if name == 'gohandle_void' {
g.type_definitions.writeln('typedef HANDLE $name;')
} else {
// Windows can only return `u32` (no void*) from a thread, so the
// V gohandle must maintain a pointer to the return value
g.type_definitions.writeln('typedef struct {')
g.type_definitions.writeln('\tvoid* ret_ptr;')
g.type_definitions.writeln('\tHANDLE handle;')
g.type_definitions.writeln('} $name;')
}
} else {
g.type_definitions.writeln('typedef pthread_t $name;')
}
}
table.SumType {
g.typedefs.writeln('typedef struct $name $name;')
g.type_definitions.writeln('')
@ -5414,9 +5434,18 @@ fn (g &Gen) is_importing_os() bool {
return 'os' in g.table.imports
}
fn (mut g Gen) go_stmt(node ast.GoStmt) {
fn (mut g Gen) go_expr(node ast.GoExpr) {
line := g.go_before_stmt(0)
handle := g.go_stmt(node.go_stmt, true)
g.empty_line = false
g.write(line)
g.write(handle)
}
fn (mut g Gen) go_stmt(node ast.GoStmt, joinable bool) string {
mut handle := ''
tmp := g.new_tmp_var()
expr := node.call_expr as ast.CallExpr
expr := node.call_expr
mut name := expr.name // util.no_dots(expr.name)
// TODO: fn call is duplicated. merge with fn_call().
if expr.generic_type != table.void_type && expr.generic_type != 0 {
@ -5454,23 +5483,87 @@ fn (mut g Gen) go_stmt(node ast.GoStmt) {
g.expr(arg.expr)
g.writeln(';')
}
s_ret_typ := g.typ(node.call_expr.return_type)
if g.pref.os == .windows && node.call_expr.return_type != table.void_type {
g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));')
}
gohandle_name := 'gohandle_' +
g.table.get_type_symbol(g.unwrap_generic(node.call_expr.return_type)).name
if g.pref.os == .windows {
g.writeln('CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);')
simple_handle := if joinable && node.call_expr.return_type != table.void_type {
'thread_handle_$tmp'
} else {
'thread_$tmp'
}
g.writeln('HANDLE $simple_handle = CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);')
if joinable && node.call_expr.return_type != table.void_type {
g.writeln('$gohandle_name thread_$tmp = {')
g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,')
g.writeln('\t.handle = thread_handle_$tmp')
g.writeln('};')
}
if !joinable {
g.writeln('CloseHandle(thread_$tmp);')
}
} else {
g.writeln('pthread_t thread_$tmp;')
g.writeln('pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);')
if !joinable {
g.writeln('pthread_detach(thread_$tmp);')
}
}
g.writeln('// endgo\n')
if joinable {
handle = 'thread_$tmp'
// create wait handler for this return type if none exists
waiter_fn_name := gohandle_name + '_wait'
if waiter_fn_name !in g.waiter_fns {
g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {')
mut c_ret_ptr_ptr := 'NULL'
if node.call_expr.return_type != table.void_type {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;')
c_ret_ptr_ptr = '&ret_ptr'
}
if g.pref.os == .windows {
if node.call_expr.return_type == table.void_type {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);')
} else {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);')
g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;')
}
} else {
g.gowrappers.writeln('\tint stat = pthread_join(thread, $c_ret_ptr_ptr);')
}
g.gowrappers.writeln('\tif (stat != 0) { v_panic(_SLIT("unable to join thread")); }')
if g.pref.os == .windows {
if node.call_expr.return_type == table.void_type {
g.gowrappers.writeln('\tCloseHandle(thread);')
} else {
g.gowrappers.writeln('\tCloseHandle(thread.handle);')
}
}
if node.call_expr.return_type != table.void_type {
g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;')
g.gowrappers.writeln('\tfree(ret_ptr);')
g.gowrappers.writeln('\treturn ret;')
} else {
g.gowrappers.writeln('\treturn;')
}
g.gowrappers.writeln('}')
g.waiter_fns << waiter_fn_name
}
}
// Register the wrapper type and function
if name in g.threaded_fns {
return
return handle
}
g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {')
if expr.is_method {
styp := g.typ(expr.receiver_type)
g.type_definitions.writeln('\t$styp arg0;')
}
if expr.args.len == 0 {
need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != table.void_type
if expr.args.len == 0 && !need_return_ptr {
g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;')
} else {
for i, arg in expr.args {
@ -5478,10 +5571,24 @@ fn (mut g Gen) go_stmt(node ast.GoStmt) {
g.type_definitions.writeln('\t$styp arg${i + 1};')
}
}
if need_return_ptr {
g.type_definitions.writeln('\tvoid* ret_ptr;')
}
g.type_definitions.writeln('} $wrapper_struct_name;')
g.type_definitions.writeln('void* ${wrapper_fn_name}($wrapper_struct_name *arg);')
g.gowrappers.writeln('void* ${wrapper_fn_name}($wrapper_struct_name *arg) {')
g.gowrappers.write('\t${name}(')
thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' }
g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);')
g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {')
if node.call_expr.return_type != table.void_type {
if g.pref.os == .windows {
g.gowrappers.write('\t*(($s_ret_typ*)(arg->ret_ptr)) = ')
} else {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));')
g.gowrappers.write('\t*ret_ptr = ')
}
} else {
g.gowrappers.write('\t')
}
g.gowrappers.write('${name}(')
if expr.is_method {
g.gowrappers.write('arg->arg0')
if expr.args.len > 0 {
@ -5495,9 +5602,15 @@ fn (mut g Gen) go_stmt(node ast.GoStmt) {
}
}
g.gowrappers.writeln(');')
g.gowrappers.writeln('\treturn 0;')
g.gowrappers.writeln('\tfree(arg);')
if g.pref.os != .windows && node.call_expr.return_type != table.void_type {
g.gowrappers.writeln('\treturn ret_ptr;')
} else {
g.gowrappers.writeln('\treturn 0;')
}
g.gowrappers.writeln('}')
g.threaded_fns << name
return handle
}
fn (mut g Gen) as_cast(node ast.AsCast) {

View File

@ -165,6 +165,9 @@ pub fn (mut g JsGen) typ(t table.Type) string {
.aggregate {
panic('TODO: unhandled aggregate in JS')
}
.gohandle {
panic('TODO: unhandled gohandle in JS')
}
}
/*
else {

View File

@ -475,6 +475,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
ast.FloatLiteral {
g.write('${g.typ(table.Type(table.f32_type))}($node.val)')
}
ast.GoExpr {
// TODO
}
ast.Ident {
g.gen_ident(node)
}
@ -923,33 +926,28 @@ fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) {
fn (mut g JsGen) gen_go_stmt(node ast.GoStmt) {
// x := node.call_expr as ast.CallEpxr // TODO
match node.call_expr {
ast.CallExpr {
mut name := node.call_expr.name
if node.call_expr.is_method {
receiver_sym := g.table.get_type_symbol(node.call_expr.receiver_type)
name = receiver_sym.name + '.' + name
}
// todo: please add a name feild without the mod name for ast.CallExpr
if name.starts_with('${node.call_expr.mod}.') {
name = name[node.call_expr.mod.len + 1..]
}
g.writeln('await new Promise(function(resolve){')
g.inc_indent()
g.write('${name}(')
for i, arg in node.call_expr.args {
g.expr(arg.expr)
if i < node.call_expr.args.len - 1 {
g.write(', ')
}
}
g.writeln(');')
g.writeln('resolve();')
g.dec_indent()
g.writeln('});')
}
else {}
mut name := node.call_expr.name
if node.call_expr.is_method {
receiver_sym := g.table.get_type_symbol(node.call_expr.receiver_type)
name = receiver_sym.name + '.' + name
}
// todo: please add a name feild without the mod name for ast.CallExpr
if name.starts_with('${node.call_expr.mod}.') {
name = name[node.call_expr.mod.len + 1..]
}
g.writeln('await new Promise(function(resolve){')
g.inc_indent()
g.write('${name}(')
for i, arg in node.call_expr.args {
g.expr(arg.expr)
if i < node.call_expr.args.len - 1 {
g.write(', ')
}
}
g.writeln(');')
g.writeln('resolve();')
g.dec_indent()
g.writeln('});')
}
fn (mut g JsGen) gen_import_stmt(it ast.Import) {

View File

@ -90,7 +90,39 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr, left_comments []ast.Comme
op := p.tok.kind
pos := p.tok.position()
p.next()
right, right_comments := p.expr_list()
mut right_comments := []ast.Comment{}
mut right := []ast.Expr{cap: left.len}
if p.tok.kind == .key_go {
spos := p.tok.position()
p.next()
right_comments = p.eat_comments()
mut mod := ''
mut language := table.Language.v
if p.peek_tok.kind == .dot {
if p.tok.lit == 'C' {
language = table.Language.c
p.check_for_impure_v(language, p.tok.position())
} else if p.tok.lit == 'JS' {
language = table.Language.js
p.check_for_impure_v(language, p.tok.position())
} else {
mod = p.tok.lit
}
p.next()
p.next()
}
call_expr := p.call_expr(language, mod)
allpos := spos.extend(p.tok.position())
right << ast.GoExpr{
go_stmt: ast.GoStmt{
call_expr: call_expr
pos: allpos
}
pos: allpos
}
} else {
right, right_comments = p.expr_list()
}
mut comments := []ast.Comment{cap: left_comments.len + right_comments.len}
comments << left_comments
comments << right_comments

View File

@ -738,10 +738,16 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
p.next()
spos := p.tok.position()
expr := p.expr(0)
// mut call_expr := &ast.CallExpr(0) // TODO
// { call_expr = it }
call_expr := if expr is ast.CallExpr {
expr
} else {
p.error_with_pos('expression in `go` must be a function call', expr.position())
ast.CallExpr{
scope: p.scope
}
}
return ast.GoStmt{
call_expr: expr
call_expr: call_expr
pos: spos.extend(p.tok.position())
}
}

View File

@ -454,6 +454,20 @@ pub fn (t &Table) chan_cname(elem_type Type, is_mut bool) string {
return 'chan_$elem_type_sym.cname' + suffix
}
[inline]
pub fn (t &Table) gohandle_name(return_type Type) string {
return_type_sym := t.get_type_symbol(return_type)
ptr := if return_type.is_ptr() { '&' } else { '' }
return 'gohandle[$ptr$return_type_sym.name]'
}
[inline]
pub fn (t &Table) gohandle_cname(return_type Type) string {
return_type_sym := t.get_type_symbol(return_type)
suffix := if return_type.is_ptr() { '_ptr' } else { '' }
return 'gohandle_$return_type_sym.cname$suffix'
}
// map_source_name generates the original name for the v source.
// e. g. map[string]int
[inline]
@ -517,6 +531,27 @@ pub fn (mut t Table) find_or_register_map(key_type Type, value_type Type) int {
return t.register_type_symbol(map_typ)
}
pub fn (mut t Table) find_or_register_gohandle(return_type Type) int {
name := t.gohandle_name(return_type)
cname := t.gohandle_cname(return_type)
// existing
existing_idx := t.type_idxs[name]
if existing_idx > 0 {
return existing_idx
}
// register
gohandle_typ := TypeSymbol{
parent_idx: gohandle_type_idx
kind: .gohandle
name: name
cname: cname
info: GoHandle{
return_type: return_type
}
}
return t.register_type_symbol(gohandle_typ)
}
pub fn (mut t Table) find_or_register_array(elem_type Type) int {
name := t.array_name(elem_type)
cname := t.array_cname(elem_type)

View File

@ -16,7 +16,7 @@ import strings
pub type Type = int
pub type TypeInfo = Aggregate | Alias | Array | ArrayFixed | Chan | Enum | FnType | GenericStructInst |
Interface | Map | MultiReturn | Struct | SumType
GoHandle | Interface | Map | MultiReturn | Struct | SumType
pub enum Language {
v
@ -306,6 +306,7 @@ pub const (
any_type_idx = 25
float_literal_type_idx = 26
int_literal_type_idx = 27
gohandle_type_idx = 28
)
pub const (
@ -350,12 +351,15 @@ pub const (
any_type = new_type(any_type_idx)
float_literal_type = new_type(float_literal_type_idx)
int_literal_type = new_type(int_literal_type_idx)
gohandle_type = new_type(gohandle_type_idx)
)
pub const (
builtin_type_names = ['void', 'voidptr', 'charptr', 'byteptr', 'i8', 'i16', 'int', 'i64', 'u16',
'u32', 'u64', 'int_literal', 'f32', 'f64', 'float_literal', 'string', 'ustring', 'char', 'byte',
'bool', 'none', 'array', 'array_fixed', 'map', 'chan', 'any', 'struct', 'mapnode', 'size_t', 'rune']
'bool', 'none', 'array', 'array_fixed', 'map', 'chan', 'any', 'struct', 'mapnode', 'size_t', 'rune',
'gohandle',
]
)
pub struct MultiReturn {
@ -417,6 +421,7 @@ pub enum Kind {
float_literal
int_literal
aggregate
gohandle
}
pub fn (t &TypeSymbol) str() string {
@ -467,6 +472,14 @@ pub fn (t &TypeSymbol) chan_info() Chan {
}
}
[inline]
pub fn (t &TypeSymbol) gohandle_info() GoHandle {
match mut t.info {
GoHandle { return t.info }
else { panic('TypeSymbol.gohandle_info(): no gohandle info for type: $t.name') }
}
}
[inline]
pub fn (t &TypeSymbol) map_info() Map {
match mut t.info {
@ -529,6 +542,7 @@ pub fn (mut t Table) register_builtin_type_symbols() {
cname: 'int_literal'
mod: 'builtin'
)
t.register_type_symbol(kind: .gohandle, name: 'gohandle', cname: 'gohandle', mod: 'builtin')
}
[inline]
@ -602,6 +616,7 @@ pub fn (k Kind) str() string {
.generic_struct_inst { 'generic_struct_inst' }
.rune { 'rune' }
.aggregate { 'aggregate' }
.gohandle { 'gohandle' }
}
return k_str
}
@ -709,6 +724,11 @@ pub mut:
is_mut bool
}
pub struct GoHandle {
pub mut:
return_type Type
}
pub struct Map {
pub mut:
key_type Type

View File

@ -0,0 +1,10 @@
fn f(x int, y f64) f64 {
return x * y
}
fn test_go_return() {
r := go f(3, 4.0)
z := r.wait()
assert typeof(z).name == 'f64'
assert z == 12.0
}

View File

@ -0,0 +1,23 @@
import time
struct St {
mut:
x f64
}
fn f(x int, y f64, shared s St) {
time.usleep(50000)
lock s {
s.x = x * y
}
return
}
fn test_go_return() {
shared t := &St{}
r := go f(3, 4.0, shared t)
r.wait()
rlock t {
assert t.x == 12.0
}
}