mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
checker, cgen: checks for shared/lock, first autolock (#5815)
This commit is contained in:
parent
1baa7ef806
commit
aa364ddaca
@ -396,6 +396,7 @@ pub:
|
|||||||
pub mut:
|
pub mut:
|
||||||
left_type table.Type
|
left_type table.Type
|
||||||
right_type table.Type
|
right_type table.Type
|
||||||
|
auto_locked string
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PostfixExpr {
|
pub struct PostfixExpr {
|
||||||
|
@ -31,6 +31,8 @@ pub mut:
|
|||||||
const_decl string
|
const_decl string
|
||||||
const_deps []string
|
const_deps []string
|
||||||
const_names []string
|
const_names []string
|
||||||
|
locked_names []string // vars that are currently locked
|
||||||
|
rlocked_names []string // vars that are currently read-locked
|
||||||
pref &pref.Preferences // Preferences shared from V struct
|
pref &pref.Preferences // Preferences shared from V struct
|
||||||
in_for_count int // if checker is currently in an for loop
|
in_for_count int // if checker is currently in an for loop
|
||||||
// checked_ident string // to avoid infinit checker loops
|
// checked_ident string // to avoid infinit checker loops
|
||||||
@ -571,7 +573,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
|
|||||||
.left_shift {
|
.left_shift {
|
||||||
if left.kind == .array {
|
if left.kind == .array {
|
||||||
// `array << elm`
|
// `array << elm`
|
||||||
c.fail_if_immutable(infix_expr.left)
|
infix_expr.auto_locked, _ = c.fail_if_immutable(infix_expr.left)
|
||||||
left_value_type := c.table.value_type(left_type)
|
left_value_type := c.table.value_type(left_type)
|
||||||
left_value_sym := c.table.get_type_symbol(left_value_type)
|
left_value_sym := c.table.get_type_symbol(left_value_type)
|
||||||
if left_value_sym.kind == .interface_ {
|
if left_value_sym.kind == .interface_ {
|
||||||
@ -656,11 +658,15 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Checker) fail_if_immutable(expr ast.Expr) {
|
// returns name and position of variable that needs write lock
|
||||||
|
fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
|
||||||
|
mut to_lock := '' // name of variable that needs lock
|
||||||
|
mut pos := token.Position{} // and its position
|
||||||
|
mut explicit_lock_needed := false
|
||||||
match expr {
|
match expr {
|
||||||
ast.CastExpr {
|
ast.CastExpr {
|
||||||
// TODO
|
// TODO
|
||||||
return
|
return '', pos
|
||||||
}
|
}
|
||||||
ast.Ident {
|
ast.Ident {
|
||||||
scope := c.file.scope.innermost(expr.pos.pos)
|
scope := c.file.scope.innermost(expr.pos.pos)
|
||||||
@ -670,24 +676,30 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) {
|
|||||||
expr.pos)
|
expr.pos)
|
||||||
}
|
}
|
||||||
v.is_changed = true
|
v.is_changed = true
|
||||||
|
if v.typ.share() == .shared_t {
|
||||||
|
if expr.name !in c.locked_names {
|
||||||
|
to_lock = expr.name
|
||||||
|
pos = expr.pos
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if expr.name in c.const_names {
|
} else if expr.name in c.const_names {
|
||||||
c.error('cannot modify constant `$expr.name`', expr.pos)
|
c.error('cannot modify constant `$expr.name`', expr.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.IndexExpr {
|
ast.IndexExpr {
|
||||||
c.fail_if_immutable(expr.left)
|
to_lock, pos = c.fail_if_immutable(expr.left)
|
||||||
}
|
}
|
||||||
ast.ParExpr {
|
ast.ParExpr {
|
||||||
c.fail_if_immutable(expr.expr)
|
to_lock, pos = c.fail_if_immutable(expr.expr)
|
||||||
}
|
}
|
||||||
ast.PrefixExpr {
|
ast.PrefixExpr {
|
||||||
c.fail_if_immutable(expr.right)
|
to_lock, pos = c.fail_if_immutable(expr.right)
|
||||||
}
|
}
|
||||||
ast.SelectorExpr {
|
ast.SelectorExpr {
|
||||||
// retrieve table.Field
|
// retrieve table.Field
|
||||||
if expr.expr_type == 0 {
|
if expr.expr_type == 0 {
|
||||||
c.error('0 type in SelectorExpr', expr.pos)
|
c.error('0 type in SelectorExpr', expr.pos)
|
||||||
return
|
return '', pos
|
||||||
}
|
}
|
||||||
typ_sym := c.table.get_type_symbol(c.unwrap_generic(expr.expr_type))
|
typ_sym := c.table.get_type_symbol(c.unwrap_generic(expr.expr_type))
|
||||||
match typ_sym.kind {
|
match typ_sym.kind {
|
||||||
@ -696,14 +708,18 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) {
|
|||||||
field_info := struct_info.find_field(expr.field_name) or {
|
field_info := struct_info.find_field(expr.field_name) or {
|
||||||
type_str := c.table.type_to_str(expr.expr_type)
|
type_str := c.table.type_to_str(expr.expr_type)
|
||||||
c.error('unknown field `${type_str}.$expr.field_name`', expr.pos)
|
c.error('unknown field `${type_str}.$expr.field_name`', expr.pos)
|
||||||
return
|
return '', pos
|
||||||
}
|
}
|
||||||
if !field_info.is_mut {
|
if !field_info.is_mut {
|
||||||
type_str := c.table.type_to_str(expr.expr_type)
|
type_str := c.table.type_to_str(expr.expr_type)
|
||||||
c.error('field `$expr.field_name` of struct `$type_str` is immutable',
|
c.error('field `$expr.field_name` of struct `$type_str` is immutable',
|
||||||
expr.pos)
|
expr.pos)
|
||||||
}
|
}
|
||||||
c.fail_if_immutable(expr.expr)
|
to_lock, pos = c.fail_if_immutable(expr.expr)
|
||||||
|
if to_lock != '' {
|
||||||
|
// No automatic lock for struct access
|
||||||
|
explicit_lock_needed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.array, .string {
|
.array, .string {
|
||||||
// This should only happen in `builtin`
|
// This should only happen in `builtin`
|
||||||
@ -721,18 +737,27 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) {
|
|||||||
ast.CallExpr {
|
ast.CallExpr {
|
||||||
// TODO: should only work for builtin method
|
// TODO: should only work for builtin method
|
||||||
if expr.name == 'slice' {
|
if expr.name == 'slice' {
|
||||||
return
|
to_lock, pos = c.fail_if_immutable(expr.left)
|
||||||
|
if to_lock != '' {
|
||||||
|
// No automatic lock for array slicing (yet(?))
|
||||||
|
explicit_lock_needed = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.error('cannot use function call as mut', expr.pos)
|
c.error('cannot use function call as mut', expr.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.ArrayInit {
|
ast.ArrayInit {
|
||||||
return
|
return '', pos
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
c.error('unexpected expression `${typeof(expr)}`', expr.position())
|
c.error('unexpected expression `${typeof(expr)}`', expr.position())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if explicit_lock_needed {
|
||||||
|
c.error('`$to_lock` is `shared` and needs explicit lock for `${typeof(expr)}`', pos)
|
||||||
|
to_lock = ''
|
||||||
|
}
|
||||||
|
return to_lock, pos
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) table.Type {
|
pub fn (mut c Checker) call_expr(mut call_expr ast.CallExpr) table.Type {
|
||||||
@ -2626,10 +2651,33 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type {
|
pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type {
|
||||||
|
scope := c.file.scope.innermost(node.pos.pos)
|
||||||
for id in node.lockeds {
|
for id in node.lockeds {
|
||||||
c.ident(mut id)
|
c.ident(mut id)
|
||||||
|
if v := scope.find_var(id.name) {
|
||||||
|
if v.typ.share() != .shared_t {
|
||||||
|
c.error('`$id.name` must be declared `shared` to be locked', id.pos)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.error('`$id.name` is not a variable and cannot be locked', id.pos)
|
||||||
|
}
|
||||||
|
if id.name in c.locked_names {
|
||||||
|
c.error('`$id.name` is already locked', id.pos)
|
||||||
|
} else if id.name in c.rlocked_names {
|
||||||
|
c.error('`$id.name` is already read-locked', id.pos)
|
||||||
|
}
|
||||||
|
if node.is_rlock {
|
||||||
|
c.rlocked_names << id.name
|
||||||
|
} else {
|
||||||
|
c.locked_names << id.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.stmts(node.stmts)
|
c.stmts(node.stmts)
|
||||||
|
if node.is_rlock {
|
||||||
|
c.rlocked_names = c.rlocked_names[.. c.rlocked_names.len - node.lockeds.len]
|
||||||
|
} else {
|
||||||
|
c.locked_names = c.locked_names[.. c.locked_names.len - node.lockeds.len]
|
||||||
|
}
|
||||||
// void for now... maybe sometime `x := lock a { a.getval() }`
|
// void for now... maybe sometime `x := lock a { a.getval() }`
|
||||||
return table.void_type
|
return table.void_type
|
||||||
}
|
}
|
||||||
|
7
vlib/v/checker/tests/lock_already_locked.out
Normal file
7
vlib/v/checker/tests/lock_already_locked.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/lock_already_locked.v:11:9: error: `a` is already locked
|
||||||
|
9 | }
|
||||||
|
10 | lock a {
|
||||||
|
11 | rlock a {
|
||||||
|
| ^
|
||||||
|
12 | a.x++
|
||||||
|
13 | }
|
16
vlib/v/checker/tests/lock_already_locked.vv
Normal file
16
vlib/v/checker/tests/lock_already_locked.vv
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
struct St {
|
||||||
|
mut:
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
shared a := &St{
|
||||||
|
x: 5
|
||||||
|
}
|
||||||
|
lock a {
|
||||||
|
rlock a {
|
||||||
|
a.x++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(a.x)
|
||||||
|
}
|
7
vlib/v/checker/tests/lock_already_rlocked.out
Normal file
7
vlib/v/checker/tests/lock_already_rlocked.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/lock_already_rlocked.v:11:8: error: `a` is already read-locked
|
||||||
|
9 | }
|
||||||
|
10 | rlock a {
|
||||||
|
11 | lock a {
|
||||||
|
| ^
|
||||||
|
12 | a.x++
|
||||||
|
13 | }
|
16
vlib/v/checker/tests/lock_already_rlocked.vv
Normal file
16
vlib/v/checker/tests/lock_already_rlocked.vv
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
struct St {
|
||||||
|
mut:
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
shared a := &St{
|
||||||
|
x: 5
|
||||||
|
}
|
||||||
|
rlock a {
|
||||||
|
lock a {
|
||||||
|
a.x++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(a.x)
|
||||||
|
}
|
7
vlib/v/checker/tests/lock_const.out
Normal file
7
vlib/v/checker/tests/lock_const.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/lock_const.v:7:8: error: `a` is not a variable and cannot be locked
|
||||||
|
5 | fn main() {
|
||||||
|
6 | mut c := 0
|
||||||
|
7 | rlock a {
|
||||||
|
| ^
|
||||||
|
8 | c = a
|
||||||
|
9 | }
|
11
vlib/v/checker/tests/lock_const.vv
Normal file
11
vlib/v/checker/tests/lock_const.vv
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const (
|
||||||
|
a = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut c := 0
|
||||||
|
rlock a {
|
||||||
|
c = a
|
||||||
|
}
|
||||||
|
println(c)
|
||||||
|
}
|
7
vlib/v/checker/tests/lock_needed.out
Normal file
7
vlib/v/checker/tests/lock_needed.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/lock_needed.v:10:2: error: `abc` is `shared` and needs explicit lock for `v.ast.SelectorExpr`
|
||||||
|
8 | x: 5
|
||||||
|
9 | }
|
||||||
|
10 | abc.x++
|
||||||
|
| ~~~
|
||||||
|
11 | println(abc.x)
|
||||||
|
12 | }
|
12
vlib/v/checker/tests/lock_needed.vv
Normal file
12
vlib/v/checker/tests/lock_needed.vv
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
struct St {
|
||||||
|
mut:
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
shared abc := &St{
|
||||||
|
x: 5
|
||||||
|
}
|
||||||
|
abc.x++
|
||||||
|
println(abc.x)
|
||||||
|
}
|
7
vlib/v/checker/tests/lock_nonshared.out
Normal file
7
vlib/v/checker/tests/lock_nonshared.out
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vlib/v/checker/tests/lock_nonshared.v:10:7: error: `a` must be declared `shared` to be locked
|
||||||
|
8 | x: 5
|
||||||
|
9 | }
|
||||||
|
10 | lock a {
|
||||||
|
| ^
|
||||||
|
11 | a.x++
|
||||||
|
12 | }
|
14
vlib/v/checker/tests/lock_nonshared.vv
Normal file
14
vlib/v/checker/tests/lock_nonshared.vv
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
struct St {
|
||||||
|
mut:
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut a := &St{
|
||||||
|
x: 5
|
||||||
|
}
|
||||||
|
lock a {
|
||||||
|
a.x++
|
||||||
|
}
|
||||||
|
println(a)
|
||||||
|
}
|
@ -1892,6 +1892,9 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||||||
// }
|
// }
|
||||||
// string + string, string == string etc
|
// string + string, string == string etc
|
||||||
// g.infix_op = node.op
|
// g.infix_op = node.op
|
||||||
|
if node.auto_locked != '' {
|
||||||
|
g.writeln('sync__RwMutex_w_lock($node.auto_locked->mtx);')
|
||||||
|
}
|
||||||
left_type := g.unwrap_generic(node.left_type)
|
left_type := g.unwrap_generic(node.left_type)
|
||||||
left_sym := g.table.get_type_symbol(left_type)
|
left_sym := g.table.get_type_symbol(left_type)
|
||||||
unaliased_left := if left_sym.kind == .alias { (left_sym.info as table.Alias).parent_type } else { left_type }
|
unaliased_left := if left_sym.kind == .alias { (left_sym.info as table.Alias).parent_type } else { left_type }
|
||||||
@ -2121,6 +2124,10 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if node.auto_locked != '' {
|
||||||
|
g.writeln(';')
|
||||||
|
g.write('sync__RwMutex_w_unlock($node.auto_locked->mtx)')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) lock_expr(node ast.LockExpr) {
|
fn (mut g Gen) lock_expr(node ast.LockExpr) {
|
||||||
|
52
vlib/v/tests/autolock_array_1.v
Normal file
52
vlib/v/tests/autolock_array_1.v
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import sync
|
||||||
|
import time
|
||||||
|
|
||||||
|
const (
|
||||||
|
iterations_per_thread = 100000
|
||||||
|
)
|
||||||
|
|
||||||
|
fn add_elements(shared foo []int, n int) {
|
||||||
|
for _ in 0 .. iterations_per_thread {
|
||||||
|
foo << n
|
||||||
|
}
|
||||||
|
// automatic lock is not yet implemented for this...
|
||||||
|
lock foo {
|
||||||
|
foo[0]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_autolocked_array() {
|
||||||
|
shared abc := &[0]
|
||||||
|
go add_elements(shared abc, 1)
|
||||||
|
go add_elements(shared abc, 3)
|
||||||
|
for _ in 0 .. iterations_per_thread {
|
||||||
|
abc << 0
|
||||||
|
}
|
||||||
|
// wait for coroutines to finish - that should really be
|
||||||
|
// done by channels, yield, semaphore...
|
||||||
|
for {
|
||||||
|
mut finished_threads := 0
|
||||||
|
rlock abc {
|
||||||
|
finished_threads = abc[0]
|
||||||
|
}
|
||||||
|
if finished_threads == 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.sleep_ms(100)
|
||||||
|
}
|
||||||
|
// create histogram of results
|
||||||
|
mut result := [0, 0, 0, 0]
|
||||||
|
rlock abc {
|
||||||
|
// automatic rlock for iteration is also not implemented, yet
|
||||||
|
for v in abc {
|
||||||
|
if v > 3 {
|
||||||
|
panic('unexpected element on array')
|
||||||
|
}
|
||||||
|
result[v]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert result[0] == iterations_per_thread
|
||||||
|
assert result[1] == iterations_per_thread
|
||||||
|
assert result[2] == 1 // number of non-main threads
|
||||||
|
assert result[3] == iterations_per_thread
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user