cgen: rewrite the closure implementation (#14223)

master
spaceface 2022-04-30 08:32:46 +02:00 committed by GitHub
parent ce31a01a70
commit dab649ec8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 243 additions and 219 deletions

View File

@ -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 = [

View File

@ -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:

View File

@ -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<REG>, start-0x10
// br x16
bytes := '0xd0, 0xff, 0xff, 0x58, 0x6<REG>, 0xff, 0xff, 0x58, 0x00, 0x02, 0x1f, 0xd6'
return bytes.replace('<REG>', nargs.str())
}
fn arm32_bytes(nargs int) string {
// start:
// ldr r9, start-0x4
// ldr r<REG>, start-0x8
// bx r9
bytes := '0x0c, 0x90, 0x1f, 0xe5, 0x14, 0x<REG>0, 0x1f, 0xe5, 0x19, 0xff, 0x2f, 0xe1'
return bytes.replace('<REG>', 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 <OR> 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 <sys/mman.h>')
}
// 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)} },
};
')
} 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 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)}
},
};
')
}
builder.write_string('
static void __closure_set_data(void *closure, void *data) {
void **p = closure;
p[-2] = data;
#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] # <wrapper>
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] # <unwrapper>
0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp>
0xc3 // ret
};
#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] # <wrapper>
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] # <unwrapper>
0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp>
0xc3 // ret
};
#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] # <wrapper>
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x58, // pop eax
0xff, 0x50, 0xdf, // call dword ptr [eax - 33] # <unwrapper>
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
0x58, // pop eax
0xff, 0x70, 0xce, // push dword ptr [eax - 50] # <_orig_rbp>
0xc3 // ret
};
#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;
}
static inline void __closure_set_unwrapper(void* closure, void* f) {
void** p = closure;
p[-4] = f;
}
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;
}
')
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 void __closure_destroy(void *closure) {
long page_size = sysconf(_SC_PAGESIZE);
munmap((char *)closure - page_size, page_size * 2);
}
')
}
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

View File

@ -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'

View File

@ -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

View File

@ -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