cgen: rewrite the closure implementation (#14223)
parent
ce31a01a70
commit
dab649ec8a
|
@ -112,8 +112,6 @@ const (
|
||||||
'vlib/context/value_test.v',
|
'vlib/context/value_test.v',
|
||||||
'vlib/orm/orm_test.v',
|
'vlib/orm/orm_test.v',
|
||||||
'vlib/v/tests/orm_sub_struct_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/websocket/ws_test.v',
|
||||||
'vlib/net/unix/unix_test.v',
|
'vlib/net/unix/unix_test.v',
|
||||||
'vlib/net/unix/use_net_and_net_unix_together_test.v',
|
'vlib/net/unix/use_net_and_net_unix_together_test.v',
|
||||||
|
@ -139,7 +137,6 @@ const (
|
||||||
'do_not_remove',
|
'do_not_remove',
|
||||||
]
|
]
|
||||||
skip_on_arm64 = [
|
skip_on_arm64 = [
|
||||||
'vlib/v/tests/closure_generator_test.v',
|
|
||||||
'do_not_remove',
|
'do_not_remove',
|
||||||
]
|
]
|
||||||
skip_on_non_amd64_or_arm64 = [
|
skip_on_non_amd64_or_arm64 = [
|
||||||
|
|
|
@ -4,7 +4,6 @@ import sync
|
||||||
// it uses an explicit passing of the voidptr parameter in
|
// it uses an explicit passing of the voidptr parameter in
|
||||||
// once.do_with_param/2, instead of passing a closure of it
|
// once.do_with_param/2, instead of passing a closure of it
|
||||||
// in once.do/1.
|
// in once.do/1.
|
||||||
// Closures are not yet implemented on Windows.
|
|
||||||
|
|
||||||
struct One {
|
struct One {
|
||||||
pub mut:
|
pub mut:
|
||||||
|
|
|
@ -58,201 +58,167 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) {
|
||||||
}
|
}
|
||||||
'
|
'
|
||||||
|
|
||||||
fn arm64_bytes(nargs int) string {
|
// Inspired from Chris Wellons's work
|
||||||
// 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
|
|
||||||
// https://nullprogram.com/blog/2017/01/08/
|
// https://nullprogram.com/blog/2017/01/08/
|
||||||
|
|
||||||
fn c_closure_helpers(pref &pref.Preferences) string {
|
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)
|
mut builder := strings.new_builder(2048)
|
||||||
if pref.os != .windows {
|
if pref.os != .windows {
|
||||||
builder.writeln('#include <sys/mman.h>')
|
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('
|
builder.write_string('
|
||||||
static unsigned char __closure_thunk[32][${amd64_bytes(31).len / 6 +
|
#ifdef _MSC_VER
|
||||||
2}] = {
|
#define __RETURN_ADDRESS() _ReturnAddress()
|
||||||
{ ${amd64_bytes(1)} },
|
#elif defined(__TINYC__) && defined(_WIN32)
|
||||||
{ ${amd64_bytes(2)} },
|
#define __RETURN_ADDRESS() __builtin_return_address(0)
|
||||||
{ ${amd64_bytes(3)} },
|
#else
|
||||||
{ ${amd64_bytes(4)} },
|
#define __RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0))
|
||||||
{ ${amd64_bytes(5)} },
|
#endif
|
||||||
{ ${amd64_bytes(6)} },
|
|
||||||
{ ${amd64_bytes(7)} },
|
#ifdef __V_amd64
|
||||||
{ ${amd64_bytes(8)} },
|
#ifdef _WIN32
|
||||||
{ ${amd64_bytes(9)} },
|
static const char __closure_thunk[] = {
|
||||||
{ ${amd64_bytes(10)} },
|
0x48, 0x89, 0x0d, 0xc1, 0xff, 0xff, 0xff, // mov qword ptr [rip - 63], rcx # <_orig_rcx>
|
||||||
{ ${amd64_bytes(11)} },
|
0x8f, 0x05, 0xc3, 0xff, 0xff, 0xff, // pop qword ptr [rip - 61] # <_orig_rbp>
|
||||||
{ ${amd64_bytes(12)} },
|
0xff, 0x15, 0xd5, 0xff, 0xff, 0xff, // call qword ptr [rip - 43] # <wrapper>
|
||||||
{ ${amd64_bytes(13)} },
|
0x48, 0x8b, 0x0d, 0xae, 0xff, 0xff, 0xff, // mov rcx, qword ptr [rip - 82] # <_orig_rcx>
|
||||||
{ ${amd64_bytes(14)} },
|
0xff, 0x15, 0xc0, 0xff, 0xff, 0xff, // call qword ptr [rip - 64] # <unwrapper>
|
||||||
{ ${amd64_bytes(15)} },
|
0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp>
|
||||||
{ ${amd64_bytes(16)} },
|
0xc3 // ret
|
||||||
{ ${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
|
||||||
} else if pref.arch == .arm64 {
|
static const char __closure_thunk[] = {
|
||||||
builder.write_string('
|
0x48, 0x89, 0x3d, 0xc1, 0xff, 0xff, 0xff, // mov qword ptr [rip - 63], rdi # <_orig_rdi>
|
||||||
static unsigned char __closure_thunk[8][12] = {
|
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>
|
||||||
${arm64_bytes(0)}
|
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>
|
||||||
${arm64_bytes(1)}
|
0xff, 0x35, 0xaa, 0xff, 0xff, 0xff, // push qword ptr [rip - 86] # <_orig_rbp>
|
||||||
}, {
|
0xc3 // ret
|
||||||
${arm64_bytes(2)}
|
|
||||||
}, {
|
|
||||||
${arm64_bytes(3)}
|
|
||||||
}, {
|
|
||||||
${arm64_bytes(4)}
|
|
||||||
}, {
|
|
||||||
${arm64_bytes(5)}
|
|
||||||
}, {
|
|
||||||
${arm64_bytes(6)}
|
|
||||||
}, {
|
|
||||||
${arm64_bytes(7)}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
')
|
#endif
|
||||||
} else if pref.arch == .arm32 {
|
#define __CLOSURE_WRAPPER_OFFSET 19
|
||||||
builder.write_string('
|
#define __CLOSURE_UNWRAPPER_OFFSET 32
|
||||||
static unsigned char __closure_thunk[4][12] = {
|
#define __CLOSURE_WRAPPER_EXTRA_PARAM void* _t
|
||||||
{
|
#define __CLOSURE_WRAPPER_EXTRA_PARAM_COMMA ,
|
||||||
${arm32_bytes(0)}
|
#elif defined(__V_x86)
|
||||||
}, {
|
static char __closure_thunk[] = {
|
||||||
${arm32_bytes(1)}
|
0xe8, 0x00, 0x00, 0x00, 0x00, // call 4
|
||||||
}, {
|
0x58, // pop eax
|
||||||
${arm32_bytes(2)}
|
0x8f, 0x40, 0xe3, // pop dword ptr [eax - 29] # <_orig_rbp>
|
||||||
}, {
|
0xff, 0x50, 0xef, // call dword ptr [eax - 17] # <wrapper>
|
||||||
${arm32_bytes(3)}
|
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
|
||||||
builder.write_string('
|
#define __CLOSURE_UNWRAPPER_OFFSET 21
|
||||||
static void __closure_set_data(void *closure, void *data) {
|
#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;
|
void** p = closure;
|
||||||
p[-2] = data;
|
p[-1] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __closure_set_function(void *closure, void *f) {
|
static inline void __closure_set_function(void* closure, void* f) {
|
||||||
void** p = closure;
|
void** p = closure;
|
||||||
p[-1] = f;
|
p[-2] = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int __closure_check_nargs(int nargs) {
|
static inline void __closure_set_wrapper(void* closure, void* f) {
|
||||||
if (nargs > (int)_ARR_LEN(__closure_thunk)) {
|
void** p = closure;
|
||||||
_v_panic(_SLIT("Closure too large. Reduce the number of parameters, or pass the parameters by reference."));
|
p[-3] = f;
|
||||||
VUNREACHABLE();
|
|
||||||
}
|
}
|
||||||
return nargs;
|
|
||||||
|
static inline void __closure_set_unwrapper(void* closure, void* f) {
|
||||||
|
void** p = closure;
|
||||||
|
p[-4] = f;
|
||||||
}
|
}
|
||||||
')
|
|
||||||
if pref.os != .windows {
|
static inline void __closure_set_base_ptr(void* closure, void* bp) {
|
||||||
builder.write_string('
|
void** p = closure;
|
||||||
static void * __closure_create(void *f, int nargs, void *userdata) {
|
p[-5] = bp;
|
||||||
long page_size = sysconf(_SC_PAGESIZE);
|
}
|
||||||
|
|
||||||
|
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 prot = PROT_READ | PROT_WRITE;
|
||||||
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
|
int flags = MAP_ANONYMOUS | MAP_PRIVATE;
|
||||||
char* p = mmap(0, page_size * 2, prot, flags, -1, 0);
|
char* p = mmap(0, page_size * 2, prot, flags, -1, 0);
|
||||||
if (p == MAP_FAILED)
|
if (p == MAP_FAILED) return 0;
|
||||||
return 0;
|
#endif
|
||||||
|
|
||||||
void* closure = p + page_size;
|
void* closure = p + page_size;
|
||||||
memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0]));
|
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);
|
mprotect(closure, page_size, PROT_READ | PROT_EXEC);
|
||||||
__closure_set_function(closure, f);
|
#endif
|
||||||
__closure_set_data(closure, userdata);
|
|
||||||
|
__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 closure;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __closure_destroy(void *closure) {
|
|
||||||
long page_size = sysconf(_SC_PAGESIZE);
|
|
||||||
munmap((char *)closure - page_size, page_size * 2);
|
|
||||||
}
|
|
||||||
')
|
')
|
||||||
}
|
|
||||||
return builder.str()
|
return builder.str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,18 +234,30 @@ const c_common_macros = '
|
||||||
#define __IRQHANDLER __attribute__((interrupt))
|
#define __IRQHANDLER __attribute__((interrupt))
|
||||||
|
|
||||||
#define __V_architecture 0
|
#define __V_architecture 0
|
||||||
#if defined(__x86_64__)
|
#if defined(__x86_64__) || defined(_M_AMD64)
|
||||||
#define __V_amd64 1
|
#define __V_amd64 1
|
||||||
#undef __V_architecture
|
#undef __V_architecture
|
||||||
#define __V_architecture 1
|
#define __V_architecture 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__aarch64__) || defined(__arm64__)
|
#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
|
||||||
#define __V_arm64 1
|
#define __V_arm64 1
|
||||||
#undef __V_architecture
|
#undef __V_architecture
|
||||||
#define __V_architecture 2
|
#define __V_architecture 2
|
||||||
#endif
|
#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:
|
// Using just __GNUC__ for detecting gcc, is not reliable because other compilers define it too:
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#define __V_GCC__
|
#define __V_GCC__
|
||||||
|
@ -329,11 +307,6 @@ const c_common_macros = '
|
||||||
#define __offsetof(PTYPE,FIELDNAME) ((size_t)((char *)&((PTYPE *)0)->FIELDNAME - (char *)0))
|
#define __offsetof(PTYPE,FIELDNAME) ((size_t)((char *)&((PTYPE *)0)->FIELDNAME - (char *)0))
|
||||||
#endif
|
#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)
|
#define OPTION_CAST(x) (x)
|
||||||
|
|
||||||
#ifndef V64_PRINTFORMAT
|
#ifndef V64_PRINTFORMAT
|
||||||
|
|
|
@ -698,6 +698,12 @@ fn (mut g Gen) comptime_if_to_ifdef(name string, is_comptime_optional bool) ?str
|
||||||
'aarch64', 'arm64' {
|
'aarch64', 'arm64' {
|
||||||
return '__V_arm64'
|
return '__V_arm64'
|
||||||
}
|
}
|
||||||
|
'arm32' {
|
||||||
|
return '__V_arm32'
|
||||||
|
}
|
||||||
|
'i386' {
|
||||||
|
return '__V_x86'
|
||||||
|
}
|
||||||
// bitness:
|
// bitness:
|
||||||
'x64' {
|
'x64' {
|
||||||
return 'TARGET_IS_64BIT'
|
return 'TARGET_IS_64BIT'
|
||||||
|
|
|
@ -195,7 +195,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
||||||
is_closure := node.scope.has_inherited_vars()
|
is_closure := node.scope.has_inherited_vars()
|
||||||
mut cur_closure_ctx := ''
|
mut cur_closure_ctx := ''
|
||||||
if is_closure {
|
if is_closure {
|
||||||
cur_closure_ctx = closure_ctx_struct(node)
|
cur_closure_ctx, _ = closure_ctx(node)
|
||||||
// declare the struct before its implementation
|
// declare the struct before its implementation
|
||||||
g.definitions.write_string(cur_closure_ctx)
|
g.definitions.write_string(cur_closure_ctx)
|
||||||
g.definitions.writeln(';')
|
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.definitions.write_string(s)
|
||||||
g.write(s)
|
g.write(s)
|
||||||
g.nr_closures++
|
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)
|
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
|
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'
|
const closure_ctx = '_V_closure_ctx'
|
||||||
|
|
||||||
fn closure_ctx_struct(node ast.FnDecl) string {
|
fn closure_ctx(node ast.FnDecl) (string, string) {
|
||||||
return 'struct _V_${node.name}_Ctx'
|
return 'struct _V_${node.name}_Ctx', 'struct _V_${node.name}_Args'
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) {
|
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)
|
g.write(node.decl.name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx_struct, arg_struct := closure_ctx(node.decl)
|
||||||
// it may be possible to optimize `memdup` out if the closure never leaves current scope
|
// 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)
|
// 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.name}_wrapper, ${node.decl.name}_unwrapper, ($ctx_struct*) memdup(&($ctx_struct){')
|
||||||
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.indent++
|
g.indent++
|
||||||
for var in node.inherited_vars {
|
for var in node.inherited_vars {
|
||||||
g.writeln('.$var.name = $var.name,')
|
g.writeln('.$var.name = $var.name,')
|
||||||
}
|
}
|
||||||
g.indent--
|
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)))')
|
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
|
g.empty_line = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,13 +557,20 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
|
||||||
node.has_gen = true
|
node.has_gen = true
|
||||||
mut builder := strings.new_builder(256)
|
mut builder := strings.new_builder(256)
|
||||||
if node.inherited_vars.len > 0 {
|
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 {')
|
builder.writeln('$ctx_struct {')
|
||||||
for var in node.inherited_vars {
|
for var in node.inherited_vars {
|
||||||
styp := g.typ(var.typ)
|
styp := g.typ(var.typ)
|
||||||
builder.writeln('\t$styp $var.name;')
|
builder.writeln('\t$styp $var.name;')
|
||||||
}
|
}
|
||||||
builder.writeln('};\n')
|
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
|
pos := g.out.len
|
||||||
was_anon_fn := g.anon_fn
|
was_anon_fn := g.anon_fn
|
||||||
|
|
|
@ -80,8 +80,9 @@ fn test_closures_with_n_args() ? {
|
||||||
mut values := all_param_values[..i]
|
mut values := all_param_values[..i]
|
||||||
if typ == 'string' {
|
if typ == 'string' {
|
||||||
values = values.map("'$it'")
|
values = values.map("'$it'")
|
||||||
}
|
} else {
|
||||||
values = values.map('${typ}($it)')
|
values = values.map('${typ}($it)')
|
||||||
|
}
|
||||||
|
|
||||||
mut expected_val := if typ == 'string' {
|
mut expected_val := if typ == 'string' {
|
||||||
s := all_param_values[..i].join('')
|
s := all_param_values[..i].join('')
|
||||||
|
@ -107,8 +108,12 @@ fn test_big_closure_${typ}_${i}() {
|
||||||
c := fn [z] (${params.join(', ')}) $return_type {
|
c := fn [z] (${params.join(', ')}) $return_type {
|
||||||
mut sum := z")
|
mut sum := z")
|
||||||
for j in 0 .. i {
|
for j in 0 .. i {
|
||||||
|
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('\t\tsum += ${return_type}(${param_names[j]})')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
v_code.writeln("
|
v_code.writeln("
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue