From 0e2c86310a85974a8b32a523627ea3918a74a53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Thu, 10 Jun 2021 20:26:17 +0200 Subject: [PATCH] GC-boehm: extend optimized mode to all `array` methods (#10406) --- vlib/builtin/array.v | 139 ++++++++++++++++++++++++++++++++++++- vlib/builtin/builtin.c.v | 11 +++ vlib/v/gen/c/array.v | 18 ++--- vlib/v/gen/c/cgen.v | 19 +++-- vlib/v/gen/c/cheaders.v | 2 + vlib/v/gen/c/fn.v | 23 ++++-- vlib/v/gen/c/index.v | 3 +- vlib/v/gen/c/json.v | 2 +- vlib/v/markused/markused.v | 15 ++++ 9 files changed, 210 insertions(+), 22 deletions(-) diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index d8c199f3e4..2de231fcbe 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -647,7 +647,11 @@ pub fn (data &byte) vbytes(len int) []byte { return unsafe { voidptr(data).vbytes(len) } } -// non-pub "noscan" versions of some above functions +// non-pub versions of array functions +// that allocale new memory using `GC_MALLOC_ATOMIC()` +// when `-gc boehm_*_opt` is used. These memory areas are not +// scanned for pointers. + fn __new_array_noscan(mylen int, cap int, elm_size int) array { cap_ := if cap < mylen { mylen } else { cap } arr := array{ @@ -704,6 +708,26 @@ fn new_array_from_c_array_noscan(len int, cap int, elm_size int, c_array voidptr return arr } +// Private function. Doubles array capacity if needed. +fn (mut a array) ensure_cap_noscan(required int) { + if required <= a.cap { + return + } + mut cap := if a.cap > 0 { a.cap } else { 2 } + for required > cap { + cap *= 2 + } + new_size := cap * a.element_size + new_data := vcalloc_noscan(new_size) + if a.data != voidptr(0) { + unsafe { C.memcpy(new_data, a.data, a.len * a.element_size) } + // TODO: the old data may be leaked when no GC is used (ref-counting?) + } + a.data = new_data + a.offset = 0 + a.cap = cap +} + // repeat returns a new array with the given array elements repeated given times. // `cgen` will replace this with an apropriate call to `repeat_to_depth()` @@ -737,14 +761,83 @@ fn (a array) repeat_to_depth_noscan(count int, depth int) array { return arr } -pub fn (a &array) clone_to_depth_noscan(depth int) array { +// insert inserts a value in the array at index `i` +fn (mut a array) insert_noscan(i int, val voidptr) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap_noscan(a.len + 1) + unsafe { + C.memmove(a.get_unsafe(i + 1), a.get_unsafe(i), (a.len - i) * a.element_size) + a.set_unsafe(i, val) + } + a.len++ +} + +// insert_many inserts many values into the array from index `i`. +[unsafe] +fn (mut a array) insert_many_noscan(i int, val voidptr, size int) { + $if !no_bounds_checking ? { + if i < 0 || i > a.len { + panic('array.insert_many: index out of range (i == $i, a.len == $a.len)') + } + } + a.ensure_cap_noscan(a.len + size) + elem_size := a.element_size + unsafe { + iptr := a.get_unsafe(i) + C.memmove(a.get_unsafe(i + size), iptr, (a.len - i) * elem_size) + C.memcpy(iptr, val, size * elem_size) + } + a.len += size +} + +// prepend prepends one value to the array. +fn (mut a array) prepend_noscan(val voidptr) { + a.insert_noscan(0, val) +} + +// prepend_many prepends another array to this array. +[unsafe] +fn (mut a array) prepend_many_noscan(val voidptr, size int) { + unsafe { a.insert_many_noscan(0, val, size) } +} + +// pop returns the last element of the array, and removes it. +fn (mut a array) pop_noscan() voidptr { + // in a sense, this is the opposite of `a << x` + $if !no_bounds_checking ? { + if a.len == 0 { + panic('array.pop: array is empty') + } + } + new_len := a.len - 1 + last_elem := unsafe { &byte(a.data) + new_len * a.element_size } + a.len = new_len + // NB: a.cap is not changed here *on purpose*, so that + // further << ops on that array will be more efficient. + return unsafe { memdup_noscan(last_elem, a.element_size) } +} + +// `clone_static_to_depth_noscan()` returns an independent copy of a given array. +// Unlike `clone_to_depth_noscan()` it has a value receiver and is used internally +// for slice-clone expressions like `a[2..4].clone()` and in -autofree generated code. +fn (a array) clone_static_to_depth_noscan(depth int) array { + return unsafe { a.clone_to_depth_noscan(depth) } +} + +// recursively clone given array - `unsafe` when called directly because depth is not checked +[unsafe] +fn (a &array) clone_to_depth_noscan(depth int) array { mut size := a.cap * a.element_size if size == 0 { size++ } mut arr := array{ element_size: a.element_size - data: if depth > 0 { vcalloc(size) } else { vcalloc_noscan(size) } + data: if depth == 0 { vcalloc_noscan(size) } else { vcalloc(size) } len: a.len cap: a.cap } @@ -765,6 +858,34 @@ pub fn (a &array) clone_to_depth_noscan(depth int) array { } } +fn (mut a array) push_noscan(val voidptr) { + a.ensure_cap_noscan(a.len + 1) + unsafe { C.memmove(&byte(a.data) + a.element_size * a.len, val, a.element_size) } + a.len++ +} + +// push_many implements the functionality for pushing another array. +// `val` is array.data and user facing usage is `a << [1,2,3]` +[unsafe] +fn (mut a3 array) push_many_noscan(val voidptr, size int) { + if a3.data == val && !isnil(a3.data) { + // handle `arr << arr` + copy := a3.clone() + a3.ensure_cap_noscan(a3.len + size) + unsafe { + // C.memcpy(a.data, copy.data, copy.element_size * copy.len) + C.memcpy(a3.get_unsafe(a3.len), copy.data, a3.element_size * size) + } + } else { + a3.ensure_cap_noscan(a3.len + size) + if !isnil(a3.data) && !isnil(val) { + unsafe { C.memcpy(a3.get_unsafe(a3.len), val, a3.element_size * size) } + } + } + a3.len += size +} + +// reverse returns a new array with the elements of the original array in reverse order. fn (a array) reverse_noscan() array { if a.len < 2 { return a @@ -780,3 +901,15 @@ fn (a array) reverse_noscan() array { } return arr } + +// grow_cap grows the array's capacity by `amount` elements. +fn (mut a array) grow_cap_noscan(amount int) { + a.ensure_cap_noscan(a.cap + amount) +} + +// grow_len ensures that an array has a.len + amount of length +[unsafe] +fn (mut a array) grow_len_noscan(amount int) { + a.ensure_cap_noscan(a.len + amount) + a.len += amount +} diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 463e2b68ef..e73c45cb1d 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -408,6 +408,17 @@ pub fn memdup(src voidptr, sz int) voidptr { } } +[unsafe] +pub fn memdup_noscan(src voidptr, sz int) voidptr { + if sz == 0 { + return vcalloc_noscan(1) + } + unsafe { + mem := vcalloc_noscan(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/array.v b/vlib/v/gen/c/array.v index 4eefe78fe4..2fddf98096 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -47,10 +47,10 @@ fn (mut g Gen) array_init(node ast.ArrayInit) { return } elem_type_str := g.typ(node.elem_type) + noscan := g.check_noscan(node.elem_type) if node.exprs.len == 0 { elem_sym := g.table.get_type_symbol(node.elem_type) is_default_array := elem_sym.kind == .array && node.has_default - noscan := g.check_noscan(node.elem_type) if is_default_array { g.write('__new_array_with_array_default${noscan}(') } else { @@ -104,7 +104,7 @@ fn (mut g Gen) array_init(node ast.ArrayInit) { if elem_sym.kind == .function { g.write('new_array_from_c_array($len, $len, sizeof(voidptr), _MOV((voidptr[$len]){') } else { - g.write('new_array_from_c_array($len, $len, sizeof($elem_type_str), _MOV(($elem_type_str[$len]){') + g.write('new_array_from_c_array${noscan}($len, $len, sizeof($elem_type_str), _MOV(($elem_type_str[$len]){') } if len > 8 { g.writeln('') @@ -194,7 +194,7 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) { } } g.writeln(';') - g.writeln('\tarray_push((array*)&$tmp, &ti);') + g.writeln('\tarray_push${noscan}((array*)&$tmp, &ti);') g.writeln('}') if !is_embed_map_filter { g.stmt_path_pos << g.out.len @@ -387,7 +387,7 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) { } } g.writeln(') {') - g.writeln('\t\tarray_push((array*)&$tmp, &it); \n\t\t}') + g.writeln('\t\tarray_push${noscan}((array*)&$tmp, &it); \n\t\t}') g.writeln('}') if !is_embed_map_filter { g.stmt_path_pos << g.out.len @@ -404,10 +404,11 @@ fn (mut g Gen) gen_array_insert(node ast.CallExpr) { elem_type_str := g.typ(left_info.elem_type) arg2_sym := g.table.get_type_symbol(node.args[1].typ) is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) if is_arg2_array { - g.write('array_insert_many(&') + g.write('array_insert_many${noscan}(&') } else { - g.write('array_insert(&') + g.write('array_insert${noscan}(&') } g.expr(node.left) g.write(', ') @@ -438,10 +439,11 @@ fn (mut g Gen) gen_array_prepend(node ast.CallExpr) { elem_type_str := g.typ(left_info.elem_type) arg_sym := g.table.get_type_symbol(node.args[0].typ) is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) if is_arg_array { - g.write('array_prepend_many(&') + g.write('array_prepend_many${noscan}(&') } else { - g.write('array_prepend(&') + g.write('array_prepend${noscan}(&') } g.expr(node.left) if is_arg_array { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index d08ef499b4..b615a4fde1 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2274,16 +2274,17 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { } } g.expr(lx) + noscan := if is_auto_heap { g.check_noscan(return_type) } else { '' } if is_opt { mr_base_styp := g.base_type(return_type) if is_auto_heap { - g.writeln(' = HEAP($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i);') + g.writeln(' = HEAP${noscan}($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i);') } else { g.writeln(' = (*($mr_base_styp*)${mr_var_name}.data).arg$i;') } } else { if is_auto_heap { - g.writeln(' = HEAP($styp, ${mr_var_name}.arg$i);') + g.writeln(' = HEAP${noscan}($styp, ${mr_var_name}.arg$i);') } else { g.writeln(' = ${mr_var_name}.arg$i;') } @@ -3750,9 +3751,10 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { // arr << val tmp := g.new_tmp_var() info := left_final_sym.info as ast.Array + noscan := g.check_noscan(info.elem_type) if right_final_sym.kind == .array && info.elem_type != g.unwrap_generic(node.right_type) { // push an array => PUSH_MANY, but not if pushing an array to 2d array (`[][]int << []int`) - g.write('_PUSH_MANY(') + g.write('_PUSH_MANY${noscan}(') mut expected_push_many_atype := left_type if !expected_push_many_atype.is_ptr() { // fn f(mut a []int) { a << [1,2,3] } -> type of `a` is `array_int*` -> no need for & @@ -3769,7 +3771,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { // push a single element elem_type_str := g.typ(info.elem_type) elem_sym := g.table.get_type_symbol(info.elem_type) - g.write('array_push((array*)') + g.write('array_push${noscan}((array*)') if !left_type.is_ptr() { g.write('&') } @@ -6653,6 +6655,15 @@ pub fn (mut g Gen) contains_ptr(el_typ ast.Type) bool { } return false } + .multi_return { + info := sym.info as ast.MultiReturn + for mrtyp in info.types { + if g.contains_ptr(mrtyp) { + return true + } + } + return false + } else { return true } diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index bc7a19a8b4..ccbfd2619a 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -201,7 +201,9 @@ const c_helper_macros = '//============================== HELPER C MACROS ====== #define ADDR(type, expr) (&((type[]){expr}[0])) // copy something to the heap #define HEAP(type, expr) ((type*)memdup((void*)&((type[]){expr}[0]), sizeof(type))) +#define HEAP_noscan(type, expr) ((type*)memdup_noscan((void*)&((type[]){expr}[0]), sizeof(type))) #define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);} +#define _PUSH_MANY_noscan(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many_noscan(arr, tmp.data, tmp.len);} ' const c_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_macros + diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index d352fd105c..32402c2a17 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -650,11 +650,19 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } mut name := util.no_dots('${receiver_type_name}_$node.name') mut array_depth := -1 + mut noscan := '' if left_sym.kind == .array { - if node.name in ['clone', 'repeat'] { + needs_depth := node.name in ['clone', 'repeat'] + if needs_depth { elem_type := (left_sym.info as ast.Array).elem_type array_depth = g.get_array_depth(elem_type) } + maybe_noscan := needs_depth + || node.name in ['pop', 'push', 'push_many', 'reverse', 'grow_cap', 'grow_len'] + if maybe_noscan { + info := left_sym.info as ast.Array + noscan = g.check_noscan(info.elem_type) + } } else if left_sym.kind == .chan { if node.name in ['close', 'try_pop', 'try_push'] { name = 'sync__Channel_$node.name' @@ -708,10 +716,14 @@ fn (mut g Gen) method_call(node ast.CallExpr) { if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name == 'str' { g.write('ptr_str(') } else { - if array_depth >= 0 { - name = name + '_to_depth' + if left_sym.kind == .array { + if array_depth >= 0 { + name = name + '_to_depth' + } + g.write('$name${noscan}(') + } else { + g.write('${name}(') } - g.write('${name}(') } if node.receiver_type.is_ptr() && (!node.left_type.is_ptr() || node.from_embed_type != 0 || (node.left_type.has_flag(.shared_f) && node.name != 'str')) { @@ -1167,7 +1179,8 @@ fn (mut g Gen) call_args(node ast.CallExpr) { g.expr(args[args.len - 1].expr) } else { if variadic_count > 0 { - g.write('new_array_from_c_array($variadic_count, $variadic_count, sizeof($elem_type), _MOV(($elem_type[$variadic_count]){') + noscan := g.check_noscan(arr_info.elem_type) + g.write('new_array_from_c_array${noscan}($variadic_count, $variadic_count, sizeof($elem_type), _MOV(($elem_type[$variadic_count]){') for j in arg_nr .. args.len { g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language) if j < args.len - 1 { diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index 5238ad96c3..e676428463 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -54,7 +54,8 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { } else if sym.kind == .array_fixed { // Convert a fixed array to V array when doing `fixed_arr[start..end]` info := sym.info as ast.ArrayFixed - g.write('array_slice(new_array_from_c_array(') + noscan := g.check_noscan(info.elem_type) + g.write('array_slice(new_array_from_c_array${noscan}(') g.write('$info.size') g.write(', $info.size') g.write(', sizeof(') diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index 63e4eafc84..b1317c8c98 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -232,7 +232,7 @@ fn (mut g Gen) decode_array(value_type ast.Type) string { cJSON_ArrayForEach(jsval, root) { $s - array_push((array*)&res, &val); + array_push${noscan}((array*)&res, &val); } ' } diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index c2b200d47f..21c081b4a3 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -121,10 +121,25 @@ pub fn mark_used(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.F if pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { all_fn_root_names << [ + 'memdup_noscan', '__new_array_noscan', '__new_array_with_default_noscan', '__new_array_with_array_default_noscan', 'new_array_from_c_array_noscan', + '21.clone_static_to_depth_noscan', + '21.clone_to_depth_noscan', + '21.reverse_noscan', + '21.repeat_to_depth_noscan', + '65557.pop_noscan', + '65557.push_noscan', + '65557.push_many_noscan', + '65557.insert_noscan', + '65557.insert_many_noscan', + '65557.prepend_noscan', + '65557.prepend_many_noscan', + '65557.reverse_noscan', + '65557.grow_cap_noscan', + '65557.grow_len_noscan', ] }