v / vlib / v2 / abi / abi_test.v
388 lines · 332 sloc · 13.31 KB · 1918e1160b29b144758ae6675de359f82939346b
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
6module abi
7
8import os
9import v2.ast
10import v2.mir
11import v2.parser
12import v2.pref
13import v2.ssa
14import v2.token
15
16fn parse_code_for_test(code string) []ast.File {
17 tmp_file := os.join_path(os.temp_dir(), 'v2_abi_parser_test_${os.getpid()}.v')
18 os.write_file(tmp_file, code) or { panic(err) }
19 defer {
20 os.rm(tmp_file) or {}
21 }
22 prefs := &pref.Preferences{}
23 mut file_set := token.FileSet.new()
24 mut p := parser.Parser.new(prefs)
25 return p.parse_files([tmp_file], mut file_set)
26}
27
28fn first_fn_decl(file ast.File) ast.FnDecl {
29 for stmt in file.stmts {
30 if stmt is ast.FnDecl {
31 return stmt
32 }
33 }
34 panic('expected function declaration in parsed file')
35}
36
37fn test_parser_sql_expr_allows_nested_braces_in_body() {
38 files := parse_code_for_test("
39module main
40
41fn main() {
42 req_status := true
43 sql db {
44 update User set
45 name = 'Jengro',
46 status = if req_status { 1 } else { 0 }
47 status = fn [req_status] () u8 { if req_status { return 1 } else { return 0 } }()
48 where id == '100'
49 }
50}
51")
52 assert files.len == 1
53 main_fn := first_fn_decl(files[0])
54 assert main_fn.stmts.len == 2
55 assert main_fn.stmts[1] is ast.ExprStmt
56 sql_stmt := main_fn.stmts[1] as ast.ExprStmt
57 assert sql_stmt.expr is ast.SqlExpr
58}
59
60fn test_arm64_large_struct_call_is_lowered_to_call_sret() {
61 mut ssa_mod := ssa.Module.new('abi_test')
62 i64_t := ssa_mod.type_store.get_int(64)
63 i8_t := ssa_mod.type_store.get_int(8)
64 ptr_t := ssa_mod.type_store.get_ptr(i8_t)
65 large_struct_t := ssa_mod.type_store.register(ssa.Type{
66 kind: .struct_t
67 fields: [ptr_t, i64_t, i64_t] // 24 bytes
68 })
69
70 callee_id := ssa_mod.new_function('callee', large_struct_t, [])
71 callee_entry := ssa_mod.add_block(callee_id, 'entry')
72 zero := ssa_mod.get_or_add_const(i64_t, '0')
73 ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
74
75 caller_id := ssa_mod.new_function('caller', i64_t, [])
76 caller_entry := ssa_mod.add_block(caller_id, 'entry')
77 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
78 call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [fn_val])
79 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
80
81 mut mir_mod := mir.lower_from_ssa(ssa_mod)
82 lower(mut mir_mod, .arm64)
83
84 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
85 assert call_instr.op == .call_sret
86}
87
88fn test_x64_large_struct_call_is_lowered_to_call_sret() {
89 mut ssa_mod := ssa.Module.new('abi_test_x64')
90 i64_t := ssa_mod.type_store.get_int(64)
91 i8_t := ssa_mod.type_store.get_int(8)
92 ptr_t := ssa_mod.type_store.get_ptr(i8_t)
93 large_struct_t := ssa_mod.type_store.register(ssa.Type{
94 kind: .struct_t
95 fields: [ptr_t, i64_t, i64_t]
96 })
97
98 callee_id := ssa_mod.new_function('callee', large_struct_t, [])
99 callee_entry := ssa_mod.add_block(callee_id, 'entry')
100 zero := ssa_mod.get_or_add_const(i64_t, '0')
101 ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
102
103 caller_id := ssa_mod.new_function('caller', i64_t, [])
104 caller_entry := ssa_mod.add_block(caller_id, 'entry')
105 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
106 call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [fn_val])
107 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
108
109 mut mir_mod := mir.lower_from_ssa(ssa_mod)
110 lower(mut mir_mod, .x64)
111
112 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
113 assert call_instr.op == .call_sret
114}
115
116fn test_x64_sysv_sixteen_byte_struct_return_stays_direct() {
117 mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_16_ret')
118 i64_t := ssa_mod.type_store.get_int(64)
119 struct16_t := ssa_mod.type_store.register(ssa.Type{
120 kind: .struct_t
121 fields: [i64_t, i64_t]
122 })
123
124 callee_id := ssa_mod.new_function('callee', struct16_t, [])
125 callee_entry := ssa_mod.add_block(callee_id, 'entry')
126 zero := ssa_mod.get_or_add_const(i64_t, '0')
127 ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
128
129 caller_id := ssa_mod.new_function('caller', i64_t, [])
130 caller_entry := ssa_mod.add_block(caller_id, 'entry')
131 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
132 call_val := ssa_mod.add_instr(.call, caller_entry, struct16_t, [fn_val])
133 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
134
135 mut mir_mod := mir.lower_from_ssa(ssa_mod)
136 lower(mut mir_mod, .x64)
137
138 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
139 assert call_instr.op == .call
140 assert !call_instr.abi_ret_indirect
141}
142
143fn test_x64_windows_struct_return_classification() {
144 mut ssa_mod := ssa.Module.new('abi_test_x64_windows_ret')
145 i64_t := ssa_mod.type_store.get_int(64)
146 i8_t := ssa_mod.type_store.get_int(8)
147 struct8_t := ssa_mod.type_store.register(ssa.Type{
148 kind: .struct_t
149 fields: [i64_t]
150 })
151 struct9_t := ssa_mod.type_store.register(ssa.Type{
152 kind: .struct_t
153 fields: [i64_t, i8_t]
154 })
155 struct16_t := ssa_mod.type_store.register(ssa.Type{
156 kind: .struct_t
157 fields: [i64_t, i64_t]
158 })
159
160 fn8 := ssa_mod.new_function('ret8', struct8_t, [])
161 fn9 := ssa_mod.new_function('ret9', struct9_t, [])
162 fn16 := ssa_mod.new_function('ret16', struct16_t, [])
163
164 mut mir_mod := mir.lower_from_ssa(ssa_mod)
165 lower_with_x64_abi(mut mir_mod, .x64, .windows)
166
167 assert !mir_mod.funcs[fn8].abi_ret_indirect
168 assert mir_mod.funcs[fn9].abi_ret_indirect
169 assert mir_mod.funcs[fn16].abi_ret_indirect
170}
171
172fn test_x64_windows_struct_param_classification() {
173 mut ssa_mod := ssa.Module.new('abi_test_x64_windows_param')
174 i64_t := ssa_mod.type_store.get_int(64)
175 i8_t := ssa_mod.type_store.get_int(8)
176 struct8_t := ssa_mod.type_store.register(ssa.Type{
177 kind: .struct_t
178 fields: [i64_t]
179 })
180 struct9_t := ssa_mod.type_store.register(ssa.Type{
181 kind: .struct_t
182 fields: [i64_t, i8_t]
183 })
184 struct16_t := ssa_mod.type_store.register(ssa.Type{
185 kind: .struct_t
186 fields: [i64_t, i64_t]
187 })
188
189 fn_id := ssa_mod.new_function('callee', i64_t, [])
190 param8 := ssa_mod.add_value_node(.argument, struct8_t, 's8', 0)
191 param9 := ssa_mod.add_value_node(.argument, struct9_t, 's9', 0)
192 param16 := ssa_mod.add_value_node(.argument, struct16_t, 's16', 0)
193 ssa_mod.funcs[fn_id].params << [param8, param9, param16]
194
195 mut mir_mod := mir.lower_from_ssa(ssa_mod)
196 lower_with_x64_abi(mut mir_mod, .x64, .windows)
197
198 assert mir_mod.funcs[fn_id].abi_param_class == [.in_reg, .indirect, .indirect]
199}
200
201fn test_x64_windows_callsite_struct_arg_classification() {
202 mut ssa_mod := ssa.Module.new('abi_test_x64_windows_call_arg')
203 i64_t := ssa_mod.type_store.get_int(64)
204 i8_t := ssa_mod.type_store.get_int(8)
205 struct8_t := ssa_mod.type_store.register(ssa.Type{
206 kind: .struct_t
207 fields: [i64_t]
208 })
209 struct9_t := ssa_mod.type_store.register(ssa.Type{
210 kind: .struct_t
211 fields: [i64_t, i8_t]
212 })
213 struct16_t := ssa_mod.type_store.register(ssa.Type{
214 kind: .struct_t
215 fields: [i64_t, i64_t]
216 })
217
218 callee_id := ssa_mod.new_function('callee', i64_t, [])
219 param8 := ssa_mod.add_value_node(.argument, struct8_t, 's8', 0)
220 param9 := ssa_mod.add_value_node(.argument, struct9_t, 's9', 0)
221 param16 := ssa_mod.add_value_node(.argument, struct16_t, 's16', 0)
222 ssa_mod.funcs[callee_id].params << [param8, param9, param16]
223
224 caller_id := ssa_mod.new_function('caller', i64_t, [])
225 arg8 := ssa_mod.add_value_node(.argument, struct8_t, 'arg8', 0)
226 arg9 := ssa_mod.add_value_node(.argument, struct9_t, 'arg9', 0)
227 arg16 := ssa_mod.add_value_node(.argument, struct16_t, 'arg16', 0)
228 ssa_mod.funcs[caller_id].params << [arg8, arg9, arg16]
229 caller_entry := ssa_mod.add_block(caller_id, 'entry')
230 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
231 call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, arg8, arg9, arg16])
232 zero := ssa_mod.get_or_add_const(i64_t, '0')
233 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
234
235 mut mir_mod := mir.lower_from_ssa(ssa_mod)
236 lower_with_x64_abi(mut mir_mod, .x64, .windows)
237
238 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
239 assert call_instr.abi_arg_class == [.in_reg, .indirect, .indirect]
240}
241
242fn test_arm64_external_large_struct_call_is_lowered_to_call_sret() {
243 mut ssa_mod := ssa.Module.new('abi_test_external')
244 i64_t := ssa_mod.type_store.get_int(64)
245 i8_t := ssa_mod.type_store.get_int(8)
246 ptr_t := ssa_mod.type_store.get_ptr(i8_t)
247 large_struct_t := ssa_mod.type_store.register(ssa.Type{
248 kind: .struct_t
249 fields: [ptr_t, i64_t, i64_t]
250 })
251
252 caller_id := ssa_mod.new_function('caller', i64_t, [])
253 caller_entry := ssa_mod.add_block(caller_id, 'entry')
254 external_fn := ssa_mod.add_value_node(.unknown, 0, 'external_make', 0)
255 call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [external_fn])
256 zero := ssa_mod.get_or_add_const(i64_t, '0')
257 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
258
259 mut mir_mod := mir.lower_from_ssa(ssa_mod)
260 lower(mut mir_mod, .arm64)
261
262 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
263 assert call_instr.op == .call_sret
264}
265
266fn test_arm64_indirect_call_large_struct_return_is_lowered_to_call_sret() {
267 mut ssa_mod := ssa.Module.new('abi_test_indirect')
268 i64_t := ssa_mod.type_store.get_int(64)
269 i8_t := ssa_mod.type_store.get_int(8)
270 ptr_t := ssa_mod.type_store.get_ptr(i8_t)
271 large_struct_t := ssa_mod.type_store.register(ssa.Type{
272 kind: .struct_t
273 fields: [ptr_t, i64_t, i64_t]
274 })
275 fn_sig_t := ssa_mod.type_store.register(ssa.Type{
276 kind: .func_t
277 params: []ssa.TypeID{}
278 ret_type: large_struct_t
279 })
280 fn_ptr_t := ssa_mod.type_store.get_ptr(fn_sig_t)
281
282 caller_id := ssa_mod.new_function('caller', i64_t, [])
283 fn_ptr := ssa_mod.add_value_node(.argument, fn_ptr_t, 'fn_ptr', 0)
284 ssa_mod.funcs[caller_id].params << fn_ptr
285 caller_entry := ssa_mod.add_block(caller_id, 'entry')
286 call_val := ssa_mod.add_instr(.call_indirect, caller_entry, large_struct_t, [
287 fn_ptr,
288 ])
289 zero := ssa_mod.get_or_add_const(i64_t, '0')
290 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
291
292 mut mir_mod := mir.lower_from_ssa(ssa_mod)
293 lower(mut mir_mod, .arm64)
294
295 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
296 assert call_instr.op == .call_sret
297}
298
299fn test_arm64_callsite_marks_large_struct_arg_indirect() {
300 mut ssa_mod := ssa.Module.new('abi_test_arg_class')
301 i64_t := ssa_mod.type_store.get_int(64)
302 i8_t := ssa_mod.type_store.get_int(8)
303 ptr_t := ssa_mod.type_store.get_ptr(i8_t)
304 large_struct_t := ssa_mod.type_store.register(ssa.Type{
305 kind: .struct_t
306 fields: [ptr_t, i64_t, i64_t]
307 })
308
309 callee_id := ssa_mod.new_function('callee', i64_t, [])
310 callee_param := ssa_mod.add_value_node(.argument, large_struct_t, 'arg', 0)
311 ssa_mod.funcs[callee_id].params << callee_param
312 callee_entry := ssa_mod.add_block(callee_id, 'entry')
313 zero := ssa_mod.get_or_add_const(i64_t, '0')
314 ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
315
316 caller_id := ssa_mod.new_function('caller', i64_t, [])
317 caller_param := ssa_mod.add_value_node(.argument, large_struct_t, 'in', 0)
318 ssa_mod.funcs[caller_id].params << caller_param
319 caller_entry := ssa_mod.add_block(caller_id, 'entry')
320 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
321 call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, caller_param])
322 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
323
324 mut mir_mod := mir.lower_from_ssa(ssa_mod)
325 lower(mut mir_mod, .arm64)
326
327 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
328 assert call_instr.abi_arg_class.len == 1
329 assert call_instr.abi_arg_class[0] == .indirect
330}
331
332fn test_arm64_callsite_marks_large_array_arg_indirect() {
333 mut ssa_mod := ssa.Module.new('abi_test_array_arg_class')
334 i64_t := ssa_mod.type_store.get_int(64)
335 large_array_t := ssa_mod.type_store.register(ssa.Type{
336 kind: .array_t
337 elem_type: i64_t
338 len: 3
339 })
340
341 callee_id := ssa_mod.new_function('callee', i64_t, [])
342 callee_param := ssa_mod.add_value_node(.argument, large_array_t, 'arg', 0)
343 ssa_mod.funcs[callee_id].params << callee_param
344 callee_entry := ssa_mod.add_block(callee_id, 'entry')
345 zero := ssa_mod.get_or_add_const(i64_t, '0')
346 ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
347
348 caller_id := ssa_mod.new_function('caller', i64_t, [])
349 caller_param := ssa_mod.add_value_node(.argument, large_array_t, 'in', 0)
350 ssa_mod.funcs[caller_id].params << caller_param
351 caller_entry := ssa_mod.add_block(caller_id, 'entry')
352 fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
353 call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, caller_param])
354 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
355
356 mut mir_mod := mir.lower_from_ssa(ssa_mod)
357 lower(mut mir_mod, .arm64)
358
359 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
360 assert call_instr.abi_arg_class.len == 1
361 assert call_instr.abi_arg_class[0] == .indirect
362}
363
364fn test_arm64_unresolved_callsite_marks_large_array_alloca_arg_indirect() {
365 mut ssa_mod := ssa.Module.new('abi_test_array_alloca_arg_class')
366 i64_t := ssa_mod.type_store.get_int(64)
367 large_array_t := ssa_mod.type_store.register(ssa.Type{
368 kind: .array_t
369 elem_type: i64_t
370 len: 3
371 })
372 large_array_ptr_t := ssa_mod.type_store.get_ptr(large_array_t)
373
374 caller_id := ssa_mod.new_function('caller', i64_t, [])
375 caller_entry := ssa_mod.add_block(caller_id, 'entry')
376 tmp := ssa_mod.add_instr(.alloca, caller_entry, large_array_ptr_t, []ssa.ValueID{})
377 external_fn := ssa_mod.add_value_node(.unknown, 0, 'external_append_like', 0)
378 call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [external_fn, tmp])
379 zero := ssa_mod.get_or_add_const(i64_t, '0')
380 ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
381
382 mut mir_mod := mir.lower_from_ssa(ssa_mod)
383 lower(mut mir_mod, .arm64)
384
385 call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
386 assert call_instr.abi_arg_class.len == 1
387 assert call_instr.abi_arg_class[0] == .indirect
388}
389