From 9914a57fc7191cfb3b4749909feee66fc01be828 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Aug 2026 14:41:04 +0100 Subject: [PATCH] Add `intrinsics.soa_copy_from_slice` and use it within `_append_soa_elems` replacing the old handwritten unrolled approach --- base/intrinsics/intrinsics.odin | 1 + base/runtime/core_builtin_soa.odin | 254 ++--------------------------- src/check_builtin.cpp | 56 +++++++ src/checker_builtin_procs.hpp | 12 +- src/llvm_backend_general.cpp | 23 +++ src/llvm_backend_proc.cpp | 89 ++++++++++ src/types.cpp | 5 + 7 files changed, 193 insertions(+), 247 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index e639aadbd..d91bc2e79 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -379,6 +379,7 @@ simd_pairwise_sub :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 simd_interleave :: proc(a, ..#simd[LANES/N]T) -> #simd[LANES]T where N >= 1 --- simd_deinterleave :: proc(a: #simd[LANES]T, $N: uint) -> (..#simd[LANES/N]T) where N >= 1, LANES % N == 0 --- // returns N multiple vectors +soa_copy_from_slice :: proc(ptr: ^$A/#soa[dynamic]$E, offset: int, args: []$E) --- // Checks if the current target supports the given target features. // diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 9dca88d9f..737aecd9f 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -491,257 +491,27 @@ _append_soa_elems :: proc(#no_alias array: ^$T/#soa[dynamic]$E, zero_memory: boo // (Note that offset and the multipointers must be read after _reserve_soa, because any growth moves them.) offset := footer.len FIELD_COUNT :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E) - when FIELD_COUNT <= 16 { - when ODIN_OPTIMIZATION_MODE <= .Size { - // Use the compiler's #soa element store lowering. - // Note that #no_bounds_check is not optional, we write at index == len. - #no_bounds_check { - for j in 0..= 1 { // nothing to do if field count is 0 - _append_soa_elems_per_field_expanded(array, offset, args[:arg_len]) - } - } else { // FIELD_COUNT > 16 - ti := type_info_base(type_info_of(typeid_of(T))) - si := &ti.variant.(Type_Info_Struct) - // si describes the SOA struct (fields are multipointers), so E's - // field offsets are not available in it; read them from E's own RTTI (si.soa_base_type). - // Note that basing on default struct layout here instead - // would misplace the fields of #packed elements. - // (> 16 fields is struct-only: array element types are capped at length 4.) - se := &type_info_base(si.soa_base_type).variant.(Type_Info_Struct) - src_base := uintptr(raw_data(args)) - for i in 0..args[0]); + if (array_ptr.mode == Addressing_Invalid) { + return false; + } + if (!is_type_pointer(array_ptr.type)) { + gbString s = type_to_string(array_ptr.type); + error(array_ptr.expr, "Expected a pointer to a #soa dynamic array, got %s", s); + gb_string_free(s); + return false; + } + Type *array_type = type_deref(array_ptr.type); + if (!is_type_soa_dynamic_array(array_type)) { + gbString s = type_to_string(array_ptr.type); + error(array_ptr.expr, "Expected a pointer to a #soa dynamic array, got %s", s); + gb_string_free(s); + return false; + } + Type *at = base_type(array_type); + GB_ASSERT(at->kind == Type_Struct); + Type *elem = at->Struct.soa_elem; + + check_expr_with_type_hint(c, &offset, ce->args[1], t_int); + if (offset.mode == Addressing_Invalid) { + return false; + } + if (!is_type_integer(offset.type)) { + gbString s = type_to_string(array_ptr.type); + error(array_ptr.expr, "Expected an integer as the offset for '%.*s', got %s", s, LIT(builtin_name)); + gb_string_free(s); + return false; + } + + Type *slice_hint = alloc_type_slice(elem); + + check_expr_with_type_hint(c, &args, ce->args[2], slice_hint); + if (args.mode == Addressing_Invalid) { + return false; + } + if (!are_types_identical(base_type(args.type), slice_hint)) { + gbString s = type_to_string(slice_hint); + gbString t = type_to_string(args.type); + error(array_ptr.expr, "Expected a %s to use as the slice '%.*s', got %s", s, LIT(builtin_name), t); + gb_string_free(t); + gb_string_free(s); + return false; + } + + operand->mode = Addressing_NoValue; + operand->type = nullptr; + break; + } + case BuiltinProc_concatenate: { Operand lhs = {}; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index e492ca7b2..457591615 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -60,6 +60,7 @@ enum BuiltinProcId { BuiltinProc_matrix_flatten, BuiltinProc_soa_struct, + BuiltinProc_soa_copy_from_slice, BuiltinProc_concatenate, @@ -99,12 +100,12 @@ enum BuiltinProcId { BuiltinProc_volatile_store, BuiltinProc_volatile_load, - + BuiltinProc_unaligned_store, BuiltinProc_unaligned_load, BuiltinProc_non_temporal_store, BuiltinProc_non_temporal_load, - + BuiltinProc_prefetch_read_instruction, BuiltinProc_prefetch_read_data, BuiltinProc_prefetch_write_instruction, @@ -242,7 +243,7 @@ BuiltinProc__simd_begin, // Platform specific SIMD intrinsics BuiltinProc_simd_x86__MM_SHUFFLE, BuiltinProc__simd_end, - + // Platform specific intrinsics BuiltinProc_syscall, BuiltinProc_syscall_bsd, @@ -328,7 +329,7 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_has_field, BuiltinProc_type_field_type, - + BuiltinProc_type_field_bit_offset, BuiltinProc_type_field_bit_size, @@ -467,8 +468,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("outer_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("matrix_flatten"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - + {STR_LIT("soa_struct"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type + {STR_LIT("soa_copy_from_slice"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("concatenate"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index d9a807d18..7af1c52be 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -330,6 +330,29 @@ gb_internal lbLoopData lb_loop_start(lbProcedure *p, isize count, Type *index_ty return data; } +gb_internal lbLoopData lb_loop_start_runtime(lbProcedure *p, lbValue count) { + lbLoopData data = {}; + + lbValue max = count; + + data.idx_addr = lb_add_local_generated(p, count.type, true); + + data.body = lb_create_block(p, "loop.body"); + data.done = lb_create_block(p, "loop.done"); + data.loop = lb_create_block(p, "loop.loop"); + + lb_emit_jump(p, data.loop); + lb_start_block(p, data.loop); + + data.idx = lb_addr_load(p, data.idx_addr); + + lbValue cond = lb_emit_comp(p, Token_Lt, data.idx, max); + lb_emit_if(p, cond, data.body, data.done); + lb_start_block(p, data.body); + + return data; +} + gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) { if (data.idx_addr.addr.value != nullptr) { lb_emit_increment(p, data.idx_addr.addr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 05691e408..68215b49a 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3355,6 +3355,95 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu // "Intrinsics" + case BuiltinProc_soa_copy_from_slice: + { + lbValue ptr = lb_build_expr(p, ce->args[0]); + lbValue offset = lb_build_expr(p, ce->args[1]); + lbValue args = lb_build_expr(p, ce->args[2]); + + Type *ptr_type = base_type(ptr.type); + Type *array_type = base_type(type_deref(ptr_type)); + GB_ASSERT(is_type_soa_dynamic_array(array_type)); + + Type *elem = array_type->Struct.soa_elem; + + i32 field_count = 0; + if (is_type_array(elem)) { + field_count = cast(i32)get_array_type_count(elem); + } else { + Type *bt = base_type(elem); + GB_ASSERT(bt->kind == Type_Struct); + field_count = cast(i32)bt->Struct.fields.count; + } + if (field_count == 0) { + return {}; + } + GB_ASSERT(field_count >= 1); + + Type *slice_type = base_type(args.type); + GB_ASSERT(slice_type->kind == Type_Slice); + GB_ASSERT(are_types_identical(slice_type->Slice.elem, elem)); + + lbValue arg_ptr = lb_slice_elem(p, args); + lbValue arg_len = lb_slice_len(p, args); + + lbValue soa_array_len = lb_soa_struct_len(p, ptr); + + offset = lb_emit_conv(p, offset, t_int); + lbValue max_soa_len = lb_emit_arith(p, Token_Sub, soa_array_len, offset, t_int); + lbValue max_len = lb_emit_min(p, t_int, arg_len, max_soa_len); + + if (field_count <= 16) { + lbValue *ps = gb_alloc_array(temporary_allocator(), lbValue, field_count); + for (i32 field_index = 0; field_index < field_count; field_index++) { + ps[field_index] = lb_emit_load(p, lb_emit_struct_ep(p, ptr, field_index)); + } + + for (i32 field_index = 0; field_index < field_count; field_index++) { + auto loop_data = lb_loop_start_runtime(p, max_len); + + lbValue j = loop_data.idx; + lbValue index = lb_emit_arith(p, Token_Add, j, offset, t_int); + + lbValue dst = lb_emit_ptr_offset(p, ps[field_index], index); + // make sure it's ^T and not [^]T + dst.type = alloc_type_multi_pointer_to_pointer(dst.type); + + lbValue src_ptr = lb_emit_ptr_offset(p, arg_ptr, j); + src_ptr = lb_emit_struct_ep(p, src_ptr, field_index); + + lbValue src = lb_emit_load(p, src_ptr); + lb_emit_store(p, dst, src); + + lb_loop_end(p, loop_data); + } + } else { + for (i32 field_index = 0; field_index < field_count; field_index++) { + Type *type = array_type->Struct.fields[field_index]->type; + GB_ASSERT(type->kind == Type_MultiPointer); + type = type->MultiPointer.elem; + + lbValue dst = lb_emit_load(p, lb_emit_struct_ep(p, ptr, field_index)); + dst = lb_emit_ptr_offset(p, dst, offset); + dst.type = alloc_type_multi_pointer_to_pointer(dst.type); + + auto loop_data = lb_loop_start_runtime(p, max_len); + + lbValue src = lb_emit_ptr_offset(p, arg_ptr, loop_data.idx); + + lbValue d = lb_emit_ptr_offset(p, dst, loop_data.idx); + lbValue s = lb_emit_struct_ep(p, src, field_index); + + GB_ASSERT(are_types_identical(d.type, s.type)); + + lb_mem_copy_non_overlapping(p, d, s, lb_const_int(p->module, t_int, type_size_of(type)), false); + + lb_loop_end(p, loop_data); + } + } + return {}; + } + case BuiltinProc_alloca: { lbValue sz = lb_build_expr(p, ce->args[0]); diff --git a/src/types.cpp b/src/types.cpp index ff5147574..0e41b1b9b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2001,6 +2001,11 @@ gb_internal bool is_type_soa_struct(Type *t) { if (t == nullptr) { return false; } return t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None; } +gb_internal bool is_type_soa_dynamic_array(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + return t->kind == Type_Struct && t->Struct.soa_kind == StructSoa_Dynamic; +} gb_internal bool is_type_raw_union(Type *t) { t = base_type(t);