cgen: ensure closures are kept alive when using the GC (#14736)

master
spaceface 2022-06-10 18:48:50 +02:00 committed by GitHub
parent b27b6b2047
commit 26d051475a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 2 deletions

View File

@ -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 ? {

View File

@ -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('&')

View File

@ -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();')

View File

@ -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,')

View File

@ -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',

View File

@ -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
}