v / vlib / v2 / ssa / build_call_from_flat_test.v
291 lines · 278 sloc · 7.25 KB · 164b30309e6337b95ec2baacacd0f101fafd3d97
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 s196: `build_call_from_flat` is the twelfth per-kind
7// arm inside `build_expr_from_flat`. CallExpr flat encoding (flat.v:1862) is
8// (.expr_call, pos, -1, -1, 0, 0, [edge0=lhs, edge1..n=args]). The cursor
9// port decodes the lhs and every arg via `decode_expr` (unavoidable:
10// build_call pattern-matches heavily on `expr.lhs is ast.SelectorExpr`,
11// `expr.lhs is ast.Ident`, and `arg is ast.ModifierExpr`), then constructs
12// ast.CallExpr{lhs, args, pos} and dispatches to existing build_call.
13module ssa
14
15import v2.ast
16import v2.token
17import v2.types
18
19// Fixture: `fn add(x int, y int) int { return x + y } fn main() { add(1, 2) }`.
20// The main body contains an ExprStmt wrapping a CallExpr with lhs=Ident('add')
21// and args=[BasicLiteral('1'), BasicLiteral('2')]. Exercises the common-case
22// free-function call path (no struct/method/builtin resolution).
23fn make_call_fixture() []ast.File {
24 return [
25 ast.File{
26 name: 'main.v'
27 mod: 'main'
28 stmts: [
29 ast.Stmt(ast.ModuleStmt{
30 name: 'main'
31 }),
32 ast.Stmt(ast.FnDecl{
33 name: 'add'
34 typ: ast.FnType{
35 params: [
36 ast.Parameter{
37 name: 'x'
38 typ: ast.Expr(ast.Ident{
39 name: 'int'
40 })
41 },
42 ast.Parameter{
43 name: 'y'
44 typ: ast.Expr(ast.Ident{
45 name: 'int'
46 })
47 },
48 ]
49 return_type: ast.Expr(ast.Ident{
50 name: 'int'
51 })
52 }
53 stmts: [
54 ast.Stmt(ast.ReturnStmt{
55 exprs: [
56 ast.Expr(ast.InfixExpr{
57 op: token.Token.plus
58 lhs: ast.Expr(ast.Ident{
59 name: 'x'
60 })
61 rhs: ast.Expr(ast.Ident{
62 name: 'y'
63 })
64 }),
65 ]
66 }),
67 ]
68 }),
69 ast.Stmt(ast.FnDecl{
70 name: 'main'
71 typ: ast.FnType{
72 return_type: ast.empty_expr
73 }
74 stmts: [
75 ast.Stmt(ast.ExprStmt{
76 expr: ast.Expr(ast.CallExpr{
77 lhs: ast.Expr(ast.Ident{
78 name: 'add'
79 })
80 args: [
81 ast.Expr(ast.BasicLiteral{
82 kind: .number
83 value: '1'
84 }),
85 ast.Expr(ast.BasicLiteral{
86 kind: .number
87 value: '2'
88 }),
89 ]
90 })
91 }),
92 ]
93 }),
94 ]
95 },
96 ]
97}
98
99fn build_via_legacy_call(files []ast.File, env &types.Environment, name string) &Module {
100 mut mod := Module.new(name)
101 mut b := Builder.new_with_env(mod, env)
102 b.register_fn_signatures(files[0])
103 b.build_fn_bodies(files[0])
104 return mod
105}
106
107fn build_via_flat_call(files []ast.File, env &types.Environment, name string) &Module {
108 flat := ast.flatten_files(files)
109 mut mod := Module.new(name)
110 mut b := Builder.new_with_env(mod, env)
111 b.register_fn_signatures_from_flat(flat.file_cursor(0))
112 stmts := flat.file_cursor(0).stmts()
113 for si in 0 .. stmts.len() {
114 c := stmts.at(si)
115 if c.kind() != .stmt_fn_decl {
116 continue
117 }
118 decl := c.fn_decl_signature()
119 fn_name := b.mangle_fn_name(decl)
120 func_idx := b.fn_index[fn_name] or { continue }
121 b.cur_func = func_idx
122 b.label_blocks = map[string]BlockID{}
123 b.vars = map[string]ValueID{}
124 entry := mod.add_block(func_idx, 'entry')
125 b.cur_block = entry
126 for param in decl.typ.params {
127 param_type := b.ast_type_to_ssa(param.typ)
128 param_val := mod.add_value_node(.argument, param_type, param.name, 0)
129 mod.func_add_param(func_idx, param_val)
130 alloca := mod.add_instr(.alloca, entry, mod.type_store.get_ptr(param_type), []ValueID{})
131 mod.add_instr(.store, entry, 0, [param_val, alloca])
132 b.vars[param.name] = alloca
133 }
134 body := c.list_at(3)
135 for bi in 0 .. body.len() {
136 stmt_c := body.at(bi)
137 if stmt_c.kind() == .stmt_return {
138 ret_expr_c := stmt_c.edge(0)
139 val := b.build_expr_from_flat(ret_expr_c)
140 mod.add_instr(.ret, b.cur_block, 0, [val])
141 } else if stmt_c.kind() == .stmt_expr {
142 expr_c := stmt_c.edge(0)
143 b.build_expr_from_flat(expr_c)
144 } else {
145 b.build_stmt_from_flat(stmt_c)
146 }
147 }
148 if !b.block_has_terminator(b.cur_block) {
149 if fn_name == 'main' {
150 zero := mod.get_or_add_const(mod.type_store.get_int(32), '0')
151 mod.add_instr(.ret, b.cur_block, 0, [zero])
152 } else {
153 mod.add_instr(.ret, b.cur_block, 0, []ValueID{})
154 }
155 }
156 }
157 return mod
158}
159
160fn test_build_call_from_flat_matches_legacy() {
161 _ = token.Token.plus
162 files := make_call_fixture()
163 env := types.Environment.new()
164 mod_legacy := build_via_legacy_call(files, env, 'call_legacy')
165 mod_flat := build_via_flat_call(files, env, 'call_flat')
166
167 assert mod_legacy.funcs.len == mod_flat.funcs.len
168 assert mod_legacy.blocks.len == mod_flat.blocks.len
169 assert mod_legacy.instrs.len == mod_flat.instrs.len
170 assert mod_legacy.values.len == mod_flat.values.len
171}
172
173fn call_test_ident(name string) ast.Expr {
174 return ast.Expr(ast.Ident{
175 name: name
176 })
177}
178
179fn call_test_param(name string, typ ast.Expr) ast.Parameter {
180 return ast.Parameter{
181 name: name
182 typ: typ
183 }
184}
185
186fn make_same_module_c_shadow_fixture() []ast.File {
187 int_type := call_test_ident('int')
188 return [
189 ast.File{
190 name: 'foo.v'
191 mod: 'foo'
192 stmts: [
193 ast.Stmt(ast.ModuleStmt{
194 name: 'foo'
195 }),
196 ast.Stmt(ast.FnDecl{
197 name: 'callee'
198 language: .c
199 typ: ast.FnType{
200 params: [
201 call_test_param('x', int_type),
202 ]
203 return_type: int_type
204 }
205 }),
206 ast.Stmt(ast.FnDecl{
207 name: 'callee'
208 typ: ast.FnType{
209 params: [
210 call_test_param('x', int_type),
211 ]
212 return_type: int_type
213 }
214 stmts: [
215 ast.Stmt(ast.ReturnStmt{
216 exprs: [
217 call_test_ident('x'),
218 ]
219 }),
220 ]
221 }),
222 ast.Stmt(ast.FnDecl{
223 name: 'caller'
224 typ: ast.FnType{
225 return_type: int_type
226 }
227 stmts: [
228 ast.Stmt(ast.ReturnStmt{
229 exprs: [
230 ast.Expr(ast.CallExpr{
231 lhs: call_test_ident('callee')
232 args: [
233 ast.Expr(ast.BasicLiteral{
234 kind: .number
235 value: '1'
236 }),
237 ]
238 }),
239 ]
240 }),
241 ]
242 }),
243 ]
244 },
245 ]
246}
247
248fn call_test_func_value_ids(b &Builder, name string) []ValueID {
249 idx := b.fn_index[name] or {
250 assert false, 'missing SSA function ${name}'
251 return []ValueID{}
252 }
253 func := b.mod.funcs[idx]
254 mut ids := []ValueID{}
255 for block_id in func.blocks {
256 for value_id in b.mod.blocks[block_id].instrs {
257 ids << value_id
258 }
259 }
260 return ids
261}
262
263fn call_test_callees(b &Builder, name string) []string {
264 mut callees := []string{}
265 for value_id in call_test_func_value_ids(b, name) {
266 value := b.mod.values[value_id]
267 if value.kind != .instruction {
268 continue
269 }
270 instr := b.mod.instrs[value.index]
271 if instr.op != .call || instr.operands.len == 0 {
272 continue
273 }
274 callee_id := instr.operands[0]
275 if callee_id > 0 && callee_id < b.mod.values.len {
276 callees << b.mod.values[callee_id].name
277 }
278 }
279 return callees
280}
281
282fn test_build_all_from_flat_prefers_same_module_fn_over_bare_c_symbol() {
283 files := make_same_module_c_shadow_fixture()
284 flat := ast.flatten_files(files)
285 env := types.Environment.new()
286 mut mod := Module.new('same_module_c_shadow')
287 mut b := Builder.new_with_env(mod, env)
288 b.build_all_from_flat(&flat)
289
290 assert call_test_callees(b, 'foo__caller') == ['foo__callee']
291}
292