Add _append_elem_ptr to optimize for append when the element is internally pointer-like

This commit is contained in:
gingerBill
2026-06-28 13:31:49 +01:00
parent 4ad085b30b
commit 7da5608121
4 changed files with 63 additions and 4 deletions

View File

@@ -185,6 +185,8 @@ type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
type_is_fixed_capacity_dynamic_array :: proc($T: typeid) -> bool ---
type_is_internally_pointer_like :: proc($T: typeid) -> bool ---
type_has_nil :: proc($T: typeid) -> bool ---
type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---

View File

@@ -739,6 +739,29 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a
return
}
_append_elem_ptr :: #force_no_inline proc(array: ^Raw_Dynamic_Array, arg: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
if array == nil {
return
}
if array.cap < array.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(array, size_of(rawptr), align_of(rawptr), cap, should_zero, loc)
}
if array.cap-array.len > 0 {
data := ([^]rawptr)(array.data)
assert(data != nil, loc=loc)
data[array.len] = arg
array.len += 1
num_appended = 1
}
return
}
// `append_elem` appends an element to the end of a dynamic array.
@builtin
append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error {
@@ -748,9 +771,11 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller
}
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else when intrinsics.type_is_internally_pointer_like(E) {
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=true, loc=loc)
} else when ODIN_OPTIMIZATION_MODE <= .Size {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=true, loc=loc)
} else {
if array == nil {
return
@@ -762,7 +787,7 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller
cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, true, loc)
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, should_zero=true, loc)
}
if arr.cap-arr.len > 0 {
// NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled,
@@ -785,9 +810,34 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc :
when size_of(E) == 0 {
(^Raw_Dynamic_Array)(array).len += 1
return 1, nil
} else {
} else when intrinsics.type_is_internally_pointer_like(E) {
return _append_elem_ptr((^Raw_Dynamic_Array)(array), rawptr(arg), should_zero=false, loc=loc)
} else when ODIN_OPTIMIZATION_MODE <= .Size {
arg := arg
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, should_zero=false, loc=loc)
} else {
if array == nil {
return
}
arg := arg
arr := (^Raw_Dynamic_Array)(array)
if arr.cap < arr.len+1 {
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY)
// do not 'or_return' here as it could be a partial success
err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, should_zero=false, loc)
}
if arr.cap-arr.len > 0 {
// NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled,
// this code path cannot rely on type erasure and `mem_copy_non_overlapping`.
// So directly inlining the call and storing the argument like this helps the optimize a lot
assert(arr.data != nil, loc=loc)
([^]E)(arr.data)[arr.len] = arg
arr.len += 1
num_appended = 1
}
return
}
}

View File

@@ -60,6 +60,8 @@ gb_global BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_bool
is_type_raw_union,
is_type_fixed_capacity_dynamic_array,
is_type_internally_pointer_like,
is_type_polymorphic_record_specialized,
is_type_polymorphic_record_unspecialized,
@@ -7060,6 +7062,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
case BuiltinProc_type_is_simd_vector:
case BuiltinProc_type_is_matrix:
case BuiltinProc_type_is_raw_union:
case BuiltinProc_type_is_fixed_capacity_dynamic_array:
case BuiltinProc_type_is_internally_pointer_like:
case BuiltinProc_type_is_specialized_polymorphic_record:
case BuiltinProc_type_is_unspecialized_polymorphic_record:
case BuiltinProc_type_has_nil:

View File

@@ -314,6 +314,7 @@ BuiltinProc__type_simple_boolean_begin,
BuiltinProc_type_is_raw_union,
BuiltinProc_type_is_fixed_capacity_dynamic_array,
BuiltinProc_type_is_internally_pointer_like,
BuiltinProc_type_is_specialized_polymorphic_record,
BuiltinProc_type_is_unspecialized_polymorphic_record,
@@ -719,6 +720,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("type_is_raw_union"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_fixed_capacity_dynamic_array"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_internally_pointer_like"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_specialized_polymorphic_record"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_unspecialized_polymorphic_record"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},