From 7da5608121ef9677d0aac168c3d3e76ec28ce4c2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Jun 2026 13:31:49 +0100 Subject: [PATCH] Add `_append_elem_ptr` to optimize for `append` when the element is internally pointer-like --- base/intrinsics/intrinsics.odin | 2 ++ base/runtime/core_builtin.odin | 58 ++++++++++++++++++++++++++++++--- src/check_builtin.cpp | 4 +++ src/checker_builtin_procs.hpp | 3 ++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 76b7bd065..e639aadbd 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -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) --- diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index d84216720..2f8660975 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -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 } } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index f8f02f715..90048f004 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -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: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 4a6b901a6..e492ca7b2 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -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},