From 46844c5d6b4a04478625853fb1bc6bfcbd1fc4ac Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 26 Nov 2024 03:17:16 +0100 Subject: [PATCH] Start fixing struct sizing --- compiler/src/llvm/context.rs | 34 +++++++++++++++++-- compiler/src/llvm/layouts.rs | 64 ++++++++---------------------------- compiler/src/llvm/passes.rs | 37 ++++++--------------- compiler/src/target.rs | 6 ---- 4 files changed, 54 insertions(+), 87 deletions(-) diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 40a4badc..659a217c 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -200,6 +200,35 @@ impl Context { } } + pub(crate) fn argument_type<'ctx>( + &'ctx self, + 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() + } + } + pub(crate) fn method_return_type<'ctx>( &'ctx self, state: &State, @@ -238,15 +267,14 @@ impl Context { // 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 typ.is_struct_type() { - let typ = typ.into_struct_type(); - + 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()); } } else { diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index c3f5322b..60d56edb 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -351,11 +351,6 @@ impl<'ctx> Layouts<'ctx> { ) { let db = &state.db; - // TODO: make reusable - let struct_max = state.config.target.pass_struct_size(); - let struct_as_ptr = - state.config.target.pass_struct_arguments_as_pointers(); - for calls in mir.dynamic_calls.values() { for (method, _) in calls { let mut args: Vec = Vec::new(); @@ -366,17 +361,10 @@ impl<'ctx> Layouts<'ctx> { .iter() .chain(method.argument_types(db)) { - // TODO: make reusable - let typ = context.llvm_type(db, self, typ); + let raw = context.llvm_type(db, self, typ); + let typ = context.argument_type(state, self, raw); - if struct_as_ptr - && typ.is_struct_type() - && self.target_data.get_abi_size(&typ) > struct_max - { - args.push(context.pointer_type().into()); - } else { - args.push(typ.into()); - } + args.push(typ.into()); } let signature = @@ -401,11 +389,6 @@ impl<'ctx> Layouts<'ctx> { ) { let db = &state.db; - // TODO: make reusable - let struct_max = state.config.target.pass_struct_size(); - let struct_as_ptr = - state.config.target.pass_struct_arguments_as_pointers(); - for mir_class in mir.classes.values() { // Define the method signatures once (so we can cheaply retrieve // them whenever needed), and assign the methods to their method @@ -431,17 +414,10 @@ impl<'ctx> Layouts<'ctx> { .iter() .chain(method.argument_types(db)) { - // TODO: make reusable - let typ = context.llvm_type(db, self, typ); - - if struct_as_ptr - && typ.is_struct_type() - && self.target_data.get_abi_size(&typ) > struct_max - { - args.push(context.pointer_type().into()); - } else { - args.push(typ.into()); - } + let raw = context.llvm_type(db, self, typ); + let typ = context.argument_type(state, self, raw); + + args.push(typ.into()); } ( @@ -466,17 +442,10 @@ impl<'ctx> Layouts<'ctx> { context.method_return_type(state, self, method, &mut args); for &typ in method.argument_types(db) { - let typ = context.llvm_type(db, self, typ); + let raw = context.llvm_type(db, self, typ); + let typ = context.argument_type(state, self, raw); - // TODO: make reusable - if struct_as_ptr - && typ.is_struct_type() - && self.target_data.get_abi_size(&typ) > struct_max - { - args.push(context.pointer_type().into()); - } else { - args.push(typ.into()); - } + args.push(typ.into()); } let typ = ret @@ -497,17 +466,10 @@ impl<'ctx> Layouts<'ctx> { context.method_return_type(state, self, method, &mut args); for &typ in method.argument_types(db) { - let typ = context.llvm_type(db, self, typ); + let raw = context.llvm_type(db, self, typ); + let typ = context.argument_type(state, self, raw); - // TODO: make reusable - if struct_as_ptr - && typ.is_struct_type() - && self.target_data.get_abi_size(&typ) > struct_max - { - args.push(context.pointer_type().into()); - } else { - args.push(typ.into()); - } + args.push(typ.into()); } let variadic = method.is_variadic(db); diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index bc30e509..dd97e4ed 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -1966,29 +1966,15 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.register_type(ins.register), ); - // TODO: make reusable - let struct_max = - self.shared.state.config.target.pass_struct_size(); - let struct_as_ptr = self - .shared - .state - .config - .target - .pass_struct_arguments_as_pointers(); - for reg in [ins.receiver].iter().chain(ins.arguments.iter()) { - let typ = self.variable_types[reg]; - - if struct_as_ptr - && typ.is_struct_type() - && self.layouts.target_data.get_abi_size(&typ) - > struct_max - { - fn_args - .push(self.builder.context.pointer_type().into()); - } else { - fn_args.push(typ.into()); - } + let raw = self.variable_types[reg]; + let typ = self.builder.context.argument_type( + self.shared.state, + self.layouts, + raw, + ); + + fn_args.push(typ.into()); } // Load the method from the method table. @@ -2613,17 +2599,14 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { None }; - // TODO: make reusable let struct_max = self.shared.state.config.target.pass_struct_size(); - let struct_as_ptr = - self.shared.state.config.target.pass_struct_arguments_as_pointers(); for reg in receiver.iter().chain(arguments.iter()) { let typ = self.variable_types[reg]; let var = self.variables[reg]; - if struct_as_ptr - && typ.is_struct_type() + // TODO: cast to [2 x i64] when necessary + if typ.is_struct_type() && self.layouts.target_data.get_abi_size(&typ) > struct_max { args.push(var.into()); diff --git a/compiler/src/target.rs b/compiler/src/target.rs index 98d0ea4f..049a5a0b 100644 --- a/compiler/src/target.rs +++ b/compiler/src/target.rs @@ -248,12 +248,6 @@ impl Target { 16 } - /// Returns `true` if large struct arguments should be passed by reference - /// instead of by value. - pub(crate) fn pass_struct_arguments_as_pointers(&self) -> bool { - matches!(self.arch, Architecture::Arm64) - } - pub(crate) fn stack_pointer_register_name(&self) -> &str { match self.arch { Architecture::Amd64 => "rsp",