all: make `lock` and `rlock` dead lock free :-) (#8534)
							parent
							
								
									f4b757e47d
								
							
						
					
					
						commit
						9dcf673216
					
				|  | @ -646,7 +646,7 @@ pub: | |||
| pub struct LockExpr { | ||||
| pub: | ||||
| 	stmts    []Stmt | ||||
| 	is_rlock bool | ||||
| 	is_rlock []bool | ||||
| 	pos      token.Position | ||||
| pub mut: | ||||
| 	lockeds []Ident // `x`, `y` in `lock x, y {`
 | ||||
|  |  | |||
|  | @ -1081,6 +1081,15 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) { | |||
| 				v.is_changed = true | ||||
| 				if v.typ.share() == .shared_t { | ||||
| 					if expr.name !in c.locked_names { | ||||
| 						if c.locked_names.len > 0 || c.rlocked_names.len > 0 { | ||||
| 							if expr.name in c.rlocked_names { | ||||
| 								c.error('$expr.name has an `rlock` but needs a `lock`', | ||||
| 									expr.pos) | ||||
| 							} else { | ||||
| 								c.error('$expr.name must be added to the `lock` list above', | ||||
| 									expr.pos) | ||||
| 							} | ||||
| 						} | ||||
| 						to_lock = expr.name | ||||
| 						pos = expr.pos | ||||
| 					} | ||||
|  | @ -2620,6 +2629,8 @@ pub fn (mut c Checker) assign_stmt(mut assign_stmt ast.AssignStmt) { | |||
| 							} | ||||
| 							left_type = left_type.set_nr_muls(1) | ||||
| 						} | ||||
| 					} else if left_type.has_flag(.shared_f) { | ||||
| 						left_type = left_type.clear_flag(.shared_f) | ||||
| 					} | ||||
| 					if ident_var_info.share == .atomic_t { | ||||
| 						left_type = left_type.set_flag(.atomic_f) | ||||
|  | @ -4540,6 +4551,9 @@ pub fn (mut c Checker) select_expr(mut node ast.SelectExpr) table.Type { | |||
| } | ||||
| 
 | ||||
| pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type { | ||||
| 	if c.rlocked_names.len > 0 || c.locked_names.len > 0 { | ||||
| 		c.error('nested `lock`/`rlock` not allowed', node.pos) | ||||
| 	} | ||||
| 	for i in 0 .. node.lockeds.len { | ||||
| 		c.ident(mut node.lockeds[i]) | ||||
| 		id := node.lockeds[i] | ||||
|  | @ -4555,18 +4569,15 @@ pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type { | |||
| 		} else if id.name in c.rlocked_names { | ||||
| 			c.error('`$id.name` is already read-locked', id.pos) | ||||
| 		} | ||||
| 		if node.is_rlock { | ||||
| 		if node.is_rlock[i] { | ||||
| 			c.rlocked_names << id.name | ||||
| 		} else { | ||||
| 			c.locked_names << id.name | ||||
| 		} | ||||
| 	} | ||||
| 	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] | ||||
| 	} | ||||
| 	c.rlocked_names = [] | ||||
| 	c.locked_names = [] | ||||
| 	// void for now... maybe sometime `x := lock a { a.getval() }`
 | ||||
| 	return table.void_type | ||||
| } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| vlib/v/checker/tests/lock_already_locked.vv:11:9: error: `a` is already locked  | ||||
| vlib/v/checker/tests/lock_already_locked.vv:11:3: error: nested `lock`/`rlock` not allowed | ||||
|     9 |     } | ||||
|    10 |     lock a { | ||||
|    11 |         rlock a { | ||||
|       |               ^ | ||||
|       |         ~~~~~ | ||||
|    12 |             a.x++ | ||||
|    13 |         } | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| vlib/v/checker/tests/lock_already_rlocked.vv:11:8: error: `a` is already read-locked  | ||||
| vlib/v/checker/tests/lock_already_rlocked.vv:11:3: error: nested `lock`/`rlock` not allowed | ||||
|     9 |     } | ||||
|    10 |     rlock a { | ||||
|    11 |         lock a { | ||||
|       |              ^ | ||||
|       |         ~~~~ | ||||
|    12 |             a.x++ | ||||
|    13 |         } | ||||
|  |  | |||
|  | @ -26,14 +26,14 @@ vlib/v/checker/tests/shared_bad_args.vv:51:13: error: a is `shared` and must be | |||
|       |                ^ | ||||
|    52 |     println('$w $x') | ||||
|    53 | } | ||||
| vlib/v/checker/tests/shared_bad_args.vv:61:3: error: r is `shared` and must be `lock`ed to be passed as `mut` | ||||
| vlib/v/checker/tests/shared_bad_args.vv:61:3: error: r must be added to the `lock` list above | ||||
|    59 |     shared r := Qr{ a: 7 } | ||||
|    60 |     lock s { | ||||
|    61 |         r.s_mut(mut s) | ||||
|       |         ^ | ||||
|    62 |     } | ||||
|    63 |     lock r { | ||||
| vlib/v/checker/tests/shared_bad_args.vv:64:15: error: s is `shared` and must be `lock`ed to be passed as `mut` | ||||
| vlib/v/checker/tests/shared_bad_args.vv:64:15: error: s must be added to the `lock` list above | ||||
|    62 |     } | ||||
|    63 |     lock r { | ||||
|    64 |         r.s_mut(mut s) | ||||
|  |  | |||
|  | @ -1480,12 +1480,43 @@ pub fn (mut f Fmt) array_decompose(node ast.ArrayDecompose) { | |||
| } | ||||
| 
 | ||||
| pub fn (mut f Fmt) lock_expr(lex ast.LockExpr) { | ||||
| 	f.write(if lex.is_rlock { 'rlock ' } else { 'lock ' }) | ||||
| 	mut num_locked := 0 | ||||
| 	mut num_rlocked := 0 | ||||
| 	for is_rlock in lex.is_rlock { | ||||
| 		if is_rlock { | ||||
| 			num_rlocked++ | ||||
| 		} else { | ||||
| 			num_locked++ | ||||
| 		} | ||||
| 	} | ||||
| 	if num_locked > 0 || num_rlocked == 0 { | ||||
| 		f.write('lock ') | ||||
| 		mut n := 0 | ||||
| 		for i, v in lex.lockeds { | ||||
| 		if i > 0 { | ||||
| 			if !lex.is_rlock[i] { | ||||
| 				if n > 0 { | ||||
| 					f.write(', ') | ||||
| 				} | ||||
| 				f.expr(v) | ||||
| 				n++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if num_rlocked > 0 { | ||||
| 		if num_locked > 0 { | ||||
| 			f.write('; ') | ||||
| 		} | ||||
| 		f.write('rlock ') | ||||
| 		mut n := 0 | ||||
| 		for i, v in lex.lockeds { | ||||
| 			if lex.is_rlock[i] { | ||||
| 				if n > 0 { | ||||
| 					f.write(', ') | ||||
| 				} | ||||
| 				f.expr(v) | ||||
| 				n++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	f.write(' {') | ||||
| 	f.writeln('') | ||||
|  |  | |||
|  | @ -3405,22 +3405,54 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { | |||
| } | ||||
| 
 | ||||
| fn (mut g Gen) lock_expr(node ast.LockExpr) { | ||||
| 	mut lock_prefixes := []string{len: 0, cap: node.lockeds.len} | ||||
| 	for id in node.lockeds { | ||||
| 	mut mtxs := '' | ||||
| 	if node.lockeds.len == 0 { | ||||
| 		// this should not happen
 | ||||
| 	} else if node.lockeds.len == 1 { | ||||
| 		id := node.lockeds[0] | ||||
| 		name := id.name | ||||
| 		deref := if id.is_mut { '->' } else { '.' } | ||||
| 		lock_prefix := if node.is_rlock { 'r' } else { '' } | ||||
| 		lock_prefixes << lock_prefix // keep for unlock
 | ||||
| 		lock_prefix := if node.is_rlock[0] { 'r' } else { '' } | ||||
| 		g.writeln('sync__RwMutex_${lock_prefix}lock(&$name${deref}mtx);') | ||||
| 	} else { | ||||
| 		mtxs = g.new_tmp_var() | ||||
| 		g.writeln('uintptr_t _arr_$mtxs[$node.lockeds.len];') | ||||
| 		g.writeln('bool _isrlck_$mtxs[$node.lockeds.len];') | ||||
| 		for i, id in node.lockeds { | ||||
| 			name := id.name | ||||
| 			deref := if id.is_mut { '->' } else { '.' } | ||||
| 			g.writeln('_arr_$mtxs[$i] = &$name${deref}mtx;') | ||||
| 			// TODO: fix `vfmt` to allow this in string interpolation
 | ||||
| 			is_rlock_str := node.is_rlock[i].str() | ||||
| 			g.writeln('_isrlck_$mtxs[$i] = $is_rlock_str;') | ||||
| 		} | ||||
| 		g.writeln('__sort_ptr(_arr_$mtxs, _isrlck_$mtxs, $node.lockeds.len);') | ||||
| 		g.writeln('for (int $mtxs=0; $mtxs<$node.lockeds.len; $mtxs++) {') | ||||
| 		g.writeln('\tif ($mtxs && _arr_$mtxs[$mtxs] == _arr_$mtxs[$mtxs-1]) continue;') | ||||
| 		g.writeln('\tif (_isrlck_$mtxs[$mtxs])') | ||||
| 		g.writeln('\t\tsync__RwMutex_rlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') | ||||
| 		g.writeln('\telse') | ||||
| 		g.writeln('\t\tsync__RwMutex_lock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') | ||||
| 		g.writeln('}') | ||||
| 	} | ||||
| 	g.stmts(node.stmts) | ||||
| 	// unlock in reverse order
 | ||||
| 	for i := node.lockeds.len - 1; i >= 0; i-- { | ||||
| 		id := node.lockeds[i] | ||||
| 		lock_prefix := lock_prefixes[i] | ||||
| 	if node.lockeds.len == 0 { | ||||
| 		// this should not happen
 | ||||
| 	} else if node.lockeds.len == 1 { | ||||
| 		id := node.lockeds[0] | ||||
| 		name := id.name | ||||
| 		deref := if id.is_mut { '->' } else { '.' } | ||||
| 		lock_prefix := if node.is_rlock[0] { 'r' } else { '' } | ||||
| 		g.writeln('sync__RwMutex_${lock_prefix}unlock(&$name${deref}mtx);') | ||||
| 	} else { | ||||
| 		// unlock in reverse order
 | ||||
| 		g.writeln('for (int $mtxs=${node.lockeds.len - 1}; $mtxs>=0; $mtxs--) {') | ||||
| 		g.writeln('\tif ($mtxs && _arr_$mtxs[$mtxs] == _arr_$mtxs[$mtxs-1]) continue;') | ||||
| 		g.writeln('\tif (_isrlck_$mtxs[$mtxs])') | ||||
| 		g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') | ||||
| 		g.writeln('\telse') | ||||
| 		g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') | ||||
| 		g.writeln('}') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -31,6 +31,21 @@ static inline voidptr __dup_shared_array(voidptr src, int sz) { | |||
| 	sync__RwMutex_init(&dest->mtx); | ||||
| 	return dest; | ||||
| } | ||||
| static inline void __sort_ptr(uintptr_t a[], bool b[], int l) | ||||
| { | ||||
| 	for (int i=1; i<l; i++) { | ||||
| 		uintptr_t ins = a[i]; | ||||
| 		bool insb = b[i]; | ||||
| 		int j = i; | ||||
| 		while(j>0 && (a[j-1] > ins || b[j-1] && !insb)) { | ||||
| 			a[j] = a[j-1]; | ||||
| 			b[j] = b[j-1]; | ||||
| 			j--; | ||||
| 		} | ||||
| 		a[j] = ins; | ||||
| 		b[j] = insb; | ||||
| 	} | ||||
| } | ||||
| ' | ||||
| 	c_common_macros               = ' | ||||
| #define EMPTY_VARG_INITIALIZATION 0 | ||||
|  |  | |||
|  | @ -7,9 +7,17 @@ fn (mut p Parser) lock_expr() ast.LockExpr { | |||
| 	// TODO Handle aliasing sync
 | ||||
| 	p.register_auto_import('sync') | ||||
| 	mut pos := p.tok.position() | ||||
| 	is_rlock := p.tok.kind == .key_rlock | ||||
| 	p.next() | ||||
| 	mut lockeds := []ast.Ident{} | ||||
| 	mut is_rlocked := []bool{} | ||||
| 	for { | ||||
| 		if p.tok.kind == .lcbr { | ||||
| 			goto start_stmts | ||||
| 		} | ||||
| 		is_rlock := p.tok.kind == .key_rlock | ||||
| 		if !is_rlock && p.tok.kind != .key_lock { | ||||
| 			p.error_with_pos('unexpected `$p.tok.lit`; `lock` or `rlock` expected', p.tok.position()) | ||||
| 		} | ||||
| 		p.next() | ||||
| 		for p.tok.kind == .name { | ||||
| 			lockeds << ast.Ident{ | ||||
| 				language: table.Language.v | ||||
|  | @ -20,18 +28,25 @@ fn (mut p Parser) lock_expr() ast.LockExpr { | |||
| 				info: ast.IdentVar{} | ||||
| 				scope: p.scope | ||||
| 			} | ||||
| 			is_rlocked << is_rlock | ||||
| 			p.next() | ||||
| 			if p.tok.kind == .lcbr { | ||||
| 				goto start_stmts | ||||
| 			} | ||||
| 			if p.tok.kind == .semicolon { | ||||
| 				p.next() | ||||
| 				break | ||||
| 			} | ||||
| 			p.check(.comma) | ||||
| 		} | ||||
| 	} | ||||
| 	start_stmts: | ||||
| 	stmts := p.parse_block() | ||||
| 	pos.update_last_line(p.prev_tok.line_nr) | ||||
| 	return ast.LockExpr{ | ||||
| 		lockeds: lockeds | ||||
| 		stmts: stmts | ||||
| 		is_rlock: is_rlock | ||||
| 		is_rlock: is_rlocked | ||||
| 		pos: pos | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,68 @@ | |||
| struct St { | ||||
| mut: | ||||
| 	a int | ||||
| } | ||||
| 
 | ||||
| fn (shared x St) f(shared y St, shared z St) { | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		lock x, y, z { | ||||
| 			tmp := z.a | ||||
| 			z.a = y.a | ||||
| 			y.a = x.a | ||||
| 			x.a = tmp | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (shared x St) g(shared y St, shared z St) { | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		lock z, x, y { | ||||
| 			tmp := x.a | ||||
| 			x.a = z.a | ||||
| 			z.a = y.a | ||||
| 			y.a = tmp | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn h(shared x St, shared y St, shared z St) { | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		lock y, x, z { | ||||
| 			tmp := y.a | ||||
| 			y.a = z.a | ||||
| 			z.a = x.a | ||||
| 			x.a = tmp | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn test_shared_receiver_lock() { | ||||
| 	shared x := &St{ | ||||
| 		a: 5 | ||||
| 	} | ||||
| 	shared y := &St{ | ||||
| 		a: 7 | ||||
| 	} | ||||
| 	shared z := &St{ | ||||
| 		a: 1 | ||||
| 	} | ||||
| 	t1 := go x.f(shared y, shared z) | ||||
| 	t2 := go x.f(shared y, shared z) | ||||
| 	t3 := go h(shared x, shared y, shared z) | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		lock z, y, x { | ||||
| 			tmp := y.a | ||||
| 			y.a = x.a | ||||
| 			x.a = z.a | ||||
| 			z.a = tmp | ||||
| 		} | ||||
| 	} | ||||
| 	t1.wait() | ||||
| 	t2.wait() | ||||
| 	t3.wait() | ||||
| 	rlock x, y, z{ | ||||
| 		assert x.a == 7 | ||||
| 		assert y.a == 1 | ||||
| 		assert z.a == 5 | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,59 @@ | |||
| struct St { | ||||
| mut: | ||||
| 	a int | ||||
| } | ||||
| 
 | ||||
| fn (shared x St) f(shared y St, shared z St) { | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		lock x; rlock y, z { | ||||
| 			x.a = y.a + z.a | ||||
| 			if x.a > 1000000 { | ||||
| 				x.a /= 2 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (shared x St) g(shared y St, shared z St) { | ||||
| 	for _ in 0 .. 10000 { | ||||
| 		rlock x; lock y, z { | ||||
| 			y.a += x.a | ||||
| 			if y.a > 1000000 { | ||||
| 				y.a /= 2 | ||||
| 			} | ||||
| 			z.a += x.a | ||||
| 			if z.a > 1000000 { | ||||
| 				z.a /= 2 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn test_shared_receiver_lock() { | ||||
| 	shared x := St{ | ||||
| 		a: 5 | ||||
| 	} | ||||
| 	shared y := St{ | ||||
| 		a: 7 | ||||
| 	} | ||||
| 	shared z := St{ | ||||
| 		a: 103 | ||||
| 	} | ||||
| 	t1 := go x.f(shared y, shared x) | ||||
| 	t2 := go y.f(shared y, shared z) | ||||
| 	t3 := go z.g(shared x, shared z) | ||||
| 	t4 := go x.g(shared x, shared x) | ||||
| 	t5 := go z.f(shared z, shared z) | ||||
| 	t1.wait() | ||||
| 	t2.wait() | ||||
| 	t3.wait() | ||||
| 	t4.wait() | ||||
| 	t5.wait() | ||||
| 	// the result is unpredictable - but should be positive
 | ||||
| 	rlock x, y, z { | ||||
| 		assert x.a > 10000 | ||||
| 		assert y.a > 10000 | ||||
| 		assert z.a > 10000 | ||||
| 		println('$x.a $y.a $z.a') | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,66 @@ | |||
| // integer values from -2^191 .. 2^191-1
 | ||||
| struct Large { | ||||
| mut: | ||||
| 	l u64 | ||||
| 	m u64 | ||||
| 	h u64 | ||||
| } | ||||
| 
 | ||||
| fn (a Large) clone() Large { | ||||
| 	r := Large{ l: a.l, m: a.m h: a.h } | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| fn (mut a Large) add(b Large) { | ||||
| 	oldl := a.l | ||||
| 	a.l += b.l | ||||
| 	oldm := a.m | ||||
| 	if a.l < oldl { a.m++ } | ||||
| 	a.m += b.m | ||||
| 	if a.m < oldm || (a.l < oldl && a.m <= oldm) { a.h++ } | ||||
| 	a.h += b.h | ||||
| } | ||||
| 
 | ||||
| fn doub_large(shared a Large, shared b Large, shared c Large, shared d Large, shared e Large) { | ||||
| 	for _ in 0..50 { | ||||
| 		lock a, b; rlock c, d, e { | ||||
| 			// double the sum by adding all objects to a or b
 | ||||
| 			old_a := a.clone() | ||||
| 			a.add(b) | ||||
| 			b.add(old_a) | ||||
| 			a.add(c) | ||||
| 			b.add(d) | ||||
| 			a.add(e) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn test_mixed_order_lock_rlock() { | ||||
| 	// initialze objects so that their sum = 1
 | ||||
| 	shared a := Large{ l: 4 } | ||||
| 	shared b := Large{ l: u64(-7), m: u64(-1) h: u64(-1) } | ||||
| 	shared c := Large{ l: 17 } | ||||
| 	shared d := Large{ l: u64(-11), m: u64(-1) h: u64(-1) } | ||||
| 	shared e := Large{ l: u64(-2), m: u64(-1) h: u64(-1) } | ||||
| 	// spawn 3 threads for doubling with different orders of objects
 | ||||
| 	t1 := go doub_large(shared a, shared b, shared c, shared d, shared e) | ||||
| 	t2 := go doub_large(shared e, shared b, shared a, shared c, shared d) | ||||
| 	t3 := go doub_large(shared b, shared a, shared e, shared d, shared c) | ||||
| 	t1.wait() | ||||
| 	t2.wait() | ||||
| 	t3.wait() | ||||
| 	// calculate the sum after 3*50 doublings
 | ||||
| 	mut sum := Large{} | ||||
| 	rlock a, b, c, d, e { | ||||
| 		sum.add(a) | ||||
| 		sum.add(b) | ||||
| 		sum.add(c) | ||||
| 		sum.add(d) | ||||
| 		sum.add(e) | ||||
| 	} | ||||
| 	// the sum should be 2^150
 | ||||
| 	assert sum.l == 0 | ||||
| 	assert sum.m == 0 | ||||
| 	assert sum.h == 4194304 | ||||
| } | ||||
| 
 | ||||
		Loading…
	
		Reference in New Issue