boehm-gc: support a `[keep_args_alive]` tag for C functions (#9641)

pull/9648/head
Uwe Krüger 2021-04-09 12:13:49 +02:00 committed by GitHub
parent 4feb09fa5b
commit 84fa1ae444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 204 additions and 5 deletions

View File

@ -109,6 +109,7 @@ const (
'vlib/v/tests/interface_edge_cases/assign_to_interface_field_test.v',
'vlib/v/tests/interface_fields_test.v',
'vlib/v/tests/interface_variadic_test.v',
'vlib/v/tests/keep_args_alive_test.v',
'vlib/v/tests/option_2_test.v',
'vlib/v/tests/operator_overloading_with_string_interpolation_test.v',
'vlib/v/tests/orm_sub_struct_test.v',

View File

@ -4222,6 +4222,11 @@ fn bar() {
foo() // will not be called if `-d debug` is not passed
}
// The memory pointed to by the pointer arguments of this function will not be
// freed by the garbage collector (if in use) before the function returns
[keep_args_alive]
fn C.my_external_function(voidptr, int, voidptr) int
// Calls to following function must be in unsafe{} blocks.
// Note that the code in the body of `risky_business()` will still be
// checked, unless you also wrap it in `unsafe {}` blocks.

View File

@ -52,6 +52,9 @@ fn C.GC_enable()
// returns non-zero if GC is disabled
fn C.GC_is_disabled() int
// protect memory block from being freed before this call
fn C.GC_reachable_here(voidptr)
// for leak detection it is advisable to do explicit garbage collections
pub fn gc_check_leaks() {
$if gcboehm_leak ? {

View File

@ -356,6 +356,7 @@ pub:
is_main bool // true for `fn main()`
is_test bool // true for `fn test_abcde`
is_conditional bool // true for `[if abc] fn abc(){}`
is_keep_alive bool // passed memory must not be freed (by GC) before function returns
receiver StructField // TODO this is not a struct field
receiver_pos token.Position // `(u User)` in `fn (u User) name()` position
is_method bool
@ -409,6 +410,7 @@ pub mut:
name string // left.name()
is_method bool
is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe)
is_keep_alive bool // GC must not free arguments before fn returns
args []CallArg
expected_arg_types []Type
language Language

View File

@ -38,6 +38,7 @@ pub:
is_main bool // `fn main(){}`
is_test bool // `fn test_abc(){}`
is_conditional bool // `[if abc]fn(){}`
is_keep_alive bool // passed memory must not be freed (by GC) before function returns
no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns.
mod string
ctdefine string // compile time define. "myflag", when [if myflag] tag

View File

@ -2060,6 +2060,7 @@ pub fn (mut c Checker) fn_call(mut call_expr ast.CallExpr) ast.Type {
// builtin C.m*, C.s* only - temp
c.warn('function `$f.name` must be called from an `unsafe` block', call_expr.pos)
}
call_expr.is_keep_alive = f.is_keep_alive
if f.mod != 'builtin' && f.language == .v && f.no_body && !c.pref.translated && !f.is_unsafe {
c.error('cannot call a function that does not have a body', call_expr.pos)
}

View File

@ -413,9 +413,11 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
defer {
g.inside_call = false
}
gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type
&& g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm]
gen_or := node.or_block.kind != .absent // && !g.is_autofree
is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result
cur_line := if is_gen_or_and_assign_rhs { // && !g.is_autofree {
cur_line := if is_gen_or_and_assign_rhs || gen_keep_alive { // && !g.is_autofree {
// `x := foo() or { ...}`
// cut everything that has been generated to prepend optional variable creation
line := g.go_before_stmt(0)
@ -425,9 +427,13 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
} else {
''
}
tmp_opt := if gen_or { g.new_tmp_var() } else { '' }
if gen_or {
styp := g.typ(node.return_type.set_flag(.optional))
tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' }
if gen_or || gen_keep_alive {
mut ret_typ := node.return_type
if gen_or {
ret_typ = ret_typ.set_flag(.optional)
}
styp := g.typ(ret_typ)
g.write('$styp $tmp_opt = ')
}
if node.is_method && !node.is_field {
@ -458,6 +464,12 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
}
}
}
} else if gen_keep_alive {
if node.return_type == ast.void_type {
g.write('\n $cur_line')
} else {
g.write('\n $cur_line $tmp_opt')
}
}
}
@ -874,13 +886,30 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
// g.writeln(';')
// g.write(cur_line + ' /* <== af cur line*/')
// }
mut tmp_cnt_save := -1
g.write('${g.get_ternary_name(name)}(')
if g.is_json_fn {
g.write(json_obj)
} else {
g.call_args(node)
if node.is_keep_alive && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm] {
cur_line := g.go_before_stmt(0)
tmp_cnt_save = g.keep_alive_call_pregen(node)
g.write(cur_line)
for i in 0 .. node.args.len {
if i > 0 {
g.write(', ')
}
g.write('__tmp_arg_${tmp_cnt_save + i}')
}
} else {
g.call_args(node)
}
}
g.write(')')
if tmp_cnt_save >= 0 {
g.writeln(';')
g.keep_alive_call_postgen(node, tmp_cnt_save)
}
}
}
g.is_c_call = false
@ -1103,6 +1132,36 @@ fn (mut g Gen) call_args(node ast.CallExpr) {
}
}
// similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions
fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int {
g.empty_line = true
g.writeln('// keep_alive_call_pregen()')
// reserve the next tmp_vars for arguments
tmp_cnt_save := g.tmp_count + 1
g.tmp_count += node.args.len
for i, arg in node.args {
// save all arguments in temp vars (not only pointers) to make sure the
// evaluation order is preserved
expected_type := node.expected_arg_types[i]
typ := g.table.get_type_symbol(expected_type).cname
g.write('$typ __tmp_arg_${tmp_cnt_save + i} = ')
// g.expr(arg.expr)
g.ref_or_deref_arg(arg, expected_type, node.language)
g.writeln(';')
}
g.empty_line = false
return tmp_cnt_save
}
fn (mut g Gen) keep_alive_call_postgen(node ast.CallExpr, tmp_cnt_save int) {
g.writeln('// keep_alive_call_postgen()')
for i, expected_type in node.expected_arg_types {
if expected_type.is_ptr() || expected_type.is_pointer() {
g.writeln('GC_reachable_here(__tmp_arg_${tmp_cnt_save + i});')
}
}
}
[inline]
fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang ast.Language) {
arg_is_ptr := expected_type.is_ptr() || expected_type.idx() in ast.pointer_type_idxs

View File

@ -179,6 +179,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_direct_arr := p.attrs.contains('direct_array_access')
is_conditional, conditional_ctdefine := p.attrs.has_comptime_define()
mut is_unsafe := p.attrs.contains('unsafe')
is_keep_alive := p.attrs.contains('keep_args_alive')
is_pub := p.tok.kind == .key_pub
if is_pub {
p.next()
@ -193,6 +194,10 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
} else if p.tok.kind == .name && p.tok.lit == 'JS' {
language = ast.Language.js
}
if is_keep_alive && language != .c {
p.error_with_pos('attribute [keep_args_alive] is only supported for C functions',
p.tok.position())
}
if language != .v {
p.next()
p.check(.dot)
@ -340,6 +345,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_main: is_main
is_test: is_test
is_conditional: is_conditional
is_keep_alive: is_keep_alive
ctdefine: conditional_ctdefine
no_body: no_body
mod: p.mod
@ -368,6 +374,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_main: is_main
is_test: is_test
is_conditional: is_conditional
is_keep_alive: is_keep_alive
ctdefine: conditional_ctdefine
no_body: no_body
mod: p.mod
@ -410,6 +417,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_main: is_main
is_test: is_test
is_conditional: is_conditional
is_keep_alive: is_keep_alive
receiver: ast.StructField{
name: rec.name
typ: rec.typ

View File

@ -0,0 +1,75 @@
/*
* To verify the effect of "[keep_args_alive]", this attribute may be commented out.
* However it is not guaranteed that then this test will fail.
* To provoke a failure it seems to be best to use `gcc` with optimization:
* `gcc -gc boehm -cc gcc-9 -prod test keep_args_alive_test.v`.
* Without optimization, pointer variables may remain on the stack even if
* not used any more.
*/
import rand
import sync
#flag -I@VROOT/vlib/v/tests
#include "keep_args_alive_test_c.h"
fn C.atomic_load_ptr(voidptr) voidptr
fn C.atomic_store_ptr(voidptr, voidptr)
[keep_args_alive]
fn C.calc_expr_after_delay(voidptr, int, voidptr) int
fn set_vals() voidptr {
unsafe {
p := &int(malloc(8000000))
q := &int(malloc(8000000))
aa := p + 769345
*aa = -4578
bb := q + 572397
*bb = 793254
p = &int(0)
q = &int(0)
r := &voidptr(malloc(1000000))
r[456] = aa
r[7932] = bb
aa = &int(0)
bb = &int(0)
return r
}
}
fn tt(mut sem sync.Semaphore) int {
waste_mem(10000, mut sem)
r := &voidptr(set_vals())
g := unsafe { C.calc_expr_after_delay(r[456], 12, r[7932]) }
return g
}
fn waste_mem(n int, mut sem sync.Semaphore) {
mut m := []voidptr{len: 30}
for j := 0; n < 0 || j < n; j++ {
i := rand.intn(30)
m[i] = unsafe { malloc(10000) }
fill := rand.intn(256)
unsafe { C.memset(m[i], fill, 10000) }
if n < 0 && sem.try_wait() {
break
}
}
}
fn test_keep_args_alive_attribute() {
mut sem := sync.new_semaphore()
$if gcboehm ? {
go waste_mem(-1, mut sem)
go waste_mem(-1, mut sem)
waste_mem(10000, mut sem)
}
r := &voidptr(set_vals())
v := unsafe { C.calc_expr_after_delay(r[456], 12, r[7932]) }
$if gcboehm ? {
sem.post()
sem.post()
}
assert v == 738318
}

View File

@ -0,0 +1,44 @@
#include <time.h>
#if defined(_WIN32)
#define __SLEEP_MS(n) Sleep(n)
#elif defined(__APPLE__)
static void __sleep_ms(int ms) {
struct timespec ts = {
.tv_sec = ms / 1000,
.tv_nsec = 1000000L * (ms % 1000)
};
struct timespec rem;
while (nanosleep(&ts, &rem) != 0) {
ts = rem;
}
}
#define __SLEEP_MS(n) __sleep_ms(n)
#else
static void __sleep_ms(int ms) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_nsec += 1000000L*((ms)%1000);
ts.tv_sec += ms/1000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_nsec -= 1000000000;
++ts.tv_sec;
}
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0);
}
#define __SLEEP_MS(n) __sleep_ms(n)
#endif
volatile static int** keep;
static int calc_expr_after_delay(int* a, int b, int* c) {
keep = malloc(1000000);
keep[43242] = a;
keep[86343] = c;
a = NULL;
c = NULL;
__SLEEP_MS(200);
int z = *keep[43242] * b + *keep[86343];
free(keep);
return z;
}