cgen: ensure closures are kept alive when using the GC (#14736)
parent
b27b6b2047
commit
26d051475a
|
@ -388,6 +388,49 @@ pub fn malloc_noscan(n isize) &u8 {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// malloc_uncollectable dynamically allocates a `n` bytes block of memory
|
||||||
|
// on the heap, which will NOT be garbage-collected (but its contents will).
|
||||||
|
[unsafe]
|
||||||
|
pub fn malloc_uncollectable(n isize) &u8 {
|
||||||
|
if n <= 0 {
|
||||||
|
panic('malloc_uncollectable($n <= 0)')
|
||||||
|
}
|
||||||
|
$if vplayground ? {
|
||||||
|
if n > 10000 {
|
||||||
|
panic('allocating more than 10 KB at once is not allowed in the V playground')
|
||||||
|
}
|
||||||
|
if total_m > 50 * 1024 * 1024 {
|
||||||
|
panic('allocating more than 50 MB is not allowed in the V playground')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if trace_malloc ? {
|
||||||
|
total_m += n
|
||||||
|
C.fprintf(C.stderr, c'malloc_uncollectable %6d total %10d\n', n, total_m)
|
||||||
|
// print_backtrace()
|
||||||
|
}
|
||||||
|
mut res := &u8(0)
|
||||||
|
$if prealloc {
|
||||||
|
return unsafe { prealloc_malloc(n) }
|
||||||
|
} $else $if gcboehm ? {
|
||||||
|
unsafe {
|
||||||
|
res = C.GC_MALLOC_UNCOLLECTABLE(n)
|
||||||
|
}
|
||||||
|
} $else $if freestanding {
|
||||||
|
res = unsafe { __malloc(usize(n)) }
|
||||||
|
} $else {
|
||||||
|
res = unsafe { C.malloc(n) }
|
||||||
|
}
|
||||||
|
if res == 0 {
|
||||||
|
panic('malloc_uncollectable($n) failed')
|
||||||
|
}
|
||||||
|
$if debug_malloc ? {
|
||||||
|
// Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
|
||||||
|
// when the calling code wrongly relies on it being zeroed.
|
||||||
|
unsafe { C.memset(res, 0x4D, n) }
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// v_realloc resizes the memory block `b` with `n` bytes.
|
// v_realloc resizes the memory block `b` with `n` bytes.
|
||||||
// The `b byteptr` must be a pointer to an existing memory block
|
// The `b byteptr` must be a pointer to an existing memory block
|
||||||
// previously allocated with `malloc`, `v_calloc` or `vcalloc`.
|
// previously allocated with `malloc`, `v_calloc` or `vcalloc`.
|
||||||
|
@ -555,6 +598,21 @@ pub fn memdup_noscan(src voidptr, sz int) voidptr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// memdup_uncollectable dynamically allocates a `sz` bytes block of memory
|
||||||
|
// on the heap, which will NOT be garbage-collected (but its contents will).
|
||||||
|
// memdup_uncollectable then copies the contents of `src` into the allocated
|
||||||
|
// space and returns a pointer to the newly allocated space.
|
||||||
|
[unsafe]
|
||||||
|
pub fn memdup_uncollectable(src voidptr, sz int) voidptr {
|
||||||
|
if sz == 0 {
|
||||||
|
return vcalloc(1)
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
mem := malloc_uncollectable(sz)
|
||||||
|
return C.memcpy(mem, src, sz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[inline]
|
[inline]
|
||||||
fn v_fixed_index(i int, len int) int {
|
fn v_fixed_index(i int, len int) int {
|
||||||
$if !no_bounds_checking ? {
|
$if !no_bounds_checking ? {
|
||||||
|
|
|
@ -3393,7 +3393,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
|
||||||
|
|
||||||
g.write('__closure_create($name, ')
|
g.write('__closure_create($name, ')
|
||||||
if !receiver.typ.is_ptr() {
|
if !receiver.typ.is_ptr() {
|
||||||
g.write('memdup(')
|
g.write('memdup_uncollectable(')
|
||||||
}
|
}
|
||||||
if !node.expr_type.is_ptr() {
|
if !node.expr_type.is_ptr() {
|
||||||
g.write('&')
|
g.write('&')
|
||||||
|
|
|
@ -94,6 +94,7 @@ fn (mut g Gen) gen_c_main_header() {
|
||||||
if g.pref.gc_mode == .boehm_leak {
|
if g.pref.gc_mode == .boehm_leak {
|
||||||
g.writeln('\tGC_set_find_leak(1);')
|
g.writeln('\tGC_set_find_leak(1);')
|
||||||
}
|
}
|
||||||
|
g.writeln('\tGC_set_pages_executable(0);')
|
||||||
g.writeln('\tGC_INIT();')
|
g.writeln('\tGC_INIT();')
|
||||||
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
||||||
g.writeln('\tGC_enable_incremental();')
|
g.writeln('\tGC_enable_incremental();')
|
||||||
|
@ -211,6 +212,7 @@ pub fn (mut g Gen) gen_c_main_for_tests() {
|
||||||
if g.pref.gc_mode == .boehm_leak {
|
if g.pref.gc_mode == .boehm_leak {
|
||||||
g.writeln('\tGC_set_find_leak(1);')
|
g.writeln('\tGC_set_find_leak(1);')
|
||||||
}
|
}
|
||||||
|
g.writeln('\tGC_set_pages_executable(0);')
|
||||||
g.writeln('\tGC_INIT();')
|
g.writeln('\tGC_INIT();')
|
||||||
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
||||||
g.writeln('\tGC_enable_incremental();')
|
g.writeln('\tGC_enable_incremental();')
|
||||||
|
|
|
@ -475,7 +475,7 @@ fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) {
|
||||||
ctx_struct := closure_ctx(node.decl)
|
ctx_struct := closure_ctx(node.decl)
|
||||||
// it may be possible to optimize `memdup` out if the closure never leaves current scope
|
// it may be possible to optimize `memdup` out if the closure never leaves current scope
|
||||||
// TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data)
|
// TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data)
|
||||||
g.write('__closure_create($node.decl.name, ($ctx_struct*) memdup(&($ctx_struct){')
|
g.write('__closure_create($node.decl.name, ($ctx_struct*) memdup_uncollectable(&($ctx_struct){')
|
||||||
g.indent++
|
g.indent++
|
||||||
for var in node.inherited_vars {
|
for var in node.inherited_vars {
|
||||||
g.writeln('.$var.name = $var.name,')
|
g.writeln('.$var.name = $var.name,')
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub fn mark_used(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.F
|
||||||
'new_array_from_c_array',
|
'new_array_from_c_array',
|
||||||
'v_fixed_index',
|
'v_fixed_index',
|
||||||
'memdup',
|
'memdup',
|
||||||
|
'memdup_uncollectable',
|
||||||
'vstrlen',
|
'vstrlen',
|
||||||
'__as_cast',
|
'__as_cast',
|
||||||
'tos',
|
'tos',
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
fn create_closure() fn () int {
|
||||||
|
x := 1234
|
||||||
|
c := fn [x] () int {
|
||||||
|
println(' >> x = $x')
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_closure_data_is_kept_alive() {
|
||||||
|
c := create_closure()
|
||||||
|
assert c() == 1234
|
||||||
|
$if gcboehm ? {
|
||||||
|
C.GC_gcollect()
|
||||||
|
}
|
||||||
|
for _ in 0 .. 1000 {
|
||||||
|
unsafe {
|
||||||
|
p := malloc(8)
|
||||||
|
C.memset(p, 0x33, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if gcboehm ? {
|
||||||
|
C.GC_gcollect()
|
||||||
|
}
|
||||||
|
assert c() == 1234
|
||||||
|
}
|
Loading…
Reference in New Issue