From dab649ec8a43fdf8c2802e118e4fe00b787c89cd Mon Sep 17 00:00:00 2001 From: spaceface Date: Sat, 30 Apr 2022 08:32:46 +0200 Subject: [PATCH] cgen: rewrite the closure implementation (#14223) --- cmd/tools/vtest-self.v | 3 - vlib/sync/once_with_param_test.v | 1 - vlib/v/gen/c/cheaders.v | 339 ++++++++++++-------------- vlib/v/gen/c/comptime.v | 6 + vlib/v/gen/c/fn.v | 94 +++++-- vlib/v/tests/closure_generator_test.v | 9 +- 6 files changed, 238 insertions(+), 214 deletions(-) diff --git a/cmd/tools/vtest-self.v b/cmd/tools/vtest-self.v index 594a06ce3..9a6dd8322 100644 --- a/cmd/tools/vtest-self.v +++ b/cmd/tools/vtest-self.v @@ -112,8 +112,6 @@ const ( 'vlib/context/value_test.v', 'vlib/orm/orm_test.v', 'vlib/v/tests/orm_sub_struct_test.v', - 'vlib/v/tests/closure_test.v', - 'vlib/v/tests/closure_generator_test.v', 'vlib/net/websocket/ws_test.v', 'vlib/net/unix/unix_test.v', 'vlib/net/unix/use_net_and_net_unix_together_test.v', @@ -139,7 +137,6 @@ const ( 'do_not_remove', ] skip_on_arm64 = [ - 'vlib/v/tests/closure_generator_test.v', 'do_not_remove', ] skip_on_non_amd64_or_arm64 = [ diff --git a/vlib/sync/once_with_param_test.v b/vlib/sync/once_with_param_test.v index 523bac4ed..c6f26efc3 100644 --- a/vlib/sync/once_with_param_test.v +++ b/vlib/sync/once_with_param_test.v @@ -4,7 +4,6 @@ import sync // it uses an explicit passing of the voidptr parameter in // once.do_with_param/2, instead of passing a closure of it // in once.do/1. -// Closures are not yet implemented on Windows. struct One { pub mut: diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index bfc1444df..9b08633f2 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -58,201 +58,167 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) { } ' -fn arm64_bytes(nargs int) string { - // start: - // ldr x16, start-0x08 - // ldr x, start-0x10 - // br x16 - bytes := '0xd0, 0xff, 0xff, 0x58, 0x6, 0xff, 0xff, 0x58, 0x00, 0x02, 0x1f, 0xd6' - return bytes.replace('', nargs.str()) -} - -fn arm32_bytes(nargs int) string { - // start: - // ldr r9, start-0x4 - // ldr r, start-0x8 - // bx r9 - bytes := '0x0c, 0x90, 0x1f, 0xe5, 0x14, 0x0, 0x1f, 0xe5, 0x19, 0xff, 0x2f, 0xe1' - return bytes.replace('', nargs.str()) -} - -// gen_amd64_bytecode generates the amd64 bytecode a closure with `nargs` parameters. -// Note: `nargs` includes the last `userdata` parameter that will be passed to the original -// function, and as such nargs must always be > 0 -fn amd64_bytes(nargs int) string { - match nargs { - 1 { - return '0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - 2 { - return '0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - 3 { - return '0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - 4 { - return '0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - 5 { - return '0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - 6 { - return '0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff' - } - else { - // see https://godbolt.org/z/64e5TEf5n for similar assembly - mut sb := strings.new_builder(256) - s := (((u8(nargs) & 1) + 1) << 3).hex() - sb.write_string('0x48, 0x83, 0xec, 0x$s, ') // sub rsp,0x8 sub rsp,0x10 - sb.write_string('0xff, 0x35, 0xe6, 0xff, 0xff, 0xff, ') // push QWORD PTR [rip+0xffffffffffffffe6] - - rsp_offset := u8(0x18 + ((u8(nargs - 7) >> 1) << 4)).hex() - for _ in 0 .. nargs - 7 { - sb.write_string('0xff, 0xb4, 0x24, 0x$rsp_offset, 0x00, 0x00, 0x00, ') // push QWORD PTR [rsp+$rsp_offset] - } - sb.write_string('0xff, 0x15, 0x${u8(256 - sb.len / 6 - 6 - 8).hex()}, 0xff, 0xff, 0xff, ') // call QWORD PTR [rip+OFFSET] - sb.write_string('0x48, 0x81, 0xc4, 0x$rsp_offset, 0x00, 0x00, 0x00, ') // add rsp,$rsp_offset - sb.write_string('0xc3') // ret - - return sb.str() - } - } -} - -// Heavily based on Chris Wellons's work +// Inspired from Chris Wellons's work // https://nullprogram.com/blog/2017/01/08/ fn c_closure_helpers(pref &pref.Preferences) string { - if pref.os == .windows { - verror('closures are not implemented on Windows yet') - } - if pref.arch !in [.amd64, .arm64, .arm32] { - verror('closures are not implemented on this architecture yet: $pref.arch') - } mut builder := strings.new_builder(2048) if pref.os != .windows { builder.writeln('#include ') } - // TODO: support additional arguments by pushing them onto the stack - // https://en.wikipedia.org/wiki/Calling_convention - if pref.arch == .amd64 { - // TODO: the `amd64_bytes()` function above should work for an arbitrary* number of arguments, - // so we should just remove the table and call the function directly at runtime - builder.write_string(' -static unsigned char __closure_thunk[32][${amd64_bytes(31).len / 6 + - 2}] = { - { ${amd64_bytes(1)} }, - { ${amd64_bytes(2)} }, - { ${amd64_bytes(3)} }, - { ${amd64_bytes(4)} }, - { ${amd64_bytes(5)} }, - { ${amd64_bytes(6)} }, - { ${amd64_bytes(7)} }, - { ${amd64_bytes(8)} }, - { ${amd64_bytes(9)} }, - { ${amd64_bytes(10)} }, - { ${amd64_bytes(11)} }, - { ${amd64_bytes(12)} }, - { ${amd64_bytes(13)} }, - { ${amd64_bytes(14)} }, - { ${amd64_bytes(15)} }, - { ${amd64_bytes(16)} }, - { ${amd64_bytes(17)} }, - { ${amd64_bytes(18)} }, - { ${amd64_bytes(19)} }, - { ${amd64_bytes(20)} }, - { ${amd64_bytes(21)} }, - { ${amd64_bytes(22)} }, - { ${amd64_bytes(23)} }, - { ${amd64_bytes(24)} }, - { ${amd64_bytes(25)} }, - { ${amd64_bytes(26)} }, - { ${amd64_bytes(27)} }, - { ${amd64_bytes(28)} }, - { ${amd64_bytes(29)} }, - { ${amd64_bytes(30)} }, - { ${amd64_bytes(31)} }, + + builder.write_string(' +#ifdef _MSC_VER + #define __RETURN_ADDRESS() _ReturnAddress() +#elif defined(__TINYC__) && defined(_WIN32) + #define __RETURN_ADDRESS() __builtin_return_address(0) +#else + #define __RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#endif + +#ifdef __V_amd64 +#ifdef _WIN32 +static const char __closure_thunk[] = { + 0x48, 0x89, 0x0d, 0xc1, 0xff, 0xff, 0xff, // mov qword ptr [rip - 63], rcx # <_orig_rcx> + 0x8f, 0x05, 0xc3, 0xff, 0xff, 0xff, // pop qword ptr [rip - 61] # <_orig_rbp> + 0xff, 0x15, 0xd5, 0xff, 0xff, 0xff, // call qword ptr [rip - 43] # + 0x48, 0x8b, 0x0d, 0xae, 0xff, 0xff, 0xff, // mov rcx, qword ptr [rip - 82] # <_orig_rcx> + 0xff, 0x15, 0xc0, 0xff, 0xff, 0xff, // call qword ptr [rip - 64] # + 0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp> + 0xc3 // ret }; -') - } else if pref.arch == .arm64 { - builder.write_string(' -static unsigned char __closure_thunk[8][12] = { - { - ${arm64_bytes(0)} - }, { - ${arm64_bytes(1)} - }, { - ${arm64_bytes(2)} - }, { - ${arm64_bytes(3)} - }, { - ${arm64_bytes(4)} - }, { - ${arm64_bytes(5)} - }, { - ${arm64_bytes(6)} - }, { - ${arm64_bytes(7)} - }, +#else +static const char __closure_thunk[] = { + 0x48, 0x89, 0x3d, 0xc1, 0xff, 0xff, 0xff, // mov qword ptr [rip - 63], rdi # <_orig_rdi> + 0x8f, 0x05, 0xc3, 0xff, 0xff, 0xff, // pop qword ptr [rip - 61] # <_orig_rbp> + 0xff, 0x15, 0xd5, 0xff, 0xff, 0xff, // call qword ptr [rip - 43] # + 0x48, 0x8b, 0x3d, 0xae, 0xff, 0xff, 0xff, // mov rdi, qword ptr [rip - 82] # <_orig_rdi> + 0xff, 0x15, 0xc0, 0xff, 0xff, 0xff, // call qword ptr [rip - 64] # + 0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp> + 0xc3 // ret }; -') - } else if pref.arch == .arm32 { - builder.write_string(' -static unsigned char __closure_thunk[4][12] = { - { - ${arm32_bytes(0)} - }, { - ${arm32_bytes(1)} - }, { - ${arm32_bytes(2)} - }, { - ${arm32_bytes(3)} - }, +#endif +#define __CLOSURE_WRAPPER_OFFSET 19 +#define __CLOSURE_UNWRAPPER_OFFSET 32 +#define __CLOSURE_WRAPPER_EXTRA_PARAM void* _t +#define __CLOSURE_WRAPPER_EXTRA_PARAM_COMMA , +#elif defined(__V_x86) +static char __closure_thunk[] = { + 0xe8, 0x00, 0x00, 0x00, 0x00, // call 4 + 0x58, // pop eax + 0x8f, 0x40, 0xe3, // pop dword ptr [eax - 29] # <_orig_rbp> + 0xff, 0x50, 0xef, // call dword ptr [eax - 17] # + 0xe8, 0x00, 0x00, 0x00, 0x00, // call 4 + 0x58, // pop eax + 0xff, 0x50, 0xdf, // call dword ptr [eax - 33] # + 0xe8, 0x00, 0x00, 0x00, 0x00, // call 4 + 0x58, // pop eax + 0xff, 0x70, 0xce, // push dword ptr [eax - 50] # <_orig_rbp> + 0xc3 // ret }; -') - } - builder.write_string(' -static void __closure_set_data(void *closure, void *data) { - void **p = closure; - p[-2] = data; + +#define __CLOSURE_WRAPPER_OFFSET 12 +#define __CLOSURE_UNWRAPPER_OFFSET 21 +#define __CLOSURE_WRAPPER_EXTRA_PARAM void* _t +#define __CLOSURE_WRAPPER_EXTRA_PARAM_COMMA , + +#elif defined(__V_arm64) +static char __closure_thunk[] = { + 0x10, 0x00, 0x00, 0x10, // adr x16, start + 0x08, 0x82, 0x1c, 0xf8, // str x8, _orig_x8 + 0x1e, 0x02, 0x1d, 0xf8, // str x30, _orig_x30 + 0xf0, 0xfe, 0xff, 0x58, // ldr x16, wrapper + 0x00, 0x02, 0x3f, 0xd6, // blr x16 + 0x70, 0xff, 0xff, 0x10, // adr x16, start + 0x08, 0x82, 0x5c, 0xf8, // ldr x8, _orig_x8 + 0x30, 0xfe, 0xff, 0x58, // ldr x16, unwrapper + 0x00, 0x02, 0x3f, 0xd6, // blr x16 + 0xf0, 0xfe, 0xff, 0x10, // adr x16, start + 0x1e, 0x02, 0x5d, 0xf8, // ldr x30, _orig_x30 + 0xc0, 0x03, 0x5f, 0xd6 // ret +}; +#define __CLOSURE_WRAPPER_OFFSET 20 +#define __CLOSURE_UNWRAPPER_OFFSET 36 +#define __CLOSURE_WRAPPER_EXTRA_PARAM +#define __CLOSURE_WRAPPER_EXTRA_PARAM_COMMA +#elif defined(__V_arm32) +static char __closure_thunk[] = { + 0x24, 0x00, 0x0f, 0xe5, // str r0, orig_r0 + 0x24, 0xe0, 0x0f, 0xe5, // str lr, orig_lr + 0x1c, 0xc0, 0x1f, 0xe5, // ldr ip, wrapper + 0x3c, 0xff, 0x2f, 0xe1, // blx ip + 0x34, 0x00, 0x1f, 0xe5, // ldr r0, orig_r0 + 0x2c, 0xc0, 0x1f, 0xe5, // ldr ip, unwrapper + 0x3c, 0xff, 0x2f, 0xe1, // blx ip + 0x3c, 0xe0, 0x1f, 0xe5, // ldr lr, orig_lr + 0x1e, 0xff, 0x2f, 0xe1 // bx lr +}; +#define __CLOSURE_WRAPPER_OFFSET 16 +#define __CLOSURE_UNWRAPPER_OFFSET 28 +#define __CLOSURE_WRAPPER_EXTRA_PARAM void* _t +#define __CLOSURE_WRAPPER_EXTRA_PARAM_COMMA , +#endif + +static int _V_PAGE_SIZE = 4096; // pre-initialized to the most common value, in case _vinit is not called (in a DLL, for example) + +static inline void __closure_set_data(void* closure, void* data) { + void** p = closure; + p[-1] = data; } -static void __closure_set_function(void *closure, void *f) { - void **p = closure; - p[-1] = f; +static inline void __closure_set_function(void* closure, void* f) { + void** p = closure; + p[-2] = f; } -static inline int __closure_check_nargs(int nargs) { - if (nargs > (int)_ARR_LEN(__closure_thunk)) { - _v_panic(_SLIT("Closure too large. Reduce the number of parameters, or pass the parameters by reference.")); - VUNREACHABLE(); - } - return nargs; +static inline void __closure_set_wrapper(void* closure, void* f) { + void** p = closure; + p[-3] = f; } -') - if pref.os != .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 inline void __closure_set_unwrapper(void* closure, void* f) { + void** p = closure; + p[-4] = f; } -static void __closure_destroy(void *closure) { - long page_size = sysconf(_SC_PAGESIZE); - munmap((char *)closure - page_size, page_size * 2); +static inline void __closure_set_base_ptr(void* closure, void* bp) { + void** p = closure; + p[-5] = bp; +} + +static void* __closure_create(void* fn, void* wrapper, void* unwrapper, void* data) { +#ifdef _WIN32 + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + uint32_t page_size = si.dwPageSize; + char* p = VirtualAlloc(NULL, page_size * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (p == NULL) return 0; +#else + uint32_t 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; +#endif + + void* closure = p + page_size; + memcpy(closure, __closure_thunk, sizeof(__closure_thunk)); + +#ifdef _WIN32 + DWORD _tmp; + VirtualProtect(closure, page_size, PAGE_EXECUTE_READ, &_tmp); +#else + mprotect(closure, page_size, PROT_READ | PROT_EXEC); +#endif + + __closure_set_data(closure, data); + __closure_set_function(closure, fn); + __closure_set_wrapper(closure, wrapper); + __closure_set_unwrapper(closure, unwrapper); + __closure_set_base_ptr(closure, p); + return closure; } ') - } return builder.str() } @@ -268,18 +234,30 @@ const c_common_macros = ' #define __IRQHANDLER __attribute__((interrupt)) #define __V_architecture 0 -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(_M_AMD64) #define __V_amd64 1 #undef __V_architecture #define __V_architecture 1 #endif -#if defined(__aarch64__) || defined(__arm64__) +#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) #define __V_arm64 1 #undef __V_architecture #define __V_architecture 2 #endif +#if defined(__arm__) || defined(_M_ARM) + #define __V_arm32 1 + #undef __V_architecture + #define __V_architecture 3 +#endif + +#if defined(__i386__) || defined(_M_IX86) + #define __V_x86 1 + #undef __V_architecture + #define __V_architecture 6 +#endif + // Using just __GNUC__ for detecting gcc, is not reliable because other compilers define it too: #ifdef __GNUC__ #define __V_GCC__ @@ -329,11 +307,6 @@ const c_common_macros = ' #define __offsetof(PTYPE,FIELDNAME) ((size_t)((char *)&((PTYPE *)0)->FIELDNAME - (char *)0)) #endif -// returns the number of CPU registers that TYPE takes up -#define _REG_WIDTH(T) (((sizeof(T) + sizeof(void*) - 1) & ~(sizeof(void*) - 1)) / sizeof(void*)) -// parameters of size <= 2 registers are spilled across those two registers; larger types are passed as one pointer to some stack location -#define _REG_WIDTH_BOUNDED(T) (_REG_WIDTH(T) <= 2 ? _REG_WIDTH(T) : 1) - #define OPTION_CAST(x) (x) #ifndef V64_PRINTFORMAT diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 8195694af..4245582cc 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -698,6 +698,12 @@ fn (mut g Gen) comptime_if_to_ifdef(name string, is_comptime_optional bool) ?str 'aarch64', 'arm64' { return '__V_arm64' } + 'arm32' { + return '__V_arm32' + } + 'i386' { + return '__V_x86' + } // bitness: 'x64' { return 'TARGET_IS_64BIT' diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ffb66eeaa..ef7b675bc 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -195,7 +195,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { is_closure := node.scope.has_inherited_vars() mut cur_closure_ctx := '' if is_closure { - cur_closure_ctx = closure_ctx_struct(node) + cur_closure_ctx, _ = closure_ctx(node) // declare the struct before its implementation g.definitions.write_string(cur_closure_ctx) g.definitions.writeln(';') @@ -298,9 +298,6 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.definitions.write_string(s) g.write(s) g.nr_closures++ - if g.pref.os == .windows { - g.error('closures are not yet implemented on windows', node.pos) - } } 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 @@ -474,8 +471,8 @@ fn (mut g Gen) c_fn_name(node &ast.FnDecl) ?string { const closure_ctx = '_V_closure_ctx' -fn closure_ctx_struct(node ast.FnDecl) string { - return 'struct _V_${node.name}_Ctx' +fn closure_ctx(node ast.FnDecl) (string, string) { + return 'struct _V_${node.name}_Ctx', 'struct _V_${node.name}_Args' } fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { @@ -484,32 +481,72 @@ fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { g.write(node.decl.name) return } + ctx_struct, arg_struct := closure_ctx(node.decl) // it may be possible to optimize `memdup` out if the closure never leaves current scope - ctx_struct := closure_ctx_struct(node.decl) // TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data) - - mut size_sb := strings.new_builder(node.decl.params.len * 50) - for param in node.decl.params { - size_sb.write_string('_REG_WIDTH_BOUNDED(${g.typ(param.typ)}) + ') - } - if g.pref.arch == .amd64 && node.decl.return_type != ast.void_type { - size_sb.write_string('(_REG_WIDTH(${g.typ(node.decl.return_type)}) > 2) + ') - } - size_sb.write_string('1') - args_size := size_sb.str() - g.writeln('') - - // ensure that nargs maps to a known entry in the __closure_thunk array - // TODO make it a compile-time error (you can't call `sizeof()` inside preprocessor `#if`s) - // Note: this won't be necessary when (if) we have functions that return the machine code for - // an arbitrary number of arguments - g.write('__closure_create($node.decl.name, __closure_check_nargs($args_size), ($ctx_struct*) memdup(&($ctx_struct){') + g.write('__closure_create($node.decl.name, ${node.decl.name}_wrapper, ${node.decl.name}_unwrapper, ($ctx_struct*) memdup(&($ctx_struct){') g.indent++ for var in node.inherited_vars { g.writeln('.$var.name = $var.name,') } g.indent-- + ps := g.table.pointer_size + is_big_cutoff := if g.pref.os == .windows || g.pref.arch == .arm32 { ps } else { ps * 2 } + is_big := g.table.type_size(node.decl.return_type) > is_big_cutoff g.write('}, sizeof($ctx_struct)))') + + mut sb := strings.new_builder(512) + ret_styp := g.typ(node.decl.return_type) + + sb.write_string(' VV_LOCAL_SYMBOL void ${node.decl.name}_wrapper(') + if is_big { + sb.write_string('__CLOSURE_WRAPPER_EXTRA_PARAM ') + if node.decl.params.len > 0 { + sb.write_string('__CLOSURE_WRAPPER_EXTRA_PARAM_COMMA ') + } + } + for i, param in node.decl.params { + if i > 0 { + sb.write_string(', ') + } + sb.write_string('${g.typ(param.typ)} a${i + 1}') + } + sb.writeln(') {') + if node.decl.params.len > 0 { + sb.writeln('void** closure_start = (void**)((char*)__RETURN_ADDRESS() - __CLOSURE_WRAPPER_OFFSET); + $arg_struct* args = closure_start[-5];') + for i in 0 .. node.decl.params.len { + sb.writeln('\targs->a${i + 1} = a${i + 1};') + } + } + + sb.writeln('}\n') + + sb.writeln(' VV_LOCAL_SYMBOL $ret_styp ${node.decl.name}_unwrapper(void) { + void** closure_start = (void**)((char*)__RETURN_ADDRESS() - __CLOSURE_UNWRAPPER_OFFSET); + void* userdata = closure_start[-1];') + sb.write_string('\t${g.typ(node.decl.return_type)} (*fn)(') + for i, param in node.decl.params { + sb.write_string('${g.typ(param.typ)} a${i + 1}, ') + } + sb.writeln('void* userdata) = closure_start[-2];') + + if node.decl.params.len > 0 { + sb.writeln('\t$arg_struct* args = closure_start[-5];') + } + + if node.decl.return_type == ast.void_type_idx { + sb.write_string('\tfn(') + } else { + sb.write_string('\treturn fn(') + } + for i in 0 .. node.decl.params.len { + sb.write_string('args->a${i + 1}, ') + } + sb.writeln('userdata); +}') + + g.anon_fn_definitions << sb.str() g.empty_line = false } @@ -520,13 +557,20 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { node.has_gen = true mut builder := strings.new_builder(256) if node.inherited_vars.len > 0 { - ctx_struct := closure_ctx_struct(node.decl) + ctx_struct, arg_struct := closure_ctx(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') + if node.decl.params.len > 0 { + builder.writeln('$arg_struct {') + for i, param in node.decl.params { + builder.writeln('\t${g.typ(param.typ)} a${i + 1};') + } + builder.writeln('};\n') + } } pos := g.out.len was_anon_fn := g.anon_fn diff --git a/vlib/v/tests/closure_generator_test.v b/vlib/v/tests/closure_generator_test.v index 70d31b971..bd46fc2b9 100644 --- a/vlib/v/tests/closure_generator_test.v +++ b/vlib/v/tests/closure_generator_test.v @@ -80,8 +80,9 @@ fn test_closures_with_n_args() ? { mut values := all_param_values[..i] if typ == 'string' { values = values.map("'$it'") + } else { + values = values.map('${typ}($it)') } - values = values.map('${typ}($it)') mut expected_val := if typ == 'string' { s := all_param_values[..i].join('') @@ -107,7 +108,11 @@ fn test_big_closure_${typ}_${i}() { c := fn [z] (${params.join(', ')}) $return_type { mut sum := z") for j in 0 .. i { - v_code.writeln('\t\tsum += ${return_type}(${param_names[j]})') + if return_type == 'string' { + v_code.writeln('\t\tsum += ${param_names[j]}') + } else { + v_code.writeln('\t\tsum += ${return_type}(${param_names[j]})') + } } v_code.writeln(" return sum -- 2.30.2