From 3b067f5f8540a7b8a3a0407579c2b68909bd1fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Sat, 4 Jul 2020 12:44:25 +0200 Subject: [PATCH] all: experimental locked concurrency support, part 1 (#5637) --- cmd/tools/modules/testing/common.v | 4 +- cmd/tools/preludes/live_shared.v | 4 +- doc/docs.md | 4 +- vlib/clipboard/clipboard_linux.c.v | 10 +- .../{shared => sharedlib}/live_sharedlib.v | 2 +- vlib/net/websocket/io.v | 2 +- vlib/net/websocket/ws.v | 22 ++-- vlib/sync/pool.v | 2 +- vlib/sync/sync_nix.c.v | 3 +- vlib/sync/sync_windows.c.v | 2 +- vlib/sync/waiter_nix.c.v | 2 +- vlib/sync/waitgroup.v | 2 +- vlib/v/ast/ast.v | 18 ++- vlib/v/checker/checker.v | 52 +++++++- vlib/v/fmt/fmt.v | 17 +++ vlib/v/gen/cgen.v | 111 +++++++++++++++++- vlib/v/gen/js/js.v | 7 ++ vlib/v/parser/assign.v | 7 ++ vlib/v/parser/fn.v | 74 ++++++++++-- vlib/v/parser/lock.v | 35 ++++++ vlib/v/parser/parse_type.v | 10 +- vlib/v/parser/parser.v | 9 +- vlib/v/parser/pratt.v | 5 +- vlib/v/table/atypes.v | 43 +++++++ vlib/v/tests/shared_lock_2_test.v | 53 +++++++++ vlib/v/tests/shared_lock_test.v | 53 +++++++++ vlib/v/token/token.v | 8 ++ 27 files changed, 510 insertions(+), 51 deletions(-) rename vlib/live/{shared => sharedlib}/live_sharedlib.v (70%) create mode 100644 vlib/v/parser/lock.v create mode 100644 vlib/v/tests/shared_lock_2_test.v create mode 100644 vlib/v/tests/shared_lock_test.v diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index f350582364..cd20ba80a1 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -28,7 +28,7 @@ pub mut: } pub fn (mut mh TestMessageHandler) append_message(msg string) { - mh.mtx.lock() + mh.mtx.m_lock() mh.messages << msg mh.mtx.unlock() } @@ -102,7 +102,7 @@ pub fn (mut ts TestSession) test() { } pub fn (mut m TestMessageHandler) display_message() { - m.mtx.lock() + m.mtx.m_lock() defer { m.messages.clear() m.mtx.unlock() diff --git a/cmd/tools/preludes/live_shared.v b/cmd/tools/preludes/live_shared.v index 0e23bfad75..bdabb0d492 100644 --- a/cmd/tools/preludes/live_shared.v +++ b/cmd/tools/preludes/live_shared.v @@ -2,8 +2,8 @@ module main // This prelude is loaded in every v program compiled with -live, // but only for the shared library. -import live.shared +import live.sharedlib const ( - no_warning_live_shared_is_used = shared.is_used + no_warning_live_shared_is_used = sharedlib.is_used ) diff --git a/doc/docs.md b/doc/docs.md index 7e10aac02c..ac1915ec64 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1390,7 +1390,7 @@ mut: fn (mut b St) g() { ... - b.mtx.lock() + b.mtx.m_lock() // read/modify/write b.x ... b.mtx.unlock() @@ -1404,7 +1404,7 @@ fn caller() { } go a.g() ... - a.mtx.lock() + a.mtx.m_lock() // read/modify/write a.x ... a.mtx.unlock() diff --git a/vlib/clipboard/clipboard_linux.c.v b/vlib/clipboard/clipboard_linux.c.v index 564e56b754..ea1cdf60d2 100644 --- a/vlib/clipboard/clipboard_linux.c.v +++ b/vlib/clipboard/clipboard_linux.c.v @@ -181,7 +181,7 @@ fn (mut cb Clipboard) free() { } fn (mut cb Clipboard) clear(){ - cb.mutex.lock() + cb.mutex.m_lock() C.XSetSelectionOwner(cb.display, cb.selection, C.Window(C.None), C.CurrentTime) C.XFlush(cb.display) cb.is_owner = false @@ -200,7 +200,7 @@ fn (cb &Clipboard) take_ownership(){ fn (mut cb Clipboard) set_text(text string) bool { if cb.window == C.Window(C.None) {return false} - cb.mutex.lock() + cb.mutex.m_lock() cb.text = text cb.is_owner = true cb.take_ownership() @@ -238,7 +238,7 @@ fn (mut cb Clipboard) transmit_selection(xse &C.XSelectionEvent) bool { targets := cb.get_supported_targets() C.XChangeProperty(xse.display, xse.requestor, xse.property, cb.get_atom(.xa_atom), 32, C.PropModeReplace, targets.data, targets.len) } else if cb.is_supported_target(xse.target) && cb.is_owner && cb.text != "" { - cb.mutex.lock() + cb.mutex.m_lock() C.XChangeProperty(xse.display, xse.requestor, xse.property, xse.target, 8, C.PropModeReplace, cb.text.str, cb.text.len) cb.mutex.unlock() } else { @@ -265,7 +265,7 @@ fn (mut cb Clipboard) start_listener(){ } C.SelectionClear { if event.xselectionclear.window == cb.window && event.xselectionclear.selection == cb.selection { - cb.mutex.lock() + cb.mutex.m_lock() cb.is_owner = false cb.text = "" cb.mutex.unlock() @@ -304,7 +304,7 @@ fn (mut cb Clipboard) start_listener(){ } else if event.xselection.target == to_be_requested { sent_request = false to_be_requested = C.Atom(0) - cb.mutex.lock() + cb.mutex.m_lock() prop := read_property(event.xselection.display, event.xselection.requestor, event.xselection.property) C.XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property) if cb.is_supported_target(prop.actual_type) { diff --git a/vlib/live/shared/live_sharedlib.v b/vlib/live/sharedlib/live_sharedlib.v similarity index 70% rename from vlib/live/shared/live_sharedlib.v rename to vlib/live/sharedlib/live_sharedlib.v index 8ee31de31a..471d28e6b9 100644 --- a/vlib/live/shared/live_sharedlib.v +++ b/vlib/live/sharedlib/live_sharedlib.v @@ -1,4 +1,4 @@ -module shared +module sharedlib import live diff --git a/vlib/net/websocket/io.v b/vlib/net/websocket/io.v index 4aaac8d934..0666fd0b20 100644 --- a/vlib/net/websocket/io.v +++ b/vlib/net/websocket/io.v @@ -2,7 +2,7 @@ module websocket fn (mut ws Client) write_to_server(buf voidptr, len int) int { mut bytes_written := 0 - ws.write_lock.lock() + ws.write_lock.m_lock() bytes_written = if ws.is_ssl { C.SSL_write(ws.ssl, buf, len) } else { diff --git a/vlib/net/websocket/ws.v b/vlib/net/websocket/ws.v index c8fe42e52e..124a893aab 100644 --- a/vlib/net/websocket/ws.v +++ b/vlib/net/websocket/ws.v @@ -19,7 +19,7 @@ pub struct Client { // cwebsocket_subprotocol *subprotocol; // cwebsocket_subprotocol *subprotocols[]; mut: - lock &sync.Mutex = sync.new_mutex() + mtx &sync.Mutex = sync.new_mutex() write_lock &sync.Mutex = sync.new_mutex() state State socket net.Socket @@ -132,9 +132,9 @@ pub fn (mut ws Client) connect() int { // do nothing } } - ws.lock.lock() + ws.mtx.m_lock() ws.state = .connecting - ws.lock.unlock() + ws.mtx.unlock() uri := ws.parse_uri() nonce := get_nonce(ws.nonce_size) seckey := base64.encode(nonce) @@ -160,17 +160,17 @@ pub fn (mut ws Client) connect() int { if ws.is_ssl { ws.connect_ssl() } - ws.lock.lock() + ws.mtx.m_lock() ws.state = .connected - ws.lock.unlock() + ws.mtx.unlock() res := ws.write_to_server(handshake.str, handshake.len) if res <= 0 { l.f('Handshake failed.') } ws.read_handshake(seckey) - ws.lock.lock() + ws.mtx.m_lock() ws.state = .open - ws.lock.unlock() + ws.mtx.unlock() ws.send_open_event() unsafe { handshake.free() @@ -182,9 +182,9 @@ pub fn (mut ws Client) connect() int { pub fn (mut ws Client) close(code int, message string) { if ws.state != .closed && ws.socket.sockfd > 1 { - ws.lock.lock() + ws.mtx.m_lock() ws.state = .closing - ws.lock.unlock() + ws.mtx.unlock() mut code32 := 0 if code > 0 { code_ := C.htons(code) @@ -223,9 +223,9 @@ pub fn (mut ws Client) close(code int, message string) { } ws.fragments = [] ws.send_close_event() - ws.lock.lock() + ws.mtx.m_lock() ws.state = .closed - ws.lock.unlock() + ws.mtx.unlock() unsafe { } // TODO impl autoreconnect diff --git a/vlib/sync/pool.v b/vlib/sync/pool.v index 90ec0db8d6..65eced2dfc 100644 --- a/vlib/sync/pool.v +++ b/vlib/sync/pool.v @@ -139,7 +139,7 @@ fn process_in_thread(mut pool PoolProcessor, task_id int) { if pool.ntask >= ilen { break } - pool.ntask_mtx.lock() + pool.ntask_mtx.m_lock() idx = pool.ntask pool.ntask++ pool.ntask_mtx.unlock() diff --git a/vlib/sync/sync_nix.c.v b/vlib/sync/sync_nix.c.v index 341a0b4245..ff5fbe6959 100644 --- a/vlib/sync/sync_nix.c.v +++ b/vlib/sync/sync_nix.c.v @@ -17,7 +17,8 @@ pub fn new_mutex() &Mutex { return m } -pub fn (mut m Mutex) lock() { +// m_lock(), for *manual* mutex handling, since `lock` is a keyword +pub fn (mut m Mutex) m_lock() { C.pthread_mutex_lock(&m.mutex) } diff --git a/vlib/sync/sync_windows.c.v b/vlib/sync/sync_windows.c.v index e4da37a2dc..88a57c6bda 100644 --- a/vlib/sync/sync_windows.c.v +++ b/vlib/sync/sync_windows.c.v @@ -43,7 +43,7 @@ pub fn new_mutex() &Mutex { return sm } -pub fn (mut m Mutex) lock() { +pub fn (mut m Mutex) m_lock() { // if mutex handle not initalized if isnil(m.mx) { m.mx = MHANDLE(C.CreateMutex(0, false, 0)) diff --git a/vlib/sync/waiter_nix.c.v b/vlib/sync/waiter_nix.c.v index 80344e34da..9c2a4b841d 100644 --- a/vlib/sync/waiter_nix.c.v +++ b/vlib/sync/waiter_nix.c.v @@ -10,7 +10,7 @@ mut: } pub fn (mut w Waiter) wait() { - w.mx.lock() + w.mx.m_lock() } pub fn (mut w Waiter) stop() { diff --git a/vlib/sync/waitgroup.v b/vlib/sync/waitgroup.v index 4963cd2d1f..29bca0b691 100644 --- a/vlib/sync/waitgroup.v +++ b/vlib/sync/waitgroup.v @@ -30,7 +30,7 @@ pub fn new_waitgroup() &WaitGroup { // add panics if task count drops below zero. pub fn (mut wg WaitGroup) add(delta int) { // protect task_count - wg.task_count_mutex.lock() + wg.task_count_mutex.m_lock() defer { wg.task_count_mutex.unlock() } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index a734b88b96..4e39ec8c22 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -11,7 +11,7 @@ pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | CastExpr | CharLiteral | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | - IndexExpr | InfixExpr | IntegerLiteral | Likely | MapInit | MatchExpr | None | OrExpr | + IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf @@ -232,6 +232,7 @@ pub: receiver_pos token.Position is_method bool rec_mut bool // is receiver mutable + rec_share table.ShareType language table.Language no_body bool // just a definition `fn C.malloc()` is_builtin bool // this function is defined in builtin/strconv @@ -271,6 +272,7 @@ pub mut: pub struct CallArg { pub: is_mut bool + share table.ShareType expr Expr pub mut: typ table.Type @@ -301,6 +303,7 @@ pub struct Var { pub: name string expr Expr + share table.ShareType is_mut bool is_arg bool // fn args should not be autofreed pub mut: @@ -344,6 +347,7 @@ pub mut: is_mut bool is_static bool is_optional bool + share table.ShareType } pub type IdentInfo = IdentFn | IdentVar @@ -438,6 +442,18 @@ pub: comment Comment } +pub struct LockExpr { +pub: + stmts []Stmt + is_rlock bool + pos token.Position +pub mut: + lockeds []Ident // `x`, `y` in `lock x, y {` + is_expr bool + is_rw bool // `rwshared` needs special special handling even in `lock` case + typ table.Type +} + pub struct MatchExpr { pub: tok_kind token.Kind diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b6ae67899d..0bb0a6d412 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1128,12 +1128,39 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { } if call_arg.is_mut { c.fail_if_immutable(call_arg.expr) + if !arg.is_mut { - c.error('`$arg.name` argument is not mutable, `mut` is not needed`', call_arg.expr.position()) + mut words := 'mutable' + mut tok := 'mut' + if call_arg.share == .shared_t { + words = 'shared' + tok = 'shared' + } else if call_arg.share == .rwshared_t { + words = 'read/write shared' + tok = 'rwshared' + } else if call_arg.share == .atomic_t { + words = 'atomic' + tok = 'atomic' + } + c.error('`$arg.name` argument is not $words, `$tok` is not needed`', call_arg.expr.position()) + } else if arg.typ.share() != call_arg.share { + c.error('wrong shared type', call_arg.expr.position()) } } else { - if arg.is_mut { - c.error('`$arg.name` is a mutable argument, you need to provide `mut`: `${call_expr.name}(mut ...)`', + if arg.is_mut && (!call_arg.is_mut || arg.typ.share() != call_arg.share) { + mut words := ' mutable' + mut tok := 'mut' + if arg.typ.share() == .shared_t { + words = ' shared' + tok = 'shared' + } else if arg.typ.share() == .rwshared_t { + words = ' read/write shared' + tok = 'rwshared' + } else if arg.typ.share() == .atomic_t { + words = 'n atomic' + tok = 'atomic' + } + c.error('`$arg.name` is a$words argument, you need to provide `$tok`: `${call_expr.name}($tok ...)`', call_arg.expr.position()) } } @@ -1497,6 +1524,13 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { } mut scope := c.file.scope.innermost(assign_stmt.pos.pos) mut ident_var_info := left.var_info() + if ident_var_info.share in [.shared_t, .rwshared_t] { + left_type = left_type.set_flag(.shared_f) + } + if ident_var_info.share in [.atomic_t, .rwshared_t] { + left_type = left_type.set_flag(.atomic_or_rw) + } + assign_stmt.left_types[i] = left_type ident_var_info.typ = left_type left.info = ident_var_info scope.update_var_type(left.name, left_type) @@ -2116,6 +2150,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { ast.IntegerLiteral { return table.any_int_type } + ast.LockExpr { + return c.lock_expr(mut node) + } ast.MapInit { return c.map_init(mut node) } @@ -2494,6 +2531,15 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol c.error(err_details, node.pos) } +pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type { + for id in node.lockeds { + c.ident(mut id) + } + c.stmts(node.stmts) + // void for now... maybe sometime `x := lock a { a.getval() }` + return table.void_type +} + pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { mut expr_required := false if c.expected_type != table.void_type { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index b64d2560ff..5dcdd48a65 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -835,6 +835,9 @@ pub fn (mut f Fmt) expr(node ast.Expr) { ast.IntegerLiteral { f.write(node.val) } + ast.LockExpr { + f.lock_expr(node) + } ast.MapInit { if node.keys.len == 0 { mut ktyp := node.key_type @@ -1164,6 +1167,20 @@ pub fn (mut f Fmt) short_module(name string) string { return '${aname}.$symname' } +pub fn (mut f Fmt) lock_expr(lex ast.LockExpr) { + f.write('lock ') + for i, v in lex.lockeds { + if i > 0 { + f.write(', ') + } + f.expr(v) + } + f.write(' {') + f.writeln('') + f.stmts(lex.stmts) + f.write('}') +} + pub fn (mut f Fmt) if_expr(it ast.IfExpr) { single_line := it.branches.len == 2 && it.has_else && it.branches[0].stmts.len == 1 && it.branches[1].stmts.len == 1 && diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 7f248afe22..e0e94ab871 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -58,7 +58,11 @@ mut: is_array_set bool is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) + is_shared bool // for initialization of hidden mutex in `[rw]shared` literals + is_rwshared bool optionals []string // to avoid duplicates TODO perf, use map + shareds []int // types with hidden mutex for which decl has been emitted + rwshareds []int // same with hidden rwmutex inside_ternary int // ?: comma separated statements on a single line inside_map_postfix bool // inside map++/-- postfix expr inside_map_infix bool // inside map< 0 { styp += strings.repeat(`*`, nr_muls) @@ -386,6 +394,27 @@ fn (mut g Gen) register_optional(t table.Type) string { return styp } +fn (mut g Gen) find_or_register_shared(t table.Type, base string) string { + is_rw := t.has_flag(.atomic_or_rw) + prefix := if is_rw { 'rw' } else { '' } + sh_typ := '__${prefix}shared__$base' + t_idx := t.idx() + if (is_rw && t_idx in g.rwshareds) || (!is_rw && t_idx in g.shareds) { + return sh_typ + } + // TODO: These two should become different... + mtx_typ := if is_rw { 'sync__Mutex' } else { 'sync__Mutex' } + g.hotcode_definitions.writeln('struct $sh_typ { $base val; $mtx_typ* mtx; };') + g.typedefs2.writeln('typedef struct $sh_typ $sh_typ;') + // println('registered shared type $sh_typ') + if is_rw { + g.rwshareds << t_idx + } else { + g.shareds << t_idx + } + return sh_typ +} + // cc_type returns the Cleaned Concrete Type name, *without ptr*, // i.e. it's always just Cat, not Cat_ptr: fn (g &Gen) cc_type(t table.Type) string { @@ -1141,7 +1170,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // `a := 1` | `a,b := 1,2` for i, left in assign_stmt.left { mut var_type := assign_stmt.left_types[i] - val_type := assign_stmt.right_types[i] + mut val_type := assign_stmt.right_types[i] val := assign_stmt.right[i] mut is_call := false mut blank_assign := false @@ -1151,6 +1180,15 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // id_info := ident.var_info() // var_type = id_info.typ blank_assign = ident.kind == .blank_ident + if ident.info is ast.IdentVar { + share := (ident.info as ast.IdentVar).share + if share in [.shared_t, .rwshared_t] { + var_type = var_type.set_flag(.shared_f) + } + if share in [.atomic_t, .rwshared_t] { + var_type = var_type.set_flag(.atomic_or_rw) + } + } } styp := g.typ(var_type) mut is_fixed_array_init := false @@ -1278,6 +1316,8 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { if unwrap_optional { g.write('*($styp*)') } + g.is_shared = var_type.has_flag(.shared_f) + g.is_rwshared = var_type.has_flag(.atomic_or_rw) if !cloned { if is_decl { if is_fixed_array_init && !has_val { @@ -1303,6 +1343,8 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { g.write(' })') g.is_array_set = false } + g.is_rwshared = false + g.is_shared = false } g.right_is_opt = false g.is_assign_rhs = false @@ -1622,6 +1664,9 @@ fn (mut g Gen) expr(node ast.Expr) { g.write(node.val) // .int().str()) } } + ast.LockExpr { + g.lock_expr(node) + } ast.MatchExpr { g.match_expr(node) } @@ -1721,6 +1766,9 @@ fn (mut g Gen) expr(node ast.Expr) { // g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /') g.write('.') } + if node.expr_type.has_flag(.shared_f) { + g.write('val.') + } if node.expr_type == 0 { verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr') } @@ -2011,6 +2059,36 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { } } +fn (mut g Gen) lock_expr(node ast.LockExpr) { + for id in node.lockeds { + name := id.name + deref := if id.is_mut { '->' } else { '.' } + // TODO: use 3 different locking functions + if node.is_rlock { + g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);') + } else if id.var_info().typ.has_flag(.atomic_or_rw) { + g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);') + } else { + g.writeln('sync__Mutex_m_lock(${name}${deref}mtx);') + } + } + g.stmts(node.stmts) + // unlock in reverse order + for i := node.lockeds.len-1; i >= 0; i-- { + id := node.lockeds[i] + name := id.name + deref := if id.is_mut { '->' } else { '.' } + // TODO: use 3 different unlocking functions + if node.is_rlock { + g.writeln('sync__Mutex_unlock(${name}${deref}mtx);') + } else if id.var_info().typ.has_flag(.atomic_or_rw) { + g.writeln('sync__Mutex_unlock(${name}${deref}mtx);') + } else { + g.writeln('sync__Mutex_unlock(${name}${deref}mtx);') + } + } +} + fn (mut g Gen) match_expr(node ast.MatchExpr) { // println('match expr typ=$it.expr_type') // TODO @@ -2150,6 +2228,10 @@ fn (mut g Gen) ident(node ast.Ident) { g.write('(*($styp*)${name}.data)') return } + if !g.is_assign_lhs && (ident_var.share == .shared_t || ident_var.share == .rwshared_t) { + g.write('${name}.val') + return + } } g.write(g.get_ternary_name(name)) } @@ -2671,6 +2753,7 @@ const ( fn (mut g Gen) struct_init(struct_init ast.StructInit) { styp := g.typ(struct_init.typ) + mut shared_styp := '' // only needed for shared &St{... if styp in skip_struct_init { g.go_back_out(3) return @@ -2680,9 +2763,22 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { g.is_amp = false // reset the flag immediately so that other struct inits in this expr are handled correctly if is_amp { g.out.go_back(1) // delete the `&` already generated in `prefix_expr() - g.write('($styp*)memdup(&($styp){') + if g.is_shared { + mut shared_typ := struct_init.typ.set_flag(.shared_f) + if g.is_rwshared { + shared_typ = shared_typ.set_flag(.atomic_or_rw) + } + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)memdup(&($shared_styp){.val = ($styp){') + } else { + g.write('($styp*)memdup(&($styp){') + } } else { - g.writeln('($styp){') + if g.is_shared { + g.writeln('{.val = {') + } else { + g.writeln('($styp){') + } } // mut fields := []string{} mut inited_fields := map[string]int{} // TODO this is done in checker, move to ast node @@ -2771,7 +2867,12 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { g.write('\n#ifndef __cplusplus\n0\n#endif\n') } g.write('}') - if is_amp { + if g.is_shared { + g.write(', .mtx = sync__new_mutex()}') + if is_amp { + g.write(', sizeof($shared_styp))') + } + } else if is_amp { g.write(', sizeof($styp))') } } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index b3903ff89d..1459a6c439 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -563,6 +563,9 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.IntegerLiteral { g.write(it.val) } + ast.LockExpr { + g.gen_lock_expr(it) + } ast.MapInit { g.gen_map_init_expr(it) } @@ -1192,6 +1195,10 @@ fn (mut g JsGen) gen_ident(node ast.Ident) { g.write(name) } +fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) { + // TODO: implement this +} + fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { type_sym := g.table.get_type_symbol(node.typ) diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index 4166b1ed6a..e34b96bc73 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -4,6 +4,7 @@ module parser import v.ast +import v.table fn (mut p Parser) assign_stmt() ast.Stmt { return p.partial_assign_stmt(p.expr_list()) @@ -106,16 +107,22 @@ fn (mut p Parser) partial_assign_stmt(left []ast.Expr) ast.Stmt { if p.scope.known_var(lx.name) { p.error_with_pos('redefinition of `$lx.name`', lx.pos) } + mut share := table.ShareType(0) + if lx.info is ast.IdentVar { + share = (lx.info as ast.IdentVar).share + } if left.len == right.len { p.scope.register(lx.name, ast.Var{ name: lx.name expr: right[i] + share: share is_mut: lx.is_mut || p.inside_for pos: lx.pos }) } else { p.scope.register(lx.name, ast.Var{ name: lx.name + share: share is_mut: lx.is_mut || p.inside_for pos: lx.pos }) diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index e938b169da..9b35c9c8a2 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -100,14 +100,16 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp pub fn (mut p Parser) call_args() []ast.CallArg { mut args := []ast.CallArg{} for p.tok.kind != .rpar { - mut is_mut := false - if p.tok.kind == .key_mut { + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic_or_rw + if is_mut { p.next() - is_mut = true } e := p.expr(0) args << ast.CallArg{ is_mut: is_mut + share: table.sharetype_from_flags(is_shared, is_atomic_or_rw) expr: e } if p.tok.kind != .rpar { @@ -149,7 +151,9 @@ fn (mut p Parser) fn_decl() ast.FnDecl { if p.tok.kind == .lpar { p.next() // ( is_method = true - rec_mut = p.tok.kind == .key_mut + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] + rec_mut = p.tok.kind == .key_mut || is_shared || is_atomic_or_rw if rec_mut { p.next() // `mut` } @@ -172,6 +176,12 @@ fn (mut p Parser) fn_decl() ast.FnDecl { if is_amp && rec_mut { p.error('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`') } + if is_shared { + rec_type = rec_type.set_flag(.shared_f) + } + if is_atomic_or_rw { + rec_type = rec_type.set_flag(.atomic_or_rw) + } args << table.Arg{ name: rec_name is_mut: rec_mut @@ -390,7 +400,9 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { mut arg_no := 1 for p.tok.kind != .rpar { arg_name := 'arg_$arg_no' - is_mut := p.tok.kind == .key_mut + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic_or_rw if is_mut { p.next() } @@ -402,13 +414,27 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { mut arg_type := p.parse_type() if is_mut { if !arg_type.has_flag(.generic) { - p.check_fn_mutable_arguments(arg_type, pos) + if is_shared { + p.check_fn_shared_arguments(arg_type, pos) + } else if is_atomic_or_rw { + p.check_fn_atomic_arguments(arg_type, pos) + } else { + p.check_fn_mutable_arguments(arg_type, pos) + } + } else if is_shared || is_atomic_or_rw { + p.error_with_pos('generic object cannot be `atomic`, `shared` or `rwshared`', pos) } // if arg_type.is_ptr() { // p.error('cannot mut') // } // arg_type = arg_type.to_ptr() arg_type = arg_type.set_nr_muls(1) + if is_shared { + arg_type = arg_type.set_flag(.shared_f) + } + if is_atomic_or_rw { + arg_type = arg_type.set_flag(.atomic_or_rw) + } } if is_variadic { arg_type = arg_type.set_flag(.variadic) @@ -430,7 +456,9 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { } } else { for p.tok.kind != .rpar { - mut is_mut := p.tok.kind == .key_mut + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] + mut is_mut := p.tok.kind == .key_mut || is_shared || is_atomic_or_rw if is_mut { p.next() } @@ -455,9 +483,23 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool, bool) { mut typ := p.parse_type() if is_mut { if !typ.has_flag(.generic) { - p.check_fn_mutable_arguments(typ, pos) + if is_shared { + p.check_fn_shared_arguments(typ, pos) + } else if is_atomic_or_rw { + p.check_fn_atomic_arguments(typ, pos) + } else { + p.check_fn_mutable_arguments(typ, pos) + } + } else if is_shared || is_atomic_or_rw { + p.error_with_pos('generic object cannot be `atomic`, `shared` or `rwshared`', pos) } typ = typ.set_nr_muls(1) + if is_shared { + typ = typ.set_flag(.shared_f) + } + if is_atomic_or_rw { + typ = typ.set_flag(.atomic_or_rw) + } } if is_variadic { typ = typ.set_flag(.variadic) @@ -497,6 +539,22 @@ fn (mut p Parser) check_fn_mutable_arguments(typ table.Type, pos token.Position) } } +fn (mut p Parser) check_fn_shared_arguments(typ table.Type, pos token.Position) { + sym := p.table.get_type_symbol(typ) + if sym.kind !in [.array, .struct_, .map, .placeholder] && !typ.is_ptr() { + p.error_with_pos('shared arguments are only allowed for arrays, maps, and structs\n', pos) + } +} + +fn (mut p Parser) check_fn_atomic_arguments(typ table.Type, pos token.Position) { + sym := p.table.get_type_symbol(typ) + if sym.kind !in [.u32, .int, .u64] { + p.error_with_pos('atomic arguments are only allowed for 32/64 bit integers\n' + + 'use shared arguments instead: `fn foo(atomic n $sym.name) {` => `fn foo(shared n $sym.name) {`', + pos) + } +} + fn (mut p Parser) fn_redefinition_error(name string) { // Find where this function was already declared // TODO diff --git a/vlib/v/parser/lock.v b/vlib/v/parser/lock.v new file mode 100644 index 0000000000..c20d32fdfd --- /dev/null +++ b/vlib/v/parser/lock.v @@ -0,0 +1,35 @@ +module parser + +import v.ast +import v.table + +fn (mut p Parser) lock_expr() ast.LockExpr { + pos := p.tok.position() + is_rlock := p.tok.kind == .key_rlock + p.next() + mut lockeds := []ast.Ident{} + for p.tok.kind == .name { + lockeds << ast.Ident{ + language: table.Language.v + kind: .variable + pos: p.tok.position() + name: p.tok.lit + is_mut: true + info: ast.IdentVar{} + } + p.next() + if p.tok.kind == .lcbr { + break + } + p.check(.comma) + } + stmts := p.parse_block() + return ast.LockExpr { + lockeds: lockeds + stmts: stmts + is_rlock: is_rlock + pos: pos + } +} + + diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 3c7f30510b..cee0aaac23 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -105,8 +105,10 @@ pub fn (mut p Parser) parse_type() table.Type { p.next() is_optional = true } + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] mut nr_muls := 0 - if p.tok.kind == .key_mut { + if p.tok.kind == .key_mut || is_shared || is_atomic_or_rw { nr_muls++ p.next() } @@ -139,6 +141,12 @@ pub fn (mut p Parser) parse_type() table.Type { if is_optional { typ = typ.set_flag(.optional) } + if is_shared { + typ = typ.set_flag(.shared_f) + } + if is_atomic_or_rw { + typ = typ.set_flag(.atomic_or_rw) + } if nr_muls > 0 { typ = typ.set_nr_muls(nr_muls) } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 451fd1ca6f..4abfa5b605 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -506,7 +506,7 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { .key_for { return p.for_stmt() } - .name, .key_mut, .key_static, .mul { + .name, .key_mut, .key_shared, .key_atomic, .key_rwshared, .key_static, .mul { if p.tok.kind == .name { if p.tok.lit == 'sql' { return p.sql_stmt() @@ -750,7 +750,7 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { left0 := left[0] if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { return p.partial_assign_stmt(left) - } else if is_top_level && tok.kind !in [.key_if, .key_match] && + } else if is_top_level && tok.kind !in [.key_if, .key_match, .key_lock, .key_rlock] && left0 !is ast.CallExpr && left0 !is ast.PostfixExpr && !(left0 is ast.InfixExpr && (left0 as ast.InfixExpr).op == .left_shift) && left0 !is ast.ComptimeCall { @@ -773,7 +773,9 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { pub fn (mut p Parser) parse_ident(language table.Language) ast.Ident { // p.warn('name ') - is_mut := p.tok.kind == .key_mut + is_shared := p.tok.kind in [.key_shared, .key_rwshared] + is_atomic_or_rw := p.tok.kind in [.key_rwshared, .key_atomic] + is_mut := p.tok.kind == .key_mut || is_shared || is_atomic_or_rw if is_mut { p.next() } @@ -811,6 +813,7 @@ pub fn (mut p Parser) parse_ident(language table.Language) ast.Ident { info: ast.IdentVar{ is_mut: is_mut is_static: is_static + share: table.sharetype_from_flags(is_shared, is_atomic_or_rw) } } } else { diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index a594282e5a..0f8808e381 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -16,7 +16,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.eat_comments() // Prefix match p.tok.kind { - .key_mut, .key_static { + .key_mut, .key_shared, .key_rwshared, .key_atomic, .key_static { node = p.name_expr() p.is_stmt_ident = is_stmt_ident } @@ -79,6 +79,9 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { .key_if { node = p.if_expr() } + .key_lock, .key_rlock { + node = p.lock_expr() + } .lsbr { if p.expecting_type { // parse json.decode type (`json.decode([]User, s)`) diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index 993afdc897..c186db3c53 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -41,6 +41,49 @@ pub enum TypeFlag { optional variadic generic + shared_f + atomic_or_rw +} + +/* + To save precious TypeFlag bits the 4 possible ShareTypes are coded in the two + bits `shared` and `atomic_or_rw` (see sharetype_from_flags() below). +*/ + +pub enum ShareType { + mut_t + shared_t + atomic_t + rwshared_t +} + +pub fn (t ShareType) str() string { + match t { + .mut_t { return 'mut' } + .shared_t { return 'shared' } + .atomic_t { return 'atomic' } + .rwshared_t { return 'rwshared' } + } +} + +// defines special typenames +pub fn (t Type) atomic_typename() string { + idx := t.idx() + match idx { + u32_type_idx { return 'atomic_uint' } + int_type_idx { return 'atomic_int' } + u64_type_idx { return 'atomic_ullong' } + i64_type_idx { return 'atomic_llong' } + else { return 'unknown_atomic' } + } +} + +pub fn sharetype_from_flags(is_shared, is_atomic_or_rw bool) ShareType { + return ShareType((int(is_atomic_or_rw) << 1) | int(is_shared)) +} + +pub fn (t Type) share() ShareType { + return sharetype_from_flags(t.has_flag(.shared_f), t.has_flag(.atomic_or_rw)) } pub fn (types []Type) contains(typ Type) bool { diff --git a/vlib/v/tests/shared_lock_2_test.v b/vlib/v/tests/shared_lock_2_test.v new file mode 100644 index 0000000000..9283968731 --- /dev/null +++ b/vlib/v/tests/shared_lock_2_test.v @@ -0,0 +1,53 @@ +import sync +import time + +struct St { +mut: + a int +} + +fn (shared x St) f(shared y St, shared z St) { + for _ in 0..101 { + lock x, y { + tmp := y.a + y.a = x.a + x.a = tmp + } + } + lock z { + z.a-- + } +} + +fn test_shared_receiver_lock() { + shared x := &St{ + a: 5 + } + shared y := &St{ + a: 7 + } + shared z := &St{ + a: 1 + } + go x.f(shared y, shared z) + for _ in 0..100 { + lock x, y { + tmp := x.a + x.a = y.a + y.a = tmp + } + } + // the following would be a good application for a channel + for finished := false; ; { + lock z { + finished = z.a == 0 + } + if finished { + break + } + time.sleep_ms(100) + } + lock x, y { + assert x.a == 7 && y.a == 5 + } +} diff --git a/vlib/v/tests/shared_lock_test.v b/vlib/v/tests/shared_lock_test.v new file mode 100644 index 0000000000..9fc2a35531 --- /dev/null +++ b/vlib/v/tests/shared_lock_test.v @@ -0,0 +1,53 @@ +import sync +import time + +struct St { +mut: + a int +} + +fn f(shared x St, shared y St, shared z St) { + for _ in 0..101 { + lock x, y { + tmp := y.a + y.a = x.a + x.a = tmp + } + } + lock z { + z.a-- + } +} + +fn test_shared_lock() { + shared x := &St{ + a: 5 + } + shared y := &St{ + a: 7 + } + shared z := &St{ + a: 1 + } + go f(shared x, shared y, shared z) + for _ in 0..100 { + lock x, y { + tmp := x.a + x.a = y.a + y.a = tmp + } + } + // the following would be a good application for a channel + for finished := false; ; { + lock z { + finished = z.a == 0 + } + if finished { + break + } + time.sleep_ms(100) + } + lock x, y { + assert x.a == 7 && y.a == 5 + } +} diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index ff7a57906c..54324d6f0a 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -108,6 +108,10 @@ pub enum Kind { key_match key_module key_mut + key_shared + key_rwshared + key_lock + key_rlock key_none key_return key_select @@ -226,6 +230,10 @@ fn build_token_str() []string { s[Kind.key_goto] = 'goto' s[Kind.key_const] = 'const' s[Kind.key_mut] = 'mut' + s[Kind.key_shared] = 'shared' + s[Kind.key_rwshared] = 'rwshared' + s[Kind.key_lock] = 'lock' + s[Kind.key_rlock] = 'rlock' s[Kind.key_type] = 'type' s[Kind.key_for] = 'for' s[Kind.key_switch] = 'switch'