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
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The `b byteptr` must be a pointer to an existing memory block
|
||||
// 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]
|
||||
fn v_fixed_index(i int, len int) int {
|
||||
$if !no_bounds_checking ? {
|
||||
|
|
|
@ -3393,7 +3393,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
|
|||
|
||||
g.write('__closure_create($name, ')
|
||||
if !receiver.typ.is_ptr() {
|
||||
g.write('memdup(')
|
||||
g.write('memdup_uncollectable(')
|
||||
}
|
||||
if !node.expr_type.is_ptr() {
|
||||
g.write('&')
|
||||
|
|
|
@ -94,6 +94,7 @@ fn (mut g Gen) gen_c_main_header() {
|
|||
if g.pref.gc_mode == .boehm_leak {
|
||||
g.writeln('\tGC_set_find_leak(1);')
|
||||
}
|
||||
g.writeln('\tGC_set_pages_executable(0);')
|
||||
g.writeln('\tGC_INIT();')
|
||||
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
||||
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 {
|
||||
g.writeln('\tGC_set_find_leak(1);')
|
||||
}
|
||||
g.writeln('\tGC_set_pages_executable(0);')
|
||||
g.writeln('\tGC_INIT();')
|
||||
if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
|
||||
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)
|
||||
// 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($node.decl.name, ($ctx_struct*) memdup(&($ctx_struct){')
|
||||
g.write('__closure_create($node.decl.name, ($ctx_struct*) memdup_uncollectable(&($ctx_struct){')
|
||||
g.indent++
|
||||
for var in node.inherited_vars {
|
||||
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',
|
||||
'v_fixed_index',
|
||||
'memdup',
|
||||
'memdup_uncollectable',
|
||||
'vstrlen',
|
||||
'__as_cast',
|
||||
'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