mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
cgen: move closure C code to V code under vlib/builtin/closure/ (#24912)
This commit is contained in:
parent
a08ea74167
commit
2d87ac4837
17 changed files with 719 additions and 311 deletions
278
bench/bench_closure.v
Normal file
278
bench/bench_closure.v
Normal file
|
@ -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()}')
|
||||||
|
}
|
|
@ -23,6 +23,9 @@ fn C.realloc(a &u8, b int) &u8
|
||||||
|
|
||||||
fn C.free(ptr voidptr)
|
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
|
fn C.aligned_alloc(align isize, size isize) voidptr
|
||||||
|
|
||||||
// windows aligned memory functions
|
// 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_msize(voidptr, align isize, offset isize) isize
|
||||||
fn C._aligned_recalloc(voidptr, num isize, size isize, align isize) voidptr
|
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]
|
@[noreturn; trusted]
|
||||||
fn C.exit(code int)
|
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.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.
|
// 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]
|
@[typedef]
|
||||||
pub struct C.SYSTEM_INFO {
|
pub struct C.SYSTEM_INFO {
|
||||||
|
|
2
vlib/builtin/closure/README.md
Normal file
2
vlib/builtin/closure/README.md
Normal file
|
@ -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.
|
241
vlib/builtin/closure/closure.c.v
Normal file
241
vlib/builtin/closure/closure.c.v
Normal file
|
@ -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] # <fn>
|
||||||
|
]
|
||||||
|
} $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
|
||||||
|
}
|
4
vlib/builtin/closure/closure.v
Normal file
4
vlib/builtin/closure/closure.v
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module closure
|
||||||
|
|
||||||
|
// placeholder
|
||||||
|
// js gen need at least one `.v` file under the module dir.
|
84
vlib/builtin/closure/closure_nix.c.v
Normal file
84
vlib/builtin/closure/closure_nix.c.v
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
module closure
|
||||||
|
|
||||||
|
$if !freestanding && !vinix {
|
||||||
|
#include <sys/mman.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
@[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)
|
||||||
|
}
|
||||||
|
}
|
53
vlib/builtin/closure/closure_windows.c.v
Normal file
53
vlib/builtin/closure/closure_windows.c.v
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
module closure
|
||||||
|
|
||||||
|
#include <synchapi.h>
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
|
@ -661,17 +661,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
|
||||||
if g.channel_definitions.len > 0 {
|
if g.channel_definitions.len > 0 {
|
||||||
b.write_string2('\n// V channel code:\n', g.channel_definitions.str())
|
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 {
|
if g.pref.is_coverage {
|
||||||
b.write_string2('\n// V coverage:\n', g.cov_declarations.str())
|
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 {
|
if g.embedded_data.len > 0 {
|
||||||
helpers.write_string2('\n// V embedded data:\n', g.embedded_data.str())
|
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 {
|
if g.pref.parallel_cc {
|
||||||
helpers.writeln('\n// V global/const non-precomputed definitions:')
|
helpers.writeln('\n// V global/const non-precomputed definitions:')
|
||||||
for var_name in g.sorted_global_const_names {
|
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.gen_closure_fn(expr_styp, m, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.write('__closure_create(${name}, ')
|
g.write('builtin__closure__closure_create(${name}, ')
|
||||||
if !receiver.typ.is_ptr() {
|
if !receiver.typ.is_ptr() {
|
||||||
g.write('memdup_uncollectable(')
|
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(';')
|
g.extern_out.writeln(';')
|
||||||
}
|
}
|
||||||
sb.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 {
|
if m.return_type != ast.void_type {
|
||||||
sb.write_string('\treturn ')
|
sb.write_string('\treturn ')
|
||||||
} else {
|
} else {
|
||||||
|
@ -6535,10 +6509,6 @@ fn (mut g Gen) write_init_function() {
|
||||||
g.writeln('\tbuiltin_init();')
|
g.writeln('\tbuiltin_init();')
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.nr_closures > 0 {
|
|
||||||
g.writeln('\t_closure_mtx_init();')
|
|
||||||
}
|
|
||||||
|
|
||||||
// reflection bootstrapping
|
// reflection bootstrapping
|
||||||
if g.has_reflection {
|
if g.has_reflection {
|
||||||
if var := g.global_const_defs['g_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('}')
|
g.writeln('}')
|
||||||
if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list {
|
if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list {
|
||||||
println(g.out.after(fn_vinit_start_pos))
|
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('void _vinit_caller() {')
|
||||||
g.writeln('\tstatic bool once = false; if (once) {return;} once = true;')
|
g.writeln('\tstatic bool once = false; if (once) {return;} once = true;')
|
||||||
if g.nr_closures > 0 {
|
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('\t_vinit(0,0);')
|
||||||
g.writeln('}')
|
g.writeln('}')
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
module c
|
module c
|
||||||
|
|
||||||
import strings
|
|
||||||
import v.pref
|
|
||||||
|
|
||||||
// Note: @@@ here serve as placeholders.
|
// Note: @@@ here serve as placeholders.
|
||||||
// They will be replaced with correct strings
|
// They will be replaced with correct strings
|
||||||
// for each constant, during C code generation.
|
// 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 <sys/mman.h>')
|
|
||||||
}
|
|
||||||
|
|
||||||
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] # <fn>
|
|
||||||
};
|
|
||||||
|
|
||||||
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 <synchapi.h>
|
|
||||||
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 = '
|
const c_common_macros = '
|
||||||
#define EMPTY_VARG_INITIALIZATION 0
|
#define EMPTY_VARG_INITIALIZATION 0
|
||||||
#define EMPTY_STRUCT_DECLARATION
|
#define EMPTY_STRUCT_DECLARATION
|
||||||
|
|
|
@ -142,9 +142,6 @@ fn (mut g Gen) gen_c_main_function_header() {
|
||||||
g.writeln('\tg_main_argv = ___argv;')
|
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() {
|
fn (mut g Gen) gen_c_main_header() {
|
||||||
|
@ -208,10 +205,6 @@ sapp_desc sokol_main(int argc, char* argv[]) {
|
||||||
(void)argc; (void)argv;')
|
(void)argc; (void)argv;')
|
||||||
g.gen_c_main_trace_calls_hook()
|
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] {
|
if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] {
|
||||||
g.writeln('#if defined(_VGCBOEHM)')
|
g.writeln('#if defined(_VGCBOEHM)')
|
||||||
if g.pref.gc_mode == .boehm_leak {
|
if g.pref.gc_mode == .boehm_leak {
|
||||||
|
|
|
@ -419,7 +419,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
|
||||||
}
|
}
|
||||||
g.writeln(') {')
|
g.writeln(') {')
|
||||||
if is_closure {
|
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 {
|
for i, is_promoted in heap_promoted {
|
||||||
if is_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)
|
ctx_struct := g.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
|
||||||
// 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(${fn_name}, (${ctx_struct}*) memdup_uncollectable(&(${ctx_struct}){')
|
g.write('builtin__closure__closure_create(${fn_name}, (${ctx_struct}*) memdup_uncollectable(&(${ctx_struct}){')
|
||||||
g.indent++
|
g.indent++
|
||||||
for var in node.inherited_vars {
|
for var in node.inherited_vars {
|
||||||
mut has_inherited := false
|
mut has_inherited := false
|
||||||
|
|
|
@ -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 {
|
if table.used_features.anon_fn {
|
||||||
core_fns << 'memdup_uncollectable'
|
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 {
|
if table.used_features.arr_map {
|
||||||
include_panic_deps = true
|
include_panic_deps = true
|
||||||
|
|
|
@ -476,6 +476,9 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr {
|
||||||
} else {
|
} else {
|
||||||
// Anonymous function
|
// Anonymous function
|
||||||
node = p.anon_fn()
|
node = p.anon_fn()
|
||||||
|
if p.file_backend_mode == .v || p.file_backend_mode == .c {
|
||||||
|
p.register_auto_import('builtin.closure')
|
||||||
|
}
|
||||||
// its a call
|
// its a call
|
||||||
// NOTE: this could be moved to just before the pratt loop
|
// NOTE: this could be moved to just before the pratt loop
|
||||||
// then anything can be a call, eg. `index[2]()` or `struct.field()`
|
// then anything can be a call, eg. `index[2]()` or `struct.field()`
|
||||||
|
|
|
@ -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']
|
is_filter := field_name in ['filter', 'map', 'any', 'all', 'count']
|
||||||
if is_filter || field_name == 'sort' || field_name == 'sorted' {
|
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()
|
p.open_scope()
|
||||||
defer {
|
defer {
|
||||||
p.close_scope()
|
p.close_scope()
|
||||||
|
|
|
@ -284,6 +284,22 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
|
||||||
// error is set in parse_type
|
// error is set in parse_type
|
||||||
return ast.StructDecl{}
|
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())
|
field_pos = field_start_pos.extend(p.prev_tok.pos())
|
||||||
if typ.has_option_or_result() {
|
if typ.has_option_or_result() {
|
||||||
option_pos = p.peek_token(-2).pos()
|
option_pos = p.peek_token(-2).pos()
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
struct C.builtin__closure__Closure {
|
||||||
|
closure_cap int
|
||||||
|
}
|
||||||
|
|
||||||
fn setup(fname string) (int, int, []int) {
|
fn setup(fname string) (int, int, []int) {
|
||||||
println(fname)
|
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() {
|
fn test_array_filter() {
|
||||||
|
@ -9,7 +13,7 @@ fn test_array_filter() {
|
||||||
println('x: ${x} | i: ${i}')
|
println('x: ${x} | i: ${i}')
|
||||||
return i < 20
|
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() {
|
fn test_array_map() {
|
||||||
|
@ -18,7 +22,7 @@ fn test_array_map() {
|
||||||
println('x: ${x} | i: ${i}')
|
println('x: ${x} | i: ${i}')
|
||||||
return x + 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() {
|
fn test_array_any() {
|
||||||
|
@ -27,7 +31,7 @@ fn test_array_any() {
|
||||||
println('x: ${x} | i: ${i}')
|
println('x: ${x} | i: ${i}')
|
||||||
return i < x
|
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() {
|
fn test_array_all() {
|
||||||
|
@ -36,5 +40,5 @@ fn test_array_all() {
|
||||||
println('x: ${x} | i: ${i}')
|
println('x: ${x} | i: ${i}')
|
||||||
return i < x
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ import runtime
|
||||||
|
|
||||||
// math.bits is needed by strconv.ftoa
|
// math.bits is needed by strconv.ftoa
|
||||||
pub const builtin_module_parts = ['math.bits', 'strconv', 'dlmalloc', 'strconv.ftoa', 'strings',
|
pub const builtin_module_parts = ['math.bits', 'strconv', 'dlmalloc', 'strconv.ftoa', 'strings',
|
||||||
'builtin']
|
'builtin', 'builtin.closure']
|
||||||
pub const bundle_modules = ['clipboard', 'fontstash', 'gg', 'gx', 'sokol', 'szip', 'ui']!
|
pub const bundle_modules = ['clipboard', 'fontstash', 'gg', 'gx', 'sokol', 'szip', 'ui',
|
||||||
|
'builtin.closure']!
|
||||||
|
|
||||||
pub const external_module_dependencies_for_tool = {
|
pub const external_module_dependencies_for_tool = {
|
||||||
'vdoc': ['markdown']
|
'vdoc': ['markdown']
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue