diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index e34967ce..0520792c 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -37,10 +37,10 @@ impl Context { self.inner.create_type_attribute(id, typ) } - pub(crate) fn enum_attribute(&self, name: &str, value: u64) -> Attribute { + pub(crate) fn attribute(&self, name: &str) -> Attribute { let id = Attribute::get_named_enum_kind_id(name); - self.inner.create_enum_attribute(id, value) + self.inner.create_enum_attribute(id, 0) } pub(crate) fn pointer_type(&self) -> PointerType<'_> { diff --git a/compiler/src/llvm/module.rs b/compiler/src/llvm/module.rs index 6a8ec238..0f90429b 100644 --- a/compiler/src/llvm/module.rs +++ b/compiler/src/llvm/module.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use std::ops::Deref; use std::path::Path; use types::module_name::ModuleName; -use types::{CallConvention, MethodId}; +use types::{Block, CallConvention, Database, MethodId}; /// A wrapper around an LLVM Module that provides some additional methods. pub(crate) struct Module<'a, 'ctx> { @@ -95,6 +95,7 @@ impl<'a, 'ctx> Module<'a, 'ctx> { pub(crate) fn add_method( &self, + db: &Database, name: &str, method: MethodId, ) -> FunctionValue<'ctx> { @@ -113,6 +114,9 @@ impl<'a, 'ctx> Module<'a, 'ctx> { fn_val.set_call_conventions(conv); + // The index of the first user-defined argument. + let mut first_arg = if method.is_instance(db) { 1 } else { 0 }; + for (idx, &arg) in info.arguments.iter().enumerate() { match arg { ArgumentType::StructValue(t) => { @@ -122,12 +126,16 @@ impl<'a, 'ctx> Module<'a, 'ctx> { ); } ArgumentType::StructReturn(t) => { + // For struct returns we use a hidden first argument, so + // we need to shift the first user-defined argument to + // the right. + first_arg += 1; + let loc = AttributeLoc::Param(0); let sret = self.context.type_attribute("sret", t.into()); - let noalias = self.context.enum_attribute("noalias", 0); - let nocapt = - self.context.enum_attribute("nocapture", 0); + let noalias = self.context.attribute("noalias"); + let nocapt = self.context.attribute("nocapture"); fn_val.add_attribute(loc, sret); fn_val.add_attribute(loc, noalias); @@ -137,6 +145,79 @@ impl<'a, 'ctx> Module<'a, 'ctx> { } } + // Add various attributes to the function arguments, in order to + // (hopefully) produce better optimized code. + if method.is_async(db) { + // async methods use the signature `fn(message)` where the + // message is unpacked into the individual arguments. + let loc = AttributeLoc::Param(0); + let ro = self.context.attribute("readonly"); + let noal = self.context.attribute("noalias"); + let nocap = self.context.attribute("nocapture"); + + fn_val.add_attribute(loc, ro); + fn_val.add_attribute(loc, noal); + fn_val.add_attribute(loc, nocap); + } else { + let llvm_args = fn_typ.get_param_types(); + + for (idx, &typ) in method.argument_types(db).enumerate() { + let idx = idx + first_arg; + let loc = AttributeLoc::Param(idx as _); + let ltyp = llvm_args[idx]; + + if ltyp.is_pointer_type() { + if !typ.allow_mutating(db) { + let attr = self.context.attribute("readonly"); + + fn_val.add_attribute(loc, attr); + } + + if typ.is_uni(db) { + let attr = self.context.attribute("noalias"); + + fn_val.add_attribute(loc, attr); + } else if typ.is_ref_or_mut(db) { + // We never release memory through borrows. + let attr = self.context.attribute("nofree"); + + fn_val.add_attribute(loc, attr); + } + + // Inko values passed as a pointer (e.g. a borrow) are + // never NULL. + if !typ.is_pointer(db) { + let attr = self.context.attribute("nonnull"); + + fn_val.add_attribute(loc, attr); + } + } + + // Inko's own types are always initialized. + if !typ.is_foreign_type(db) { + let attr = self.context.attribute("noundef"); + + fn_val.add_attribute(loc, attr); + } + } + } + + // Inko doesn't use unwinding, doesn't support it, nor do we support + // binding/dealing with C++. + let nounwind = self.context.attribute("nounwind"); + + fn_val.add_attribute(AttributeLoc::Function, nounwind); + + if method.return_type(db).is_never(db) { + let cold = self.context.attribute("cold"); + let noin = self.context.attribute("noinline"); + let noret = self.context.attribute("noreturn"); + + fn_val.add_attribute(AttributeLoc::Function, cold); + fn_val.add_attribute(AttributeLoc::Function, noin); + fn_val.add_attribute(AttributeLoc::Function, noret); + } + fn_val }) } diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index c5af272b..81fcc53d 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -941,8 +941,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { module: &'module mut Module<'shared, 'ctx>, method: &'shared Method, ) -> Self { - let function = - module.add_method(&shared.names.methods[&method.id], method.id); + let name = &shared.names.methods[&method.id]; + let function = module.add_method(&shared.state.db, name, method.id); let builder = Builder::new(module.context, function); let entry_block = builder.add_block(); @@ -1820,7 +1820,11 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.set_debug_location(ins.location); let name = ins.method.name(&self.shared.state.db); - let fn_val = self.module.add_method(name, ins.method); + let fn_val = self.module.add_method( + &self.shared.state.db, + name, + ins.method, + ); let kind = CallKind::Direct(fn_val); let layout = &self.layouts.methods[ins.method.0 as usize]; @@ -1830,7 +1834,11 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.set_debug_location(ins.location); let func_name = &self.shared.names.methods[&ins.method]; - let func = self.module.add_method(func_name, ins.method); + let func = self.module.add_method( + &self.shared.state.db, + func_name, + ins.method, + ); let kind = CallKind::Direct(func); let layout = &self.layouts.methods[ins.method.0 as usize]; @@ -1840,7 +1848,11 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.set_debug_location(ins.location); let name = &self.shared.names.methods[&ins.method]; - let func = self.module.add_method(name, ins.method); + let func = self.module.add_method( + &self.shared.state.db, + name, + ins.method, + ); let kind = CallKind::Direct(func); let layout = &self.layouts.methods[ins.method.0 as usize]; @@ -2093,7 +2105,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let method_name = &self.shared.names.methods[&ins.method]; let method = self .module - .add_method(method_name, ins.method) + .add_method(&self.shared.state.db, method_name, ins.method) .as_global_value() .as_pointer_value() .into(); @@ -2282,7 +2294,11 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { Instruction::MethodPointer(ins) => { let reg_var = self.variables[&ins.register]; let func_name = &self.shared.names.methods[&ins.method]; - let func = self.module.add_method(func_name, ins.method); + let func = self.module.add_method( + &self.shared.state.db, + func_name, + ins.method, + ); let ptr = func.as_global_value().as_pointer_value(); self.builder.store(reg_var, ptr);