all: initial support for closures (x64 / linux-only) (#11114)

pull/11134/head
Enzo 2021-08-10 20:27:15 +02:00 committed by GitHub
parent 2cfb8fd697
commit da53f818df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 607 additions and 185 deletions

View File

@ -97,6 +97,7 @@ const (
skip_on_windows = [
'vlib/orm/orm_test.v',
'vlib/v/tests/orm_sub_struct_test.v',
'vlib/v/tests/closure_test.v',
'vlib/net/websocket/ws_test.v',
'vlib/net/unix/unix_test.v',
'vlib/net/websocket/websocket_test.v',

View File

@ -22,8 +22,6 @@ pub type Stmt = AsmStmt | AssertStmt | AssignStmt | Block | BranchStmt | CompFor
GlobalDecl | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | NodeError |
Return | SqlStmt | StructDecl | TypeDecl
// NB: when you add a new Expr or Stmt type with a .pos field, remember to update
// the .position() token.Position methods too.
pub type ScopeObject = AsmRegister | ConstField | GlobalField | Var
// TODO: replace Param
@ -353,9 +351,10 @@ pub:
// anonymous function
pub struct AnonFn {
pub mut:
decl FnDecl
typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
has_gen bool // has been generated
decl FnDecl
inherited_vars []Param
typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated
has_gen bool // has been generated
}
// function or method declaration
@ -498,6 +497,7 @@ pub:
is_autofree_tmp bool
is_arg bool // fn args should not be autofreed
is_auto_deref bool
is_inherited bool
pub mut:
typ Type
orig_type Type // original sumtype type; 0 if it's not a sumtype
@ -1539,7 +1539,7 @@ pub fn (expr Expr) position() token.Position {
AnonFn {
return expr.decl.pos
}
EmptyExpr {
CTempVar, EmptyExpr {
// println('compiler bug, unhandled EmptyExpr position()')
return token.Position{}
}
@ -1571,9 +1571,6 @@ pub fn (expr Expr) position() token.Position {
last_line: right_pos.last_line
}
}
CTempVar {
return token.Position{}
}
// Please, do NOT use else{} here.
// This match is exhaustive *on purpose*, to help force
// maintaining/implementing proper .pos fields.

View File

@ -41,20 +41,6 @@ fn (s &Scope) dont_lookup_parent() bool {
return isnil(s.parent) || s.detached_from_parent
}
pub fn (s &Scope) find_with_scope(name string) ?(ScopeObject, &Scope) {
mut sc := s
for {
if name in sc.objects {
return sc.objects[name], sc
}
if sc.dont_lookup_parent() {
break
}
sc = sc.parent
}
return none
}
pub fn (s &Scope) find(name string) ?ScopeObject {
for sc := s; true; sc = sc.parent {
if name in sc.objects {
@ -82,14 +68,6 @@ pub fn (s &Scope) find_struct_field(name string, struct_type Type, field_name st
return none
}
pub fn (s &Scope) is_known(name string) bool {
if _ := s.find(name) {
return true
} else {
}
return false
}
pub fn (s &Scope) find_var(name string) ?&Var {
if obj := s.find(name) {
match obj {
@ -121,23 +99,16 @@ pub fn (s &Scope) find_const(name string) ?&ConstField {
}
pub fn (s &Scope) known_var(name string) bool {
if _ := s.find_var(name) {
return true
}
return false
s.find_var(name) or { return false }
return true
}
pub fn (mut s Scope) update_var_type(name string, typ Type) {
s.end_pos = s.end_pos // TODO mut bug
mut obj := s.objects[name]
match mut obj {
Var {
if obj.typ == typ {
return
}
if mut obj is Var {
if obj.typ != typ {
obj.typ = typ
}
else {}
}
}
@ -152,29 +123,10 @@ pub fn (mut s Scope) register_struct_field(name string, field ScopeStructField)
}
pub fn (mut s Scope) register(obj ScopeObject) {
name := if obj is ConstField {
obj.name
} else if obj is GlobalField {
obj.name
} else {
(obj as Var).name
}
if name == '_' {
if obj.name == '_' || obj.name in s.objects {
return
}
if name in s.objects {
// println('existing obect: $name')
return
}
s.objects[name] = obj
}
pub fn (s &Scope) outermost() &Scope {
mut sc := s
for !sc.dont_lookup_parent() {
sc = sc.parent
}
return sc
s.objects[obj.name] = obj
}
// returns the innermost scope containing pos
@ -211,6 +163,17 @@ pub fn (s &Scope) contains(pos int) bool {
return pos >= s.start_pos && pos <= s.end_pos
}
pub fn (s &Scope) has_inherited_vars() bool {
for _, obj in s.objects {
if obj is Var {
if obj.is_inherited {
return true
}
}
}
return false
}
pub fn (sc Scope) show(depth int, max_depth int) string {
mut out := ''
mut indent := ''

View File

@ -34,50 +34,61 @@ pub fn (node &CallExpr) fkey() string {
}
// These methods are used only by vfmt, vdoc, and for debugging.
pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) string {
mut f := strings.new_builder(30)
f.write_string('fn ')
if node.inherited_vars.len > 0 {
f.write_string('[')
for i, var in node.inherited_vars {
if i > 0 {
f.write_string(', ')
}
if var.is_mut {
f.write_string('mut ')
}
f.write_string(var.name)
}
f.write_string('] ')
}
stringify_fn_after_name(node.decl, mut f, t, cur_mod, m2a)
return f.str()
}
pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string {
mut f := strings.new_builder(30)
if node.is_pub {
f.write_string('pub ')
}
mut receiver := ''
f.write_string('fn ')
if node.is_method {
f.write_string('(')
mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)),
cur_mod)
m := if node.rec_mut { node.receiver.typ.share().str() + ' ' } else { '' }
if node.rec_mut {
f.write_string(node.receiver.typ.share().str() + ' ')
styp = styp[1..] // remove &
}
f.write_string(node.receiver.name + ' ')
styp = util.no_cur_mod(styp, cur_mod)
if node.params[0].is_auto_rec {
styp = styp.trim('&')
}
receiver = '($m$node.receiver.name $styp) '
/*
sym := t.get_type_symbol(node.receiver.typ)
name := sym.name.after('.')
mut m := if node.rec_mut { 'mut ' } else { '' }
if !node.rec_mut && node.receiver.typ.is_ptr() {
m = '&'
}
receiver = '($node.receiver.name $m$name) '
*/
f.write_string(styp + ') ')
}
mut name := if node.is_anon { '' } else { node.name }
if !node.is_anon && !node.is_method && node.language == .v {
name = node.name.all_after_last('.')
name := if !node.is_method && node.language == .v {
node.name.all_after_last('.')
} else {
node.name
}
// mut name := if node.is_anon { '' } else { node.name.after_char(`.`) }
// if !node.is_method {
// if node.language == .c {
// name = 'C.$name'
// } else if node.language == .js {
// name = 'JS.$name'
// }
// }
f.write_string('fn $receiver$name')
f.write_string(name)
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] {
f.write_string(' ')
}
stringify_fn_after_name(node, mut f, t, cur_mod, m2a)
return f.str()
}
fn stringify_fn_after_name(node &FnDecl, mut f strings.Builder, t &Table, cur_mod string, m2a map[string]string) {
mut add_para_types := true
if node.generic_names.len > 0 {
if node.is_method {
@ -151,7 +162,6 @@ pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string)
}
f.write_string(' ' + rs)
}
return f.str()
}
// Expressions in string interpolations may have to be put in braces if they

View File

@ -2737,7 +2737,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
}
call_expr.is_noreturn = func.is_noreturn
if !found_in_args {
if _ := call_expr.scope.find_var(fn_name) {
if call_expr.scope.known_var(fn_name) {
c.error('ambiguous call to: `$fn_name`, may refer to fn `$fn_name` or variable `$fn_name`',
call_expr.pos)
}
@ -5210,14 +5210,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
return node.typ
}
ast.AnonFn {
c.inside_anon_fn = true
keep_fn := c.table.cur_fn
c.table.cur_fn = unsafe { &node.decl }
c.stmts(node.decl.stmts)
c.fn_decl(mut node.decl)
c.table.cur_fn = keep_fn
c.inside_anon_fn = false
return node.typ
return c.anon_fn(mut node)
}
ast.ArrayDecompose {
typ := c.expr(node.expr)
@ -8167,6 +8160,30 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
node.source_file = c.file
}
fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type {
keep_fn := c.table.cur_fn
keep_inside_anon := c.inside_anon_fn
defer {
c.table.cur_fn = keep_fn
c.inside_anon_fn = keep_inside_anon
}
c.table.cur_fn = unsafe { &node.decl }
c.inside_anon_fn = true
for mut var in node.inherited_vars {
parent_var := node.decl.scope.parent.find_var(var.name) or {
panic('unexpected checker error: cannot find parent of inherited variable `$var.name`')
}
if var.is_mut && !parent_var.is_mut {
c.error('original `$parent_var.name` is immutable, declare it with `mut` to make it mutable',
var.pos)
}
var.typ = parent_var.typ
}
c.stmts(node.decl.stmts)
c.fn_decl(mut node.decl)
return node.typ
}
// NB: has_top_return/1 should be called on *already checked* stmts,
// which do have their stmt.expr.is_noreturn set properly:
fn has_top_return(stmts []ast.Stmt) bool {

View File

@ -0,0 +1,21 @@
vlib/v/checker/tests/closure_immutable.vv:4:3: error: `a` is immutable, declare it with `mut` to make it mutable
2 | a := 1
3 | f1 := fn [a] () {
4 | a++
| ^
5 | println(a)
6 | }
vlib/v/checker/tests/closure_immutable.vv:7:16: error: original `a` is immutable, declare it with `mut` to make it mutable
5 | println(a)
6 | }
7 | f2 := fn [mut a] () {
| ^
8 | a++
9 | println(a)
vlib/v/checker/tests/closure_immutable.vv:13:3: error: `b` is immutable, declare it with `mut` to make it mutable
11 | mut b := 2
12 | f3 := fn [b] () {
13 | b++
| ^
14 | println(b)
15 | }

View File

@ -0,0 +1,19 @@
fn my_fn() {
a := 1
f1 := fn [a] () {
a++
println(a)
}
f2 := fn [mut a] () {
a++
println(a)
}
mut b := 2
f3 := fn [b] () {
b++
println(b)
}
f1()
f2()
f3()
}

View File

@ -391,8 +391,7 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) {
eprintln('stmt: ${node.pos:-42} | node: ${node.type_name():-20}')
}
match node {
ast.NodeError {}
ast.EmptyStmt {}
ast.EmptyStmt, ast.NodeError {}
ast.AsmStmt {
f.asm_stmt(node)
}
@ -491,7 +490,7 @@ pub fn (mut f Fmt) expr(node ast.Expr) {
ast.NodeError {}
ast.EmptyExpr {}
ast.AnonFn {
f.fn_decl(node.decl)
f.anon_fn(node)
}
ast.ArrayDecompose {
f.array_decompose(node)
@ -868,6 +867,15 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) {
pub fn (mut f Fmt) fn_decl(node ast.FnDecl) {
f.attrs(node.attrs)
f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
f.fn_body(node)
}
pub fn (mut f Fmt) anon_fn(node ast.AnonFn) {
f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast
f.fn_body(node.decl)
}
fn (mut f Fmt) fn_body(node ast.FnDecl) {
if node.language == .v {
if !node.no_body {
f.write(' {')
@ -1007,7 +1015,7 @@ pub fn (mut f Fmt) global_decl(node ast.GlobalDecl) {
pub fn (mut f Fmt) go_expr(node ast.GoExpr) {
f.write('go ')
f.expr(node.call_expr)
f.call_expr(node.call_expr)
}
pub fn (mut f Fmt) goto_label(node ast.GotoLabel) {
@ -1500,7 +1508,7 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) {
} else {
f.write_language_prefix(node.language)
if node.left is ast.AnonFn {
f.fn_decl(node.left.decl)
f.anon_fn(node.left)
} else if node.language != .v {
f.write('${node.name.after_char(`.`)}')
} else {

View File

@ -0,0 +1,10 @@
my_var := 1
my_simple_closure := fn [my_var] () {
println(my_var)
}
my_closure_returns := fn [my_var] () int {
return my_var
}
my_closure_has_params := fn [my_var, my_closure_returns] (add int) {
println(my_var + my_closure_returns() + add)
}

View File

@ -180,6 +180,7 @@ mut:
defer_vars []string
anon_fn bool
array_sort_fn map[string]bool
nr_closures int
}
pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
@ -381,6 +382,10 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
}
}
if g.anon_fn_definitions.len > 0 {
if g.nr_closures > 0 {
b.writeln('\n// V closure helpers')
b.writeln(c_closure_helpers())
}
for fn_def in g.anon_fn_definitions {
b.writeln(fn_def)
}
@ -613,9 +618,7 @@ fn (mut g Gen) generic_fn_name(types []ast.Type, before string, is_decl bool) st
fn (mut g Gen) expr_string(expr ast.Expr) string {
pos := g.out.len
g.expr(expr)
expr_str := g.out.after(pos)
g.out.go_back(expr_str.len)
return expr_str.trim_space()
return g.out.cut_to(pos).trim_space()
}
// Surround a potentially multi-statement expression safely with `prepend` and `append`.
@ -623,13 +626,13 @@ fn (mut g Gen) expr_string(expr ast.Expr) string {
fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string {
pos := g.out.len
g.stmt_path_pos << pos
defer {
g.stmt_path_pos.delete_last()
}
g.write(prepend)
g.expr(expr)
g.write(append)
expr_str := g.out.after(pos)
g.out.go_back(expr_str.len)
g.stmt_path_pos.delete_last()
return expr_str
return g.out.cut_to(pos)
}
// TODO this really shouldnt be seperate from typ
@ -823,8 +826,7 @@ pub fn (mut g Gen) write_typedef_types() {
if elem_sym.info is ast.FnType {
pos := g.out.len
g.write_fn_ptr_decl(&elem_sym.info, '')
fixed = g.out.after(pos)
g.out.go_back(fixed.len)
fixed = g.out.cut_to(pos)
mut def_str := 'typedef $fixed;'
def_str = def_str.replace_once('(*)', '(*$styp[$len])')
g.type_definitions.writeln(def_str)
@ -2623,7 +2625,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
ret_styp := g.typ(val.decl.return_type)
g.write('$ret_styp (*$ident.name) (')
def_pos := g.definitions.len
g.fn_args(val.decl.params, val.decl.is_variadic, voidptr(0))
g.fn_args(val.decl.params, voidptr(0))
g.definitions.go_back(g.definitions.len - def_pos)
g.write(') = ')
} else {
@ -2764,7 +2766,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
ret_styp := g.typ(func.func.return_type)
g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (')
def_pos := g.definitions.len
g.fn_args(func.func.params, func.func.is_variadic, voidptr(0))
g.fn_args(func.func.params, voidptr(0))
g.definitions.go_back(g.definitions.len - def_pos)
g.write(')')
} else {
@ -3084,6 +3086,10 @@ fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int
g.trace_autofree('// skipping tmp var "$obj.name"')
continue
}
if obj.is_inherited {
g.trace_autofree('// skipping inherited var "$obj.name"')
continue
}
// if var.typ == 0 {
// // TODO why 0?
// continue
@ -3194,19 +3200,6 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) {
}
}
fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
if !node.has_gen {
pos := g.out.len
g.anon_fn = true
g.stmt(node.decl)
g.anon_fn = false
fn_body := g.out.after(pos)
g.out.go_back(fn_body.len)
g.anon_fn_definitions << fn_body
node.has_gen = true
}
}
fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) {
mut hash_fn := ''
mut key_eq_fn := ''
@ -3270,10 +3263,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.error('g.expr(): unhandled EmptyExpr', token.Position{})
}
ast.AnonFn {
// TODO: dont fiddle with buffers
g.gen_anon_fn_decl(mut node)
fsym := g.table.get_type_symbol(node.typ)
g.write(fsym.name)
g.gen_anon_fn(mut node)
}
ast.ArrayDecompose {
g.expr(node.expr)
@ -4439,6 +4429,9 @@ fn (mut g Gen) ident(node ast.Ident) {
return
}
}
if v.is_inherited {
g.write(closure_ctx + '->')
}
}
} else if node_info is ast.IdentFn {
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
@ -5703,8 +5696,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
// optional and we dont want that
styp, base := g.optional_type_name(field.typ)
if styp !in g.optionals {
last_text := g.type_definitions.after(start_pos).clone()
g.type_definitions.go_back_to(start_pos)
last_text := g.type_definitions.cut_to(start_pos).clone()
g.optionals << styp
g.typedefs2.writeln('typedef struct $styp $styp;')
g.type_definitions.writeln('${g.optional_type_text(styp,
@ -5796,8 +5788,7 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
if elem_sym.info is ast.FnType {
pos := g.out.len
g.write_fn_ptr_decl(&elem_sym.info, '')
fixed_elem_name = g.out.after(pos)
g.out.go_back(fixed_elem_name.len)
fixed_elem_name = g.out.cut_to(pos)
mut def_str := 'typedef $fixed_elem_name;'
def_str = def_str.replace_once('(*)', '(*$styp[$len])')
g.type_definitions.writeln(def_str)
@ -5883,9 +5874,7 @@ fn (g &Gen) nth_stmt_pos(n int) int {
fn (mut g Gen) go_before_stmt(n int) string {
stmt_pos := g.nth_stmt_pos(n)
cur_line := g.out.after(stmt_pos)
g.out.go_back(cur_line.len)
return cur_line
return g.out.cut_to(stmt_pos)
}
[inline]
@ -6167,8 +6156,7 @@ fn (mut g Gen) go_expr(node ast.GoExpr) {
name = receiver_sym.name + '_' + name
} else if mut expr.left is ast.AnonFn {
g.gen_anon_fn_decl(mut expr.left)
fsym := g.table.get_type_symbol(expr.left.typ)
name = fsym.name
name = expr.left.decl.name
}
name = util.no_dots(name)
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
@ -6465,7 +6453,7 @@ fn (mut g Gen) interface_table() string {
arg := method.params[i]
methods_struct_def.write_string(', ${g.typ(arg.typ)} $arg.name')
}
// TODO g.fn_args(method.args[1..], method.is_variadic)
// TODO g.fn_args(method.args[1..])
methods_struct_def.writeln(');')
}
methods_struct_def.writeln('};')
@ -6603,7 +6591,7 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype
...params[0]
typ: params[0].typ.set_nr_muls(1)
}
fargs, _, _ := g.fn_args(params, false, voidptr(0)) // second argument is ignored anyway
fargs, _, _ := g.fn_args(params, voidptr(0))
methods_wrapper.write_string(g.out.cut_last(g.out.len - params_start_pos))
methods_wrapper.writeln(') {')
methods_wrapper.write_string('\t')

View File

@ -1,5 +1,7 @@
module c
import strings
// NB: @@@ here serve as placeholders.
// They will be replaced with correct strings
// for each constant, during C code generation.
@ -49,6 +51,82 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) {
}
'
// Heavily based on Chris Wellons's work
// https://nullprogram.com/blog/2017/01/08/
fn c_closure_helpers() string {
$if windows {
verror('closures are not implemented on Windows yet')
}
$if !x64 {
verror('closures are not implemented on this architecture yet')
}
mut builder := strings.new_builder(1024)
$if !windows {
builder.writeln('#include <sys/mman.h>')
}
$if x64 {
builder.write_string('
static unsigned char __closure_thunk[6][13] = {
{
0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
}, {
0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
}, {
0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
}, {
0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
}, {
0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
}, {
0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff,
0xff, 0x25, 0xeb, 0xff, 0xff, 0xff
},
};
')
}
builder.write_string('
static void __closure_set_data(void *closure, void *data) {
void **p = closure;
p[-2] = data;
}
static void __closure_set_function(void *closure, void *f) {
void **p = closure;
p[-1] = f;
}
')
$if !windows {
builder.write_string('
static void * __closure_create(void *f, int nargs, void *userdata) {
long page_size = sysconf(_SC_PAGESIZE);
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
char *p = mmap(0, page_size * 2, prot, flags, -1, 0);
if (p == MAP_FAILED)
return 0;
void *closure = p + page_size;
memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0]));
mprotect(closure, page_size, PROT_READ | PROT_EXEC);
__closure_set_function(closure, f);
__closure_set_data(closure, userdata);
return closure;
}
static void __closure_destroy(void *closure) {
long page_size = sysconf(_SC_PAGESIZE);
munmap((char *)closure - page_size, page_size * 2);
}
')
}
return builder.str()
}
const c_common_macros = '
#define EMPTY_VARG_INITIALIZATION 0
#define EMPTY_STRUCT_DECLARATION

View File

@ -3,6 +3,7 @@
// that can be found in the LICENSE file.
module c
import strings
import v.ast
import v.util
@ -39,7 +40,6 @@ fn (mut g Gen) process_fn_decl(node ast.FnDecl) {
return
}
g.gen_attrs(node.attrs)
// g.tmp_count = 0 TODO
mut skip := false
pos := g.out.len
should_bundle_module := util.should_bundle_module(node.mod)
@ -166,6 +166,15 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
g.table.cur_fn = node
}
fn_start_pos := g.out.len
is_closure := node.scope.has_inherited_vars()
mut cur_closure_ctx := ''
if is_closure {
cur_closure_ctx = closure_ctx_struct(node)
// declare the struct before its implementation
g.definitions.write_string(cur_closure_ctx)
g.definitions.writeln(';')
}
g.write_v_source_line_info(node.pos)
msvc_attrs := g.write_fn_attrs(node.attrs)
// Live
@ -264,7 +273,19 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
g.write(fn_header)
}
arg_start_pos := g.out.len
fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.is_variadic, node.scope)
fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.scope)
if is_closure {
mut s := '$cur_closure_ctx *$c.closure_ctx'
if node.params.len > 0 {
s = ', ' + s
} else {
// remove generated `void`
g.out.cut_to(arg_start_pos)
}
g.definitions.write_string(s)
g.write(s)
g.nr_closures++
}
arg_str := g.out.after(arg_start_pos)
if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin
&& !g.is_test) || skip {
@ -386,6 +407,59 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
}
}
const closure_ctx = '_V_closure_ctx'
fn closure_ctx_struct(node ast.FnDecl) string {
return 'struct _V_${node.name}_Ctx'
}
fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) {
g.gen_anon_fn_decl(mut node)
if !node.decl.scope.has_inherited_vars() {
g.write(node.decl.name)
return
}
// it may be possible to optimize `memdup` out if the closure never leaves current scope
ctx_var := g.new_tmp_var()
cur_line := g.go_before_stmt(0)
ctx_struct := closure_ctx_struct(node.decl)
g.writeln('$ctx_struct* $ctx_var = ($ctx_struct*) memdup(&($ctx_struct){')
g.indent++
for var in node.inherited_vars {
g.writeln('.$var.name = $var.name,')
}
g.indent--
g.writeln('}, sizeof($ctx_struct));')
g.empty_line = false
g.write(cur_line)
// TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data)
g.write('__closure_create($node.decl.name, ${node.decl.params.len + 1}, $ctx_var)')
}
fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
if node.has_gen {
return
}
node.has_gen = true
mut builder := strings.new_builder(256)
if node.inherited_vars.len > 0 {
ctx_struct := closure_ctx_struct(node.decl)
builder.writeln('$ctx_struct {')
for var in node.inherited_vars {
styp := g.typ(var.typ)
builder.writeln('\t$styp $var.name;')
}
builder.writeln('};\n')
}
pos := g.out.len
was_anon_fn := g.anon_fn
g.anon_fn = true
g.process_fn_decl(node.decl)
g.anon_fn = was_anon_fn
builder.write_string(g.out.cut_to(pos))
g.anon_fn_definitions << builder.str()
}
fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string {
return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn'
}
@ -405,7 +479,7 @@ fn (mut g Gen) write_defer_stmts_when_needed() {
}
// fn decl args
fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([]string, []string, []bool) {
fn (mut g Gen) fn_args(args []ast.Param, scope &ast.Scope) ([]string, []string, []bool) {
mut fargs := []string{}
mut fargtypes := []string{}
mut heap_promoted := []bool{}
@ -423,7 +497,7 @@ fn (mut g Gen) fn_args(args []ast.Param, is_variadic bool, scope &ast.Scope) ([]
func := info.func
g.write('${g.typ(func.return_type)} (*$caname)(')
g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(')
g.fn_args(func.params, func.is_variadic, voidptr(0))
g.fn_args(func.params, voidptr(0))
g.write(')')
g.definitions.write_string(')')
fargs << caname

View File

@ -633,9 +633,11 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
old_inside_defer := p.inside_defer
p.inside_defer = false
p.open_scope()
if !p.pref.backend.is_js() {
p.scope.detached_from_parent = true
defer {
p.close_scope()
}
p.scope.detached_from_parent = true
inherited_vars := if p.tok.kind == .lsbr { p.closure_vars() } else { []ast.Param{} }
// TODO generics
args, _, is_variadic := p.fn_args()
for arg in args {
@ -691,7 +693,6 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
p.label_names = tmp
}
p.cur_fn_name = keep_fn_name
p.close_scope()
func.name = name
idx := p.table.find_or_register_fn_type(p.mod, func, true, false)
typ := ast.new_type(idx)
@ -714,6 +715,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn {
scope: p.scope
label_names: label_names
}
inherited_vars: inherited_vars
typ: typ
}
}
@ -910,6 +912,50 @@ fn (mut p Parser) fn_args() ([]ast.Param, bool, bool) {
return args, types_only, is_variadic
}
fn (mut p Parser) closure_vars() []ast.Param {
p.check(.lsbr)
mut vars := []ast.Param{cap: 5}
for {
is_shared := p.tok.kind == .key_shared
is_atomic := p.tok.kind == .key_atomic
is_mut := p.tok.kind == .key_mut || is_shared || is_atomic
// FIXME is_shared & is_atomic aren't used further
if is_mut {
p.next()
}
var_pos := p.tok.position()
p.check(.name)
var_name := p.prev_tok.lit
mut var := p.scope.parent.find_var(var_name) or {
p.error_with_pos('undefined ident: `$var_name`', p.prev_tok.position())
continue
}
var.is_used = true
if is_mut {
var.is_changed = true
}
p.scope.register(ast.Var{
...(*var)
pos: var_pos
is_inherited: true
is_used: false
is_changed: false
is_mut: is_mut
})
vars << ast.Param{
pos: var_pos
name: var_name
is_mut: is_mut
}
if p.tok.kind != .comma {
break
}
p.next()
}
p.check(.rsbr)
return vars
}
fn (mut p Parser) check_fn_mutable_arguments(typ ast.Type, pos token.Position) {
sym := p.table.get_type_symbol(typ)
if sym.kind in [.array, .array_fixed, .interface_, .map, .placeholder, .struct_, .generic_inst,

View File

@ -1784,49 +1784,49 @@ pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident {
if is_static {
p.next()
}
if p.tok.kind == .name {
pos := p.tok.position()
mut name := p.check_name()
if name == '_' {
return ast.Ident{
tok_kind: p.tok.kind
name: '_'
comptime: p.comp_if_cond
kind: .blank_ident
pos: pos
info: ast.IdentVar{
is_mut: false
is_static: false
}
scope: p.scope
}
}
if p.inside_match_body && name == 'it' {
// p.warn('it')
}
if p.expr_mod.len > 0 {
name = '${p.expr_mod}.$name'
if p.tok.kind != .name {
p.error('unexpected token `$p.tok.lit`')
return ast.Ident{
scope: p.scope
}
}
pos := p.tok.position()
mut name := p.check_name()
if name == '_' {
return ast.Ident{
tok_kind: p.tok.kind
kind: .unresolved
name: name
name: '_'
comptime: p.comp_if_cond
language: language
mod: p.mod
kind: .blank_ident
pos: pos
is_mut: is_mut
mut_pos: mut_pos
info: ast.IdentVar{
is_mut: is_mut
is_static: is_static
share: ast.sharetype_from_flags(is_shared, is_atomic)
is_mut: false
is_static: false
}
scope: p.scope
}
}
p.error('unexpected token `$p.tok.lit`')
if p.inside_match_body && name == 'it' {
// p.warn('it')
}
if p.expr_mod.len > 0 {
name = '${p.expr_mod}.$name'
}
return ast.Ident{
tok_kind: p.tok.kind
kind: .unresolved
name: name
comptime: p.comp_if_cond
language: language
mod: p.mod
pos: pos
is_mut: is_mut
mut_pos: mut_pos
info: ast.IdentVar{
is_mut: is_mut
is_static: is_static
share: ast.sharetype_from_flags(is_shared, is_atomic)
}
scope: p.scope
}
}

View File

@ -0,0 +1,7 @@
vlib/v/parser/tests/closure_not_declared.vv:4:9: error: undefined ident: `a`
2 | a := 1
3 | f := fn () {
4 | print(a)
| ^
5 | }
6 | f()

View File

@ -0,0 +1,8 @@
fn my_fn() {
a := 1
f := fn () {
print(a)
}
f()
_ = a
}

View File

@ -0,0 +1,7 @@
vlib/v/parser/tests/closure_not_used.vv:3:11: error: unused variable: `a`
1 | fn my_fn() {
2 | a := 1
3 | f := fn [a] () {
| ^
4 | print("hello")
5 | }

View File

@ -0,0 +1,7 @@
fn my_fn() {
a := 1
f := fn [a] () {
print("hello")
}
f()
}

View File

@ -0,0 +1,6 @@
vlib/v/parser/tests/closure_undefined_var.vv:2:11: error: undefined ident: `dont_exist`
1 | fn my_fn() {
2 | f := fn [dont_exist] () {
| ~~~~~~~~~~
3 | print(dont_exist)
4 | }

View File

@ -0,0 +1,6 @@
fn my_fn() {
f := fn [dont_exist] () {
print(dont_exist)
}
f()
}

View File

@ -0,0 +1,153 @@
fn test_decl_assignment() {
my_var := 12
c1 := fn [my_var] () int {
return my_var
}
assert c1() == 12
}
fn test_assignment() {
v1 := 1
mut c := fn [v1] () int {
return v1
}
v2 := 3
c = fn [v2] () int {
return v2
}
assert c() == 3
}
fn call_anon_with_3(f fn (int) int) int {
return f(3)
}
fn test_closure_in_call() {
my_var := int(12)
r1 := call_anon_with_3(fn [my_var] (add int) int {
return my_var + add
})
assert r1 == 15
}
fn create_simple_counter() fn () int {
mut c := -1
return fn [mut c] () int {
c++
return c
}
}
fn override_stack() {
// just create some variables to modify the stack
a := 1
b := a + 2
c := b + 3
d := c + 4
e := d + 5
f := e + 6
g := f + 7
_ = g
}
fn test_closure_exit_original_scope() {
mut c := create_simple_counter()
assert c() == 0
override_stack()
assert c() == 1
}
fn test_vars_are_changed() {
mut my_var := 1
f1 := fn [mut my_var] (expected int) {
assert my_var == expected
}
f1(1)
my_var = 3
f1(1)
my_var = -1
f1(1)
}
struct Counter {
mut:
i u64
}
fn (mut c Counter) incr() {
c.i++
}
fn (mut c Counter) next() u64 {
c.incr()
return c.i
}
fn test_call_methods() {
mut c := Counter{}
f1 := fn [mut c] () u64 {
return c.next()
}
assert f1() == 1
assert f1() == 2
c.incr()
assert f1() == 3
}
fn test_call_methods_on_pointer() {
mut c := &Counter{}
f1 := fn [mut c] () u64 {
return c.next()
}
assert f1() == 1
assert f1() == 2
c.incr()
assert f1() == 4
}
/*
fn test_methods_as_variables() {
mut c := Counter{}
f1 := c.next
assert f1() == 1
assert f1() == 2
c.incr()
assert f1() == 3
}
*/
/*
fn takes_callback(get fn () u64) u64 {
return get()
}
fn test_methods_as_callback() {
mut c := Counter{}
assert takes_callback(c.next) == 1
}
*/
struct ZeroSize {}
fn test_zero_size_ctx() {
ctx := ZeroSize{}
c1 := fn [ctx] () int {
return 123
}
assert c1() == 123
}
/*
fn test_go_call_closure() {
my_var := 12
ch := chan int{}
go fn [my_var, ch] () {
ch <- my_var
}()
assert <-ch == 12
go fn [ch] (arg int) {
ch <- arg
}(15)
assert <-ch == 15
}
*/

View File

@ -13,10 +13,6 @@ pub mut:
last_line int // the line number where the ast object ends (used by vfmt)
}
pub fn (pos Position) str() string {
return 'Position{ line_nr: $pos.line_nr, last_line: $pos.last_line, pos: $pos.pos, col: $pos.col, len: $pos.len }'
}
pub fn (pos Position) extend(end Position) Position {
return Position{
...pos
@ -29,9 +25,9 @@ pub fn (pos Position) extend_with_last_line(end Position, last_line int) Positio
return Position{
len: end.pos - pos.pos + end.len
line_nr: pos.line_nr
last_line: last_line - 1
pos: pos.pos
col: pos.col
last_line: last_line - 1
}
}