diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 1864cb61cd..10f676446e 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -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 ? { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 81bace58d2..0502aab411 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -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('&') diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v index 25432022f5..8ca3cb27c8 100644 --- a/vlib/v/gen/c/cmain.v +++ b/vlib/v/gen/c/cmain.v @@ -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();') diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 1df58817b5..2f8c157262 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -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,') diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 2329590854..02cfa15fcc 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -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', diff --git a/vlib/v/tests/closure_data_with_gc_test.v b/vlib/v/tests/closure_data_with_gc_test.v new file mode 100644 index 0000000000..b310c5d8c0 --- /dev/null +++ b/vlib/v/tests/closure_data_with_gc_test.v @@ -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 +}