Skip to content

Commit 0581c58

Browse files
committed
wasm: fix function-value calls for fn consts, expression callees, and eval order
Address the three review comments on #27532: - fn consts (`const cb = add1; cb(1)`): push_fn_value did `scope.find(node.name)`, but a fn const lives in the global scope under its qualified name (node.const_name), so the lexical lookup missed and aborted with 'cannot resolve function value'. Resolve is_fn_a_const callees through node.const_name instead. - expression callees that return a function (`make_cb()(1)`): the checker leaves is_fn_var false here, with an empty name and a fn-typed left_type, so detection fell through to call('') and panicked. Detect name=='' with a fn-typed left_type as a function value and dispatch via call_indirect. - evaluation order (`make_box().op(next())`): the callee was evaluated after the argument loop, reversing V's left-to-right order for callees with side effects. Evaluate the callee's table index into a temp local before the args and reload it on top for call_indirect. Extend tests/fn_value.vv with all three scenarios (covering struct returns too).
1 parent 38398a2 commit 0581c58

3 files changed

Lines changed: 78 additions & 8 deletions

File tree

vlib/v/gen/wasm/gen.v

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -809,10 +809,23 @@ fn (mut g Gen) push_fn_value(node ast.CallExpr, fn_typ ast.Type) {
809809
typ: fn_typ
810810
}, fn_typ)
811811
} else if node.name == '' {
812-
// the callee is the expression itself, e.g. `(expr)(x)`
812+
// the callee is the expression itself, e.g. `(expr)(x)` or `make_cb()(x)`
813813
g.expr(node.left, fn_typ)
814+
} else if node.is_fn_a_const {
815+
// a const of function type. The const is registered in the global scope
816+
// under its fully-qualified name (`node.const_name`, e.g. `main.cb`), not
817+
// the lexical call name, so a plain `scope.find(node.name)` misses it.
818+
obj := g.table.global_scope.find(node.const_name) or {
819+
g.w_error('wasm: cannot resolve function const `${node.const_name}`')
820+
}
821+
v := g.get_var_from_ident(ast.Ident{
822+
name: node.const_name
823+
scope: node.scope
824+
obj: obj
825+
})
826+
g.get(v)
814827
} else {
815-
// a named local/global variable or const of function type
828+
// a named local/param variable of function type
816829
obj := node.scope.find(node.name) or {
817830
g.w_error('wasm: cannot resolve function value `${node.name}`')
818831
}
@@ -830,8 +843,10 @@ pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvar
830843
mut name := node.name
831844

832845
// Detect a call to a function value: either a fn-typed local/param/const
833-
// (is_fn_var/is_fn_a_const), or a fn-typed struct field, which the checker
834-
// represents as a method call (`b.op()`).
846+
// (is_fn_var/is_fn_a_const), a fn-typed struct field, which the checker
847+
// represents as a method call (`b.op()`), or an expression callee that
848+
// evaluates to a function (`make_cb()(x)`, `(expr)(x)`), which the checker
849+
// leaves with an empty name and a fn-typed `left_type`.
835850
mut is_fn_value := false
836851
mut fn_value_typ := ast.void_type
837852
if node.is_fn_var || node.is_fn_a_const {
@@ -844,6 +859,11 @@ pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvar
844859
fn_value_typ = field.typ
845860
}
846861
}
862+
} else if node.name == '' && node.left_type != 0 {
863+
if g.table.final_sym(node.left_type).info is ast.FnType {
864+
is_fn_value = true
865+
fn_value_typ = node.left_type
866+
}
847867
}
848868

849869
is_print := name in ['panic', 'println', 'print', 'eprintln', 'eprint']
@@ -902,6 +922,21 @@ pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvar
902922
}
903923
}
904924

925+
// {callee}
926+
//
927+
// Evaluate the callee of a function-value call *before* its arguments, so a
928+
// callee with side effects (e.g. `make_box().op(next())`) keeps V's
929+
// left-to-right evaluation order. `call_indirect` wants the table index on
930+
// top of the stack (after the args), so stash it in a temp and reload it
931+
// once the arguments are in place.
932+
mut fn_value_idx := Var{}
933+
if is_fn_value {
934+
g.is_leaf_function = false
935+
fn_value_idx = g.new_local('', fn_value_typ)
936+
g.push_fn_value(node, fn_value_typ)
937+
g.set(fn_value_idx)
938+
}
939+
905940
// {arguments}
906941
//
907942
for idx, arg in node.args {
@@ -935,11 +970,10 @@ pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvar
935970
}
936971

937972
if is_fn_value {
938-
// args are already on the stack; push the callee's table index on top,
939-
// then dispatch through the indirect function table
940-
g.is_leaf_function = false
973+
// args are already on the stack; reload the callee's table index (computed
974+
// before the args, above) on top, then dispatch through the indirect table
941975
typeidx := g.mod.new_functype(g.fn_value_functype(fn_value_typ))
942-
g.push_fn_value(node, fn_value_typ)
976+
g.get(fn_value_idx)
943977
g.func.call_indirect(typeidx, 0)
944978
} else if namespace := wasm_ns {
945979
// import calls won't touch `__vsp` !

vlib/v/gen/wasm/tests/fn_value.vv

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@ fn apply(f fn (int) int, x int) int {
1414
return f(x)
1515
}
1616

17+
// a fn-typed const, called through the const name
18+
const cb = add1
19+
20+
// make_cb returns a function value, so `make_cb()(x)` is an expression callee
21+
fn make_cb() fn (int) int {
22+
return double
23+
}
24+
25+
// logged_box / logged_arg make the evaluation order observable: the callee
26+
// (the receiver) must be evaluated before the argument.
27+
fn logged_box() Box {
28+
println('callee')
29+
return Box{
30+
op: add1
31+
}
32+
}
33+
34+
fn logged_arg() int {
35+
println('arg')
36+
return 100
37+
}
38+
1739
fn main() {
1840
// a top-level fn taken as a local fn value, then called through the value
1941
f := add1
@@ -39,4 +61,13 @@ fn main() {
3961
println(g(0)) // 1
4062
g = double
4163
println(g(50)) // 100
64+
65+
// a fn-typed const, called through the const name
66+
println(cb(99)) // 100
67+
68+
// an expression callee that evaluates to a function value
69+
println(make_cb()(5)) // 10
70+
71+
// the callee is evaluated before the argument: prints `callee`, then `arg`
72+
println(logged_box().op(logged_arg())) // 101
4273
}

vlib/v/gen/wasm/tests/fn_value.vv.out

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
49
55
1
66
100
7+
100
8+
10
9+
callee
10+
arg
11+
101

0 commit comments

Comments
 (0)