cgen: implement channel `select` block code generation (#6424)

pull/6425/head
Uwe Krüger 2020-09-19 02:14:35 +02:00 committed by GitHub
parent 5782f562ae
commit 31a52cdf21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 247 additions and 4 deletions

View File

@ -0,0 +1,97 @@
import time
import sync
struct St {
a int
}
fn f1(ch1 chan int, ch2 chan St, ch3 chan int, sem sync.Semaphore) {
mut a := 5
st := St{}
select {
a = <-ch3 {
a = 0
}
b := <-ch2 {
a = b.a
}
ch2 <- st {
a = 2
}
> 300 * time.millisecond {
a = 3
}
}
assert a == 3
sem.post()
}
fn f2(ch1 chan St, ch2 chan int, sem sync.Semaphore) {
mut r := 23
for i in 0 .. 2 {
select {
b := <- ch1 {
r = b.a
}
ch2 <- r {
r = 17
}
}
if i == 0 {
assert r == 17
} else {
assert r == 13
}
}
sem.post()
}
fn test_select_blocks() {
ch1 := chan int{cap: 1}
ch2 := chan St{}
ch3 := chan int{}
sem := sync.new_semaphore()
mut r := false
t := select {
b := <- ch1 {
println(b)
}
else {
// no channel ready
r = true
}
}
assert r == true
assert t == true
go f2(ch2, ch3, sem)
n := <-ch3
assert n == 23
ch2 <- St{a: 13}
sem.wait()
stopwatch := time.new_stopwatch({})
go f1(ch1, ch2, ch3, sem)
sem.wait()
elapsed_ms := f64(stopwatch.elapsed()) / time.millisecond
assert elapsed_ms >= 295.0
ch1.close()
ch2.close()
mut h := 7
u := select {
b := <- ch2 {
h = 0
}
ch1 <- h {
h = 1
}
else {
h = 2
}
}
// no branch should have run
assert h == 7
// since all channels are closed `select` should return `false`
assert u == false
}

View File

@ -536,9 +536,9 @@ pub struct SelectExpr {
pub:
branches []SelectBranch
pos token.Position
has_exception bool
pub mut:
is_expr bool // returns a value
return_type table.Type
expected_type table.Type // for debugging only
}

View File

@ -2656,8 +2656,7 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type {
return table.void_type
}
ast.SelectExpr {
// TODO: to be implemented
return table.void_type
return c.select_expr(mut node)
}
ast.SelectorExpr {
return c.selector_expr(mut node)
@ -3060,6 +3059,16 @@ 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) select_expr(mut node ast.SelectExpr) table.Type {
node.is_expr = c.expected_type != table.void_type
node.expected_type = c.expected_type
for branch in node.branches {
c.stmt(branch.stmt)
c.stmts(branch.stmts)
}
return table.bool_type
}
pub fn (mut c Checker) lock_expr(mut node ast.LockExpr) table.Type {
for id in node.lockeds {
c.ident(mut id)

View File

@ -2129,7 +2129,7 @@ fn (mut g Gen) expr(node ast.Expr) {
// Only used in IndexExpr
}
ast.SelectExpr {
// TODO: to be implemented
g.select_expr(node)
}
ast.SizeOf {
if node.is_type {
@ -2739,6 +2739,142 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) {
}
}
fn (mut g Gen) select_expr(node ast.SelectExpr) {
is_expr := node.is_expr || g.inside_ternary > 0
cur_line := if is_expr {
g.empty_line = true
g.go_before_stmt(0)
} else {
''
}
n_channels := if node.has_exception { node.branches.len - 1 } else { node.branches.len }
mut channels := []ast.Expr{cap: n_channels}
mut objs := []ast.Expr{cap: n_channels}
mut tmp_objs := []string{cap: n_channels}
mut elem_types := []string{cap: n_channels}
mut is_push := []bool{cap: n_channels}
mut has_else := false
mut has_timeout := false
mut timeout_expr := ast.Expr{}
mut exception_branch := -1
for j, branch in node.branches {
if branch.is_else {
has_else = true
exception_branch = j
} else if branch.is_timeout {
has_timeout = true
exception_branch = j
timeout_expr = (branch.stmt as ast.ExprStmt).expr
} else {
match branch.stmt as stmt {
ast.ExprStmt {
expr := stmt.expr as ast.InfixExpr
channels << expr.left
objs << expr.right
tmp_objs << ''
elem_types << ''
is_push << true
}
ast.AssignStmt {
rec_expr := stmt.right[0] as ast.PrefixExpr
channels << rec_expr.right
is_push << false
// create tmp unless the object with *exactly* the type we need exists already
if stmt.op == .decl_assign || stmt.right_types[0] != stmt.left_types[0] {
tmp_obj := g.new_tmp_var()
tmp_objs << tmp_obj
el_stype := g.typ(stmt.right_types[0])
elem_types << if stmt.op == .decl_assign {
el_stype + ' '
} else {
''
}
g.writeln('$el_stype $tmp_obj;')
} else {
tmp_objs << ''
elem_types << ''
}
objs << stmt.left[0]
}
else {}
}
}
}
chan_array := g.new_tmp_var()
g.write('array_sync__Channel_ptr $chan_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Channel*), _MOV((sync__Channel*[$n_channels]){')
for i in 0 .. n_channels {
if i > 0 {
g.write(', ')
}
g.write('(sync__Channel*)(')
g.expr(channels[i])
g.write(')')
}
g.writeln('}));')
directions_array := g.new_tmp_var()
g.write('array_sync__Direction $directions_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Direction), _MOV((sync__Direction[$n_channels]){')
for i in 0 .. n_channels {
if i > 0 {
g.write(', ')
}
if is_push[i] {
g.write('sync__Direction_push')
} else {
g.write('sync__Direction_pop')
}
}
g.writeln('}));')
objs_array := g.new_tmp_var()
g.write('array_voidptr $objs_array = new_array_from_c_array($n_channels, $n_channels, sizeof(voidptr), _MOV((voidptr[$n_channels]){')
for i in 0 .. n_channels {
g.write(if i > 0 {
', &'
} else {
'&'
})
if tmp_objs[i] == '' {
g.expr(objs[i])
} else {
g.write(tmp_objs[i])
}
}
g.writeln('}));')
select_result := g.new_tmp_var()
g.write('int $select_result = sync__channel_select(&/*arr*/$chan_array, $directions_array, &/*arr*/$objs_array, ')
if has_timeout {
g.expr(timeout_expr)
} else if has_else {
g.write('0')
} else {
g.write('-1')
}
g.writeln(');')
mut i := 0
for j in 0 .. node.branches.len {
if j > 0 {
g.write('} else ')
}
g.write('if ($select_result == ')
if j == exception_branch {
g.writeln('-1) {')
} else {
g.writeln('$i) {')
if tmp_objs[i] != '' {
g.write('\t${elem_types[i]}')
g.expr(objs[i])
g.writeln(' = ${tmp_objs[i]};')
}
i++
}
g.stmts(node.branches[j].stmts)
}
g.writeln('}')
if is_expr {
g.write(cur_line)
g.write('($select_result != -2)')
}
}
fn (mut g Gen) ident(node ast.Ident) {
if node.name == 'lld' {
return

View File

@ -442,5 +442,6 @@ fn (mut p Parser) select_expr() ast.SelectExpr {
return ast.SelectExpr{
branches: branches
pos: pos
has_exception: has_else || has_timeout
}
}