Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,638 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module abi
|
||||
|
||||
import v2.mir
|
||||
import v2.pref
|
||||
|
||||
pub enum X64Abi {
|
||||
sysv
|
||||
windows
|
||||
}
|
||||
|
||||
const sysv_int_arg_reg_count = 6
|
||||
const sysv_sse_arg_reg_count = 8
|
||||
const sysv_max_direct_aggregate_size = 16
|
||||
|
||||
struct SysVLocationState {
|
||||
mut:
|
||||
int_regs int
|
||||
sse_regs int
|
||||
stack_slots int
|
||||
}
|
||||
|
||||
// lower annotates MIR with ABI classification metadata.
|
||||
// Current scope is intentionally conservative: it classifies which arguments
|
||||
// and return values must be passed indirectly for each function.
|
||||
pub fn lower(mut m mir.Module, arch pref.Arch) {
|
||||
lower_with_x64_abi(mut m, arch, .sysv)
|
||||
}
|
||||
|
||||
pub fn lower_with_x64_abi(mut m mir.Module, arch pref.Arch, x64_abi X64Abi) {
|
||||
is_x64 := arch == .x64
|
||||
mut fn_by_name := map[string]int{}
|
||||
for i := 0; i < m.funcs.len; i++ {
|
||||
mut f := &m.funcs[i]
|
||||
fn_by_name[f.name] = i
|
||||
if is_x64 {
|
||||
f.abi_ret_class = abi_value_class(m, f.typ, arch, x64_abi)
|
||||
f.abi_ret_indirect = abi_class_is_indirect(f.abi_ret_class, m, f.typ, arch, x64_abi)
|
||||
} else {
|
||||
f.abi_ret_indirect = needs_indirect(m, f.typ, arch, x64_abi)
|
||||
}
|
||||
f.abi_param_class = []mir.AbiArgClass{len: f.params.len, init: .in_reg}
|
||||
if is_x64 {
|
||||
f.abi_param_classes = []mir.AbiValueClass{len: f.params.len}
|
||||
f.abi_param_layouts = []mir.AbiValueLayout{len: f.params.len}
|
||||
}
|
||||
mut param_loc_state := SysVLocationState{}
|
||||
if arch == .x64 && x64_abi == .sysv && f.abi_ret_indirect {
|
||||
param_loc_state.int_regs = 1
|
||||
}
|
||||
for pi, param_id in f.params {
|
||||
if param_id < 0 || param_id >= m.values.len {
|
||||
continue
|
||||
}
|
||||
param_typ := m.values[param_id].typ
|
||||
if is_x64 {
|
||||
param_class := abi_value_class(m, param_typ, arch, x64_abi)
|
||||
f.abi_param_classes[pi] = param_class
|
||||
if x64_abi == .sysv {
|
||||
f.abi_param_layouts[pi] = sysv_assign_value_layout(param_class, mut
|
||||
param_loc_state)
|
||||
}
|
||||
if abi_class_is_indirect(param_class, m, param_typ, arch, x64_abi) {
|
||||
f.abi_param_class[pi] = .indirect
|
||||
}
|
||||
} else if needs_indirect(m, param_typ, arch, x64_abi) {
|
||||
f.abi_param_class[pi] = .indirect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lower_calls(mut m, arch, x64_abi, fn_by_name)
|
||||
}
|
||||
|
||||
fn needs_indirect(m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) bool {
|
||||
ssa_mod := m.ssa()
|
||||
if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
|
||||
return false
|
||||
}
|
||||
typ := ssa_mod.type_store.types[typ_id]
|
||||
if typ.kind !in [.struct_t, .array_t] {
|
||||
return false
|
||||
}
|
||||
size := m.type_size(typ_id)
|
||||
return match arch {
|
||||
.arm64 {
|
||||
size > 16
|
||||
}
|
||||
.x64 {
|
||||
match x64_abi {
|
||||
.sysv { size > sysv_max_direct_aggregate_size }
|
||||
.windows { size !in [1, 2, 4, 8] }
|
||||
}
|
||||
}
|
||||
else {
|
||||
size > 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn abi_class_is_indirect(value_class mir.AbiValueClass, m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) bool {
|
||||
if value_class.mode == .indirect {
|
||||
return true
|
||||
}
|
||||
return needs_indirect(m, typ_id, arch, x64_abi)
|
||||
}
|
||||
|
||||
fn abi_value_class(m mir.Module, typ_id int, arch pref.Arch, x64_abi X64Abi) mir.AbiValueClass {
|
||||
ssa_mod := m.ssa()
|
||||
size := m.type_size(typ_id)
|
||||
if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
|
||||
return mir.AbiValueClass{
|
||||
mode: .direct
|
||||
size: size
|
||||
}
|
||||
}
|
||||
typ := ssa_mod.type_store.types[typ_id]
|
||||
if arch == .x64 && x64_abi == .sysv {
|
||||
if typ.kind !in [.struct_t, .array_t] {
|
||||
return sysv_scalar_abi_value_class(m, typ_id)
|
||||
}
|
||||
return sysv_abi_value_class(m, typ_id)
|
||||
}
|
||||
indirect := needs_indirect(m, typ_id, arch, x64_abi)
|
||||
return mir.AbiValueClass{
|
||||
mode: if indirect { .indirect } else { .direct }
|
||||
size: size
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_scalar_abi_value_class(m mir.Module, typ_id int) mir.AbiValueClass {
|
||||
ssa_mod := m.ssa()
|
||||
size := m.type_size(typ_id)
|
||||
if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
|
||||
return mir.AbiValueClass{
|
||||
mode: .direct
|
||||
size: size
|
||||
}
|
||||
}
|
||||
typ := ssa_mod.type_store.types[typ_id]
|
||||
classes := match typ.kind {
|
||||
.int_t, .ptr_t, .func_t {
|
||||
[mir.AbiEightbyteClass.integer]
|
||||
}
|
||||
.float_t {
|
||||
match typ.width {
|
||||
32, 64 { [mir.AbiEightbyteClass.sse] }
|
||||
128 { [mir.AbiEightbyteClass.sse, .sseup] }
|
||||
else { []mir.AbiEightbyteClass{} }
|
||||
}
|
||||
}
|
||||
else {
|
||||
[]mir.AbiEightbyteClass{}
|
||||
}
|
||||
}
|
||||
|
||||
return mir.AbiValueClass{
|
||||
mode: .direct
|
||||
size: size
|
||||
classes: classes
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_abi_value_class(m mir.Module, typ_id int) mir.AbiValueClass {
|
||||
size := m.type_size(typ_id)
|
||||
if size <= 0 {
|
||||
return mir.AbiValueClass{
|
||||
mode: .direct
|
||||
size: size
|
||||
}
|
||||
}
|
||||
if sysv_aggregate_must_be_memory_before_classification(size) {
|
||||
return sysv_memory_value_class(size)
|
||||
}
|
||||
mut classes := []mir.AbiEightbyteClass{len: (size + 7) / 8, init: .no_class}
|
||||
mut visiting := map[int]bool{}
|
||||
if !sysv_classify_type_into(m, typ_id, 0, mut classes, mut visiting) {
|
||||
return sysv_memory_value_class(size)
|
||||
}
|
||||
classes = sysv_post_merge_classes(size, classes)
|
||||
if classes.len == 1 && classes[0] == .memory {
|
||||
return sysv_memory_value_class(size)
|
||||
}
|
||||
return mir.AbiValueClass{
|
||||
mode: .direct
|
||||
size: size
|
||||
classes: classes
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_aggregate_must_be_memory_before_classification(size int) bool {
|
||||
// Current SSA aggregate types do not model a SysV vector aggregate larger
|
||||
// than two eightbytes, so larger struct/array aggregates cannot be passed
|
||||
// in registers and should not be classified element by element.
|
||||
return size > sysv_max_direct_aggregate_size
|
||||
}
|
||||
|
||||
fn sysv_memory_value_class(size int) mir.AbiValueClass {
|
||||
return mir.AbiValueClass{
|
||||
mode: .indirect
|
||||
size: size
|
||||
classes: [.memory]
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_classify_type_into(m mir.Module, typ_id int, byte_offset int, mut classes []mir.AbiEightbyteClass, mut visiting map[int]bool) bool {
|
||||
ssa_mod := m.ssa()
|
||||
if ssa_mod == unsafe { nil } || typ_id <= 0 || typ_id >= ssa_mod.type_store.types.len {
|
||||
return true
|
||||
}
|
||||
if visiting[typ_id] {
|
||||
return sysv_merge_class_range(mut classes, byte_offset, 8, .integer)
|
||||
}
|
||||
visiting[typ_id] = true
|
||||
typ := ssa_mod.type_store.types[typ_id]
|
||||
ok := match typ.kind {
|
||||
.int_t, .ptr_t, .func_t {
|
||||
sysv_merge_class_range(mut classes, byte_offset, m.type_size(typ_id), .integer)
|
||||
}
|
||||
.float_t {
|
||||
match typ.width {
|
||||
32, 64 {
|
||||
sysv_merge_class_range(mut classes, byte_offset, m.type_size(typ_id), .sse)
|
||||
}
|
||||
80 {
|
||||
false
|
||||
}
|
||||
128 {
|
||||
sysv_merge_class_range(mut classes, byte_offset, 8, .sse)
|
||||
&& sysv_merge_class_range(mut classes, byte_offset + 8, 8, .sseup)
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
.array_t {
|
||||
sysv_classify_array_into(m, typ.elem_type, typ.len, byte_offset, mut classes, mut
|
||||
visiting)
|
||||
}
|
||||
.struct_t {
|
||||
sysv_classify_struct_into(m, typ.fields, byte_offset, mut classes, mut visiting)
|
||||
}
|
||||
.void_t, .label_t, .metadata_t {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
visiting.delete(typ_id)
|
||||
return ok
|
||||
}
|
||||
|
||||
fn 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 {
|
||||
elem_size := m.type_size(elem_type)
|
||||
for i := 0; i < len; i++ {
|
||||
if !sysv_classify_type_into(m, elem_type, byte_offset + i * elem_size, mut classes, mut
|
||||
visiting) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn sysv_classify_struct_into(m mir.Module, fields []int, byte_offset int, mut classes []mir.AbiEightbyteClass, mut visiting map[int]bool) bool {
|
||||
mut field_offset := 0
|
||||
for field_typ in fields {
|
||||
align := m.type_align(field_typ)
|
||||
if align > 1 && field_offset % align != 0 {
|
||||
field_offset = (field_offset + align - 1) & ~(align - 1)
|
||||
}
|
||||
if !sysv_classify_type_into(m, field_typ, byte_offset + field_offset, mut classes, mut
|
||||
visiting) {
|
||||
return false
|
||||
}
|
||||
field_offset += m.type_size(field_typ)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn sysv_merge_class_range(mut classes []mir.AbiEightbyteClass, byte_offset int, size int, class mir.AbiEightbyteClass) bool {
|
||||
if size <= 0 {
|
||||
return true
|
||||
}
|
||||
start := byte_offset / 8
|
||||
end := (byte_offset + size + 7) / 8
|
||||
if start < 0 || end > classes.len {
|
||||
return false
|
||||
}
|
||||
for i := start; i < end; i++ {
|
||||
classes[i] = sysv_merge_eightbyte_class(classes[i], class)
|
||||
if classes[i] == .memory {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn sysv_merge_eightbyte_class(a mir.AbiEightbyteClass, b mir.AbiEightbyteClass) mir.AbiEightbyteClass {
|
||||
if a == b {
|
||||
return a
|
||||
}
|
||||
if a == .no_class {
|
||||
return b
|
||||
}
|
||||
if b == .no_class {
|
||||
return a
|
||||
}
|
||||
if a == .memory || b == .memory {
|
||||
return .memory
|
||||
}
|
||||
if a == .integer || b == .integer {
|
||||
return .integer
|
||||
}
|
||||
return .sse
|
||||
}
|
||||
|
||||
fn sysv_post_merge_classes(size int, input []mir.AbiEightbyteClass) []mir.AbiEightbyteClass {
|
||||
mut classes := input.clone()
|
||||
for class in classes {
|
||||
if class == .memory {
|
||||
return [.memory]
|
||||
}
|
||||
}
|
||||
if size > sysv_max_direct_aggregate_size {
|
||||
mut vector_like := classes.len > 0 && classes[0] == .sse
|
||||
for i := 1; i < classes.len; i++ {
|
||||
if classes[i] != .sseup {
|
||||
vector_like = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !vector_like {
|
||||
return [.memory]
|
||||
}
|
||||
}
|
||||
for i, class in classes {
|
||||
if class == .sseup && (i == 0 || classes[i - 1] !in [.sse, .sseup]) {
|
||||
classes[i] = .sse
|
||||
}
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
fn sysv_assign_value_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
|
||||
if value_class.classes.len == 0 {
|
||||
return mir.AbiValueLayout{
|
||||
value_class: value_class
|
||||
}
|
||||
}
|
||||
if value_class.mode == .indirect || value_class.classes == [mir.AbiEightbyteClass.memory] {
|
||||
return sysv_indirect_pointer_layout(value_class, mut state)
|
||||
}
|
||||
|
||||
mut needed_int := 0
|
||||
mut needed_sse := 0
|
||||
for class in value_class.classes {
|
||||
match class {
|
||||
.integer { needed_int++ }
|
||||
.sse { needed_sse++ }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
if state.int_regs + needed_int > sysv_int_arg_reg_count
|
||||
|| state.sse_regs + needed_sse > sysv_sse_arg_reg_count {
|
||||
return sysv_stack_value_layout(value_class, mut state)
|
||||
}
|
||||
|
||||
mut locs := []mir.AbiLocation{cap: value_class.classes.len}
|
||||
mut last_sse := -1
|
||||
for i, class in value_class.classes {
|
||||
offset := i * 8
|
||||
match class {
|
||||
.integer {
|
||||
locs << mir.AbiLocation{
|
||||
kind: .int_reg
|
||||
index: state.int_regs
|
||||
offset: offset
|
||||
class: class
|
||||
}
|
||||
state.int_regs++
|
||||
}
|
||||
.sse {
|
||||
last_sse = state.sse_regs
|
||||
locs << mir.AbiLocation{
|
||||
kind: .sse_reg
|
||||
index: state.sse_regs
|
||||
offset: offset
|
||||
class: class
|
||||
}
|
||||
state.sse_regs++
|
||||
}
|
||||
.sseup {
|
||||
locs << mir.AbiLocation{
|
||||
kind: .sse_reg
|
||||
index: if last_sse >= 0 { last_sse } else { state.sse_regs }
|
||||
offset: offset
|
||||
class: class
|
||||
}
|
||||
}
|
||||
else {
|
||||
locs << mir.AbiLocation{
|
||||
kind: .none
|
||||
index: -1
|
||||
offset: offset
|
||||
class: class
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mir.AbiValueLayout{
|
||||
value_class: value_class
|
||||
locs: locs
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_indirect_pointer_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
|
||||
mut locs := []mir.AbiLocation{cap: 1}
|
||||
if state.int_regs < sysv_int_arg_reg_count {
|
||||
locs << mir.AbiLocation{
|
||||
kind: .int_reg
|
||||
index: state.int_regs
|
||||
offset: 0
|
||||
class: .integer
|
||||
}
|
||||
state.int_regs++
|
||||
} else {
|
||||
locs << mir.AbiLocation{
|
||||
kind: .stack
|
||||
index: state.stack_slots
|
||||
offset: 0
|
||||
class: .integer
|
||||
}
|
||||
state.stack_slots++
|
||||
}
|
||||
return mir.AbiValueLayout{
|
||||
value_class: value_class
|
||||
locs: locs
|
||||
}
|
||||
}
|
||||
|
||||
fn sysv_stack_value_layout(value_class mir.AbiValueClass, mut state SysVLocationState) mir.AbiValueLayout {
|
||||
slots := if value_class.size > 0 { (value_class.size + 7) / 8 } else { value_class.classes.len }
|
||||
mut locs := []mir.AbiLocation{cap: slots}
|
||||
for i := 0; i < slots; i++ {
|
||||
class := if i < value_class.classes.len {
|
||||
value_class.classes[i]
|
||||
} else {
|
||||
mir.AbiEightbyteClass.memory
|
||||
}
|
||||
locs << mir.AbiLocation{
|
||||
kind: .stack
|
||||
index: state.stack_slots
|
||||
offset: i * 8
|
||||
class: class
|
||||
}
|
||||
state.stack_slots++
|
||||
}
|
||||
return mir.AbiValueLayout{
|
||||
value_class: value_class
|
||||
locs: locs
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_arg_type(m mir.Module, arg_id int) int {
|
||||
if arg_id < 0 || arg_id >= m.values.len {
|
||||
return 0
|
||||
}
|
||||
if logical_typ := logical_arg_type_from_value(m, arg_id, 0) {
|
||||
return logical_typ
|
||||
}
|
||||
return m.values[arg_id].typ
|
||||
}
|
||||
|
||||
fn logical_arg_type_from_value(m mir.Module, val_id int, depth int) ?int {
|
||||
if depth > 8 || val_id < 0 || val_id >= m.values.len {
|
||||
return none
|
||||
}
|
||||
val := m.values[val_id]
|
||||
if val.kind != .instruction {
|
||||
return none
|
||||
}
|
||||
instr := m.instrs[val.index]
|
||||
match instr.op {
|
||||
.alloca {
|
||||
if val.typ <= 0 || val.typ >= m.type_store.types.len {
|
||||
return none
|
||||
}
|
||||
typ := m.type_store.types[val.typ]
|
||||
if typ.kind != .ptr_t || typ.elem_type <= 0 || typ.elem_type >= m.type_store.types.len {
|
||||
return none
|
||||
}
|
||||
elem_typ := m.type_store.types[typ.elem_type]
|
||||
if elem_typ.kind in [.struct_t, .array_t] {
|
||||
return typ.elem_type
|
||||
}
|
||||
}
|
||||
.bitcast {
|
||||
if instr.operands.len > 0 {
|
||||
return logical_arg_type_from_value(m, instr.operands[0], depth + 1)
|
||||
}
|
||||
}
|
||||
.assign {
|
||||
if instr.operands.len > 1 {
|
||||
return logical_arg_type_from_value(m, instr.operands[1], depth + 1)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn lower_calls(mut m mir.Module, arch pref.Arch, x64_abi X64Abi, fn_by_name map[string]int) {
|
||||
if arch !in [.arm64, .x64] {
|
||||
return
|
||||
}
|
||||
is_x64 := arch == .x64
|
||||
|
||||
for i := 0; i < m.instrs.len; i++ {
|
||||
mut instr := &m.instrs[i]
|
||||
if instr.op !in [.call, .call_indirect, .call_sret] || instr.operands.len == 0 {
|
||||
continue
|
||||
}
|
||||
ret_typ, sig_param_types := call_signature(m, instr, fn_by_name)
|
||||
ret_class := if is_x64 {
|
||||
abi_value_class(m, ret_typ, arch, x64_abi)
|
||||
} else {
|
||||
mir.AbiValueClass{}
|
||||
}
|
||||
ret_indirect := if is_x64 {
|
||||
abi_class_is_indirect(ret_class, m, ret_typ, arch, x64_abi)
|
||||
} else {
|
||||
needs_indirect(m, ret_typ, arch, x64_abi)
|
||||
}
|
||||
num_args := instr.operands.len - 1
|
||||
instr.abi_arg_class = []mir.AbiArgClass{len: num_args, init: .in_reg}
|
||||
if is_x64 {
|
||||
instr.abi_arg_classes = []mir.AbiValueClass{len: num_args}
|
||||
instr.abi_arg_layouts = []mir.AbiValueLayout{len: num_args}
|
||||
}
|
||||
mut arg_loc_state := SysVLocationState{}
|
||||
if arch == .x64 && x64_abi == .sysv && ret_indirect {
|
||||
arg_loc_state.int_regs = 1
|
||||
}
|
||||
for arg_idx := 0; arg_idx < num_args; arg_idx++ {
|
||||
mut arg_typ := 0
|
||||
if arg_idx < sig_param_types.len && sig_param_types[arg_idx] > 0 {
|
||||
arg_typ = sig_param_types[arg_idx]
|
||||
} else {
|
||||
arg_id := instr.operands[arg_idx + 1]
|
||||
arg_typ = fallback_arg_type(m, arg_id)
|
||||
}
|
||||
if is_x64 {
|
||||
arg_class := abi_value_class(m, arg_typ, arch, x64_abi)
|
||||
instr.abi_arg_classes[arg_idx] = arg_class
|
||||
if x64_abi == .sysv {
|
||||
instr.abi_arg_layouts[arg_idx] = sysv_assign_value_layout(arg_class, mut
|
||||
arg_loc_state)
|
||||
}
|
||||
if abi_class_is_indirect(arg_class, m, arg_typ, arch, x64_abi) {
|
||||
instr.abi_arg_class[arg_idx] = .indirect
|
||||
}
|
||||
} else if needs_indirect(m, arg_typ, arch, x64_abi) {
|
||||
instr.abi_arg_class[arg_idx] = .indirect
|
||||
}
|
||||
}
|
||||
|
||||
instr.abi_ret_class = ret_class
|
||||
instr.abi_ret_indirect = ret_indirect
|
||||
// Lower ABI-indirect returns to call_sret for backend consumption.
|
||||
if instr.abi_ret_indirect {
|
||||
instr.op = .call_sret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_signature(m mir.Module, instr &mir.Instruction, fn_by_name map[string]int) (int, []int) {
|
||||
mut ret_typ := instr.typ
|
||||
mut param_types := []int{}
|
||||
if instr.operands.len == 0 {
|
||||
return ret_typ, param_types
|
||||
}
|
||||
|
||||
if instr.op in [.call, .call_sret] {
|
||||
callee_id := instr.operands[0]
|
||||
if callee_id >= 0 && callee_id < m.values.len {
|
||||
callee_name := m.values[callee_id].name
|
||||
if callee_name != '' && callee_name in fn_by_name {
|
||||
fn_idx := fn_by_name[callee_name]
|
||||
if fn_idx >= 0 && fn_idx < m.funcs.len {
|
||||
callee := m.funcs[fn_idx]
|
||||
ret_typ = callee.typ
|
||||
param_types = []int{len: callee.params.len}
|
||||
for i, pid in callee.params {
|
||||
if pid >= 0 && pid < m.values.len {
|
||||
param_types[i] = m.values[pid].typ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if instr.op == .call_indirect {
|
||||
callee_id := instr.operands[0]
|
||||
if callee_id >= 0 && callee_id < m.values.len {
|
||||
fn_ptr_typ_id := m.values[callee_id].typ
|
||||
if fn_ptr_typ_id > 0 && fn_ptr_typ_id < m.type_store.types.len {
|
||||
fn_ptr_typ := m.type_store.types[fn_ptr_typ_id]
|
||||
if fn_ptr_typ.kind == .ptr_t && fn_ptr_typ.elem_type > 0
|
||||
&& fn_ptr_typ.elem_type < m.type_store.types.len {
|
||||
fn_typ := m.type_store.types[fn_ptr_typ.elem_type]
|
||||
if fn_typ.kind == .func_t {
|
||||
ret_typ = fn_typ.ret_type
|
||||
param_types = fn_typ.params.clone()
|
||||
}
|
||||
} else if fn_ptr_typ.kind == .func_t {
|
||||
// Function pointer extracted from struct field via extractvalue
|
||||
// has func_t type directly (not wrapped in ptr_t).
|
||||
ret_typ = fn_ptr_typ.ret_type
|
||||
param_types = fn_ptr_typ.params.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to call operand types when we could not resolve a signature.
|
||||
if param_types.len == 0 {
|
||||
num_args := if instr.operands.len > 0 { instr.operands.len - 1 } else { 0 }
|
||||
param_types = []int{len: num_args}
|
||||
for i := 0; i < num_args; i++ {
|
||||
arg_id := instr.operands[i + 1]
|
||||
param_types[i] = fallback_arg_type(m, arg_id)
|
||||
}
|
||||
}
|
||||
return ret_typ, param_types
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
// vtest build: macos
|
||||
|
||||
module abi
|
||||
|
||||
import os
|
||||
import v2.ast
|
||||
import v2.mir
|
||||
import v2.parser
|
||||
import v2.pref
|
||||
import v2.ssa
|
||||
import v2.token
|
||||
|
||||
fn parse_code_for_test(code string) []ast.File {
|
||||
tmp_file := os.join_path(os.temp_dir(), 'v2_abi_parser_test_${os.getpid()}.v')
|
||||
os.write_file(tmp_file, code) or { panic(err) }
|
||||
defer {
|
||||
os.rm(tmp_file) or {}
|
||||
}
|
||||
prefs := &pref.Preferences{}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut p := parser.Parser.new(prefs)
|
||||
return p.parse_files([tmp_file], mut file_set)
|
||||
}
|
||||
|
||||
fn first_fn_decl(file ast.File) ast.FnDecl {
|
||||
for stmt in file.stmts {
|
||||
if stmt is ast.FnDecl {
|
||||
return stmt
|
||||
}
|
||||
}
|
||||
panic('expected function declaration in parsed file')
|
||||
}
|
||||
|
||||
fn test_parser_sql_expr_allows_nested_braces_in_body() {
|
||||
files := parse_code_for_test("
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
req_status := true
|
||||
sql db {
|
||||
update User set
|
||||
name = 'Jengro',
|
||||
status = if req_status { 1 } else { 0 }
|
||||
status = fn [req_status] () u8 { if req_status { return 1 } else { return 0 } }()
|
||||
where id == '100'
|
||||
}
|
||||
}
|
||||
")
|
||||
assert files.len == 1
|
||||
main_fn := first_fn_decl(files[0])
|
||||
assert main_fn.stmts.len == 2
|
||||
assert main_fn.stmts[1] is ast.ExprStmt
|
||||
sql_stmt := main_fn.stmts[1] as ast.ExprStmt
|
||||
assert sql_stmt.expr is ast.SqlExpr
|
||||
}
|
||||
|
||||
fn test_arm64_large_struct_call_is_lowered_to_call_sret() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
ptr_t := ssa_mod.type_store.get_ptr(i8_t)
|
||||
large_struct_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [ptr_t, i64_t, i64_t] // 24 bytes
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', large_struct_t, [])
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [fn_val])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call_sret
|
||||
}
|
||||
|
||||
fn test_x64_large_struct_call_is_lowered_to_call_sret() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
ptr_t := ssa_mod.type_store.get_ptr(i8_t)
|
||||
large_struct_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [ptr_t, i64_t, i64_t]
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', large_struct_t, [])
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [fn_val])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .x64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call_sret
|
||||
}
|
||||
|
||||
fn test_x64_sysv_sixteen_byte_struct_return_stays_direct() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_16_ret')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct16_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i64_t]
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', struct16_t, [])
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, struct16_t, [fn_val])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .x64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call
|
||||
assert !call_instr.abi_ret_indirect
|
||||
}
|
||||
|
||||
fn test_x64_windows_struct_return_classification() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_ret')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
struct8_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t]
|
||||
})
|
||||
struct9_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i8_t]
|
||||
})
|
||||
struct16_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i64_t]
|
||||
})
|
||||
|
||||
fn8 := ssa_mod.new_function('ret8', struct8_t, [])
|
||||
fn9 := ssa_mod.new_function('ret9', struct9_t, [])
|
||||
fn16 := ssa_mod.new_function('ret16', struct16_t, [])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
assert !mir_mod.funcs[fn8].abi_ret_indirect
|
||||
assert mir_mod.funcs[fn9].abi_ret_indirect
|
||||
assert mir_mod.funcs[fn16].abi_ret_indirect
|
||||
}
|
||||
|
||||
fn test_x64_windows_struct_param_classification() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_param')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
struct8_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t]
|
||||
})
|
||||
struct9_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i8_t]
|
||||
})
|
||||
struct16_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i64_t]
|
||||
})
|
||||
|
||||
fn_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
param8 := ssa_mod.add_value_node(.argument, struct8_t, 's8', 0)
|
||||
param9 := ssa_mod.add_value_node(.argument, struct9_t, 's9', 0)
|
||||
param16 := ssa_mod.add_value_node(.argument, struct16_t, 's16', 0)
|
||||
ssa_mod.funcs[fn_id].params << [param8, param9, param16]
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
assert mir_mod.funcs[fn_id].abi_param_class == [.in_reg, .indirect, .indirect]
|
||||
}
|
||||
|
||||
fn test_x64_windows_callsite_struct_arg_classification() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_call_arg')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
struct8_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t]
|
||||
})
|
||||
struct9_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i8_t]
|
||||
})
|
||||
struct16_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [i64_t, i64_t]
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
param8 := ssa_mod.add_value_node(.argument, struct8_t, 's8', 0)
|
||||
param9 := ssa_mod.add_value_node(.argument, struct9_t, 's9', 0)
|
||||
param16 := ssa_mod.add_value_node(.argument, struct16_t, 's16', 0)
|
||||
ssa_mod.funcs[callee_id].params << [param8, param9, param16]
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
arg8 := ssa_mod.add_value_node(.argument, struct8_t, 'arg8', 0)
|
||||
arg9 := ssa_mod.add_value_node(.argument, struct9_t, 'arg9', 0)
|
||||
arg16 := ssa_mod.add_value_node(.argument, struct16_t, 'arg16', 0)
|
||||
ssa_mod.funcs[caller_id].params << [arg8, arg9, arg16]
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, arg8, arg9, arg16])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.abi_arg_class == [.in_reg, .indirect, .indirect]
|
||||
}
|
||||
|
||||
fn test_arm64_external_large_struct_call_is_lowered_to_call_sret() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_external')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
ptr_t := ssa_mod.type_store.get_ptr(i8_t)
|
||||
large_struct_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [ptr_t, i64_t, i64_t]
|
||||
})
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
external_fn := ssa_mod.add_value_node(.unknown, 0, 'external_make', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, large_struct_t, [external_fn])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call_sret
|
||||
}
|
||||
|
||||
fn test_arm64_indirect_call_large_struct_return_is_lowered_to_call_sret() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_indirect')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
ptr_t := ssa_mod.type_store.get_ptr(i8_t)
|
||||
large_struct_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [ptr_t, i64_t, i64_t]
|
||||
})
|
||||
fn_sig_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .func_t
|
||||
params: []ssa.TypeID{}
|
||||
ret_type: large_struct_t
|
||||
})
|
||||
fn_ptr_t := ssa_mod.type_store.get_ptr(fn_sig_t)
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
fn_ptr := ssa_mod.add_value_node(.argument, fn_ptr_t, 'fn_ptr', 0)
|
||||
ssa_mod.funcs[caller_id].params << fn_ptr
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
call_val := ssa_mod.add_instr(.call_indirect, caller_entry, large_struct_t, [
|
||||
fn_ptr,
|
||||
])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call_sret
|
||||
}
|
||||
|
||||
fn test_arm64_callsite_marks_large_struct_arg_indirect() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_arg_class')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
ptr_t := ssa_mod.type_store.get_ptr(i8_t)
|
||||
large_struct_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: [ptr_t, i64_t, i64_t]
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
callee_param := ssa_mod.add_value_node(.argument, large_struct_t, 'arg', 0)
|
||||
ssa_mod.funcs[callee_id].params << callee_param
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_param := ssa_mod.add_value_node(.argument, large_struct_t, 'in', 0)
|
||||
ssa_mod.funcs[caller_id].params << caller_param
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, caller_param])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.abi_arg_class.len == 1
|
||||
assert call_instr.abi_arg_class[0] == .indirect
|
||||
}
|
||||
|
||||
fn test_arm64_callsite_marks_large_array_arg_indirect() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_array_arg_class')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
large_array_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .array_t
|
||||
elem_type: i64_t
|
||||
len: 3
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
callee_param := ssa_mod.add_value_node(.argument, large_array_t, 'arg', 0)
|
||||
ssa_mod.funcs[callee_id].params << callee_param
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_param := ssa_mod.add_value_node(.argument, large_array_t, 'in', 0)
|
||||
ssa_mod.funcs[caller_id].params << caller_param
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, caller_param])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.abi_arg_class.len == 1
|
||||
assert call_instr.abi_arg_class[0] == .indirect
|
||||
}
|
||||
|
||||
fn test_arm64_unresolved_callsite_marks_large_array_alloca_arg_indirect() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_array_alloca_arg_class')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
large_array_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .array_t
|
||||
elem_type: i64_t
|
||||
len: 3
|
||||
})
|
||||
large_array_ptr_t := ssa_mod.type_store.get_ptr(large_array_t)
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
tmp := ssa_mod.add_instr(.alloca, caller_entry, large_array_ptr_t, []ssa.ValueID{})
|
||||
external_fn := ssa_mod.add_value_node(.unknown, 0, 'external_append_like', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [external_fn, tmp])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower(mut mir_mod, .arm64)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.abi_arg_class.len == 1
|
||||
assert call_instr.abi_arg_class[0] == .indirect
|
||||
}
|
||||
@@ -0,0 +1,999 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module abi
|
||||
|
||||
import v2.mir
|
||||
import v2.ssa
|
||||
|
||||
fn register_x64_matrix_struct_bytes(mut ssa_mod ssa.Module, size int) ssa.TypeID {
|
||||
i8_t := ssa_mod.type_store.get_int(8)
|
||||
mut fields := []ssa.TypeID{}
|
||||
for _ in 0 .. size {
|
||||
fields << i8_t
|
||||
}
|
||||
return register_x64_matrix_struct_fields(mut ssa_mod, fields)
|
||||
}
|
||||
|
||||
fn register_x64_matrix_struct_fields(mut ssa_mod ssa.Module, fields []ssa.TypeID) ssa.TypeID {
|
||||
return ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .struct_t
|
||||
fields: fields
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_abi_value_class(class mir.AbiValueClass, mode mir.AbiPassMode, classes []mir.AbiEightbyteClass) {
|
||||
assert class.mode == mode
|
||||
assert class.classes == classes
|
||||
}
|
||||
|
||||
fn assert_abi_layout_locs(layout mir.AbiValueLayout, kinds []mir.AbiLocationKind, indexes []int) {
|
||||
assert layout.locs.len == kinds.len
|
||||
assert indexes.len == kinds.len
|
||||
for i, kind in kinds {
|
||||
assert layout.locs[i].kind == kind
|
||||
assert layout.locs[i].index == indexes[i]
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_abi_layout(layout mir.AbiValueLayout, kinds []mir.AbiLocationKind, indexes []int, offsets []int, classes []mir.AbiEightbyteClass) {
|
||||
assert layout.locs.len == kinds.len
|
||||
assert indexes.len == kinds.len
|
||||
assert offsets.len == kinds.len
|
||||
assert classes.len == kinds.len
|
||||
for i, kind in kinds {
|
||||
assert layout.locs[i].kind == kind
|
||||
assert layout.locs[i].index == indexes[i]
|
||||
assert layout.locs[i].offset == offsets[i]
|
||||
assert layout.locs[i].class == classes[i]
|
||||
}
|
||||
}
|
||||
|
||||
fn build_x64_matrix_direct_call_module(x64_abi X64Abi) (mir.Module, ssa.ValueID) {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_direct_call_matrix')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
mut struct_types := []ssa.TypeID{}
|
||||
for size in sizes {
|
||||
struct_types << register_x64_matrix_struct_bytes(mut ssa_mod, size)
|
||||
}
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
for i, struct_t in struct_types {
|
||||
param := ssa_mod.add_value_node(.argument, struct_t, 'p${sizes[i]}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i, struct_t in struct_types {
|
||||
arg := ssa_mod.add_value_node(.argument, struct_t, 'a${sizes[i]}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, x64_abi)
|
||||
return mir_mod, call_val
|
||||
}
|
||||
|
||||
fn build_x64_matrix_return_call_module(x64_abi X64Abi, size int) (mir.Module, ssa.ValueID) {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_return_call_matrix')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct_t := register_x64_matrix_struct_bytes(mut ssa_mod, size)
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', struct_t, [])
|
||||
callee_entry := ssa_mod.add_block(callee_id, 'entry')
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, callee_entry, 0, [zero])
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, struct_t, [fn_val])
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, x64_abi)
|
||||
return mir_mod, call_val
|
||||
}
|
||||
|
||||
fn build_sysv_layout_call_module(prefix_count int, prefix_float bool, aggregate_kind string) (mir.Module, int, ssa.ValueID, int) {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_location_metadata')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
prefix_t := if prefix_float { f64_t } else { i64_t }
|
||||
aggregate_t := match aggregate_kind {
|
||||
'i64_i64' { register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, i64_t]) }
|
||||
'f64_f64' { register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t]) }
|
||||
'i64_f64' { register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, f64_t]) }
|
||||
else { register_x64_matrix_struct_fields(mut ssa_mod, [i64_t]) }
|
||||
}
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
mut param_types := []ssa.TypeID{len: prefix_count, init: prefix_t}
|
||||
param_types << aggregate_t
|
||||
for i, typ in param_types {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p${i}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i, typ in param_types {
|
||||
arg := ssa_mod.add_value_node(.argument, typ, 'a${i}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
return mir_mod, callee_id, call_val, prefix_count
|
||||
}
|
||||
|
||||
fn build_sysv_scalar_boundary_module(mut ssa_mod ssa.Module, typ ssa.TypeID, count int) (mir.Module, int, ssa.ValueID) {
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
for i := 0; i < count; i++ {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p${i}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i := 0; i < count; i++ {
|
||||
arg := ssa_mod.add_value_node(.argument, typ, 'a${i}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
return mir_mod, callee_id, call_val
|
||||
}
|
||||
|
||||
fn test_x64_windows_struct_size_matrix_classification() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_size_matrix')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
direct_sizes := [1, 2, 4, 8]
|
||||
mut struct_types := []ssa.TypeID{}
|
||||
for size in sizes {
|
||||
struct_types << register_x64_matrix_struct_bytes(mut ssa_mod, size)
|
||||
}
|
||||
|
||||
param_fn_id := ssa_mod.new_function('param_matrix', i64_t, [])
|
||||
for i, struct_t in struct_types {
|
||||
param := ssa_mod.add_value_node(.argument, struct_t, 's${sizes[i]}', 0)
|
||||
ssa_mod.funcs[param_fn_id].params << param
|
||||
}
|
||||
mut ret_fn_ids := []int{}
|
||||
for i, struct_t in struct_types {
|
||||
ret_fn_ids << ssa_mod.new_function('ret${sizes[i]}', struct_t, [])
|
||||
}
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
for i, size in sizes {
|
||||
should_be_indirect := size !in direct_sizes
|
||||
assert (mir_mod.funcs[param_fn_id].abi_param_class[i] == .indirect) == should_be_indirect
|
||||
assert mir_mod.funcs[ret_fn_ids[i]].abi_ret_indirect == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_windows_callsite_return_size_matrix_classification() {
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
direct_sizes := [1, 2, 4, 8]
|
||||
for size in sizes {
|
||||
mir_mod, call_val := build_x64_matrix_return_call_module(.windows, size)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
should_be_indirect := size !in direct_sizes
|
||||
assert call_instr.abi_ret_indirect == should_be_indirect
|
||||
assert (call_instr.op == .call_sret) == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_windows_direct_callsite_struct_size_matrix_classification() {
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
direct_sizes := [1, 2, 4, 8]
|
||||
mir_mod, call_val := build_x64_matrix_direct_call_module(.windows)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert call_instr.abi_arg_class.len == sizes.len
|
||||
for i, size in sizes {
|
||||
should_be_indirect := size !in direct_sizes
|
||||
assert (call_instr.abi_arg_class[i] == .indirect) == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_non_sysv_legacy_thresholds_stay_unchanged_with_metadata_fields() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_non_sysv_legacy_metadata_guard')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct9_t := register_x64_matrix_struct_bytes(mut ssa_mod, 9)
|
||||
struct16_t := register_x64_matrix_struct_bytes(mut ssa_mod, 16)
|
||||
struct17_t := register_x64_matrix_struct_bytes(mut ssa_mod, 17)
|
||||
|
||||
param_fn_id := ssa_mod.new_function('param_matrix', i64_t, [])
|
||||
for typ in [struct9_t, struct16_t, struct17_t] {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p', 0)
|
||||
ssa_mod.funcs[param_fn_id].params << param
|
||||
}
|
||||
ret16_id := ssa_mod.new_function('ret16', struct16_t, [])
|
||||
ret17_id := ssa_mod.new_function('ret17', struct17_t, [])
|
||||
|
||||
mut win_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut win_mod, .x64, .windows)
|
||||
assert win_mod.funcs[param_fn_id].abi_param_class == [.indirect, .indirect, .indirect]
|
||||
assert win_mod.funcs[ret16_id].abi_ret_indirect == true
|
||||
assert win_mod.funcs[ret17_id].abi_ret_indirect == true
|
||||
for layout in win_mod.funcs[param_fn_id].abi_param_layouts {
|
||||
assert layout.locs.len == 0
|
||||
}
|
||||
|
||||
mut arm_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut arm_mod, .arm64, .sysv)
|
||||
assert arm_mod.funcs[param_fn_id].abi_param_class == [.in_reg, .in_reg, .indirect]
|
||||
assert arm_mod.funcs[ret16_id].abi_ret_indirect == false
|
||||
assert arm_mod.funcs[ret17_id].abi_ret_indirect == true
|
||||
for layout in arm_mod.funcs[param_fn_id].abi_param_layouts {
|
||||
assert layout.locs.len == 0
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_rolls_back_integer_aggregate_when_gprs_are_insufficient() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(6, false,
|
||||
'i64_i64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.stack,
|
||||
.stack,
|
||||
], [0, 1])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[aggregate_idx], [.stack, .stack], [
|
||||
0,
|
||||
1,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_uses_two_gprs_when_available_for_integer_aggregate() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(4, false,
|
||||
'i64_i64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.int_reg,
|
||||
.int_reg,
|
||||
], [4, 5])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[aggregate_idx], [.int_reg, .int_reg], [
|
||||
4,
|
||||
5,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_rolls_back_integer_aggregate_after_five_gprs() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(5, false,
|
||||
'i64_i64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.stack,
|
||||
.stack,
|
||||
], [0, 1])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[aggregate_idx], [.stack, .stack], [
|
||||
0,
|
||||
1,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_rolls_back_sse_aggregate_when_xmms_are_insufficient() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(7, true, 'f64_f64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.stack,
|
||||
.stack,
|
||||
], [0, 1])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[aggregate_idx], [.stack, .stack], [
|
||||
0,
|
||||
1,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_assigns_mixed_integer_sse_aggregate() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(0, false,
|
||||
'i64_f64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.int_reg,
|
||||
.sse_reg,
|
||||
], [0, 0])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[aggregate_idx], [.int_reg, .sse_reg], [
|
||||
0,
|
||||
0,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_reserves_rdi_for_hidden_sret_pointer() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_sret_location_metadata')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
ret_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, i64_t, i64_t])
|
||||
aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, f64_t])
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', ret_t, [])
|
||||
callee_param := ssa_mod.add_value_node(.argument, aggregate_t, 'p', 0)
|
||||
ssa_mod.funcs[callee_id].params << callee_param
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_arg := ssa_mod.add_value_node(.argument, aggregate_t, 'a', 0)
|
||||
ssa_mod.funcs[caller_id].params << caller_arg
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, ret_t, [fn_val, caller_arg])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert mir_mod.funcs[callee_id].abi_ret_indirect
|
||||
assert call_instr.abi_ret_indirect
|
||||
assert call_instr.op == .call_sret
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [
|
||||
.int_reg,
|
||||
.sse_reg,
|
||||
], [1, 0], [0, 8], [.integer, .sse])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.int_reg, .sse_reg], [
|
||||
1,
|
||||
0,
|
||||
], [0, 8], [.integer, .sse])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_places_seventh_integer_scalar_on_stack() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_integer_scalar_boundary')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
mir_mod, callee_id, call_val := build_sysv_scalar_boundary_module(mut ssa_mod, i64_t, 7)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[6], [.stack], [0], [
|
||||
0,
|
||||
], [.integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[6], [.stack], [0], [0], [.integer])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_places_ninth_sse_scalar_on_stack() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_sse_scalar_boundary')
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
mir_mod, callee_id, call_val := build_sysv_scalar_boundary_module(mut ssa_mod, f64_t, 9)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[8], [.stack], [0], [
|
||||
0,
|
||||
], [.sse])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[8], [.stack], [0], [0], [.sse])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_float128_uses_one_sse_register() {
|
||||
// x86-64 psABI 3.2.3 classifies __float128 as SSE plus SSEUP; SSEUP is
|
||||
// carried in the upper half of the previous SSE register, not a new XMM.
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_float128_one_xmm')
|
||||
f128_t := ssa_mod.type_store.get_float(128)
|
||||
mir_mod, callee_id, call_val := build_sysv_scalar_boundary_module(mut ssa_mod, f128_t, 1)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [.sse_reg, .sse_reg], [
|
||||
0,
|
||||
0,
|
||||
], [0, 8], [.sse, .sseup])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.sse_reg, .sse_reg], [
|
||||
0,
|
||||
0,
|
||||
], [0, 8], [.sse, .sseup])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_float128_rolls_back_when_sse_regs_are_exhausted() {
|
||||
// x86-64 psABI 3.2.3 says an argument that cannot fit entirely in the
|
||||
// available registers is passed on the stack; this guards the SSE/SSEUP
|
||||
// rollback path without claiming codegen consumes the metadata yet.
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_float128_sse_exhausted')
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
f128_t := ssa_mod.type_store.get_float(128)
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
mut param_types := []ssa.TypeID{len: 8, init: f64_t}
|
||||
param_types << f128_t
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
for i, typ in param_types {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p${i}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i, typ in param_types {
|
||||
arg := ssa_mod.add_value_node(.argument, typ, 'a${i}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[8], [.stack, .stack], [
|
||||
0,
|
||||
1,
|
||||
], [0, 8], [.sse, .sseup])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[8], [.stack, .stack], [0, 1], [
|
||||
0,
|
||||
8,
|
||||
], [.sse, .sseup])
|
||||
assert call_instr.abi_arg_class[8] == .in_reg
|
||||
assert call_instr.op == .call
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_assigns_two_xmms_when_available_for_sse_aggregate() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(0, true, 'f64_f64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.sse_reg,
|
||||
.sse_reg,
|
||||
], [0, 1], [0, 8], [.sse, .sse])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[aggregate_idx], [.sse_reg, .sse_reg], [
|
||||
0,
|
||||
1,
|
||||
], [0, 8], [.sse, .sse])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_passes_memory_class_aggregate_indirectly() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_memory_class_layout')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
memory_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t, f64_t])
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
param := ssa_mod.add_value_node(.argument, memory_t, 'p', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
arg := ssa_mod.add_value_node(.argument, memory_t, 'a', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, arg])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert mir_mod.funcs[callee_id].abi_param_class == [.indirect]
|
||||
assert call_instr.abi_arg_class == [.indirect]
|
||||
assert_abi_value_class(mir_mod.funcs[callee_id].abi_param_classes[0], .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(call_instr.abi_arg_classes[0], .indirect, [.memory])
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [.int_reg], [0], [
|
||||
0,
|
||||
], [.integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.int_reg], [0], [0], [.integer])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_rolls_back_mixed_aggregate_when_gprs_are_exhausted() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(6, false,
|
||||
'i64_f64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.stack,
|
||||
.stack,
|
||||
], [0, 1], [0, 8], [.integer, .sse])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[aggregate_idx], [.stack, .stack], [0, 1], [
|
||||
0,
|
||||
8,
|
||||
], [.integer, .sse])
|
||||
assert call_instr.abi_arg_class[aggregate_idx] == .in_reg
|
||||
assert call_instr.op == .call
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_rolls_back_mixed_aggregate_when_xmms_are_exhausted() {
|
||||
mir_mod, callee_id, call_val, aggregate_idx := build_sysv_layout_call_module(8, true, 'i64_f64')
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[aggregate_idx], [
|
||||
.stack,
|
||||
.stack,
|
||||
], [0, 1], [0, 8], [.integer, .sse])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[aggregate_idx], [.stack, .stack], [0, 1], [
|
||||
0,
|
||||
8,
|
||||
], [.integer, .sse])
|
||||
assert call_instr.abi_arg_class[aggregate_idx] == .in_reg
|
||||
assert call_instr.op == .call
|
||||
}
|
||||
|
||||
fn test_x64_sysv_location_metadata_stack_indexes_continue_after_scalar_spill() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_stack_continuation_types')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct_i64_i64_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, i64_t])
|
||||
mut param_types := []ssa.TypeID{len: 6, init: i64_t}
|
||||
param_types << i64_t
|
||||
param_types << struct_i64_i64_t
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
for i, typ in param_types {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p${i}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i, typ in param_types {
|
||||
arg := ssa_mod.add_value_node(.argument, typ, 'a${i}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[6], [.stack], [
|
||||
0,
|
||||
])
|
||||
assert_abi_layout_locs(mir_mod.funcs[callee_id].abi_param_layouts[7], [.stack, .stack], [
|
||||
1,
|
||||
2,
|
||||
])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[6], [.stack], [0])
|
||||
assert_abi_layout_locs(call_instr.abi_arg_layouts[7], [.stack, .stack], [1, 2])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_direct_callsite_struct_size_matrix_current_threshold() {
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
mir_mod, call_val := build_x64_matrix_direct_call_module(.sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert call_instr.abi_arg_class.len == sizes.len
|
||||
for i, size in sizes {
|
||||
should_be_indirect := size > 16
|
||||
assert (call_instr.abi_arg_class[i] == .indirect) == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_sysv_callsite_return_size_matrix_current_threshold() {
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
for size in sizes {
|
||||
mir_mod, call_val := build_x64_matrix_return_call_module(.sysv, size)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
should_be_indirect := size > 16
|
||||
assert call_instr.abi_ret_indirect == should_be_indirect
|
||||
assert (call_instr.op == .call_sret) == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_sysv_struct_size_matrix_current_lowering_threshold() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_size_matrix')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
sizes := [1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 32]
|
||||
mut struct_types := []ssa.TypeID{}
|
||||
for size in sizes {
|
||||
struct_types << register_x64_matrix_struct_bytes(mut ssa_mod, size)
|
||||
}
|
||||
|
||||
param_fn_id := ssa_mod.new_function('param_matrix', i64_t, [])
|
||||
for i, struct_t in struct_types {
|
||||
param := ssa_mod.add_value_node(.argument, struct_t, 's${sizes[i]}', 0)
|
||||
ssa_mod.funcs[param_fn_id].params << param
|
||||
}
|
||||
mut ret_fn_ids := []int{}
|
||||
for i, struct_t in struct_types {
|
||||
ret_fn_ids << ssa_mod.new_function('ret${sizes[i]}', struct_t, [])
|
||||
}
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
|
||||
for i, size in sizes {
|
||||
should_be_indirect := size > 16
|
||||
assert (mir_mod.funcs[param_fn_id].abi_param_class[i] == .indirect) == should_be_indirect
|
||||
assert mir_mod.funcs[ret_fn_ids[i]].abi_ret_indirect == should_be_indirect
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_sysv_aggregate_memory_class_for_representable_mir_types() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_memory_class')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
f80_t := ssa_mod.type_store.get_float(80)
|
||||
f128_t := ssa_mod.type_store.get_float(128)
|
||||
two_eightbytes_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t])
|
||||
three_eightbytes_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t, f64_t])
|
||||
x87_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f80_t])
|
||||
float128_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f128_t])
|
||||
|
||||
param_fn_id := ssa_mod.new_function('sysv_memory_params', i64_t, [])
|
||||
for typ in [two_eightbytes_t, three_eightbytes_t, x87_aggregate_t, float128_aggregate_t] {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p', 0)
|
||||
ssa_mod.funcs[param_fn_id].params << param
|
||||
}
|
||||
ret_two_id := ssa_mod.new_function('ret_two_eightbytes', two_eightbytes_t, [])
|
||||
ret_three_id := ssa_mod.new_function('ret_three_eightbytes', three_eightbytes_t, [])
|
||||
ret_x87_id := ssa_mod.new_function('ret_x87_aggregate', x87_aggregate_t, [])
|
||||
ret_float128_id := ssa_mod.new_function('ret_float128_aggregate', float128_aggregate_t, [])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
|
||||
assert_abi_value_class(mir_mod.funcs[param_fn_id].abi_param_classes[0], .direct, [
|
||||
.sse,
|
||||
.sse,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[param_fn_id].abi_param_classes[1], .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[param_fn_id].abi_param_classes[2], .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[param_fn_id].abi_param_classes[3], .direct, [
|
||||
.sse,
|
||||
.sseup,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[ret_two_id].abi_ret_class, .direct, [.sse, .sse])
|
||||
assert_abi_value_class(mir_mod.funcs[ret_three_id].abi_ret_class, .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[ret_x87_id].abi_ret_class, .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[ret_float128_id].abi_ret_class, .direct, [
|
||||
.sse,
|
||||
.sseup,
|
||||
])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_metadata_eightbyte_classification_for_representable_types() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_eightbyte_metadata')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f64_t := ssa_mod.type_store.get_float(64)
|
||||
f80_t := ssa_mod.type_store.get_float(80)
|
||||
f128_t := ssa_mod.type_store.get_float(128)
|
||||
struct_i64_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t])
|
||||
struct_i64_i64_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, i64_t])
|
||||
struct_f64_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t])
|
||||
struct_f64_f64_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t])
|
||||
struct_i64_f64_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, f64_t])
|
||||
struct_f64_i64_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, i64_t])
|
||||
too_large_t := register_x64_matrix_struct_fields(mut ssa_mod, [f64_t, f64_t, f64_t])
|
||||
x87_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f80_t])
|
||||
float128_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f128_t])
|
||||
types := [
|
||||
struct_i64_t,
|
||||
struct_i64_i64_t,
|
||||
struct_f64_t,
|
||||
struct_f64_f64_t,
|
||||
struct_i64_f64_t,
|
||||
struct_f64_i64_t,
|
||||
too_large_t,
|
||||
x87_aggregate_t,
|
||||
float128_aggregate_t,
|
||||
]
|
||||
|
||||
param_fn_id := ssa_mod.new_function('sysv_metadata_params', i64_t, [])
|
||||
for typ in types {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p', 0)
|
||||
ssa_mod.funcs[param_fn_id].params << param
|
||||
}
|
||||
mut ret_fn_ids := []int{}
|
||||
for i, typ in types {
|
||||
ret_fn_ids << ssa_mod.new_function('sysv_metadata_ret_${i}', typ, [])
|
||||
}
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
|
||||
expected_classes := [
|
||||
[mir.AbiEightbyteClass.integer],
|
||||
[mir.AbiEightbyteClass.integer, .integer],
|
||||
[mir.AbiEightbyteClass.sse],
|
||||
[mir.AbiEightbyteClass.sse, .sse],
|
||||
[mir.AbiEightbyteClass.integer, .sse],
|
||||
[mir.AbiEightbyteClass.sse, .integer],
|
||||
[mir.AbiEightbyteClass.memory],
|
||||
[mir.AbiEightbyteClass.memory],
|
||||
[mir.AbiEightbyteClass.sse, .sseup],
|
||||
]
|
||||
expected_modes := [
|
||||
mir.AbiPassMode.direct,
|
||||
.direct,
|
||||
.direct,
|
||||
.direct,
|
||||
.direct,
|
||||
.direct,
|
||||
.indirect,
|
||||
.indirect,
|
||||
.direct,
|
||||
]
|
||||
for i in 0 .. types.len {
|
||||
assert_abi_value_class(mir_mod.funcs[param_fn_id].abi_param_classes[i], expected_modes[i],
|
||||
expected_classes[i])
|
||||
assert_abi_value_class(mir_mod.funcs[ret_fn_ids[i]].abi_ret_class, expected_modes[i],
|
||||
expected_classes[i])
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_sysv_memory_class_projects_to_legacy_indirect_flags() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_callsite_memory_class')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f80_t := ssa_mod.type_store.get_float(80)
|
||||
x87_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f80_t])
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', x87_aggregate_t, [])
|
||||
callee_param := ssa_mod.add_value_node(.argument, x87_aggregate_t, 'p', 0)
|
||||
ssa_mod.funcs[callee_id].params << callee_param
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
arg := ssa_mod.add_value_node(.argument, x87_aggregate_t, 'a', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, x87_aggregate_t, [fn_val, arg])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
|
||||
assert_abi_value_class(mir_mod.funcs[callee_id].abi_param_classes[0], .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert_abi_value_class(mir_mod.funcs[callee_id].abi_ret_class, .indirect, [
|
||||
.memory,
|
||||
])
|
||||
assert mir_mod.funcs[callee_id].abi_param_class == [.indirect]
|
||||
assert mir_mod.funcs[callee_id].abi_ret_indirect == true
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [.int_reg], [1], [
|
||||
0,
|
||||
], [.integer])
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert_abi_value_class(call_instr.abi_arg_classes[0], .indirect, [.memory])
|
||||
assert_abi_value_class(call_instr.abi_ret_class, .indirect, [.memory])
|
||||
assert call_instr.abi_arg_class == [.indirect]
|
||||
assert call_instr.abi_ret_indirect == true
|
||||
assert call_instr.op == .call_sret
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.int_reg], [1], [0], [.integer])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_direct_aggregate_size_guard_boundary() {
|
||||
assert !sysv_aggregate_must_be_memory_before_classification(16)
|
||||
assert sysv_aggregate_must_be_memory_before_classification(17)
|
||||
}
|
||||
|
||||
fn test_x64_sysv_huge_fixed_array_uses_early_memory_guard_and_bounded_metadata() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_huge_fixed_array_memory')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
u8_t := ssa_mod.type_store.get_uint(8)
|
||||
huge_array_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .array_t
|
||||
elem_type: u8_t
|
||||
len: 1_000_000
|
||||
})
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', huge_array_t, [])
|
||||
param := ssa_mod.add_value_node(.argument, huge_array_t, 'p', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
arg := ssa_mod.add_value_node(.argument, huge_array_t, 'a', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, huge_array_t, [fn_val, arg])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
huge_size := mir_mod.type_size(huge_array_t)
|
||||
assert huge_size == 1_000_000
|
||||
assert sysv_aggregate_must_be_memory_before_classification(huge_size)
|
||||
direct_class := sysv_abi_value_class(mir_mod, huge_array_t)
|
||||
assert_abi_value_class(direct_class, .indirect, [.memory])
|
||||
assert direct_class.classes.len == 1
|
||||
assert direct_class.classes.len < (huge_size + 7) / 8
|
||||
|
||||
param_class := mir_mod.funcs[callee_id].abi_param_classes[0]
|
||||
ret_class := mir_mod.funcs[callee_id].abi_ret_class
|
||||
assert_abi_value_class(param_class, .indirect, [.memory])
|
||||
assert_abi_value_class(ret_class, .indirect, [.memory])
|
||||
assert param_class.classes.len == 1
|
||||
assert ret_class.classes.len == 1
|
||||
assert mir_mod.funcs[callee_id].abi_param_class == [.indirect]
|
||||
assert mir_mod.funcs[callee_id].abi_ret_indirect
|
||||
assert mir_mod.funcs[callee_id].abi_param_layouts[0].locs.len == 1
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [.int_reg], [1], [
|
||||
0,
|
||||
], [.integer])
|
||||
|
||||
assert_abi_value_class(call_instr.abi_arg_classes[0], .indirect, [.memory])
|
||||
assert_abi_value_class(call_instr.abi_ret_class, .indirect, [.memory])
|
||||
assert call_instr.abi_arg_classes[0].classes.len == 1
|
||||
assert call_instr.abi_ret_class.classes.len == 1
|
||||
assert call_instr.abi_arg_class == [.indirect]
|
||||
assert call_instr.abi_ret_indirect
|
||||
assert call_instr.op == .call_sret
|
||||
assert call_instr.abi_arg_layouts[0].locs.len == 1
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.int_reg], [1], [0], [.integer])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_indirect_argument_consumes_gpr_before_direct_aggregate_layout() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_indirect_arg_gpr_consumption')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f80_t := ssa_mod.type_store.get_float(80)
|
||||
x87_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f80_t])
|
||||
integer_pair_t := register_x64_matrix_struct_fields(mut ssa_mod, [i64_t, i64_t])
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
memory_param := ssa_mod.add_value_node(.argument, x87_aggregate_t, 'memory_arg', 0)
|
||||
pair_param := ssa_mod.add_value_node(.argument, integer_pair_t, 'pair_arg', 0)
|
||||
ssa_mod.funcs[callee_id].params << [memory_param, pair_param]
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
memory_arg := ssa_mod.add_value_node(.argument, x87_aggregate_t, 'memory_arg', 0)
|
||||
pair_arg := ssa_mod.add_value_node(.argument, integer_pair_t, 'pair_arg', 0)
|
||||
ssa_mod.funcs[caller_id].params << [memory_arg, pair_arg]
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [fn_val, memory_arg, pair_arg])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert mir_mod.funcs[callee_id].abi_param_class == [.indirect, .in_reg]
|
||||
assert call_instr.abi_arg_class == [.indirect, .in_reg]
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[0], [.int_reg], [0], [
|
||||
0,
|
||||
], [.integer])
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[1], [
|
||||
.int_reg,
|
||||
.int_reg,
|
||||
], [1, 2], [0, 8], [.integer, .integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[0], [.int_reg], [0], [0], [.integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[1], [.int_reg, .int_reg], [1, 2], [
|
||||
0,
|
||||
8,
|
||||
], [.integer, .integer])
|
||||
}
|
||||
|
||||
fn test_x64_sysv_indirect_argument_spills_after_gprs_are_exhausted() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_sysv_indirect_arg_stack_spill')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
f80_t := ssa_mod.type_store.get_float(80)
|
||||
x87_aggregate_t := register_x64_matrix_struct_fields(mut ssa_mod, [f80_t])
|
||||
mut param_types := []ssa.TypeID{len: 6, init: i64_t}
|
||||
param_types << x87_aggregate_t
|
||||
param_types << i64_t
|
||||
|
||||
callee_id := ssa_mod.new_function('callee', i64_t, [])
|
||||
for i, typ in param_types {
|
||||
param := ssa_mod.add_value_node(.argument, typ, 'p${i}', 0)
|
||||
ssa_mod.funcs[callee_id].params << param
|
||||
}
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
mut args := []ssa.ValueID{}
|
||||
for i, typ in param_types {
|
||||
arg := ssa_mod.add_value_node(.argument, typ, 'a${i}', 0)
|
||||
ssa_mod.funcs[caller_id].params << arg
|
||||
args << arg
|
||||
}
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
fn_val := ssa_mod.add_value_node(.unknown, 0, 'callee', 0)
|
||||
mut operands := [fn_val]
|
||||
operands << args
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, operands)
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .sysv)
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
|
||||
assert mir_mod.funcs[callee_id].abi_param_class[6] == .indirect
|
||||
assert call_instr.abi_arg_class[6] == .indirect
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[6], [.stack], [0], [
|
||||
0,
|
||||
], [.integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[6], [.stack], [0], [0], [.integer])
|
||||
assert_abi_layout(mir_mod.funcs[callee_id].abi_param_layouts[7], [.stack], [1], [
|
||||
0,
|
||||
], [.integer])
|
||||
assert_abi_layout(call_instr.abi_arg_layouts[7], [.stack], [1], [0], [.integer])
|
||||
}
|
||||
|
||||
fn test_x64_windows_indirect_call_uses_function_pointer_signature_for_aggregate_args() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_indirect_call_matrix')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct8_t := register_x64_matrix_struct_bytes(mut ssa_mod, 8)
|
||||
struct9_t := register_x64_matrix_struct_bytes(mut ssa_mod, 9)
|
||||
fn_sig_t := ssa_mod.type_store.register(ssa.Type{
|
||||
kind: .func_t
|
||||
params: [struct8_t, struct9_t]
|
||||
ret_type: i64_t
|
||||
})
|
||||
fn_ptr_t := ssa_mod.type_store.get_ptr(fn_sig_t)
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
fn_ptr := ssa_mod.add_value_node(.argument, fn_ptr_t, 'fn_ptr', 0)
|
||||
arg8 := ssa_mod.add_value_node(.argument, struct8_t, 'arg8', 0)
|
||||
arg9 := ssa_mod.add_value_node(.argument, struct9_t, 'arg9', 0)
|
||||
ssa_mod.funcs[caller_id].params << [fn_ptr, arg8, arg9]
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
call_val := ssa_mod.add_instr(.call_indirect, caller_entry, i64_t, [fn_ptr, arg8, arg9])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.op == .call_indirect
|
||||
assert call_instr.abi_arg_class == [.in_reg, .indirect]
|
||||
}
|
||||
|
||||
fn test_x64_windows_unresolved_callsite_uses_alloca_logical_aggregate_type() {
|
||||
mut ssa_mod := ssa.Module.new('abi_test_x64_windows_alloca_logical_arg')
|
||||
i64_t := ssa_mod.type_store.get_int(64)
|
||||
struct9_t := register_x64_matrix_struct_bytes(mut ssa_mod, 9)
|
||||
struct9_ptr_t := ssa_mod.type_store.get_ptr(struct9_t)
|
||||
|
||||
caller_id := ssa_mod.new_function('caller', i64_t, [])
|
||||
caller_entry := ssa_mod.add_block(caller_id, 'entry')
|
||||
tmp := ssa_mod.add_instr(.alloca, caller_entry, struct9_ptr_t, []ssa.ValueID{})
|
||||
external_fn := ssa_mod.add_value_node(.unknown, 0, 'external_accept_struct9', 0)
|
||||
call_val := ssa_mod.add_instr(.call, caller_entry, i64_t, [external_fn, tmp])
|
||||
zero := ssa_mod.get_or_add_const(i64_t, '0')
|
||||
ssa_mod.add_instr(.ret, caller_entry, 0, [zero])
|
||||
|
||||
mut mir_mod := mir.lower_from_ssa(ssa_mod)
|
||||
lower_with_x64_abi(mut mir_mod, .x64, .windows)
|
||||
|
||||
call_instr := mir_mod.instrs[mir_mod.values[call_val].index]
|
||||
assert call_instr.abi_arg_class == [.indirect]
|
||||
}
|
||||
Reference in new issue
Block a user