Skip to content

Commit

Permalink
Add various attributes to LLVM functions
Browse files Browse the repository at this point in the history
Depending on how LLVM decides to optimize things, these attributes may
help improve code generation, though it's difficult to say for certain
how much at this stage.

See #595 for more details.

Changelog: performance
  • Loading branch information
yorickpeterse committed Nov 29, 2024
1 parent 7a9cad9 commit 9d9f949
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 13 deletions.
4 changes: 2 additions & 2 deletions compiler/src/llvm/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_> {
Expand Down
89 changes: 85 additions & 4 deletions compiler/src/llvm/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -95,6 +95,7 @@ impl<'a, 'ctx> Module<'a, 'ctx> {

pub(crate) fn add_method(
&self,
db: &Database,
name: &str,
method: MethodId,
) -> FunctionValue<'ctx> {
Expand All @@ -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) => {
Expand All @@ -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);
Expand All @@ -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
})
}
Expand Down
30 changes: 23 additions & 7 deletions compiler/src/llvm/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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];

Expand All @@ -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];

Expand All @@ -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];

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 9d9f949

Please sign in to comment.