Merge pull request #7259 from corleypc/soa-swizzle-fix

Fixes swizzles on array elements in soa containers
This commit is contained in:
gingerBill
2026-08-09 12:05:07 +02:00
committed by GitHub
7 changed files with 463 additions and 32 deletions

View File

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

View File

@@ -40,6 +40,7 @@ enum lbAddrKind {
lbAddr_Swizzle,
lbAddr_SwizzleLarge,
lbAddr_SwizzleSoa,
lbAddr_BitField,
};
@@ -73,6 +74,12 @@ struct lbAddr {
Type *type;
Slice<i32> indices;
} swizzle_large;
struct {
lbValue index;
Ast *index_expr;
Type *type;
Slice<i32> 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);

View File

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

View File

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

View File

@@ -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<i
return v;
}
gb_internal lbAddr lb_addr_swizzle_soa(lbValue addr, lbValue index, Ast *index_expr, Type *type, Slice<i32> 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<lbValue>(temporary_allocator(), n);
auto dst_ptrs = slice_make<lbValue>(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);

View File

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

View File

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