v / vlib / v2 / ssa / build_array_init_from_flat_test.v
264 lines · 247 sloc · 6.85 KB · e78b7311ad580c800e5a3a0bd0afe3876e609685
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// vtest build: macos
5//
6// Bit-equality pin for the cursor-native rewrite of `build_array_init_from_flat`.
7// The previous helper rehydrated every ArrayInitExpr field via `decode_expr`
8// (typ, init, cap, len, update_expr, and each element) and dispatched to
9// legacy `build_array_init_expr`. The cursor-native rewrite mirrors
10// `build_array_init_expr` directly: elements walk `c.edge(5)..edge_count()` via
11// `build_expr_from_flat`; the declared element type is resolved from `typ_c`
12// using a `.typ_array` / `.typ_array_fixed` kind match (`edge(0)` / `edge(1)`
13// for elem_type) plus an `ast_type_to_ssa_from_flat` call; the fixed-array
14// marker check inspects `len_c.kind() == .expr_postfix` with `aux == .not`
15// (the `!` suffix) or `typ_c.kind() == .typ_array_fixed`. Empty dynamic
16// arrays with explicit `len`/`cap` now read those edges and lower to the same
17// runtime allocation call as the legacy builder. Pin asserts module-count
18// parity for the common literal `[1, 2, 3]` path and the dynamic
19// `[]int{len: 3, cap: 5}` path.
20module ssa
21
22import v2.ast
23import v2.types
24
25fn ai_ident(name string) ast.Expr {
26 return ast.Expr(ast.Ident{
27 name: name
28 })
29}
30
31fn ai_num(value string) ast.Expr {
32 return ast.Expr(ast.BasicLiteral{
33 kind: .number
34 value: value
35 })
36}
37
38fn ai_array_type(elem ast.Expr) ast.Expr {
39 return ast.Expr(ast.Type(ast.ArrayType{
40 elem_type: elem
41 }))
42}
43
44fn ai_field(name string, typ string) ast.FieldDecl {
45 return ast.FieldDecl{
46 name: name
47 typ: ai_ident(typ)
48 }
49}
50
51fn make_array_init_fixture() []ast.File {
52 mut stmts := [
53 ast.Stmt(ast.ModuleStmt{
54 name: 'main'
55 }),
56 ]
57 // fn make_arr() { a := [1, 2, 3] _ = a }
58 stmts << ast.Stmt(ast.FnDecl{
59 name: 'make_arr'
60 typ: ast.FnType{
61 return_type: ast.empty_expr
62 }
63 stmts: [
64 ast.Stmt(ast.AssignStmt{
65 op: .decl_assign
66 lhs: [ai_ident('a')]
67 rhs: [
68 ast.Expr(ast.ArrayInitExpr{
69 exprs: [ai_num('1'), ai_num('2'), ai_num('3')]
70 }),
71 ]
72 }),
73 ]
74 })
75 // fn make_dynamic_arr() { a := []int{len: 3, cap: 5} _ = a }
76 stmts << ast.Stmt(ast.FnDecl{
77 name: 'make_dynamic_arr'
78 typ: ast.FnType{
79 return_type: ast.empty_expr
80 }
81 stmts: [
82 ast.Stmt(ast.AssignStmt{
83 op: .decl_assign
84 lhs: [ai_ident('a')]
85 rhs: [
86 ast.Expr(ast.ArrayInitExpr{
87 typ: ai_array_type(ai_ident('int'))
88 len: ai_num('3')
89 cap: ai_num('5')
90 }),
91 ]
92 }),
93 ]
94 })
95 return [
96 ast.File{
97 name: 'main.v'
98 mod: 'main'
99 stmts: stmts
100 },
101 ]
102}
103
104fn make_dynamic_array_init_fixture() []ast.File {
105 builtin_file := ast.File{
106 name: 'builtin.v'
107 mod: 'builtin'
108 stmts: [
109 ast.Stmt(ast.ModuleStmt{
110 name: 'builtin'
111 }),
112 ast.Stmt(ast.StructDecl{
113 name: 'array'
114 fields: [
115 ai_field('data', 'voidptr'),
116 ai_field('offset', 'int'),
117 ai_field('len', 'int'),
118 ai_field('cap', 'int'),
119 ai_field('flags', 'int'),
120 ai_field('element_size', 'int'),
121 ]
122 }),
123 ast.Stmt(ast.FnDecl{
124 name: 'builtin____new_array_noscan'
125 language: .c
126 typ: ast.FnType{
127 params: [
128 ast.Parameter{
129 name: 'len'
130 typ: ai_ident('int')
131 },
132 ast.Parameter{
133 name: 'cap'
134 typ: ai_ident('int')
135 },
136 ast.Parameter{
137 name: 'elem_size'
138 typ: ai_ident('int')
139 },
140 ]
141 return_type: ai_ident('array')
142 }
143 }),
144 ]
145 }
146 main_file := ast.File{
147 name: 'main.v'
148 mod: 'main'
149 stmts: [
150 ast.Stmt(ast.ModuleStmt{
151 name: 'main'
152 }),
153 ast.Stmt(ast.FnDecl{
154 name: 'make_dynamic'
155 typ: ast.FnType{
156 return_type: ai_array_type(ai_ident('int'))
157 }
158 stmts: [
159 ast.Stmt(ast.ReturnStmt{
160 exprs: [
161 ast.Expr(ast.ArrayInitExpr{
162 typ: ai_array_type(ai_ident('int'))
163 len: ai_num('3')
164 cap: ai_num('5')
165 }),
166 ]
167 }),
168 ]
169 }),
170 ]
171 }
172 return [builtin_file, main_file]
173}
174
175fn build_via_legacy_array_init(files []ast.File, env &types.Environment, name string) &Module {
176 mut mod := Module.new(name)
177 mut b := Builder.new_with_env(mod, env)
178 b.register_fn_signatures(files[0])
179 b.build_fn_bodies(files[0])
180 return mod
181}
182
183fn build_via_flat_array_init(files []ast.File, env &types.Environment, name string) &Module {
184 flat := ast.flatten_files(files)
185 mut mod := Module.new(name)
186 mut b := Builder.new_with_env(mod, env)
187 b.register_fn_signatures_from_flat(flat.file_cursor(0))
188 b.build_fn_bodies_from_flat(flat.file_cursor(0))
189 return mod
190}
191
192fn build_all_via_legacy_array_init(files []ast.File, env &types.Environment, name string) &Module {
193 mut mod := Module.new(name)
194 mut b := Builder.new_with_env(mod, env)
195 b.minimal_runtime_roots = true
196 b.build_all(files)
197 return mod
198}
199
200fn build_all_via_flat_array_init(files []ast.File, env &types.Environment, name string) &Module {
201 flat := ast.flatten_files(files)
202 mut mod := Module.new(name)
203 mut b := Builder.new_with_env(mod, env)
204 b.minimal_runtime_roots = true
205 b.build_all_from_flat(&flat)
206 return mod
207}
208
209fn ai_call_operands(m &Module, func_name string, callee_name string) []ValueID {
210 for func in m.funcs {
211 if func.name != func_name {
212 continue
213 }
214 for block_id in func.blocks {
215 for value_id in m.blocks[block_id].instrs {
216 value := m.values[value_id]
217 if value.kind != .instruction {
218 continue
219 }
220 instr := m.instrs[value.index]
221 if instr.op != .call || instr.operands.len == 0 {
222 continue
223 }
224 callee_id := instr.operands[0]
225 if callee_id > 0 && callee_id < m.values.len
226 && m.values[callee_id].name == callee_name {
227 return instr.operands
228 }
229 }
230 }
231 }
232 assert false, 'missing call to ${callee_name} in ${func_name}'
233 return []ValueID{}
234}
235
236fn ai_assert_dynamic_array_operands(m &Module) {
237 operands := ai_call_operands(m, 'make_dynamic', 'builtin____new_array_noscan')
238 assert operands.len == 4
239 assert m.values[operands[1]].name == '3'
240 assert m.values[operands[2]].name == '5'
241 assert m.values[operands[3]].name == '4'
242}
243
244fn test_build_array_init_from_flat_matches_legacy() {
245 files := make_array_init_fixture()
246 env := types.Environment.new()
247 mod_legacy := build_via_legacy_array_init(files, env, 'array_init_legacy')
248 mod_flat := build_via_flat_array_init(files, env, 'array_init_flat')
249
250 assert mod_legacy.funcs.len == mod_flat.funcs.len
251 assert mod_legacy.blocks.len == mod_flat.blocks.len
252 assert mod_legacy.instrs.len == mod_flat.instrs.len
253 assert mod_legacy.values.len == mod_flat.values.len
254}
255
256fn test_build_array_init_from_flat_dynamic_len_cap_operands_match_legacy() {
257 files := make_dynamic_array_init_fixture()
258 env := types.Environment.new()
259 mod_legacy := build_all_via_legacy_array_init(files, env, 'array_init_dynamic_legacy')
260 mod_flat := build_all_via_flat_array_init(files, env, 'array_init_dynamic_flat')
261
262 ai_assert_dynamic_array_operands(mod_legacy)
263 ai_assert_dynamic_array_operands(mod_flat)
264}
265