v / vlib / v2 / abi / abi.v
638 lines · 601 sloc · 16.64 KB · a7153322629091f3f2d79f0bf318d0fb2c13c425
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
5module abi
6
7import v2.mir
8import v2.pref
9
10pub enum X64Abi {
11 sysv
12 windows
13}
14
15const sysv_int_arg_reg_count = 6
16const sysv_sse_arg_reg_count = 8
17const sysv_max_direct_aggregate_size = 16
18
19struct SysVLocationState {
20mut:
21 int_regs int
22 sse_regs int
23 stack_slots int
24}
25
26// lower annotates MIR with ABI classification metadata.
27// Current scope is intentionally conservative: it classifies which arguments
28// and return values must be passed indirectly for each function.
29pub fn lower(mut m mir.Module, arch pref.Arch) {
30 lower_with_x64_abi(mut m, arch, .sysv)
31}
32
33pub fn lower_with_x64_abi(mut m mir.Module, arch pref.Arch, x64_abi X64Abi) {
34 is_x64 := arch == .x64
35 mut fn_by_name := map[string]int{}
36 for i := 0; i < m.funcs.len; i++ {
37 mut f := &m.funcs[i]
38 fn_by_name[f.name] = i
39 if is_x64 {
40 f.abi_ret_class = abi_value_class(m, f.typ, arch, x64_abi)
41 f.abi_ret_indirect = abi_class_is_indirect(f.abi_ret_class, m, f.typ, arch, x64_abi)
42 } else {
43 f.abi_ret_indirect = needs_indirect(m, f.typ, arch, x64_abi)
44 }
45 f.abi_param_class = []mir.AbiArgClass{len: f.params.len, init: .in_reg}
46 if is_x64 {
47 f.abi_param_classes = []mir.AbiValueClass{len: f.params.len}
48 f.abi_param_layouts = []mir.AbiValueLayout{len: f.params.len}
49 }
50 mut param_loc_state := SysVLocationState{}
51 if arch == .x64 && x64_abi == .sysv && f.abi_ret_indirect {
52 param_loc_state.int_regs = 1
53 }
54 for pi, param_id in f.params {
55 if param_id < 0 || param_id >= m.values.len {
56 continue
57 }
58 param_typ := m.values[param_id].typ
59 if is_x64 {
60 param_class := abi_value_class(m, param_typ, arch, x64_abi)
61 f.abi_param_classes[pi] = param_class
62 if x64_abi == .sysv {
63 f.abi_param_layouts[pi] = sysv_assign_value_layout(param_class, mut
64 param_loc_state)
65 }
66 if abi_class_is_indirect(param_class, m, param_typ, arch, x64_abi) {
67 f.abi_param_class[pi] = .indirect
68 }
69 } else if needs_indirect(m, param_typ, arch, x64_abi) {
70 f.abi_param_class[pi] = .indirect
71 }
72 }
73 }
74
75 lower_calls(mut m, arch, x64_abi, fn_by_name)
76}
77
78fn needs_indirect(m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) bool {
79 ssa_mod := m.ssa()
80 if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
81 return false
82 }
83 typ := ssa_mod.type_store.types[typ_id]
84 if typ.kind !in [.struct_t, .array_t] {
85 return false
86 }
87 size := m.type_size(typ_id)
88 return match arch {
89 .arm64 {
90 size > 16
91 }
92 .x64 {
93 match x64_abi {
94 .sysv { size > sysv_max_direct_aggregate_size }
95 .windows { size !in [1, 2, 4, 8] }
96 }
97 }
98 else {
99 size > 16
100 }
101 }
102}
103
104fn abi_class_is_indirect(value_class mir.AbiValueClass, m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) bool {
105 if value_class.mode == .indirect {
106 return true
107 }
108 return needs_indirect(m, typ_id, arch, x64_abi)
109}
110
111fn abi_value_class(m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) mir.AbiValueClass {
112 ssa_mod := m.ssa()
113 size := m.type_size(typ_id)
114 if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
115 return mir.AbiValueClass{
116 mode: .direct
117 size: size
118 }
119 }
120 typ := ssa_mod.type_store.types[typ_id]
121 if arch == .x64 && x64_abi == .sysv {
122 if typ.kind !in [.struct_t, .array_t] {
123 return sysv_scalar_abi_value_class(m, typ_id)
124 }
125 return sysv_abi_value_class(m, typ_id)
126 }
127 indirect := needs_indirect(m, typ_id, arch, x64_abi)
128 return mir.AbiValueClass{
129 mode: if indirect { .indirect } else { .direct }
130 size: size
131 }
132}
133
134fn sysv_scalar_abi_value_class(m mir.Module, typ_id int) mir.AbiValueClass {
135 ssa_mod := m.ssa()
136 size := m.type_size(typ_id)
137 if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
138 return mir.AbiValueClass{
139 mode: .direct
140 size: size
141 }
142 }
143 typ := ssa_mod.type_store.types[typ_id]
144 classes := match typ.kind {
145 .int_t, .ptr_t, .func_t {
146 [mir.AbiEightbyteClass.integer]
147 }
148 .float_t {
149 match typ.width {
150 32, 64 { [mir.AbiEightbyteClass.sse] }
151 128 { [mir.AbiEightbyteClass.sse, .sseup] }
152 else { []mir.AbiEightbyteClass{} }
153 }
154 }
155 else {
156 []mir.AbiEightbyteClass{}
157 }
158 }
159
160 return mir.AbiValueClass{
161 mode: .direct
162 size: size
163 classes: classes
164 }
165}
166
167fn sysv_abi_value_class(m mir.Module, typ_id int) mir.AbiValueClass {
168 size := m.type_size(typ_id)
169 if size <= 0 {
170 return mir.AbiValueClass{
171 mode: .direct
172 size: size
173 }
174 }
175 if sysv_aggregate_must_be_memory_before_classification(size) {
176 return sysv_memory_value_class(size)
177 }
178 mut classes := []mir.AbiEightbyteClass{len: (size + 7) / 8, init: .no_class}
179 mut visiting := map[int]bool{}
180 if !sysv_classify_type_into(m, typ_id, 0, mut classes, mut visiting) {
181 return sysv_memory_value_class(size)
182 }
183 classes = sysv_post_merge_classes(size, classes)
184 if classes.len == 1 && classes[0] == .memory {
185 return sysv_memory_value_class(size)
186 }
187 return mir.AbiValueClass{
188 mode: .direct
189 size: size
190 classes: classes
191 }
192}
193
194fn sysv_aggregate_must_be_memory_before_classification(size int) bool {
195 // Current SSA aggregate types do not model a SysV vector aggregate larger
196 // than two eightbytes, so larger struct/array aggregates cannot be passed
197 // in registers and should not be classified element by element.
198 return size > sysv_max_direct_aggregate_size
199}
200
201fn sysv_memory_value_class(size int) mir.AbiValueClass {
202 return mir.AbiValueClass{
203 mode: .indirect
204 size: size
205 classes: [.memory]
206 }
207}
208
209fn sysv_classify_type_into(m mir.Module, typ_id int, byte_offset int, mut classes []mir.AbiEightbyteClass, mut visiting map[int]bool) bool {
210 ssa_mod := m.ssa()
211 if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
212 return true
213 }
214 if visiting[typ_id] {
215 return sysv_merge_class_range(mut classes, byte_offset, 8, .integer)
216 }
217 visiting[typ_id] = true
218 typ := ssa_mod.type_store.types[typ_id]
219 ok := match typ.kind {
220 .int_t, .ptr_t, .func_t {
221 sysv_merge_class_range(mut classes, byte_offset, m.type_size(typ_id), .integer)
222 }
223 .float_t {
224 match typ.width {
225 32, 64 {
226 sysv_merge_class_range(mut classes, byte_offset, m.type_size(typ_id), .sse)
227 }
228 80 {
229 false
230 }
231 128 {
232 sysv_merge_class_range(mut classes, byte_offset, 8, .sse)
233 && sysv_merge_class_range(mut classes, byte_offset + 8, 8, .sseup)
234 }
235 else {
236 false
237 }
238 }
239 }
240 .array_t {
241 sysv_classify_array_into(m, typ.elem_type, typ.len, byte_offset, mut classes, mut
242 visiting)
243 }
244 .struct_t {
245 sysv_classify_struct_into(m, typ.fields, byte_offset, mut classes, mut visiting)
246 }
247 .void_t, .label_t, .metadata_t {
248 true
249 }
250 }
251
252 visiting.delete(typ_id)
253 return ok
254}
255
256fn sysv_classify_array_into(m mir.Module, elem_type int, len int, byte_offset int, mut classes []mir.AbiEightbyteClass, mut visiting map[int]bool) bool {
257 elem_size := m.type_size(elem_type)
258 for i := 0; i < len; i++ {
259 if !sysv_classify_type_into(m, elem_type, byte_offset + i * elem_size, mut classes, mut
260 visiting) {
261 return false
262 }
263 }
264 return true
265}
266
267fn sysv_classify_struct_into(m mir.Module, fields []int, byte_offset int, mut classes []mir.AbiEightbyteClass, mut visiting map[int]bool) bool {
268 mut field_offset := 0
269 for field_typ in fields {
270 align := m.type_align(field_typ)
271 if align > 1 && field_offset % align != 0 {
272 field_offset = (field_offset + align - 1) & ~(align - 1)
273 }
274 if !sysv_classify_type_into(m, field_typ, byte_offset + field_offset, mut classes, mut
275 visiting) {
276 return false
277 }
278 field_offset += m.type_size(field_typ)
279 }
280 return true
281}
282
283fn sysv_merge_class_range(mut classes []mir.AbiEightbyteClass, byte_offset int, size int, class mir.AbiEightbyteClass) bool {
284 if size <= 0 {
285 return true
286 }
287 start := byte_offset / 8
288 end := (byte_offset + size + 7) / 8
289 if start < 0 || end > classes.len {
290 return false
291 }
292 for i := start; i < end; i++ {
293 classes[i] = sysv_merge_eightbyte_class(classes[i], class)
294 if classes[i] == .memory {
295 return false
296 }
297 }
298 return true
299}
300
301fn sysv_merge_eightbyte_class(a mir.AbiEightbyteClass, b mir.AbiEightbyteClass) mir.AbiEightbyteClass {
302 if a == b {
303 return a
304 }
305 if a == .no_class {
306 return b
307 }
308 if b == .no_class {
309 return a
310 }
311 if a == .memory || b == .memory {
312 return .memory
313 }
314 if a == .integer || b == .integer {
315 return .integer
316 }
317 return .sse
318}
319
320fn sysv_post_merge_classes(size int, input []mir.AbiEightbyteClass) []mir.AbiEightbyteClass {
321 mut classes := input.clone()
322 for class in classes {
323 if class == .memory {
324 return [.memory]
325 }
326 }
327 if size > sysv_max_direct_aggregate_size {
328 mut vector_like := classes.len > 0 && classes[0] == .sse
329 for i := 1; i < classes.len; i++ {
330 if classes[i] != .sseup {
331 vector_like = false
332 break
333 }
334 }
335 if !vector_like {
336 return [.memory]
337 }
338 }
339 for i, class in classes {
340 if class == .sseup && (i == 0 || classes[i - 1] !in [.sse, .sseup]) {
341 classes[i] = .sse
342 }
343 }
344 return classes
345}
346
347fn sysv_assign_value_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
348 if value_class.classes.len == 0 {
349 return mir.AbiValueLayout{
350 value_class: value_class
351 }
352 }
353 if value_class.mode == .indirect || value_class.classes == [mir.AbiEightbyteClass.memory] {
354 return sysv_indirect_pointer_layout(value_class, mut state)
355 }
356
357 mut needed_int := 0
358 mut needed_sse := 0
359 for class in value_class.classes {
360 match class {
361 .integer { needed_int++ }
362 .sse { needed_sse++ }
363 else {}
364 }
365 }
366 if state.int_regs + needed_int > sysv_int_arg_reg_count
367 || state.sse_regs + needed_sse > sysv_sse_arg_reg_count {
368 return sysv_stack_value_layout(value_class, mut state)
369 }
370
371 mut locs := []mir.AbiLocation{cap: value_class.classes.len}
372 mut last_sse := -1
373 for i, class in value_class.classes {
374 offset := i * 8
375 match class {
376 .integer {
377 locs << mir.AbiLocation{
378 kind: .int_reg
379 index: state.int_regs
380 offset: offset
381 class: class
382 }
383 state.int_regs++
384 }
385 .sse {
386 last_sse = state.sse_regs
387 locs << mir.AbiLocation{
388 kind: .sse_reg
389 index: state.sse_regs
390 offset: offset
391 class: class
392 }
393 state.sse_regs++
394 }
395 .sseup {
396 locs << mir.AbiLocation{
397 kind: .sse_reg
398 index: if last_sse >= 0 { last_sse } else { state.sse_regs }
399 offset: offset
400 class: class
401 }
402 }
403 else {
404 locs << mir.AbiLocation{
405 kind: .none
406 index: -1
407 offset: offset
408 class: class
409 }
410 }
411 }
412 }
413 return mir.AbiValueLayout{
414 value_class: value_class
415 locs: locs
416 }
417}
418
419fn sysv_indirect_pointer_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
420 mut locs := []mir.AbiLocation{cap: 1}
421 if state.int_regs < sysv_int_arg_reg_count {
422 locs << mir.AbiLocation{
423 kind: .int_reg
424 index: state.int_regs
425 offset: 0
426 class: .integer
427 }
428 state.int_regs++
429 } else {
430 locs << mir.AbiLocation{
431 kind: .stack
432 index: state.stack_slots
433 offset: 0
434 class: .integer
435 }
436 state.stack_slots++
437 }
438 return mir.AbiValueLayout{
439 value_class: value_class
440 locs: locs
441 }
442}
443
444fn sysv_stack_value_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
445 slots := if value_class.size > 0 { (value_class.size + 7) / 8 } else { value_class.classes.len }
446 mut locs := []mir.AbiLocation{cap: slots}
447 for i := 0; i < slots; i++ {
448 class := if i < value_class.classes.len {
449 value_class.classes[i]
450 } else {
451 mir.AbiEightbyteClass.memory
452 }
453 locs << mir.AbiLocation{
454 kind: .stack
455 index: state.stack_slots
456 offset: i * 8
457 class: class
458 }
459 state.stack_slots++
460 }
461 return mir.AbiValueLayout{
462 value_class: value_class
463 locs: locs
464 }
465}
466
467fn fallback_arg_type(m mir.Module, arg_id int) int {
468 if arg_id < 0 || arg_id >= m.values.len {
469 return 0
470 }
471 if logical_typ := logical_arg_type_from_value(m, arg_id, 0) {
472 return logical_typ
473 }
474 return m.values[arg_id].typ
475}
476
477fn logical_arg_type_from_value(m mir.Module, val_id int, depth int) ?int {
478 if depth > 8 || val_id < 0 || val_id >= m.values.len {
479 return none
480 }
481 val := m.values[val_id]
482 if val.kind != .instruction {
483 return none
484 }
485 instr := m.instrs[val.index]
486 match instr.op {
487 .alloca {
488 if val.typ <= 0 || val.typ >= m.type_store.types.len {
489 return none
490 }
491 typ := m.type_store.types[val.typ]
492 if typ.kind != .ptr_t || typ.elem_type <= 0 || typ.elem_type >= m.type_store.types.len {
493 return none
494 }
495 elem_typ := m.type_store.types[typ.elem_type]
496 if elem_typ.kind in [.struct_t, .array_t] {
497 return typ.elem_type
498 }
499 }
500 .bitcast {
501 if instr.operands.len > 0 {
502 return logical_arg_type_from_value(m, instr.operands[0], depth + 1)
503 }
504 }
505 .assign {
506 if instr.operands.len > 1 {
507 return logical_arg_type_from_value(m, instr.operands[1], depth + 1)
508 }
509 }
510 else {}
511 }
512
513 return none
514}
515
516fn lower_calls(mut m mir.Module, arch pref.Arch, x64_abi X64Abi, fn_by_name map[string]int) {
517 if arch !in [.arm64, .x64] {
518 return
519 }
520 is_x64 := arch == .x64
521
522 for i := 0; i < m.instrs.len; i++ {
523 mut instr := &m.instrs[i]
524 if instr.op !in [.call, .call_indirect, .call_sret] || instr.operands.len == 0 {
525 continue
526 }
527 ret_typ, sig_param_types := call_signature(m, instr, fn_by_name)
528 ret_class := if is_x64 {
529 abi_value_class(m, ret_typ, arch, x64_abi)
530 } else {
531 mir.AbiValueClass{}
532 }
533 ret_indirect := if is_x64 {
534 abi_class_is_indirect(ret_class, m, ret_typ, arch, x64_abi)
535 } else {
536 needs_indirect(m, ret_typ, arch, x64_abi)
537 }
538 num_args := instr.operands.len - 1
539 instr.abi_arg_class = []mir.AbiArgClass{len: num_args, init: .in_reg}
540 if is_x64 {
541 instr.abi_arg_classes = []mir.AbiValueClass{len: num_args}
542 instr.abi_arg_layouts = []mir.AbiValueLayout{len: num_args}
543 }
544 mut arg_loc_state := SysVLocationState{}
545 if arch == .x64 && x64_abi == .sysv && ret_indirect {
546 arg_loc_state.int_regs = 1
547 }
548 for arg_idx := 0; arg_idx < num_args; arg_idx++ {
549 mut arg_typ := 0
550 if arg_idx < sig_param_types.len && sig_param_types[arg_idx] > 0 {
551 arg_typ = sig_param_types[arg_idx]
552 } else {
553 arg_id := instr.operands[arg_idx + 1]
554 arg_typ = fallback_arg_type(m, arg_id)
555 }
556 if is_x64 {
557 arg_class := abi_value_class(m, arg_typ, arch, x64_abi)
558 instr.abi_arg_classes[arg_idx] = arg_class
559 if x64_abi == .sysv {
560 instr.abi_arg_layouts[arg_idx] = sysv_assign_value_layout(arg_class, mut
561 arg_loc_state)
562 }
563 if abi_class_is_indirect(arg_class, m, arg_typ, arch, x64_abi) {
564 instr.abi_arg_class[arg_idx] = .indirect
565 }
566 } else if needs_indirect(m, arg_typ, arch, x64_abi) {
567 instr.abi_arg_class[arg_idx] = .indirect
568 }
569 }
570
571 instr.abi_ret_class = ret_class
572 instr.abi_ret_indirect = ret_indirect
573 // Lower ABI-indirect returns to call_sret for backend consumption.
574 if instr.abi_ret_indirect {
575 instr.op = .call_sret
576 }
577 }
578}
579
580fn call_signature(m mir.Module, instr &mir.Instruction, fn_by_name map[string]int) (int, []int) {
581 mut ret_typ := instr.typ
582 mut param_types := []int{}
583 if instr.operands.len == 0 {
584 return ret_typ, param_types
585 }
586
587 if instr.op in [.call, .call_sret] {
588 callee_id := instr.operands[0]
589 if callee_id >= 0 && callee_id < m.values.len {
590 callee_name := m.values[callee_id].name
591 if callee_name != '' && callee_name in fn_by_name {
592 fn_idx := fn_by_name[callee_name]
593 if fn_idx >= 0 && fn_idx < m.funcs.len {
594 callee := m.funcs[fn_idx]
595 ret_typ = callee.typ
596 param_types = []int{len: callee.params.len}
597 for i, pid in callee.params {
598 if pid >= 0 && pid < m.values.len {
599 param_types[i] = m.values[pid].typ
600 }
601 }
602 }
603 }
604 }
605 } else if instr.op == .call_indirect {
606 callee_id := instr.operands[0]
607 if callee_id >= 0 && callee_id < m.values.len {
608 fn_ptr_typ_id := m.values[callee_id].typ
609 if fn_ptr_typ_id > 0 && fn_ptr_typ_id < m.type_store.types.len {
610 fn_ptr_typ := m.type_store.types[fn_ptr_typ_id]
611 if fn_ptr_typ.kind == .ptr_t && fn_ptr_typ.elem_type > 0
612 && fn_ptr_typ.elem_type < m.type_store.types.len {
613 fn_typ := m.type_store.types[fn_ptr_typ.elem_type]
614 if fn_typ.kind == .func_t {
615 ret_typ = fn_typ.ret_type
616 param_types = fn_typ.params.clone()
617 }
618 } else if fn_ptr_typ.kind == .func_t {
619 // Function pointer extracted from struct field via extractvalue
620 // has func_t type directly (not wrapped in ptr_t).
621 ret_typ = fn_ptr_typ.ret_type
622 param_types = fn_ptr_typ.params.clone()
623 }
624 }
625 }
626 }
627
628 // Fallback to call operand types when we could not resolve a signature.
629 if param_types.len == 0 {
630 num_args := if instr.operands.len > 0 { instr.operands.len - 1 } else { 0 }
631 param_types = []int{len: num_args}
632 for i := 0; i < num_args; i++ {
633 arg_id := instr.operands[i + 1]
634 param_types[i] = fallback_arg_type(m, arg_id)
635 }
636 }
637 return ret_typ, param_types
638}
639