From 2d87ac4837e319397a8e8a366fc9458a09a9ebf7 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sun, 27 Jul 2025 22:44:06 +0800 Subject: [PATCH] cgen: move closure C code to V code under vlib/builtin/closure/ (#24912) --- bench/bench_closure.v | 278 ++++++++++++++++++ vlib/builtin/cfns.c.v | 10 + vlib/builtin/closure/README.md | 2 + vlib/builtin/closure/closure.c.v | 241 +++++++++++++++ vlib/builtin/closure/closure.v | 4 + vlib/builtin/closure/closure_nix.c.v | 84 ++++++ vlib/builtin/closure/closure_windows.c.v | 53 ++++ vlib/v/gen/c/cgen.v | 40 +-- vlib/v/gen/c/cheaders.v | 261 ---------------- vlib/v/gen/c/cmain.v | 7 - vlib/v/gen/c/fn.v | 6 +- vlib/v/markused/markused.v | 3 + vlib/v/parser/expr.v | 3 + vlib/v/parser/parser.v | 3 + vlib/v/parser/struct.v | 16 + ...array_ops_create_just_one_closure_test.c.v | 14 +- vlib/v/util/util.v | 5 +- 17 files changed, 719 insertions(+), 311 deletions(-) create mode 100644 bench/bench_closure.v create mode 100644 vlib/builtin/closure/README.md create mode 100644 vlib/builtin/closure/closure.c.v create mode 100644 vlib/builtin/closure/closure.v create mode 100644 vlib/builtin/closure/closure_nix.c.v create mode 100644 vlib/builtin/closure/closure_windows.c.v diff --git a/bench/bench_closure.v b/bench/bench_closure.v new file mode 100644 index 0000000000..a375c6c3bf --- /dev/null +++ b/bench/bench_closure.v @@ -0,0 +1,278 @@ +module main + +import time +import sync +import os +import runtime +import v.util.version + +// Define closure type alias +type ClosureFN = fn () int + +// Test closures with different capture sizes +fn create_closure_small() ClosureFN { + a := 0 + return fn [a] () int { + return a + } +} + +fn create_closure_medium() ClosureFN { + a, b, c, d := 1, 2, 3, 4 + return fn [a, b, c, d] () int { + return a + b - c * d + } +} + +struct LargeData { + array [10]int +} + +fn create_closure_large() ClosureFN { + data := LargeData{ + array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]! + } + return fn [data] () int { + mut sum := 0 + for i in 0 .. 10 { + sum += data.array[i] + } + return sum + } +} + +// Result structs +struct TestResult { + test_name string + iterations int + time_ms i64 + ops_per_sec f64 // Operations per second + notes string +} + +struct MemoryResult { + test_name string + count int + start_mem_kb int + end_mem_kb int + delta_kb int + bytes_per_closure int + check_sum int +} + +// Benchmark group - returns result structs +fn benchmark_closure_creation(iterations int) []TestResult { + mut results := []TestResult{} + + // Test small closure creation + mut start := time.ticks() + for _ in 0 .. iterations { + _ = create_closure_small() + } + small_time := time.ticks() - start + mut ops_per_sec := f64(iterations) * 1000.0 / f64(small_time) + results << TestResult{'Small Closure Creation', iterations, small_time, ops_per_sec, ''} + + // Test medium closure creation + start = time.ticks() + for _ in 0 .. iterations { + _ = create_closure_medium() + } + medium_time := time.ticks() - start + ops_per_sec = f64(iterations) * 1000.0 / f64(medium_time) + results << TestResult{'Medium Closure Creation', iterations, medium_time, ops_per_sec, ''} + + // Test large closure creation + large_iter := iterations / 10 + start = time.ticks() + for _ in 0 .. large_iter { + _ = create_closure_large() + } + large_time := time.ticks() - start + ops_per_sec = f64(large_iter) * 1000.0 / f64(large_time) + results << TestResult{'Large Closure Creation', large_iter, large_time, ops_per_sec, ''} //, "Equivalent iterations: ${iterations/10}"} + return results +} + +fn benchmark_closure_call(iterations int) []TestResult { + mut results := []TestResult{} + + closure_small := create_closure_small() + closure_medium := create_closure_medium() + closure_large := create_closure_large() + + // Test small closure call + mut start := time.ticks() + for _ in 0 .. iterations { + _ = closure_small() + } + small_time := time.ticks() - start + mut ops_per_sec := f64(iterations) * 1000.0 / f64(small_time) + results << TestResult{'Small Closure Call', iterations, small_time, ops_per_sec, ''} + + // Test medium closure call + start = time.ticks() + for _ in 0 .. iterations { + _ = closure_medium() + } + medium_time := time.ticks() - start + ops_per_sec = f64(iterations) * 1000.0 / f64(medium_time) + results << TestResult{'Medium Closure Call', iterations, medium_time, ops_per_sec, ''} + + // Test large closure call + large_iter := iterations / 10 + start = time.ticks() + for _ in 0 .. large_iter { + _ = closure_large() + } + large_time := time.ticks() - start + ops_per_sec = f64(large_iter) * 1000.0 / f64(large_time) + results << TestResult{'Large Closure Call', large_iter, large_time, ops_per_sec, ''} + + return results +} + +fn benchmark_threaded_creation(threads int, iterations_per_thread int) TestResult { + total_iterations := threads * iterations_per_thread + + mut wg := sync.new_waitgroup() + wg.add(threads) + + start := time.ticks() + + for _ in 0 .. threads { + go fn [mut wg, iterations_per_thread] () { + defer { wg.done() } + for _ in 0 .. iterations_per_thread { + _ = create_closure_medium() + } + }() + } + + wg.wait() + elapsed := time.ticks() - start + ops_per_sec := f64(total_iterations) * 1000.0 / f64(elapsed) + + return TestResult{ + test_name: 'Multi-threaded Creation' + iterations: total_iterations + time_ms: elapsed + ops_per_sec: ops_per_sec + notes: 'Threads: ${threads} Iterations per thread: ${iterations_per_thread}' + } +} + +fn baseline_call_performance(iterations int) TestResult { + start := time.ticks() + for _ in 0 .. iterations { + _ = normal_function() + } + elapsed := time.ticks() - start + ops_per_sec := f64(iterations) * 1000.0 / f64(elapsed) + + return TestResult{ + test_name: 'Normal Function Call' + iterations: iterations + time_ms: elapsed + ops_per_sec: ops_per_sec + notes: 'Baseline' + } +} + +fn benchmark_memory_usage(count int) MemoryResult { + mut closures := []ClosureFN{} + start_mem := runtime.used_memory() or { panic(err) } + + for i in 0 .. count { + the_closure := create_closure_medium() + closures << the_closure + + if i % 1000 == 0 { + _ = the_closure() + } + } + + end_mem := runtime.used_memory() or { panic(err) } + delta := int(end_mem) - int(start_mem) + bytes_per_closure := delta / count + + // Calculate verification sum + mut check_sum := 0 + n := if closures.len < 100 { closures.len } else { 100 } + for idx in 0 .. n { + check_sum += closures[idx]() + } + + return MemoryResult{ + test_name: 'Closure Memory Overhead' + count: count + start_mem_kb: int(start_mem / 1024) + end_mem_kb: int(end_mem / 1024) + delta_kb: delta / 1024 + bytes_per_closure: bytes_per_closure + check_sum: check_sum + } +} + +fn normal_function() int { + return 42 +} + +// Format performance data for readability +fn format_perf(ops_per_sec f64) string { + if ops_per_sec >= 1_000_000 { + return '${ops_per_sec / 1_000_000:5.2f} Mop/s' + } else if ops_per_sec >= 1_000 { + return '${ops_per_sec / 1_000:5.2f} Kop/s' + } else { + return '${ops_per_sec:5.2f} op/s' + } +} + +fn print_results_table(results []TestResult, title string) { + println('|---------------------------|------------|----------|--------------|--------------|') + for res in results { + perf_str := format_perf(res.ops_per_sec) + println('| ${res.test_name:-25} | ${res.iterations:10} | ${res.time_ms:8} | ${perf_str:-12} | ${res.notes:-12} |') + } +} + +fn main() { + println('# V Language Closure Performance Benchmark Report') + + // Configurable test parameters + base_iter := 100_000_000 // 100 million iterations + creation_iter := 10_000_000 // 1 million iterations + mem_count := 100_000 + threads := 8 + thread_iter := 125_000 + + // Execute tests + baseline_result := baseline_call_performance(base_iter) + creation_results := benchmark_closure_creation(creation_iter) + call_results := benchmark_closure_call(base_iter) + thread_result := benchmark_threaded_creation(threads, thread_iter) + mem_result := benchmark_memory_usage(mem_count) + + // Print result tables + println('\n## 1. Closure Performance Analysis') + println('| Test Name | Iterations | Time(ms) | Ops/sec | Notes |') + print_results_table([baseline_result], '1. Performance Baseline') + print_results_table(creation_results, '2. Closure Creation Performance') + print_results_table(call_results, '3. Closure Call Performance') + print_results_table([thread_result], '4. Multi-threaded Performance') + + // Print memory results + println('\n## 2. Memory Overhead Analysis') + println('| Test Name | Closure Count | Start Mem(KB) | End Mem(KB) | Delta(KB) | Bytes/Closure |') + println('|-------------------------|---------------|---------------|------------|-----------|---------------|') + println('| ${mem_result.test_name:-20} | ${mem_result.count:13} | ${mem_result.start_mem_kb:13} | ${mem_result.end_mem_kb:10} | ${mem_result.delta_kb:9} | ${mem_result.bytes_per_closure:13} |') + println('\n**Verification Sum: ${mem_result.check_sum}** (Calculated from random sample of 100 closures)') + + println('\n## Test Environment') + println('- V Language Version: ${version.full_v_version(false)}') + println('- CPU Cores: ${runtime.nr_cpus()}') + println('- System Memory: ${runtime.total_memory()! / 1024 / 1024} MB') + println('- Operating System: ${os.user_os()}') + println('\n> Test Time: ${time.now().format_ss_micro()}') +} diff --git a/vlib/builtin/cfns.c.v b/vlib/builtin/cfns.c.v index d60fd55848..c30a2945fc 100644 --- a/vlib/builtin/cfns.c.v +++ b/vlib/builtin/cfns.c.v @@ -23,6 +23,9 @@ fn C.realloc(a &u8, b int) &u8 fn C.free(ptr voidptr) +fn C.mmap(addr_length int, length isize, prot int, flags int, fd int, offset u64) voidptr +fn C.mprotect(addr_length int, len isize, prot int) int + fn C.aligned_alloc(align isize, size isize) voidptr // windows aligned memory functions @@ -34,6 +37,9 @@ fn C._aligned_offset_realloc(voidptr, size isize, align isize, offset isize) voi fn C._aligned_msize(voidptr, align isize, offset isize) isize fn C._aligned_recalloc(voidptr, num isize, size isize, align isize) voidptr +fn C.VirtualAlloc(voidptr, isize, u32, u32) voidptr +fn C.VirtualProtect(voidptr, isize, u32, &u32) bool + @[noreturn; trusted] fn C.exit(code int) @@ -529,6 +535,10 @@ fn C.abs(number int) int fn C.GetDiskFreeSpaceExA(const_path &char, free_bytes_available_to_caller &u64, total_number_of_bytes &u64, total_number_of_free_bytes &u64) bool +fn C.GetNativeSystemInfo(voidptr) + +fn C.sysconf(name int) int + // C.SYSTEM_INFO contains information about the current computer system. This includes the architecture and type of the processor, the number of processors in the system, the page size, and other such information. @[typedef] pub struct C.SYSTEM_INFO { diff --git a/vlib/builtin/closure/README.md b/vlib/builtin/closure/README.md new file mode 100644 index 0000000000..bf35a2c02d --- /dev/null +++ b/vlib/builtin/closure/README.md @@ -0,0 +1,2 @@ +The files in this directory implement the `closure` feature of the V language, which is called +internally by the V compiler. diff --git a/vlib/builtin/closure/closure.c.v b/vlib/builtin/closure/closure.c.v new file mode 100644 index 0000000000..8bdd146122 --- /dev/null +++ b/vlib/builtin/closure/closure.c.v @@ -0,0 +1,241 @@ +@[has_globals] +module closure + +// Inspired from Chris Wellons's work +// https://nullprogram.com/blog/2017/01/08/ + +const assumed_page_size = int(0x4000) + +@[heap] +struct Closure { + ClosureMutex +mut: + closure_ptr voidptr + closure_get_data fn () voidptr = unsafe { nil } + closure_cap int + v_page_size int = int(0x4000) +} + +__global g_closure = Closure{} + +enum MemoryProtectAtrr { + read_exec + read_write +} + +// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly +// vfmt off +pub const closure_thunk = $if amd64 { + [ + u8(0xF3), 0x44, 0x0F, 0x7E, 0x3D, 0xF7, 0xBF, 0xFF, 0xFF, // movq xmm15, QWORD PTR [rip - userdata] + 0xFF, 0x25, 0xF9, 0xBF, 0xFF, 0xFF // jmp QWORD PTR [rip - fn] + ] +} $else $if i386 { + [ + u8(0xe8), 0x00, 0x00, 0x00, 0x00, // call here + // here: + 0x59, // pop ecx + 0x66, 0x0F, 0x6E, 0xF9, // movd xmm7, ecx + 0xff, 0xA1, 0xff, 0xbf, 0xff, 0xff, // jmp DWORD PTR [ecx - 0x4001] # + ] +} $else $if arm64 { + [ + u8(0x11), 0x00, 0xFE, 0x5C, // ldr d17, userdata + 0x30, 0x00, 0xFE, 0x58, // ldr x16, fn + 0x00, 0x02, 0x1F, 0xD6 // br x16 + ] +} $else $if arm32 { + [ + u8(0x04), 0xC0, 0x4F, 0xE2, // adr ip, here + // here: + 0x01, 0xC9, 0x4C, 0xE2, // sub ip, ip, #0x4000 + 0x90, 0xCA, 0x07, 0xEE, // vmov s15, ip + 0x00, 0xC0, 0x9C, 0xE5, // ldr ip, [ip, 0] + 0x1C, 0xFF, 0x2F, 0xE1 // bx ip + ] +} $else $if rv64 { + [ + u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc + 0x03, 0xBF, 0x8F, 0x00, // ld t5, 8(t6) + 0x07, 0xB3, 0x0F, 0x00, // fld ft6, 0(t6) + 0x67, 0x00, 0x0F, 0x00, // jr t5 + ] +} $else $if rv32 { + [ + u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc + 0x03, 0xAF, 0x4F, 0x00, // lw t5, 4(t6) + 0x07, 0xAB, 0x0F, 0x00, // flw fs6, 0(t6) + 0x67, 0x00, 0x0F, 0x00 // jr t5 + ] +} $else $if s390x { + [ + u8(0xC0), 0x70, 0xFF, 0xFF, 0xE0, 0x00, // larl %r7, -16384 + 0x68, 0xF0, 0x70, 0x00, // ld %f15, 0(%r7) + 0xE3, 0x70, 0x70, 0x08, 0x00, 0x04, // lg %r7, 8(%r7) + 0x07, 0xF7, // br %r7 + ] +} $else $if ppc64le { + [ + u8(0xa6), 0x02, 0x08, 0x7c, // mflr %r0 + 0x05, 0x00, 0x00, 0x48, // bl here + 0xa6, 0x02, 0xc8, 0x7d, // here: mflr %r14 + 0xf8, 0xbf, 0xce, 0x39, // addi %r14, %r14, -16392 + 0x00, 0x00, 0xce, 0xc9, // lfd %f14, 0(%r14) + 0x08, 0x00, 0xce, 0xe9, // ld %r14, 8(%r14) + 0xa6, 0x03, 0x08, 0x7c, // mtlr %r0 + 0xa6, 0x03, 0xc9, 0x7d, // mtctr %r14 + 0x20, 0x04, 0x80, 0x4e, // bctr + ] +} $else $if loongarch64 { + [ + u8(0x92), 0xFF, 0xFF, 0x1D, // pcaddu12i t6, -4 + 0x48, 0x02, 0x80, 0x2B, // fld.d f8, t6, 0 + 0x51, 0x22, 0xC0, 0x28, // ld.d t5, t6, 8 + 0x20, 0x02, 0x00, 0x4C, // jr t5 + ] +} $else { + []u8{} +} + +const closure_get_data_bytes = $if amd64 { + [ + u8(0x66), 0x4C, 0x0F, 0x7E, 0xF8, // movq rax, xmm15 + 0xC3 // ret + ] +} $else $if i386 { + [ + u8(0x66), 0x0F, 0x7E, 0xF8, // movd eax, xmm7 + 0x8B, 0x80, 0xFB, 0xBF, 0xFF, 0xFF, // mov eax, DWORD PTR [eax - 0x4005] + 0xc3 // ret + ] +} $else $if arm64 { + [ + u8(0x20), 0x02, 0x66, 0x9E, // fmov x0, d17 + 0xC0, 0x03, 0x5F, 0xD6 // ret + ] +} $else $if arm32 { + [ + u8(0x90), 0x0A, 0x17, 0xEE, // vmov r0, s15 + 0x04, 0x00, 0x10, 0xE5, // ldr r0, [r0, #-4] + 0x1E, 0xFF, 0x2F, 0xE1 // bx lr + ] +} $else $if rv64 { + [ + u8(0x53), 0x05, 0x03, 0xE2, // fmv.x.d a0, ft6 + 0x67, 0x80, 0x00, 0x00, // ret + ] +} $else $if rv32 { + [ + u8(0x53), 0x05, 0x0B, 0xE0, // fmv.x.w a0, fs6 + 0x67, 0x80, 0x00, 0x00 // ret + ] +} $else $if s390x { + [ + u8(0xB3), 0xCD, 0x00, 0x2F, // lgdr %r2, %f15 + 0x07, 0xFE, // br %r14 + ] +} $else $if ppc64le { + [ + u8(0x66), 0x00, 0xc3, 0x7d, // mfvsrd %r3, %f14 + 0x20, 0x00, 0x80, 0x4e, // blr + ] +} $else $if loongarch64 { + [ + u8(0x04), 0xB9, 0x14, 0x01, // movfr2gr.d a0, f8 + 0x20, 0x00, 0x00, 0x4C, // ret + ] +} $else { + []u8{} +} +// vfmt on + +// equal to `max(2*sizeof(void*), sizeof(__closure_thunk))`, rounded up to the next multiple of `sizeof(void*)` +// NOTE: This is a workaround for `-usecache` bug, as it can't include `fn get_closure_size()` needed by `const closure_size` in `build-module` mode. +const closure_size_1 = if 2 * u32(sizeof(voidptr)) > u32(closure_thunk.len) { + 2 * u32(sizeof(voidptr)) +} else { + u32(closure_thunk.len) + u32(sizeof(voidptr)) - 1 +} +const closure_size = int(closure_size_1 & ~(u32(sizeof(voidptr)) - 1)) + +// closure_alloc allocates executable memory pages for closures(INTERNAL COMPILER USE ONLY). +fn closure_alloc() { + p := closure_alloc_platform() + if isnil(p) { + return + } + // Setup executable and guard pages + x := unsafe { p + g_closure.v_page_size } // End of guard page + mut remaining := g_closure.v_page_size / closure_size // Calculate slot count + g_closure.closure_ptr = x // Current allocation pointer + g_closure.closure_cap = remaining // Remaining slot count + + // Fill page with closure templates + for remaining > 0 { + unsafe { vmemcpy(x, closure_thunk.data, closure_thunk.len) } // Copy template + remaining-- + unsafe { + x += closure_size // Move to next slot + } + } + closure_memory_protect_platform(g_closure.closure_ptr, g_closure.v_page_size, .read_exec) +} + +// closure_init initializes global closure subsystem(INTERNAL COMPILER USE ONLY). +fn closure_init() { + // Determine system page size + mut page_size := get_page_size_platform() + g_closure.v_page_size = page_size // Store calculated size + + // Initialize thread-safety lock + closure_mtx_lock_init_platform() + + // Initial memory allocation + closure_alloc() + + // Install closure handler template + unsafe { + // Temporarily enable write access to executable memory + closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_write) + // Copy closure entry stub code + vmemcpy(g_closure.closure_ptr, closure_get_data_bytes.data, closure_get_data_bytes.len) + // Re-enormalize execution protection + closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_exec) + } + // Setup global closure handler pointer + g_closure.closure_get_data = g_closure.closure_ptr + + // Advance allocation pointer past header + unsafe { + g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size + } + g_closure.closure_cap-- // Account for header slot +} + +// closure_create creates closure objects at compile-time(INTERNAL COMPILER USE ONLY). +@[direct_array_access] +fn closure_create(func voidptr, data voidptr) voidptr { + closure_mtx_lock_platform() + + // Handle memory exhaustion + if g_closure.closure_cap == 0 { + closure_alloc() // Allocate new memory page + } + g_closure.closure_cap-- // Decrement slot counter + + // Claim current closure slot + mut curr_closure := g_closure.closure_ptr + unsafe { + // Move to next available slot + g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size + + // Write closure metadata (data + function pointer) + mut p := &voidptr(&u8(curr_closure) - assumed_page_size) + p[0] = data // Stored closure context + p[1] = func // Target function to execute + } + closure_mtx_unlock_platform() + + // Return executable closure object + return curr_closure +} diff --git a/vlib/builtin/closure/closure.v b/vlib/builtin/closure/closure.v new file mode 100644 index 0000000000..56be4878d1 --- /dev/null +++ b/vlib/builtin/closure/closure.v @@ -0,0 +1,4 @@ +module closure + +// placeholder +// js gen need at least one `.v` file under the module dir. diff --git a/vlib/builtin/closure/closure_nix.c.v b/vlib/builtin/closure/closure_nix.c.v new file mode 100644 index 0000000000..34352f1446 --- /dev/null +++ b/vlib/builtin/closure/closure_nix.c.v @@ -0,0 +1,84 @@ +module closure + +$if !freestanding && !vinix { + #include +} + +@[typedef] +pub struct C.pthread_mutex_t {} + +struct ClosureMutex { + closure_mtx C.pthread_mutex_t +} + +@[inline] +fn closure_alloc_platform() &u8 { + mut p := &u8(unsafe { nil }) + $if freestanding { + // Freestanding environments (no OS) use simple malloc + p = unsafe { malloc(g_closure.v_page_size * 2) } + if isnil(p) { + return unsafe { nil } + } + } $else { + // Main OS environments use mmap to get aligned pages + p = unsafe { + C.mmap(0, g_closure.v_page_size * 2, C.PROT_READ | C.PROT_WRITE, C.MAP_ANONYMOUS | C.MAP_PRIVATE, + -1, 0) + } + if p == &u8(C.MAP_FAILED) { + return unsafe { nil } + } + } + return p +} + +@[inline] +fn closure_memory_protect_platform(ptr voidptr, size isize, attr MemoryProtectAtrr) { + $if freestanding { + // No memory protection in freestanding mode + } $else { + match attr { + .read_exec { + unsafe { C.mprotect(ptr, size, C.PROT_READ | C.PROT_EXEC) } + } + .read_write { + unsafe { C.mprotect(ptr, size, C.PROT_READ | C.PROT_WRITE) } + } + } + } +} + +@[inline] +fn get_page_size_platform() int { + // Determine system page size + mut page_size := 0x4000 + $if !freestanding { + // Query actual page size in OS environments + page_size = unsafe { int(C.sysconf(C._SC_PAGESIZE)) } + } + // Calculate required allocation size + page_size = page_size * (((assumed_page_size - 1) / page_size) + 1) + return page_size +} + +@[inline] +fn closure_mtx_lock_init_platform() { + $if !freestanding || vinix { + C.pthread_mutex_init(&g_closure.closure_mtx, 0) + } +} + +@[inline] +fn closure_mtx_lock_platform() { + $if !freestanding || vinix { + C.pthread_mutex_lock(&g_closure.closure_mtx) + } +} + +@[inline] +fn closure_mtx_unlock_platform() { + $if !freestanding || vinix { + C.pthread_mutex_unlock(&g_closure.closure_mtx) + } +} diff --git a/vlib/builtin/closure/closure_windows.c.v b/vlib/builtin/closure/closure_windows.c.v new file mode 100644 index 0000000000..c4b24b79c8 --- /dev/null +++ b/vlib/builtin/closure/closure_windows.c.v @@ -0,0 +1,53 @@ +module closure + +#include + +struct ClosureMutex { + closure_mtx C.SRWLOCK +} + +@[inline] +fn closure_alloc_platform() &u8 { + p := &u8(C.VirtualAlloc(0, g_closure.v_page_size * 2, C.MEM_COMMIT | C.MEM_RESERVE, + C.PAGE_READWRITE)) + return p +} + +@[inline] +fn closure_memory_protect_platform(ptr voidptr, size isize, attr MemoryProtectAtrr) { + mut tmp := u32(0) + match attr { + .read_exec { + _ := C.VirtualProtect(ptr, size, C.PAGE_EXECUTE_READ, &tmp) + } + .read_write { + _ := C.VirtualProtect(ptr, size, C.PAGE_READWRITE, &tmp) + } + } +} + +@[inline] +fn get_page_size_platform() int { + // Determine system page size + mut si := C.SYSTEM_INFO{} + C.GetNativeSystemInfo(&si) + + // Calculate required allocation size + page_size := int(si.dwPageSize) * (((assumed_page_size - 1) / int(si.dwPageSize)) + 1) + return page_size +} + +@[inline] +fn closure_mtx_lock_init_platform() { + C.InitializeSRWLock(&g_closure.closure_mtx) +} + +@[inline] +fn closure_mtx_lock_platform() { + C.AcquireSRWLockExclusive(&g_closure.closure_mtx) +} + +@[inline] +fn closure_mtx_unlock_platform() { + C.ReleaseSRWLockExclusive(&g_closure.closure_mtx) +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 75ca05157f..9e3e701270 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -661,17 +661,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO if g.channel_definitions.len > 0 { b.write_string2('\n// V channel code:\n', g.channel_definitions.str()) } - if g.anon_fn_definitions.len > 0 { - if g.nr_closures > 0 { - b.writeln2('\n// V closure helpers', c_closure_helpers(g.pref)) - } - /* - b.writeln('\n// V anon functions:') - for fn_def in g.anon_fn_definitions { - b.writeln(fn_def) - } - */ - } if g.pref.is_coverage { b.write_string2('\n// V coverage:\n', g.cov_declarations.str()) } @@ -687,21 +676,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO if g.embedded_data.len > 0 { helpers.write_string2('\n// V embedded data:\n', g.embedded_data.str()) } - if g.anon_fn_definitions.len > 0 { - if g.nr_closures > 0 { - helpers.writeln2('\n// V closure helpers', c_closure_fn_helpers(g.pref)) - } - /* - b.writeln('\n// V anon functions:') - for fn_def in g.anon_fn_definitions { - b.writeln(fn_def) - } - */ - if g.pref.parallel_cc { - g.extern_out.writeln('extern void* __closure_create(void* fn, void* data);') - g.extern_out.writeln('extern void __closure_init();') - } - } if g.pref.parallel_cc { helpers.writeln('\n// V global/const non-precomputed definitions:') for var_name in g.sorted_global_const_names { @@ -4367,7 +4341,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { g.gen_closure_fn(expr_styp, m, name) } } - g.write('__closure_create(${name}, ') + g.write('builtin__closure__closure_create(${name}, ') if !receiver.typ.is_ptr() { g.write('memdup_uncollectable(') } @@ -4523,7 +4497,7 @@ fn (mut g Gen) gen_closure_fn(expr_styp string, m ast.Fn, name string) { g.extern_out.writeln(';') } sb.writeln(' {') - sb.writeln('\t${data_styp}* a0 = __CLOSURE_GET_DATA();') + sb.writeln('\t${data_styp}* a0 = g_closure.closure_get_data();') if m.return_type != ast.void_type { sb.write_string('\treturn ') } else { @@ -6535,10 +6509,6 @@ fn (mut g Gen) write_init_function() { g.writeln('\tbuiltin_init();') } - if g.nr_closures > 0 { - g.writeln('\t_closure_mtx_init();') - } - // reflection bootstrapping if g.has_reflection { if var := g.global_const_defs['g_reflection'] { @@ -6597,6 +6567,10 @@ fn (mut g Gen) write_init_function() { } } + if g.nr_closures > 0 { + g.writeln('\tbuiltin__closure__closure_init();') + } + g.writeln('}') if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list { println(g.out.after(fn_vinit_start_pos)) @@ -6644,7 +6618,7 @@ fn (mut g Gen) write_init_function() { g.writeln('void _vinit_caller() {') g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') if g.nr_closures > 0 { - g.writeln('\t__closure_init(); // vinit_caller()') + g.writeln('\tbuiltin__closure__closure_init(); // vinit_caller()') } g.writeln('\t_vinit(0,0);') g.writeln('}') diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index a835dce7cd..9bf953a52a 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -1,8 +1,5 @@ module c -import strings -import v.pref - // Note: @@@ here serve as placeholders. // They will be replaced with correct strings // for each constant, during C code generation. @@ -51,264 +48,6 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) { } ' -// Inspired from Chris Wellons's work -// https://nullprogram.com/blog/2017/01/08/ - -fn c_closure_helpers(pref_ &pref.Preferences) string { - mut builder := strings.new_builder(2048) - if pref_.os != .windows && pref_.is_bare == false { - builder.writeln('#include ') - } - - builder.write_string(' -#ifdef _MSC_VER - #define __RETURN_ADDRESS() ((char*)_ReturnAddress()) -#elif defined(__TINYC__) && defined(_WIN32) - #define __RETURN_ADDRESS() ((char*)__builtin_return_address(0)) -#else - #define __RETURN_ADDRESS() ((char*)__builtin_extract_return_addr(__builtin_return_address(0))) -#endif - -static int _V_page_size = 0x4000; // 16K -#define ASSUMED_PAGE_SIZE 0x4000 -#define _CLOSURE_SIZE (((2*sizeof(void*) > sizeof(__closure_thunk) ? 2*sizeof(void*) : sizeof(__closure_thunk)) + sizeof(void*) - 1) & ~(sizeof(void*) - 1)) -// equal to `max(2*sizeof(void*), sizeof(__closure_thunk))`, rounded up to the next multiple of `sizeof(void*)` - -// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly -#ifdef __V_amd64 -static const char __closure_thunk[] = { - 0xF3, 0x44, 0x0F, 0x7E, 0x3D, 0xF7, 0xBF, 0xFF, 0xFF, // movq xmm15, QWORD PTR [rip - userdata] - 0xFF, 0x25, 0xF9, 0xBF, 0xFF, 0xFF // jmp QWORD PTR [rip - fn] -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x66, 0x4C, 0x0F, 0x7E, 0xF8, // movq rax, xmm15 - 0xC3 // ret -}; -#elif defined(__V_x86) -static char __closure_thunk[] = { - 0xe8, 0x00, 0x00, 0x00, 0x00, // call here - // here: - 0x59, // pop ecx - 0x66, 0x0F, 0x6E, 0xF9, // movd xmm7, ecx - 0xff, 0xA1, 0xff, 0xbf, 0xff, 0xff, // jmp DWORD PTR [ecx - 0x4001] # -}; - -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x66, 0x0F, 0x7E, 0xF8, // movd eax, xmm7 - 0x8B, 0x80, 0xFB, 0xBF, 0xFF, 0xFF, // mov eax, DWORD PTR [eax - 0x4005] - 0xc3 // ret -}; - -#elif defined(__V_arm64) -static char __closure_thunk[] = { - 0x11, 0x00, 0xFE, 0x5C, // ldr d17, userdata - 0x30, 0x00, 0xFE, 0x58, // ldr x16, fn - 0x00, 0x02, 0x1F, 0xD6 // br x16 -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x20, 0x02, 0x66, 0x9E, // fmov x0, d17 - 0xC0, 0x03, 0x5F, 0xD6 // ret -}; -#elif defined(__V_arm32) -static char __closure_thunk[] = { - 0x04, 0xC0, 0x4F, 0xE2, // adr ip, here - // here: - 0x01, 0xC9, 0x4C, 0xE2, // sub ip, ip, #0x4000 - 0x90, 0xCA, 0x07, 0xEE, // vmov s15, ip - 0x00, 0xC0, 0x9C, 0xE5, // ldr ip, [ip, 0] - 0x1C, 0xFF, 0x2F, 0xE1 // bx ip -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x90, 0x0A, 0x17, 0xEE, // vmov r0, s15 - 0x04, 0x00, 0x10, 0xE5, // ldr r0, [r0, #-4] - 0x1E, 0xFF, 0x2F, 0xE1 // bx lr -}; -#elif defined (__V_rv64) -static char __closure_thunk[] = { - 0x97, 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc - 0x03, 0xBF, 0x8F, 0x00, // ld t5, 8(t6) - 0x07, 0xB3, 0x0F, 0x00, // fld ft6, 0(t6) - 0x67, 0x00, 0x0F, 0x00, // jr t5 -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x53, 0x05, 0x03, 0xE2, // fmv.x.d a0, ft6 - 0x67, 0x80, 0x00, 0x00, // ret -}; -#elif defined (__V_rv32) -static char __closure_thunk[] = { - 0x97, 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc - 0x03, 0xAF, 0x4F, 0x00, // lw t5, 4(t6) - 0x07, 0xAB, 0x0F, 0x00, // flw fs6, 0(t6) - 0x67, 0x00, 0x0F, 0x00 // jr t5 -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x53, 0x05, 0x0B, 0xE0, // fmv.x.w a0, fs6 - 0x67, 0x80, 0x00, 0x00 // ret -}; -#elif defined (__V_s390x) -static char __closure_thunk[] = { - 0xC0, 0x70, 0xFF, 0xFF, 0xE0, 0x00, // larl %r7, -16384 - 0x68, 0xF0, 0x70, 0x00, // ld %f15, 0(%r7) - 0xE3, 0x70, 0x70, 0x08, 0x00, 0x04, // lg %r7, 8(%r7) - 0x07, 0xF7, // br %r7 -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0xB3, 0xCD, 0x00, 0x2F, // lgdr %r2, %f15 - 0x07, 0xFE, // br %r14 -}; -#elif defined (__V_ppc64le) -static char __closure_thunk[] = { - 0xa6, 0x02, 0x08, 0x7c, // mflr %r0 - 0x05, 0x00, 0x00, 0x48, // bl here - 0xa6, 0x02, 0xc8, 0x7d, // here: mflr %r14 - 0xf8, 0xbf, 0xce, 0x39, // addi %r14, %r14, -16392 - 0x00, 0x00, 0xce, 0xc9, // lfd %f14, 0(%r14) - 0x08, 0x00, 0xce, 0xe9, // ld %r14, 8(%r14) - 0xa6, 0x03, 0x08, 0x7c, // mtlr %r0 - 0xa6, 0x03, 0xc9, 0x7d, // mtctr %r14 - 0x20, 0x04, 0x80, 0x4e, // bctr -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x66, 0x00, 0xc3, 0x7d, // mfvsrd %r3, %f14 - 0x20, 0x00, 0x80, 0x4e, // blr -}; -#elif defined (__V_loongarch64) -static char __closure_thunk[] = { - 0x92, 0xFF, 0xFF, 0x1D, // pcaddu12i t6, -4 - 0x48, 0x02, 0x80, 0x2B, // fld.d f8, t6, 0 - 0x51, 0x22, 0xC0, 0x28, // ld.d t5, t6, 8 - 0x20, 0x02, 0x00, 0x4C, // jr t5 -}; -static char __CLOSURE_GET_DATA_BYTES[] = { - 0x04, 0xB9, 0x14, 0x01, // movfr2gr.d a0, f8 - 0x20, 0x00, 0x00, 0x4C, // ret -}; -#endif - -static void*(*__CLOSURE_GET_DATA)(void) = 0; - -static inline void __closure_set_data(char* closure, void* data) { - void** p = (void**)(closure - ASSUMED_PAGE_SIZE); - p[0] = data; -} - -static inline void __closure_set_function(char* closure, void* f) { - void** p = (void**)(closure - ASSUMED_PAGE_SIZE); - p[1] = f; -} - -#ifdef _WIN32 -#include -static SRWLOCK _closure_mtx; -#define _closure_mtx_init() InitializeSRWLock(&_closure_mtx) -#define _closure_mtx_lock() AcquireSRWLockExclusive(&_closure_mtx) -#define _closure_mtx_unlock() ReleaseSRWLockExclusive(&_closure_mtx) -#elif defined(_VFREESTANDING) -#define _closure_mtx_init() -#define _closure_mtx_lock() -#define _closure_mtx_unlock() -#else -static pthread_mutex_t _closure_mtx; -#define _closure_mtx_init() pthread_mutex_init(&_closure_mtx, 0) -#define _closure_mtx_lock() pthread_mutex_lock(&_closure_mtx) -#define _closure_mtx_unlock() pthread_mutex_unlock(&_closure_mtx) -#endif -') - return builder.str() -} - -fn c_closure_fn_helpers(pref_ &pref.Preferences) string { - static_non_parallel := if pref_.parallel_cc { '' } else { 'static ' } - - mut builder := strings.new_builder(2048) - builder.write_string(' - ${static_non_parallel}char* _closure_ptr = 0; - ${static_non_parallel}int _closure_cap = 0; - -static void __closure_alloc(void) { -#ifdef _WIN32 - char* p = VirtualAlloc(NULL, _V_page_size * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (p == NULL) return; -#elif defined(_VFREESTANDING) - char *p = malloc(_V_page_size * 2); - if (p == NULL) return; -#else - char* p = mmap(0, _V_page_size * 2, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (p == MAP_FAILED) return; -#endif - char* x = p + _V_page_size; - int remaining = _V_page_size / _CLOSURE_SIZE; - _closure_ptr = x; - _closure_cap = remaining; - while (remaining > 0) { - memcpy(x, __closure_thunk, sizeof(__closure_thunk)); - remaining--; - x += _CLOSURE_SIZE; - } -#ifdef _WIN32 - DWORD _tmp; - VirtualProtect(_closure_ptr, _V_page_size, PAGE_EXECUTE_READ, &_tmp); -#elif defined(_VFREESTANDING) -#else - mprotect(_closure_ptr, _V_page_size, PROT_READ | PROT_EXEC); -#endif -} - -#ifdef _WIN32 -void __closure_init() { - SYSTEM_INFO si; - GetNativeSystemInfo(&si); - uint32_t page_size = si.dwPageSize * (((ASSUMED_PAGE_SIZE - 1) / si.dwPageSize) + 1); - _V_page_size = page_size; - __closure_alloc(); - DWORD _tmp; - VirtualProtect(_closure_ptr, page_size, PAGE_READWRITE, &_tmp); - memcpy(_closure_ptr, __CLOSURE_GET_DATA_BYTES, sizeof(__CLOSURE_GET_DATA_BYTES)); - VirtualProtect(_closure_ptr, page_size, PAGE_EXECUTE_READ, &_tmp); - __CLOSURE_GET_DATA = (void*)_closure_ptr; - _closure_ptr += _CLOSURE_SIZE; - _closure_cap--; -} -#else -${static_non_parallel}void __closure_init() { -#ifndef _VFREESTANDING - uint32_t page_size = sysconf(_SC_PAGESIZE); -#else - uint32_t page_size = 0x4000; -#endif - page_size = page_size * (((ASSUMED_PAGE_SIZE - 1) / page_size) + 1); - _V_page_size = page_size; - __closure_alloc(); -#ifndef _VFREESTANDING - mprotect(_closure_ptr, page_size, PROT_READ | PROT_WRITE); -#endif - memcpy(_closure_ptr, __CLOSURE_GET_DATA_BYTES, sizeof(__CLOSURE_GET_DATA_BYTES)); -#ifndef _VFREESTANDING - mprotect(_closure_ptr, page_size, PROT_READ | PROT_EXEC); -#endif - __CLOSURE_GET_DATA = (void*)_closure_ptr; - _closure_ptr += _CLOSURE_SIZE; - _closure_cap--; -} -#endif - -${static_non_parallel}void* __closure_create(void* fn, void* data) { - _closure_mtx_lock(); - if (_closure_cap == 0) { - __closure_alloc(); - } - _closure_cap--; - void* closure = _closure_ptr; - _closure_ptr += _CLOSURE_SIZE; - __closure_set_data(closure, data); - __closure_set_function(closure, fn); - _closure_mtx_unlock(); - return closure; -} -') - return builder.str() -} - const c_common_macros = ' #define EMPTY_VARG_INITIALIZATION 0 #define EMPTY_STRUCT_DECLARATION diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v index 2012a3804d..9bb080cc71 100644 --- a/vlib/v/gen/c/cmain.v +++ b/vlib/v/gen/c/cmain.v @@ -142,9 +142,6 @@ fn (mut g Gen) gen_c_main_function_header() { g.writeln('\tg_main_argv = ___argv;') } } - if g.nr_closures > 0 { - g.writeln('\t__closure_init(); // main()') - } } fn (mut g Gen) gen_c_main_header() { @@ -208,10 +205,6 @@ sapp_desc sokol_main(int argc, char* argv[]) { (void)argc; (void)argv;') g.gen_c_main_trace_calls_hook() - if g.nr_closures > 0 { - g.writeln('\t__closure_init(); // main()') - } - if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { g.writeln('#if defined(_VGCBOEHM)') if g.pref.gc_mode == .boehm_leak { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index c1f1abd6b9..ca334f4e0d 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -419,7 +419,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { } g.writeln(') {') if is_closure { - g.writeln('${cur_closure_ctx}* ${closure_ctx} = __CLOSURE_GET_DATA();') + g.writeln('${cur_closure_ctx}* ${closure_ctx} = g_closure.closure_get_data();') } for i, is_promoted in heap_promoted { if is_promoted { @@ -620,8 +620,8 @@ fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { } ctx_struct := g.closure_ctx(node.decl) // it may be possible to optimize `memdup` out if the closure never leaves current scope - // 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(${fn_name}, (${ctx_struct}*) memdup_uncollectable(&(${ctx_struct}){') + // TODO: in case of an assignment, this should only call "closure_set_data" and "closure_set_function" (and free the former data) + g.write('builtin__closure__closure_create(${fn_name}, (${ctx_struct}*) memdup_uncollectable(&(${ctx_struct}){') g.indent++ for var in node.inherited_vars { mut has_inherited := false diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 5839a35774..0645beba4c 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -184,6 +184,9 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a } if table.used_features.anon_fn { core_fns << 'memdup_uncollectable' + core_fns << 'builtin.closure.closure_alloc' + core_fns << 'builtin.closure.closure_init' + core_fns << 'builtin.closure.closure_create' } if table.used_features.arr_map { include_panic_deps = true diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 6bf877dffb..59771c23aa 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -476,6 +476,9 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { } else { // Anonymous function node = p.anon_fn() + if p.file_backend_mode == .v || p.file_backend_mode == .c { + p.register_auto_import('builtin.closure') + } // its a call // NOTE: this could be moved to just before the pratt loop // then anything can be a call, eg. `index[2]()` or `struct.field()` diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 7af252379c..d42a76178a 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2007,6 +2007,9 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { } is_filter := field_name in ['filter', 'map', 'any', 'all', 'count'] if is_filter || field_name == 'sort' || field_name == 'sorted' { + if p.file_backend_mode == .v || p.file_backend_mode == .c { + p.register_auto_import('builtin.closure') + } p.open_scope() defer { p.close_scope() diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index dab6d23b86..553e4f2d2b 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -284,6 +284,22 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { // error is set in parse_type return ast.StructDecl{} } + + // for field_name []fn, cgen will generate closure, so detect here + if p.file_backend_mode == .v || p.file_backend_mode == .c { + sym := p.table.sym(typ) + mut elem_kind := ast.Kind.placeholder + if sym.kind == .array && (sym.info is ast.Array || sym.info is ast.Alias) { + elem_kind = p.table.sym(sym.array_info().elem_type).kind + } else if sym.kind == .array_fixed + && (sym.info is ast.ArrayFixed || sym.info is ast.Alias) { + elem_kind = p.table.sym(sym.array_fixed_info().elem_type).kind + } + if elem_kind == .function { + p.register_auto_import('builtin.closure') + } + } + field_pos = field_start_pos.extend(p.prev_tok.pos()) if typ.has_option_or_result() { option_pos = p.peek_token(-2).pos() diff --git a/vlib/v/tests/builtin_arrays/array_ops_create_just_one_closure_test.c.v b/vlib/v/tests/builtin_arrays/array_ops_create_just_one_closure_test.c.v index 94ba507d60..4703776058 100644 --- a/vlib/v/tests/builtin_arrays/array_ops_create_just_one_closure_test.c.v +++ b/vlib/v/tests/builtin_arrays/array_ops_create_just_one_closure_test.c.v @@ -1,6 +1,10 @@ +struct C.builtin__closure__Closure { + closure_cap int +} + fn setup(fname string) (int, int, []int) { println(fname) - return C._closure_cap, 42, []int{len: 5, init: index * 5} + return unsafe { &C.builtin__closure__Closure(voidptr(&C.g_closure)).closure_cap }, 42, []int{len: 5, init: index * 5} } fn test_array_filter() { @@ -9,7 +13,7 @@ fn test_array_filter() { println('x: ${x} | i: ${i}') return i < 20 })) - assert start_closure_cap - C._closure_cap == 1 + assert start_closure_cap - unsafe { &C.builtin__closure__Closure(voidptr(&C.g_closure)).closure_cap } == 1 } fn test_array_map() { @@ -18,7 +22,7 @@ fn test_array_map() { println('x: ${x} | i: ${i}') return x + i })) - assert start_closure_cap - C._closure_cap == 1 + assert start_closure_cap - unsafe { &C.builtin__closure__Closure(voidptr(&C.g_closure)).closure_cap } == 1 } fn test_array_any() { @@ -27,7 +31,7 @@ fn test_array_any() { println('x: ${x} | i: ${i}') return i < x })) - assert start_closure_cap - C._closure_cap == 1 + assert start_closure_cap - unsafe { &C.builtin__closure__Closure(voidptr(&C.g_closure)).closure_cap } == 1 } fn test_array_all() { @@ -36,5 +40,5 @@ fn test_array_all() { println('x: ${x} | i: ${i}') return i < x })) - assert start_closure_cap - C._closure_cap == 1 + assert start_closure_cap - unsafe { &C.builtin__closure__Closure(voidptr(&C.g_closure)).closure_cap } == 1 } diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index 1bd894a63e..6305ee2749 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -15,8 +15,9 @@ import runtime // math.bits is needed by strconv.ftoa pub const builtin_module_parts = ['math.bits', 'strconv', 'dlmalloc', 'strconv.ftoa', 'strings', - 'builtin'] -pub const bundle_modules = ['clipboard', 'fontstash', 'gg', 'gx', 'sokol', 'szip', 'ui']! + 'builtin', 'builtin.closure'] +pub const bundle_modules = ['clipboard', 'fontstash', 'gg', 'gx', 'sokol', 'szip', 'ui', + 'builtin.closure']! pub const external_module_dependencies_for_tool = { 'vdoc': ['markdown']