v / vlib / v2 / gen / cleanc / soa.v
148 lines · 138 sloc · 5.03 KB · 10a27a35f62541cee8c09d75d37629f27d89d055
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5// Structure of Arrays (SoA) code generation.
6//
7// When a struct is annotated with @[soa], the compiler generates a companion
8// SoA container struct that stores each field in a separate contiguous array.
9// This layout provides significantly better cache performance for batch
10// operations that touch only a subset of fields (common in game math, ECS,
11// particle systems, physics simulations, etc.).
12//
13// Example:
14// @[soa]
15// struct Particle {
16// x f32
17// y f32
18// vx f32
19// vy f32
20// }
21//
22// Generates a companion type `Particle_SOA` with:
23// struct Particle_SOA {
24// int len;
25// int cap;
26// f32* x;
27// f32* y;
28// f32* vx;
29// f32* vy;
30// };
31//
32// And helper functions:
33// Particle_SOA Particle_SOA_new(int len, int cap);
34// void Particle_SOA_push(Particle_SOA* soa, Particle val);
35// Particle Particle_SOA_get(Particle_SOA soa, int i);
36// void Particle_SOA_set(Particle_SOA* soa, int i, Particle val);
37// void Particle_SOA_free(Particle_SOA* soa);
38module cleanc
39
40import v2.types
41
42// gen_soa_companion generates the SoA container struct and helper functions
43// for a struct annotated with @[soa].
44fn (mut g Gen) gen_soa_companion(name string, s types.Struct) {
45 soa_name := '${name}_SOA'
46
47 // --- SoA container struct ---
48 g.sb.writeln('// SoA (Structure of Arrays) companion for ${name}')
49 g.sb.writeln('typedef struct {')
50 g.sb.writeln('\tint len;')
51 g.sb.writeln('\tint cap;')
52 for field in s.fields {
53 c_type := g.types_type_to_c(field.typ)
54 fname := escape_c_keyword(field.name)
55 g.sb.writeln('\t${c_type}* ${fname};')
56 }
57 g.sb.writeln('} ${soa_name};')
58 g.sb.writeln('')
59
60 // --- new: allocate SoA container ---
61 g.sb.writeln('static inline ${soa_name} ${soa_name}_new(int len, int cap) {')
62 g.sb.writeln('\tif (cap < len) cap = len;')
63 g.sb.writeln('\t${soa_name} soa;')
64 g.sb.writeln('\tsoa.len = len;')
65 g.sb.writeln('\tsoa.cap = cap;')
66 for field in s.fields {
67 c_type := g.types_type_to_c(field.typ)
68 fname := escape_c_keyword(field.name)
69 if g.has_freestanding_hook_capability('alloc') {
70 g.sb.writeln('\tsoa.${fname} = (${c_type}*)v_platform_malloc(cap * sizeof(${c_type}));')
71 g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
72 } else if g.is_freestanding_target() {
73 g.sb.writeln('\tsoa.${fname} = (${c_type}*)${g.c_heap_malloc_call('cap * sizeof(${c_type})')};')
74 g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
75 } else {
76 g.sb.writeln('\tsoa.${fname} = (${c_type}*)calloc(cap, sizeof(${c_type}));')
77 }
78 }
79 g.sb.writeln('\treturn soa;')
80 g.sb.writeln('}')
81 g.sb.writeln('')
82
83 // --- get: retrieve element at index as original struct ---
84 g.sb.writeln('static inline ${name} ${soa_name}_get(${soa_name} soa, int i) {')
85 g.sb.writeln('\treturn (${name}){')
86 for i, field in s.fields {
87 comma := if i < s.fields.len - 1 { ',' } else { '' }
88 fname := escape_c_keyword(field.name)
89 g.sb.writeln('\t\t.${fname} = soa.${fname}[i]${comma}')
90 }
91 g.sb.writeln('\t};')
92 g.sb.writeln('}')
93 g.sb.writeln('')
94
95 // --- set: set element at index from original struct ---
96 g.sb.writeln('static inline void ${soa_name}_set(${soa_name}* soa, int i, ${name} val) {')
97 for field in s.fields {
98 fname := escape_c_keyword(field.name)
99 g.sb.writeln('\tsoa->${fname}[i] = val.${fname};')
100 }
101 g.sb.writeln('}')
102 g.sb.writeln('')
103
104 // --- push: append element, growing capacity if needed ---
105 g.sb.writeln('static inline void ${soa_name}_push(${soa_name}* soa, ${name} val) {')
106 g.sb.writeln('\tif (soa->len >= soa->cap) {')
107 g.sb.writeln('\t\tint new_cap = soa->cap < 8 ? 8 : soa->cap * 2;')
108 for field in s.fields {
109 c_type := g.types_type_to_c(field.typ)
110 fname := escape_c_keyword(field.name)
111 g.sb.writeln('\t\tsoa->${fname} = (${c_type}*)${g.c_heap_realloc_call('soa->${fname}',
112 'new_cap * sizeof(${c_type})')};')
113 }
114 g.sb.writeln('\t\tsoa->cap = new_cap;')
115 g.sb.writeln('\t}')
116 for field in s.fields {
117 fname := escape_c_keyword(field.name)
118 g.sb.writeln('\tsoa->${fname}[soa->len] = val.${fname};')
119 }
120 g.sb.writeln('\tsoa->len++;')
121 g.sb.writeln('}')
122 g.sb.writeln('')
123
124 // --- pop: remove and return last element ---
125 g.sb.writeln('static inline ${name} ${soa_name}_pop(${soa_name}* soa) {')
126 g.sb.writeln('\tif (soa->len == 0) return (${name}){0};')
127 g.sb.writeln('\tsoa->len--;')
128 g.sb.writeln('\treturn (${name}){')
129 for i, field in s.fields {
130 comma := if i < s.fields.len - 1 { ',' } else { '' }
131 fname := escape_c_keyword(field.name)
132 g.sb.writeln('\t\t.${fname} = soa->${fname}[soa->len]${comma}')
133 }
134 g.sb.writeln('\t};')
135 g.sb.writeln('}')
136 g.sb.writeln('')
137
138 // --- free: deallocate all field arrays ---
139 g.sb.writeln('static inline void ${soa_name}_free(${soa_name}* soa) {')
140 for field in s.fields {
141 fname := escape_c_keyword(field.name)
142 g.sb.writeln('\t${g.c_heap_free_call('soa->${fname}')};')
143 }
144 g.sb.writeln('\tsoa->len = 0;')
145 g.sb.writeln('\tsoa->cap = 0;')
146 g.sb.writeln('}')
147 g.sb.writeln('')
148}
149