Skip to content

Commit

Permalink
ABI compliance when dealing with structs
Browse files Browse the repository at this point in the history
  • Loading branch information
yorickpeterse committed Nov 26, 2024
1 parent 46844c5 commit ba72277
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 408 deletions.
20 changes: 3 additions & 17 deletions compiler/src/llvm/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::llvm::context::Context;
use crate::llvm::module::Module;
use crate::llvm::runtime_function::RuntimeFunction;
use crate::symbol_names::SymbolNames;
use inkwell::attributes::AttributeLoc;
use inkwell::basic_block::BasicBlock;
use inkwell::builder;
use inkwell::debug_info::{
Expand Down Expand Up @@ -197,7 +196,7 @@ impl<'ctx> Builder<'ctx> {
self.load(self.context.pointer_type(), variable).into_pointer_value()
}

pub(crate) fn call(
pub(crate) fn call_with_return(
&self,
function: FunctionValue<'ctx>,
arguments: &[BasicMetadataValueEnum<'ctx>],
Expand All @@ -219,27 +218,14 @@ impl<'ctx> Builder<'ctx> {
self.inner.build_indirect_call(typ, func, args, "").unwrap()
}

pub(crate) fn call_void(
pub(crate) fn direct_call(
&self,
function: FunctionValue<'ctx>,
arguments: &[BasicMetadataValueEnum<'ctx>],
) -> CallSiteValue<'ctx> {
self.inner.build_call(function, arguments, "").unwrap()
}

pub(crate) fn load_for_struct_return(
&self,
call: CallSiteValue<'ctx>,
typ: StructType<'ctx>,
tmp: PointerValue<'ctx>,
result: PointerValue<'ctx>,
) {
let sret = self.context.type_attribute("sret", typ.into());

call.add_attribute(AttributeLoc::Param(0), sret);
self.store(result, self.load(typ, tmp));
}

pub(crate) fn pointer_to_int(
&self,
value: PointerValue<'ctx>,
Expand Down Expand Up @@ -792,7 +778,7 @@ impl<'ctx> Builder<'ctx> {

// The block to jump to when the allocation failed.
self.switch_to_block(err_block);
self.call_void(err_func, &[size.into()]);
self.direct_call(err_func, &[size.into()]);
self.unreachable();

// The block to jump to when the allocation succeeds.
Expand Down
139 changes: 80 additions & 59 deletions compiler/src/llvm/context.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::llvm::layouts::Layouts;
use crate::llvm::layouts::{ArgumentType, Layouts, ReturnType};
use crate::state::State;
use crate::target::Architecture;
use inkwell::attributes::Attribute;
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::module::Module;
use inkwell::types::{
AnyTypeEnum, ArrayType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum,
FloatType, IntType, PointerType, StructType, VoidType,
AnyTypeEnum, ArrayType, BasicType, BasicTypeEnum, FloatType, IntType,
PointerType, StructType, VoidType,
};
use inkwell::values::FunctionValue;
use inkwell::{context, AddressSpace};
Expand Down Expand Up @@ -50,6 +51,10 @@ impl Context {
self.inner.bool_type()
}

pub(crate) fn custom_int(&self, bits: u32) -> IntType {
self.inner.custom_width_int_type(bits)
}

pub(crate) fn i8_type(&self) -> IntType {
self.inner.i8_type()
}
Expand Down Expand Up @@ -205,27 +210,38 @@ impl Context {
state: &State,
layouts: &Layouts<'ctx>,
typ: BasicTypeEnum<'ctx>,
) -> BasicTypeEnum<'ctx> {
// TODO: on AMD64 we can pass around structs <= 16 bytes as-is, but only
// if there are only two fields. On ARM64 we need to round them up to
// one or two pairs of i64 values:
//
// | Arch | Original | Result
// |-------|-------------------|-----------------
// | AMD64 | { i64, i32 } | { i64, i32 }
// | AMD64 | { i64, i32, i32 } | ptr
// | ARM64 | { i64, i32 } | { i64, i64 }
// | ARM64 | { i64, i32, i32 } | ptr (I think?)
//
// For anything larger than 16 bytes, pass as a pointer.
let BasicTypeEnum::StructType(typ) = typ else { return typ };
let max = state.config.target.pass_struct_size();
let size = layouts.target_data.get_abi_size(&typ);

if size > max {
self.pointer_type().as_basic_type_enum()
} else {
typ.as_basic_type_enum()
) -> ArgumentType<'ctx> {
let BasicTypeEnum::StructType(typ) = typ else {
return ArgumentType::Regular(typ);
};
let bytes = layouts.target_data.get_abi_size(&typ) as u32;

match state.config.target.arch {
Architecture::Amd64 => {
if bytes <= 8 {
let bits = self.custom_int(bytes * 8);

ArgumentType::Regular(bits.as_basic_type_enum())
} else if bytes <= 16 {
ArgumentType::Regular(typ.as_basic_type_enum())
} else {
ArgumentType::StructValue(typ)
}
}
Architecture::Arm64 => {
if bytes <= 8 {
ArgumentType::Regular(self.i64_type().as_basic_type_enum())
} else if bytes <= 16 {
let word = self.i64_type().into();
let sret = self.struct_type(&[word, word]);

ArgumentType::Regular(sret.as_basic_type_enum())
} else {
// clang and Rust don't use "byval" for ARM64 when the
// struct is too large, so neither do we.
ArgumentType::Pointer
}
}
}
}

Expand All @@ -234,54 +250,59 @@ impl Context {
state: &State,
layouts: &Layouts<'ctx>,
method: MethodId,
args: &mut Vec<BasicMetadataTypeEnum<'ctx>>,
) -> (Option<BasicTypeEnum<'ctx>>, Option<StructType<'ctx>>) {
) -> ReturnType<'ctx> {
if method.returns_value(&state.db) {
self.return_type(
state,
let typ = self.llvm_type(
&state.db,
layouts,
args,
method.return_type(&state.db),
)
);

self.return_type(state, layouts, typ)
} else {
(None, None)
ReturnType::None
}
}

pub(crate) fn return_type<'ctx>(
&'ctx self,
state: &State,
layouts: &Layouts<'ctx>,
args: &mut Vec<BasicMetadataTypeEnum<'ctx>>,
returns: TypeRef,
) -> (Option<BasicTypeEnum<'ctx>>, Option<StructType<'ctx>>) {
// The regular return type, and the type of the structure to pass with
// the `sret` attribute. If `ret` is `None`, it means the function
// returns `void`. If `sret` is `None`, it means the function doesn't
// return a struct.
let mut ret = None;
let mut sret = None;
let typ = self.llvm_type(&state.db, layouts, returns);

// The C ABI mandates that structures are either passed through
// registers (if small enough), or using a pointer. LLVM doesn't
// detect when this is needed for us, so sadly we (and everybody
// else using LLVM) have to do this ourselves.
if let BasicTypeEnum::StructType(typ) = typ {
if layouts.target_data.get_abi_size(&typ)
> state.config.target.pass_struct_size()
{
args.push(self.pointer_type().into());
sret = Some(typ);
} else {
// TODO: pass as [2 x i64] when necessary
ret = Some(typ.as_basic_type_enum());
typ: BasicTypeEnum<'ctx>,
) -> ReturnType<'ctx> {
let BasicTypeEnum::StructType(typ) = typ else {
return ReturnType::Regular(typ);
};

let bytes = layouts.target_data.get_abi_size(&typ) as u32;

match state.config.target.arch {
Architecture::Amd64 => {
if bytes <= 8 {
let bits = self.custom_int(bytes * 8);

ReturnType::Regular(bits.as_basic_type_enum())
} else if bytes <= 16 {
ReturnType::Regular(typ.as_basic_type_enum())
} else {
ReturnType::Struct(typ)
}
}
Architecture::Arm64 => {
if bytes <= 8 {
let bits = self.custom_int(bytes * 8);

ReturnType::Regular(bits.as_basic_type_enum())
} else if bytes <= 16 {
let word = self.i64_type().into();
let sret = self.struct_type(&[word, word]);

ReturnType::Regular(sret.as_basic_type_enum())
} else {
ReturnType::Struct(typ)
}
}
} else {
ret = Some(typ);
}

(ret, sret)
}
}

Expand Down
Loading

0 comments on commit ba72277

Please sign in to comment.