diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3d0a25bb7..e73ec65c4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2973,10 +2973,28 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * return; } - if (o->mode == Addressing_SoaVariable) { + Type *soa_for_in_type = nullptr; + if (node->kind == Ast_UnaryExpr) { ast_node(ue, UnaryExpr, node); - if (ast_node_expect(ue->expr, Ast_IndexExpr)) { - ast_node(ie, IndexExpr, ue->expr); + Entity *e = entity_of_node(ue->expr); + if (e != nullptr && e->kind == Entity_Variable && + (e->flags & EntityFlag_SoaPtrField) != 0 && + e->Variable.for_loop_parent_type != nullptr) { + Type *soa_type = type_deref(e->Variable.for_loop_parent_type); + if (is_type_soa_struct(soa_type)) { + soa_for_in_type = soa_type; + } + } + } + + if (soa_for_in_type != nullptr) { + // &v in for-in loop over #soa container + o->type = alloc_type_soa_pointer(soa_for_in_type); + } else if (o->mode == Addressing_SoaVariable) { + ast_node(ue, UnaryExpr, node); + Ast *index_expr = unparen_expr(ue->expr); + if (ast_node_expect(index_expr, Ast_IndexExpr)) { + ast_node(ie, IndexExpr, index_expr); Type *soa_type = type_deref(type_of_expr(ie->expr)); GB_ASSERT(is_type_soa_struct(soa_type)); o->type = alloc_type_soa_pointer(soa_type); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index e8315966c..a38f67d86 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -40,6 +40,7 @@ enum lbAddrKind { lbAddr_Swizzle, lbAddr_SwizzleLarge, + lbAddr_SwizzleSoa, lbAddr_BitField, }; @@ -73,6 +74,12 @@ struct lbAddr { Type *type; Slice indices; } swizzle_large; + struct { + lbValue index; + Ast *index_expr; + Type *type; + Slice indices; // soa element array is max len 4, but swizzle may repeat components + } swizzle_soa; struct { Type *type; i64 bit_offset; @@ -440,11 +447,15 @@ gb_internal lbValue lb_emit_array_epi(lbProcedure *p, lbValue value, isize index gb_internal lbValue lb_emit_array_ep(lbProcedure *p, lbValue s, lbValue index); gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection sel); gb_internal lbValue lb_emit_deep_field_ev(lbProcedure *p, lbValue e, Selection sel); +gb_internal void lb_emit_bounds_check(lbProcedure *p, Token token, lbValue index, lbValue len); gb_internal lbValue lb_emit_matrix_ep(lbProcedure *p, lbValue s, lbValue row, lbValue column); gb_internal lbValue lb_emit_matrix_epi(lbProcedure *p, lbValue s, isize row, isize column); gb_internal lbValue lb_emit_matrix_ev(lbProcedure *p, lbValue s, isize row, isize column); +gb_internal lbAddr lb_get_soa_variable_addr(lbProcedure *p, Entity *e); +gb_internal lbValue lb_soa_variable_make_pointer(lbProcedure *p, lbAddr const &addr); + gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type); gb_internal lbValue lb_emit_byte_swap(lbProcedure *p, lbValue value, Type *end_type); @@ -575,6 +586,7 @@ gb_internal void lb_emit_init_context(lbProcedure *p, lbAddr addr); gb_internal lbBranchBlocks lb_lookup_branch_blocks(lbProcedure *p, Ast *ident); gb_internal lbStructFieldRemapping lb_get_struct_remapping(lbModule *m, Type *t); +gb_internal i32 lb_convert_struct_index(lbModule *m, Type *t, i32 index); gb_internal LLVMTypeRef lb_type_padding_filler(lbModule *m, i64 padding, i64 padding_align); gb_internal LLVMValueRef llvm_basic_shuffle(lbProcedure *p, LLVMValueRef vector, LLVMValueRef mask); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index d7e4523dd..4e26cb32f 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1393,7 +1393,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty for (i64 i = 0; i < elem_count; i++) { bool found = false; - for (isize j = 0; j < elem_count; j++) { + for (isize j = 0; j < cl->elems.count; j++) { Ast *elem = cl->elems[j]; ast_node(fv, FieldValue, elem); if (is_ast_range(fv->field)) { @@ -1453,10 +1453,12 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty GB_ASSERT(array_type->kind == Type_Array); Type *field_type = array_type->Array.elem; + // the element constant carries llvm padding members; remap the Odin field index + unsigned src_index = cast(unsigned)lb_convert_struct_index(m, base_type(elem_type), cast(i32)i); for (isize j = 0; j < elem_count; j++) { LLVMValueRef v = aos_values[j]; if (v != nullptr) { - values[j] = llvm_const_extract_value(m, v, cast(unsigned)i); + values[j] = llvm_const_extract_value(m, v, src_index); } else { values[j] = LLVMConstNull(lb_type(m, field_type)); } @@ -1498,10 +1500,12 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty GB_ASSERT(array_type->kind == Type_Array); Type *field_type = array_type->Array.elem; + // the element constant carries llvm padding members; remap the Odin field index + unsigned src_index = cast(unsigned)lb_convert_struct_index(m, base_type(elem_type), cast(i32)i); for (isize j = 0; j < elem_count; j++) { LLVMValueRef v = aos_values[j]; if (v != nullptr) { - values[j] = llvm_const_extract_value(m, v, cast(unsigned)i); + values[j] = llvm_const_extract_value(m, v, src_index); } else { values[j] = LLVMConstNull(lb_type(m, field_type)); } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 29dd75c9f..5a478edd0 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4134,6 +4134,13 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { return lb_addr_load(p, res); } else if (is_type_soa_pointer(tv.type)) { + Entity *e = entity_of_node(ue_expr); + if (e != nullptr && (e->flags & EntityFlag_SoaPtrField) != 0) { + // &v in a for-in loop over an #soa container; + // the loop already holds the element's lbAddr_SoaVariable; + // no bounds check needed, the index is in range by construction + return lb_soa_variable_make_pointer(p, lb_get_soa_variable_addr(p, e)); + } ast_node(ie, IndexExpr, ue_expr); lbValue addr = lb_build_addr_ptr(p, ie->expr); @@ -4143,10 +4150,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { GB_ASSERT(is_type_pointer(addr.type)); lbValue index = lb_build_expr(p, ie->index); - - if (!build_context.no_bounds_check) { - // TODO(bill): soa bounds checking - } + lb_emit_soa_index_bounds_check(p, addr, index, ie->index); return lb_make_soa_pointer(p, tv.type, addr, index); } else if (ue_expr->kind == Ast_CompoundLit) { @@ -4787,7 +4791,8 @@ gb_internal lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { is_soa = true; // NOTE(bill): using SOA value (probably from for-in statement) lbAddr parent_addr = lb_get_soa_variable_addr(p, parent); - v = lb_addr_get_ptr(p, parent_addr); + // the element has no single address, so make a soa pointer for lb_emit_deep_field_gep + v = lb_address_from_load_or_generate_local(p, lb_soa_variable_make_pointer(p, parent_addr)); } else if (pv != nullptr) { v = *pv; } else { @@ -4856,21 +4861,23 @@ gb_internal lbAddr lb_build_array_swizzle_addr(lbProcedure *p, AstCallExpr *ce, if (index_count == 0) { return addr; } - Type *type = base_type(lb_addr_type(addr)); - GB_ASSERT(type->kind == Type_Array); - i64 count = type->Array.count; - if (count <= 4 && index_count <= 4) { - u8 indices[4] = {}; - u8 index_count = 0; - for (i32 i = 1; i < ce->args.count; i++) { - TypeAndValue tv = type_and_value_of_expr(ce->args[i]); - GB_ASSERT(is_type_integer(tv.type)); - GB_ASSERT(tv.value.kind == ExactValue_Integer); + if (addr.kind != lbAddr_SoaVariable) { + Type *type = base_type(lb_addr_type(addr)); + GB_ASSERT(type->kind == Type_Array); + i64 count = type->Array.count; + if (count <= 4 && index_count <= 4) { + u8 indices[4] = {}; + u8 index_count = 0; + for (i32 i = 1; i < ce->args.count; i++) { + TypeAndValue tv = type_and_value_of_expr(ce->args[i]); + GB_ASSERT(is_type_integer(tv.type)); + GB_ASSERT(tv.value.kind == ExactValue_Integer); - i64 src_index = big_int_to_i64(&tv.value.value_integer); - indices[index_count++] = cast(u8)src_index; + i64 src_index = big_int_to_i64(&tv.value.value_integer); + indices[index_count++] = cast(u8)src_index; + } + return lb_addr_swizzle(lb_addr_get_ptr(p, addr), tv.type, index_count, indices); } - return lb_addr_swizzle(lb_addr_get_ptr(p, addr), tv.type, index_count, indices); } auto indices = slice_make(permanent_allocator(), ce->args.count-1); isize index_index = 0; @@ -4882,6 +4889,9 @@ gb_internal lbAddr lb_build_array_swizzle_addr(lbProcedure *p, AstCallExpr *ce, i64 src_index = big_int_to_i64(&tv.value.value_integer); indices[index_index++] = cast(i32)src_index; } + if (addr.kind == lbAddr_SoaVariable) { + return lb_addr_swizzle_soa(addr.addr, addr.soa.index, addr.soa.index_expr, tv.type, indices); + } return lb_addr_swizzle_large(lb_addr_get_ptr(p, addr), tv.type, indices); } @@ -6514,7 +6524,31 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { if (is_type_pointer(tav.type)) { a = lb_build_expr(p, se->expr); } else { - lbAddr addr = lb_build_addr(p, se->expr); + lbAddr addr; + if (is_type_soa_pointer(tav.type)) { + // auto-deref p.xy, where p is soa pointer; + // base p doesn't lower to an lbAddr_SoaVariable on its own + // (it is a local holding the soa pointer), so build the element + // addr here the same way an explicit p^ does + lbValue value = lb_build_expr(p, se->expr); + lbValue ptr = lb_emit_struct_ev(p, value, 0); + lbValue idx = lb_emit_struct_ev(p, value, 1); + addr = lb_addr_soa_variable(ptr, idx, nullptr); + } else { + addr = lb_build_addr(p, se->expr); + } + if (addr.kind == lbAddr_SoaVariable) { + // soa[i].xy + Type *type = type_deref(expr->tav.type); + GB_ASSERT_MSG(is_type_array(type), "%s", type_to_string(type)); + + auto soa_indices = slice_make(permanent_allocator(), swizzle_count); + for (u8 i = 0; i < swizzle_count; i++) { + soa_indices[i] = cast(i32)swizzle_indices[i]; + } + return lb_addr_swizzle_soa(addr.addr, addr.soa.index, addr.soa.index_expr, + type, soa_indices); + } a = lb_addr_get_ptr(p, addr); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 7af1c52be..72b11bac5 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -616,6 +616,38 @@ gb_internal lbAddr lb_addr_soa_variable(lbValue addr, lbValue index, Ast *index_ return v; } +// pointer to the index element of the field_index component +gb_internal lbValue lb_soa_field_elem_ptr(lbProcedure *p, lbValue soa_ptr, i32 field_index, lbValue index) { + Type *t = base_type(type_deref(soa_ptr.type)); + GB_ASSERT_MSG(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None, "%s", type_to_string(t)); + + lbValue field = lb_emit_struct_ep(p, soa_ptr, field_index); + if (t->Struct.soa_kind == StructSoa_Fixed) { + return lb_emit_array_ep(p, field, index); + } + return lb_emit_ptr_offset(p, lb_emit_load(p, field), index); +} + +// bounds check for an #soa element index +gb_internal void lb_emit_soa_index_bounds_check(lbProcedure *p, lbValue soa_ptr, lbValue index, Ast *index_expr) { + if (index_expr == nullptr) { + return; + } + Type *t = base_type(type_deref(soa_ptr.type)); + GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None); + if (lb_is_const(index) && t->Struct.soa_kind == StructSoa_Fixed) { + return; + } + + lbValue len = {}; + if (t->Struct.soa_kind == StructSoa_Fixed) { + len = lb_const_int(p->module, t_int, t->Struct.soa_count); + } else { + len = lb_soa_struct_len(p, soa_ptr); + } + lb_emit_bounds_check(p, ast_token(index_expr), index, len); +} + gb_internal lbAddr lb_addr_swizzle(lbValue addr, Type *array_type, u8 swizzle_count, u8 swizzle_indices[4]) { GB_ASSERT(is_type_array(array_type) || is_type_simd_vector(array_type)); GB_ASSERT(1 < swizzle_count && swizzle_count <= 4); @@ -634,6 +666,16 @@ gb_internal lbAddr lb_addr_swizzle_large(lbValue addr, Type *array_type, Slice const &swizzle_indices) { + GB_ASSERT(swizzle_indices.count > 0); + lbAddr v = {lbAddr_SwizzleSoa, addr}; + v.swizzle_soa.index = index; + v.swizzle_soa.index_expr = index_expr; + v.swizzle_soa.type = type; + v.swizzle_soa.indices = swizzle_indices; + return v; +} + gb_internal lbAddr lb_addr_bit_field(lbValue addr, Type *type, i64 bit_offset, i64 bit_size) { GB_ASSERT(is_type_pointer(addr.type)); Type *mt = type_deref(addr.type); @@ -662,6 +704,13 @@ gb_internal Type *lb_addr_type(lbAddr const &addr) { return addr.swizzle.type; case lbAddr_SwizzleLarge: return addr.swizzle_large.type; + case lbAddr_SwizzleSoa: + return addr.swizzle_soa.type; + case lbAddr_SoaVariable: + // deliberately the container type (#soa[N]T), not the element type the addr denotes; + // lb_soa_variable_make_pointer and lb_build_assign_stmt depend on this, + // if this gets changed to Struct.soa_elem, these must be fixed with it. + return type_deref(addr.addr.type); case lbAddr_Context: if (addr.ctx.sel.index.count > 0) { Type *t = t_context; @@ -686,6 +735,17 @@ gb_internal lbValue lb_make_soa_pointer(lbProcedure *p, Type *type, lbValue cons return lb_addr_load(p, v); } +// the soa pointer denoting an lbAddr_SoaVariable element is the only pointer the +// soa element can have; the generic lb_addr_get_ptr deliberately panics for this kind +gb_internal lbValue lb_soa_variable_make_pointer(lbProcedure *p, lbAddr const &addr) { + GB_ASSERT(addr.kind == lbAddr_SoaVariable); + // lb_addr_type on an SoaVariable returns the container type + // (see the SoaVariable case in lb_addr_type), + // which is what the soa pointer is parameterized by + Type *soa_ptr_type = alloc_type_soa_pointer(lb_addr_type(addr)); + return lb_make_soa_pointer(p, soa_ptr_type, addr.addr, addr.soa.index); +} + gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { if (addr.addr.value == nullptr) { GB_PANIC("Illegal addr -> nullptr"); @@ -697,12 +757,10 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { return lb_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key); case lbAddr_SoaVariable: - { - Type *soa_ptr_type = alloc_type_soa_pointer(lb_addr_type(addr)); - return lb_address_from_load_or_generate_local(p, lb_make_soa_pointer(p, soa_ptr_type, addr.addr, addr.soa.index)); - // TODO(bill): FIX THIS HACK - // return lb_address_from_load(p, lb_addr_load(p, addr)); - } + // use lb_addr_load/lb_addr_store or lb_soa_field_elem_ptr for a single component; + // callers that need the soa pointer get it via lb_soa_variable_make_pointer + GB_PANIC("lbAddr_SoaVariable should be handled elsewhere"); + break; case lbAddr_Context: GB_PANIC("lbAddr_Context should be handled elsewhere"); @@ -715,6 +773,10 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { case lbAddr_SwizzleLarge: GB_PANIC("lbAddr_SwizzleLarge should be handled elsewhere"); break; + + case lbAddr_SwizzleSoa: + GB_PANIC("lbAddr_SwizzleSoa should be handled elsewhere"); + break; } return addr.addr; @@ -1264,6 +1326,28 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { } } return; + } else if (addr.kind == lbAddr_SwizzleSoa) { + GB_ASSERT(value.value != nullptr); + value = lb_emit_conv(p, value, lb_addr_type(addr)); + + lb_emit_soa_index_bounds_check(p, addr.addr, addr.swizzle_soa.index, addr.swizzle_soa.index_expr); + + TEMPORARY_ALLOCATOR_GUARD(); + + isize n = addr.swizzle_soa.indices.count; + lbValue src = lb_address_from_load_or_generate_local(p, value); + auto src_loads = slice_make(temporary_allocator(), n); + auto dst_ptrs = slice_make(temporary_allocator(), n); + for (isize i = 0; i < n; i++) { + src_loads[i] = lb_emit_load(p, lb_emit_array_epi(p, src, i)); + } + for (isize i = 0; i < n; i++) { + dst_ptrs[i] = lb_soa_field_elem_ptr(p, addr.addr, addr.swizzle_soa.indices[i], addr.swizzle_soa.index); + } + for (isize i = 0; i < n; i++) { + lb_emit_store(p, dst_ptrs[i], src_loads[i]); + } + return; } else if (addr.kind == lbAddr_SwizzleLarge) { GB_ASSERT(value.value != nullptr); value = lb_emit_conv(p, value, lb_addr_type(addr)); @@ -1314,7 +1398,9 @@ gb_internal void lb_emit_store(lbProcedure *p, lbValue ptr, lbValue value) { Type *a = type_deref(ptr.type, true); if (LLVMIsNull(value.value)) { - LLVMTypeRef src_t = llvm_addr_type(p->module, ptr); + // used to be llvm_addr_type: for a multi-pointer typed ptr the latter is `ptr`, + // and ConstNull of it would store 8 bytes over an element of any size + LLVMTypeRef src_t = lb_type(p->module, a); if (is_type_proc(a)) { LLVMTypeRef rawptr_type = lb_type(p->module, t_rawptr); LLVMTypeRef rawptr_ptr_type = LLVMPointerType(rawptr_type, 0); @@ -1640,6 +1726,17 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { } } return lb_addr_load(p, res); + } else if (addr.kind == lbAddr_SwizzleSoa) { + lb_emit_soa_index_bounds_check(p, addr.addr, addr.swizzle_soa.index, addr.swizzle_soa.index_expr); + + // gather one component per field, no vector path like for lbAddr_Swizzle; + lbAddr res = lb_add_local_generated(p, addr.swizzle_soa.type, false); + for (isize i = 0; i < addr.swizzle_soa.indices.count; i++) { + lbValue src = lb_soa_field_elem_ptr(p, addr.addr, addr.swizzle_soa.indices[i], addr.swizzle_soa.index); + lbValue dst = lb_emit_array_epi(p, res.addr, i); + lb_emit_store(p, dst, lb_emit_load(p, src)); + } + return lb_addr_load(p, res); } else if (addr.kind == lbAddr_SwizzleLarge) { Type *array_type = base_type(addr.swizzle_large.type); GB_ASSERT(array_type->kind == Type_Array); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 1d42b497a..c834dc192 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1348,6 +1348,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc switch (addr.kind) { case lbAddr_Swizzle: case lbAddr_SwizzleLarge: + case lbAddr_SwizzleSoa: // NOTE(laytan): apply the swizzle. array = lb_address_from_load(p, lb_addr_load(p, addr)); break; @@ -3015,6 +3016,50 @@ gb_internal void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr lb_emit_store(p, lhs_ptrs[i], ops[i]); } return; + } else if (lhs.kind == lbAddr_SwizzleSoa) { + // the three cases largely mirror each other, + // perhaps unify them? + GB_ASSERT(is_type_array(lhs_type)); + + // the index list may repeat components (and so be longer than 4), but the + // element array itself has at most 4, so distinct destinations do fit in [4]; + // each dest is done on the first occurrence + bool indices_handled[4] = {}; + i32 indices[4] = {}; + i32 index_count = 0; + for (i32 index : lhs.swizzle_soa.indices) { + GB_ASSERT(index < 4); + if (indices_handled[index]) { + continue; + } + indices_handled[index] = true; + indices[index_count++] = index; + } + + lb_emit_soa_index_bounds_check(p, lhs.addr, lhs.swizzle_soa.index, lhs.swizzle_soa.index_expr); + + lbValue lhs_ptrs[4] = {}; + lbValue x_loads[4] = {}; + lbValue y_loads[4] = {}; + lbValue ops[4] = {}; + + for (i32 i = 0; i < index_count; i++) { + lhs_ptrs[i] = lb_soa_field_elem_ptr(p, lhs.addr, indices[i], lhs.swizzle_soa.index); + } + for (i32 i = 0; i < index_count; i++) { + x_loads[i] = lb_emit_load(p, lhs_ptrs[i]); + } + for (i32 i = 0; i < index_count; i++) { + y_loads[i].value = LLVMBuildExtractValue(p->builder, rhs.value, i, ""); + y_loads[i].type = elem_type; + } + for (i32 i = 0; i < index_count; i++) { + ops[i] = lb_emit_arith(p, op, x_loads[i], y_loads[i], elem_type); + } + for (i32 i = 0; i < index_count; i++) { + lb_emit_store(p, lhs_ptrs[i], ops[i]); + } + return; } diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 5f2d852d7..7db985dea 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -1,4 +1,5 @@ #+feature dynamic-literals +#+feature using-stmt package test_core_runtime import "base:intrinsics" @@ -229,6 +230,226 @@ test_soa_make_len :: proc(t: ^testing.T) { testing.expect_value(t, array[1], [2]int{3, 4}) } +// storing an all-zero constant into a slice/dynamic #soa element +@(test) +test_soa_zero_elem_store :: proc(t: ^testing.T) { + + V :: struct {a: u8, b: u16, c: u32, d: u64, e: u128} + one := V{1, 2, 3, 4, 5} + + array := make(#soa[dynamic]V, 0, 8) + defer delete(array) + for _ in 0 ..< 8 { + append(&array, one) + } + + array[2] = {} + array[5] = V{0, 0, 0, 0, 0} + + s: #soa[]V = array[:] + s[7] = V{} + + for i in 0 ..< 8 { + expected := one if i != 2 && i != 5 && i != 7 else V{} + testing.expect_value(t, array[i], expected) + } +} + + +V_Padded :: struct {a: i32, b: f64, c: f32} +soa_padded_global := #soa[3]V_Padded{ + {a = 1, b = 1.0, c = 1.0}, + {a = 2, b = 2.0, c = 1.0}, + {a = 3, b = 3.0, c = 1.0}, +} + +// fixed #soa compound literals with a padded element struct +@(test) +test_soa_fixed_compound_literal :: proc(t: ^testing.T) { + for i in 0 ..< 3 { + testing.expect_value(t, soa_padded_global[i], V_Padded{i32(i + 1), f64(i + 1), 1.0}) + } + + local := #soa[3]V_Padded{ + {a = 1, b = 1.0, c = 1.0}, + {a = 2, b = 2.0, c = 1.0}, + {a = 3, b = 3.0, c = 1.0}, + } + for i in 0 ..< 3 { + testing.expect_value(t, local[i], V_Padded{i32(i + 1), f64(i + 1), 1.0}) + } + + sparse := #soa[4]V_Padded{ + 0 ..= 1 = {a = 7, b = 7.0, c = 7.0}, + 3 = {a = 9, b = 9.0, c = 9.0}, + } + testing.expect_value(t, sparse[0], V_Padded{7, 7.0, 7.0}) + testing.expect_value(t, sparse[1], V_Padded{7, 7.0, 7.0}) + testing.expect_value(t, sparse[2], V_Padded{}) + testing.expect_value(t, sparse[3], V_Padded{9, 9.0, 9.0}) +} + +// swizzling an element of an #soa container with an array element type +@(test) +test_soa_array_elem_swizzle :: proc(t: ^testing.T) { + + ref := [4]u16{1, 2, 3, 4} // reference + + fixed: #soa[3][4]u16 + fixed.x[0], fixed.y[0], fixed.z[0], fixed.w[0] = 90, 91, 92, 93 + fixed.x[1], fixed.y[1], fixed.z[1], fixed.w[1] = 1, 2, 3, 4 + + testing.expect_value(t, fixed[1].x, ref.x) + testing.expect_value(t, fixed[1].xy, ref.xy) + testing.expect_value(t, fixed[1].xyz, ref.xyz) + testing.expect_value(t, fixed[1].xyzw, ref.xyzw) + testing.expect_value(t, fixed[1].yx, ref.yx) // permuted + testing.expect_value(t, fixed[1].xx, ref.xx) // repeated + testing.expect_value(t, fixed[1].wzyx, ref.wzyx) + + // swizzle may repeat components and count can go > the array len + testing.expect_value(t, swizzle(fixed[1], 0, 1), swizzle(ref, 0, 1)) + testing.expect_value(t, swizzle(fixed[1], 3, 0, 1), swizzle(ref, 3, 0, 1)) + testing.expect_value(t, swizzle(fixed[1], 0, 1, 0, 1, 0), swizzle(ref, 0, 1, 0, 1, 0)) + testing.expect_value(t, swizzle(fixed[1], 3, 3, 3, 3, 3, 3), swizzle(ref, 3, 3, 3, 3, 3, 3)) + + // runtime element index + i := 1 + testing.expect_value(t, fixed[i].xy, ref.xy) + + // scatter writes + fixed[1].xy = [2]u16{10, 11} + testing.expect_value(t, fixed.x[1], 10) + testing.expect_value(t, fixed.y[1], 11) + testing.expect_value(t, fixed.z[1], 3) + // scatter writes permuted + fixed[1].yx = [2]u16{20, 21} + testing.expect_value(t, fixed.y[1], 20) + testing.expect_value(t, fixed.x[1], 21) + testing.expect_value(t, fixed.x[0], 90) + testing.expect_value(t, fixed.w[0], 93) + + // dynamic and slice kinds + dyn := make(#soa[dynamic][4]u16, 2) + defer delete(dyn) + dyn[0] = [4]u16{1, 2, 3, 4} + dyn[1] = [4]u16{5, 6, 7, 8} + + testing.expect_value(t, dyn[0].xy, ref.xy) + testing.expect_value(t, dyn[0].zx, ref.zx) + dyn[1].xy = [2]u16{40, 41} + testing.expect_value(t, dyn.x[1], 40) + testing.expect_value(t, dyn.y[1], 41) + testing.expect_value(t, dyn.x[0], 1) + + s := dyn[:] + testing.expect_value(t, s[0].zw, ref.zw) + s[0].zw = [2]u16{50, 51} + testing.expect_value(t, dyn.z[0], 50) + testing.expect_value(t, dyn.w[0], 51) + + // through #soa pointer + p := &dyn[0] + testing.expect_value(t, p^.x, u16(1)) + testing.expect_value(t, p^.xy, [2]u16{1, 2}) + + // auto-deref through the #soa pointer + testing.expect_value(t, p.x, u16(1)) + testing.expect_value(t, p.xy, [2]u16{1, 2}) + testing.expect_value(t, p.yx, [2]u16{2, 1}) + p.zw = [2]u16{60, 61} + testing.expect_value(t, dyn.z[0], 60) + testing.expect_value(t, dyn.w[0], 61) + p.xy += [2]u16{1, 1} + testing.expect_value(t, dyn.x[0], 2) + testing.expect_value(t, dyn.y[0], 3) + + // read-modify-write + fixed[1].xy += [2]u16{9, 10} + testing.expect_value(t, fixed.x[1], 30) + testing.expect_value(t, fixed.y[1], 30) + fixed[1].xx += [2]u16{5, 100} + testing.expect_value(t, fixed.x[1], 35) + + // range over an element swizzle + sum: u16 + for c in fixed[1].wz { + sum += c + } + testing.expect_value(t, sum, u16(7)) + + // shuffle + fixed[1].xy = [2]u16{9, 10} + fixed[1].xy = fixed[1].yx + testing.expect_value(t, fixed[1].xy, [2]u16{10, 9}) +} + +// "using" on an #soa for-in looping variable +@(test) +test_soa_for_in_using :: proc(t: ^testing.T) { + S :: struct { + a: int, + b: int, + c: int, + } + s: #soa[2]S = {{a = 1, b = 2, c = 3}, {a = 4, b = 5, c = 6}} + + sum := 0 + for v in s { + using v + sum += a + c + } + testing.expect_value(t, sum, 14) + + for &v in s { + using v + b += 10 + } + testing.expect_value(t, s.b[0], 12) + testing.expect_value(t, s.b[1], 15) +} + +// &v in for-in over soa container +@(test) +test_soa_for_in_addr :: proc(t: ^testing.T) { + S :: struct { + a: int, + b: int, + } + s: #soa[2]S = {{a = 1, b = 2}, {a = 3, b = 4}} + + for &v, i in s { + p := &v + testing.expect_value(t, p.a, s.a[i]) + p.b += 10 * (i + 1) + } + testing.expect_value(t, s.b[0], 12) + testing.expect_value(t, s.b[1], 24) + + // &v is the same type as &s[i] + q := &s[0] + for &v in s { + q = &v + } + + // still valid + testing.expect_value(t, q.a, 3) + q.a = 30 + testing.expect_value(t, s.a[1], 30) + + // array element type + arr: #soa[2][4]u16 + arr[1] = [4]u16{1, 2, 3, 4} + for &v, i in arr { + pv := &v + if i == 1 { + testing.expect_value(t, pv.x, u16(1)) + pv.y = 20 + } + } + testing.expect_value(t, arr.y[1], 20) +} + @(test) test_soa_array_allocator_resize :: proc(t: ^testing.T) {