GC-boehm: extend optimized mode to all `array` methods (#10406)

pull/10413/head
Uwe Krüger 2021-06-10 20:26:17 +02:00 committed by GitHub
parent 9a9d539e6f
commit 0e2c86310a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 210 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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