From 05706864b7cdbd8fabf09dd8cb4d55e1e9174acf Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sat, 23 Aug 2025 12:55:07 +0200 Subject: [PATCH 1/7] Support using allocator resize in `_reserve_soa` (fixes #5615) --- base/runtime/core_builtin_soa.odin | 60 ++++++++++++++++++++--- tests/core/runtime/test_core_runtime.odin | 26 ++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index ff27a4559..5baeb9e24 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -249,17 +249,63 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo old_data := (^rawptr)(array)^ - new_bytes := array.allocator.procedure( - array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align, - nil, old_size, loc, - ) or_return + new_bytes, resize_err := array.allocator.procedure( + array.allocator.data, .Resize if zero_memory else .Resize_Non_Zeroed, new_size, max_align, + old_data, old_size, loc, + ) new_data := raw_data(new_bytes) - - footer.cap = capacity - old_offset := 0 new_offset := 0 + + if resize_err == .None { + + footer.cap = capacity + + // Adjust layout + // before: |x x y y z z| + // now: |x x y y z z _ _ _| + // after: |x x _ y y _ z z _| + + for i in 0.. Date: Sat, 23 Aug 2025 13:10:19 +0200 Subject: [PATCH 2/7] Only zero memory when requested --- base/runtime/core_builtin_soa.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 5baeb9e24..7548f6735 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -262,10 +262,9 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo footer.cap = capacity - // Adjust layout - // before: |x x y y z z| - // now: |x x y y z z _ _ _| - // after: |x x _ y y _ z z _| + // Correct data memory + // from: |x x y y z z _ _ _| + // to: |x x _ y y _ z z _| for i in 0.. Date: Sat, 23 Aug 2025 16:55:12 +0200 Subject: [PATCH 3/7] Avoid overlap issues when correcting memory after resize in _reserve_soa --- base/runtime/core_builtin_soa.odin | 19 ++++++-- tests/core/runtime/test_core_runtime.odin | 58 +++++++++++++++++------ 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 7548f6735..46c767751 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -266,6 +266,12 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo // from: |x x y y z z _ _ _| // to: |x x _ y y _ z z _| + // move old data to the end of the new allocation to avoid overlap + old_start := uintptr(new_data) + uintptr(new_size - old_size) + mem_copy(rawptr(old_start), old_data, old_size) + + // now: |_ _ _ x x y y z z| + for i in 0.. Date: Sat, 23 Aug 2025 17:07:57 +0200 Subject: [PATCH 4/7] Always use .Resize_Non_Zeroed in _reserve_soa --- base/runtime/core_builtin_soa.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 46c767751..33cb760ed 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -250,7 +250,7 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo old_data := (^rawptr)(array)^ new_bytes, resize_err := array.allocator.procedure( - array.allocator.data, .Resize if zero_memory else .Resize_Non_Zeroed, new_size, max_align, + array.allocator.data, .Resize_Non_Zeroed, new_size, max_align, old_data, old_size, loc, ) new_data := raw_data(new_bytes) From 66f4c9342098e5d5c0cf10b5aea7b57c5e0dd1d3 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 24 Aug 2025 15:04:19 +0200 Subject: [PATCH 5/7] Handle nil old data case --- base/runtime/core_builtin_soa.odin | 89 ++++++++++++++++-------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 33cb760ed..d436b9a3a 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -249,68 +249,73 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo old_data := (^rawptr)(array)^ - new_bytes, resize_err := array.allocator.procedure( - array.allocator.data, .Resize_Non_Zeroed, new_size, max_align, - old_data, old_size, loc, - ) - new_data := raw_data(new_bytes) + if old_data != nil { + new_bytes, resize_err := array.allocator.procedure( + array.allocator.data, .Resize_Non_Zeroed, new_size, max_align, + old_data, old_size, loc, + ) + new_data := raw_data(new_bytes) - old_offset := 0 - new_offset := 0 + if resize_err == .None { - if resize_err == .None { + footer.cap = capacity - footer.cap = capacity + old_offset := 0 + new_offset := 0 - // Correct data memory - // from: |x x y y z z _ _ _| - // to: |x x _ y y _ z z _| + // Correct data memory + // from: |x x y y z z _ _ _| + // to: |x x _ y y _ z z _| - // move old data to the end of the new allocation to avoid overlap - old_start := uintptr(new_data) + uintptr(new_size - old_size) - mem_copy(rawptr(old_start), old_data, old_size) + // move old data to the end of the new allocation to avoid overlap + old_start := uintptr(new_data) + uintptr(new_size - old_size) + mem_copy(rawptr(old_start), new_data, old_size) - // now: |_ _ _ x x y y z z| + // now: |_ _ _ x x y y z z| - for i in 0.. Date: Sun, 24 Aug 2025 15:09:08 +0200 Subject: [PATCH 6/7] More soa tests --- tests/core/runtime/test_core_runtime.odin | 95 ++++++++++++++--------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 1bc100348..3ed252026 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -179,6 +179,22 @@ test_map_get :: proc(t: ^testing.T) { } } +@(test) +test_soa_make_len :: proc(t: ^testing.T) { + + array, err := make(#soa[dynamic][2]int, 2) + defer delete(array) + array[0] = [2]int{1, 2} + array[1] = [2]int{3, 4} + + testing.expect_value(t, err, nil) + testing.expect_value(t, len(array), 2) + testing.expect_value(t, cap(array), 2) + + testing.expect_value(t, array[0], [2]int{1, 2}) + testing.expect_value(t, array[1], [2]int{3, 4}) +} + @(test) test_soa_array_allocator_resize :: proc(t: ^testing.T) { @@ -186,53 +202,60 @@ test_soa_array_allocator_resize :: proc(t: ^testing.T) { context.allocator = runtime.arena_allocator(&arena) defer runtime.arena_destroy(&arena) - { - // |1 3 _ 2 4 _| - // |1 3 _ _ 2 4 _ _| + // |1 3 _ 2 4 _| + // |1 3 _ _ 2 4 _ _| - array, err := make(#soa[dynamic][2]int, 2, 3) - array[0] = [2]int{1, 2} - array[1] = [2]int{3, 4} + array, err := make(#soa[dynamic][2]int, 2, 3) + defer delete(array) + array[0] = [2]int{1, 2} + array[1] = [2]int{3, 4} - testing.expect_value(t, err, nil) - testing.expect_value(t, len(array), 2) - testing.expect_value(t, cap(array), 3) + testing.expect_value(t, err, nil) + testing.expect_value(t, len(array), 2) + testing.expect_value(t, cap(array), 3) - err = resize(&array, 4) + err = resize(&array, 4) - testing.expect_value(t, err, nil) - testing.expect_value(t, len(array), 4) - testing.expect_value(t, cap(array), 4) + testing.expect_value(t, err, nil) + testing.expect_value(t, len(array), 4) + testing.expect_value(t, cap(array), 4) - testing.expect_value(t, array[0], [2]int{1, 2}) - testing.expect_value(t, array[1], [2]int{3, 4}) - testing.expect_value(t, array[2], [2]int{0, 0}) - testing.expect_value(t, array[3], [2]int{0, 0}) - } + testing.expect_value(t, array[0], [2]int{1, 2}) + testing.expect_value(t, array[1], [2]int{3, 4}) + testing.expect_value(t, array[2], [2]int{0, 0}) + testing.expect_value(t, array[3], [2]int{0, 0}) +} - { - // |1 4 2 5 3 6| - // |1 4 _ _ 2 5 _ _ 3 6 _ _| - array, err := make(#soa[dynamic][3]int, 2, 2) - array[0] = [3]int{1, 2, 3} - array[1] = [3]int{4, 5, 6} +@(test) +test_soa_array_allocator_resize_overlapping :: proc(t: ^testing.T) { - testing.expect_value(t, err, nil) - testing.expect_value(t, len(array), 2) - testing.expect_value(t, cap(array), 2) + arena: runtime.Arena + context.allocator = runtime.arena_allocator(&arena) + defer runtime.arena_destroy(&arena) - err = resize(&array, 4) + // |1 4 2 5 3 6| + // |1 4 _ _ 2 5 _ _ 3 6 _ _| - testing.expect_value(t, err, nil) - testing.expect_value(t, len(array), 4) - testing.expect_value(t, cap(array), 4) + array, err := make(#soa[dynamic][3]int, 2, 2) + defer delete(array) + array[0] = [3]int{1, 2, 3} + array[1] = [3]int{4, 5, 6} - testing.expect_value(t, array[0], [3]int{1, 2, 3}) - testing.expect_value(t, array[1], [3]int{4, 5, 6}) - testing.expect_value(t, array[2], [3]int{0, 0, 0}) - testing.expect_value(t, array[3], [3]int{0, 0, 0}) - } + testing.expect_value(t, err, nil) + testing.expect_value(t, len(array), 2) + testing.expect_value(t, cap(array), 2) + + err = resize(&array, 4) + + testing.expect_value(t, err, nil) + testing.expect_value(t, len(array), 4) + testing.expect_value(t, cap(array), 4) + + testing.expect_value(t, array[0], [3]int{1, 2, 3}) + testing.expect_value(t, array[1], [3]int{4, 5, 6}) + testing.expect_value(t, array[2], [3]int{0, 0, 0}) + testing.expect_value(t, array[3], [3]int{0, 0, 0}) } @(test) From 95cfad67b776c4d839aa494fa88832a67c0c28cc Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 24 Aug 2025 15:17:13 +0200 Subject: [PATCH 7/7] =?UTF-8?q?Cleanup=20soa=20resize=20logic=E2=80=94redu?= =?UTF-8?q?ce=20nesting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base/runtime/core_builtin_soa.odin | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index d436b9a3a..9d042267a 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -249,60 +249,60 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo old_data := (^rawptr)(array)^ - if old_data != nil { + resize: if old_data != nil { + new_bytes, resize_err := array.allocator.procedure( array.allocator.data, .Resize_Non_Zeroed, new_size, max_align, old_data, old_size, loc, ) new_data := raw_data(new_bytes) - if resize_err == .None { + #partial switch resize_err { + case .Mode_Not_Implemented: break resize + case .None: // continue resizing + case: return resize_err + } - footer.cap = capacity + footer.cap = capacity - old_offset := 0 - new_offset := 0 + old_offset := 0 + new_offset := 0 - // Correct data memory - // from: |x x y y z z _ _ _| - // to: |x x _ y y _ z z _| + // Correct data memory + // from: |x x y y z z _ _ _| + // to: |x x _ y y _ z z _| - // move old data to the end of the new allocation to avoid overlap - old_start := uintptr(new_data) + uintptr(new_size - old_size) - mem_copy(rawptr(old_start), new_data, old_size) + // move old data to the end of the new allocation to avoid overlap + old_data = rawptr(uintptr(new_data) + uintptr(new_size - old_size)) + mem_copy(old_data, new_data, old_size) - // now: |_ _ _ x x y y z z| + // now: |_ _ _ x x y y z z| - for i in 0..