diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b249f5b8..1f52fbf5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -76,13 +76,12 @@ jobs: key: amd64-linux-gnu-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} - name: Run compiler tests run: cargo test - # We run tests with and without optimizations, such that we can catch any - # potential miscompilations introduced by optimizations. We only do this - # for this particular target as our optimizations aren't target specific. - - name: Run stdlib tests with optimizations + - name: Run stdlib tests with default optimizations run: 'cd std && cargo run -- test' - name: Run stdlib tests without optimizations run: 'cd std && cargo run -- test --opt=none' + - name: Run stdlib tests with aggressive optimizations + run: 'cd std && cargo run -- test --opt=aggressive' amd64-linux-musl: timeout-minutes: 15 @@ -137,8 +136,12 @@ jobs: run: ./ci/mac.sh - name: Run compiler tests run: cargo test - - name: Run stdlib tests + - name: Run stdlib tests with default optimizations run: 'cd std && cargo run -- test' + - name: Run stdlib tests without optimizations + run: 'cd std && cargo run -- test --opt=none' + - name: Run stdlib tests with aggressive optimizations + run: 'cd std && cargo run -- test --opt=aggressive' amd64-freebsd-native: timeout-minutes: 15 diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index e3c78d2e..7089476a 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -1,10 +1,12 @@ use crate::llvm::layouts::{ArgumentType, Layouts, ReturnType}; use crate::state::State; use crate::target::Architecture; +use crate::target::Target; use inkwell::attributes::Attribute; use inkwell::basic_block::BasicBlock; use inkwell::builder::Builder; use inkwell::module::Module; +use inkwell::targets::TargetData; use inkwell::types::{ AnyTypeEnum, ArrayType, BasicType, BasicTypeEnum, FloatType, IntType, PointerType, StructType, VoidType, @@ -110,12 +112,6 @@ impl Context { self.inner.struct_type(fields, false) } - pub(crate) fn two_words(&self) -> StructType { - let word = self.i64_type().into(); - - self.inner.struct_type(&[word, word], false) - } - pub(crate) fn empty_type<'a>( &'a self, method_type: StructType<'a>, @@ -212,31 +208,26 @@ impl Context { pub(crate) fn argument_type<'ctx>( &'ctx self, - state: &State, - layouts: &Layouts<'ctx>, + target_data: &TargetData, + target: Target, typ: BasicTypeEnum<'ctx>, ) -> ArgumentType<'ctx> { let BasicTypeEnum::StructType(typ) = typ else { return ArgumentType::Regular(typ); }; - let bytes = layouts.target_data.get_abi_size(&typ) as u32; + let bytes = target_data.get_abi_size(&typ) as u32; - match state.config.target.arch { + match target.arch { Architecture::Amd64 => { if bytes <= 8 { let bits = self.custom_int(size_in_bits(bytes)); ArgumentType::Regular(bits.as_basic_type_enum()) } else if bytes <= 16 { - // The AMD64 ABI doesn't handle types such as - // `{ i16, i64 }`. While it does handle `{ i64, i16 }`, this - // requires re-ordering the fields and their corresponding - // access sites. - // - // To avoid the complexity of that we take the same approach - // as Rust: if the struct is larger than 8 bytes, we turn it - // into two 64 bits values. - ArgumentType::Regular(self.two_words().as_basic_type_enum()) + ArgumentType::Regular( + self.small_amd64_struct(target_data, typ) + .as_basic_type_enum(), + ) } else { ArgumentType::StructValue(typ) } @@ -245,7 +236,11 @@ impl Context { if bytes <= 8 { ArgumentType::Regular(self.i64_type().as_basic_type_enum()) } else if bytes <= 16 { - ArgumentType::Regular(self.two_words().as_basic_type_enum()) + // TODO: handle 4x float values + ArgumentType::Regular( + self.small_arm64_struct(target_data, typ) + .as_basic_type_enum(), + ) } else { // clang and Rust don't use "byval" for ARM64 when the // struct is too large, so neither do we. @@ -268,7 +263,7 @@ impl Context { method.return_type(&state.db), ); - self.return_type(state, layouts, typ) + self.return_type(layouts.target_data, state.config.target, typ) } else { ReturnType::None } @@ -276,37 +271,97 @@ impl Context { pub(crate) fn return_type<'ctx>( &'ctx self, - state: &State, - layouts: &Layouts<'ctx>, + target_data: &TargetData, + target: Target, 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; + let bytes = target_data.get_abi_size(&typ) as u32; - match state.config.target.arch { - // For both AMD64 and ARM64 the way structs are returned is the - // same. For more details, refer to argument_type(). - Architecture::Amd64 | Architecture::Arm64 => { + match target.arch { + Architecture::Amd64 => { if bytes <= 8 { let bits = self.custom_int(size_in_bits(bytes)); ReturnType::Regular(bits.as_basic_type_enum()) } else if bytes <= 16 { - ReturnType::Regular(self.two_words().as_basic_type_enum()) + ReturnType::Regular( + self.small_amd64_struct(target_data, typ) + .as_basic_type_enum(), + ) + } else { + ReturnType::Struct(typ) + } + } + Architecture::Arm64 => { + if bytes <= 8 { + ReturnType::Regular(self.i64_type().as_basic_type_enum()) + } else if bytes <= 16 { + // TODO: handle 4x float values + ReturnType::Regular( + self.small_arm64_struct(target_data, typ) + .as_basic_type_enum(), + ) } else { ReturnType::Struct(typ) } } } } + + fn small_arm64_struct<'ctx>( + &'ctx self, + target_data: &TargetData, + typ: StructType<'ctx>, + ) -> StructType<'ctx> { + let size = target_data.get_abi_size(&typ) as u32; + let align = target_data.get_abi_alignment(&typ); + let rest = max(size - 8, align); + let a = self.i64_type().into(); + let b = self.custom_int(size_in_bits(rest)).into(); + + self.struct_type(&[a, b]) + } + + fn small_amd64_struct<'ctx>( + &'ctx self, + target_data: &TargetData, + typ: StructType<'ctx>, + ) -> StructType<'ctx> { + let size = target_data.get_abi_size(&typ) as u32; + let align = target_data.get_abi_alignment(&typ); + let rest = max(size - 8, align); + let a = self.i64_type().into(); + let b = self.custom_int(size_in_bits(rest)).into(); + + self.struct_type(&[a, b]) + } } #[cfg(test)] mod tests { use super::*; + use crate::target::{Abi, Architecture, OperatingSystem}; + use inkwell::targets::{ + CodeModel, InitializationConfig, RelocMode, Target as LlvmTarget, + TargetMachine, TargetTriple, + }; + use inkwell::OptimizationLevel; + + fn setup(target: Target) -> TargetMachine { + let triple = TargetTriple::create(&target.llvm_triple()); + let reloc = RelocMode::PIC; + let model = CodeModel::Default; + let level = OptimizationLevel::None; + + LlvmTarget::from_triple(&triple) + .unwrap() + .create_target_machine(&triple, "", "", level, reloc, model) + .unwrap() + } #[test] fn test_context_type_sizes() { @@ -318,4 +373,22 @@ mod tests { assert_eq!(ctx.rust_string_type().len(), 24); assert_eq!(ctx.rust_vec_type().len(), 24); } + + #[test] + fn test_argument_type_for_amd64_with_less_than_one_word() { + LlvmTarget::initialize_x86(&InitializationConfig::default()); + + let target = + Target::new(Architecture::Amd64, OperatingSystem::Linux, Abi::Gnu); + let machine = setup(target); + let ctx = Context::new(); + let inp = ctx.struct_type(&[ctx.i8_type().into()]).as_basic_type_enum(); + let ArgumentType::Regular(BasicTypeEnum::IntType(out)) = + ctx.argument_type(&machine.get_target_data(), target, inp) + else { + panic!("unexpected type") + }; + + assert_eq!(out.get_bit_width(), 8); + } } diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index b1654bb9..60c5df41 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -132,7 +132,11 @@ impl<'ctx> Method<'ctx> { .chain(method.argument_types(db)) { let raw = context.llvm_type(db, layouts, typ); - let typ = context.argument_type(state, layouts, raw); + let typ = context.argument_type( + layouts.target_data, + state.config.target, + raw, + ); args.push(typ); } diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index 042bb59d..f9e1c0df 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -2062,8 +2062,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let mut layout = MethodLayout::new(); layout.returns = self.builder.context.return_type( - self.shared.state, - self.layouts, + self.layouts.target_data, + self.shared.state.config.target, reg_typ, ); @@ -2074,8 +2074,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { for reg in [ins.receiver].iter().chain(ins.arguments.iter()) { let raw = self.variable_types[reg]; let typ = self.builder.context.argument_type( - self.shared.state, - self.layouts, + self.layouts.target_data, + self.shared.state.config.target, raw, ); @@ -2130,8 +2130,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { layout.returns = ReturnType::Regular(reg_typ); layout.arguments.push(self.builder.context.argument_type( - self.shared.state, - self.layouts, + self.layouts.target_data, + self.shared.state.config.target, rec_typ, )); diff --git a/compiler/src/target.rs b/compiler/src/target.rs index be3873da..97ae4aea 100644 --- a/compiler/src/target.rs +++ b/compiler/src/target.rs @@ -7,7 +7,7 @@ use std::fmt; pub(crate) const MAC_SDK_VERSION: &str = "11.0.0"; /// The supported CPU architectures. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] pub(crate) enum Architecture { Amd64, Arm64, @@ -34,7 +34,7 @@ impl Architecture { } /// The supported operating systems. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] pub(crate) enum OperatingSystem { Freebsd, Linux, @@ -73,7 +73,7 @@ impl OperatingSystem { } /// The ABI to target. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] pub(crate) enum Abi { Native, Gnu, @@ -107,7 +107,7 @@ impl Abi { /// A type describing the compile target, such as the operating system and /// architecture. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Copy, Clone)] pub struct Target { pub(crate) arch: Architecture, pub(crate) os: OperatingSystem,