Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add test for inlinealways function attribute #426

Merged
merged 4 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04]
llvm-version:
- ["4.0", "4-0"]
- ["5.0", "5-0"]
Expand All @@ -29,13 +28,20 @@ jobs:
- ["14.0", "14-0"]
- ["15.0", "15-0"]
- ["16.0", "16-0"]
# only use ubuntu-22.04 for llvm 16 and higher
include:
- os: ubuntu-20.04
- os: ubuntu-22.04
llvm-version: ["16.0", "16-0"]
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
with:
version: ${{ matrix.llvm-version[0] }}
- name: llvm-config
run: llvm-config --version --bindir --libdir
- name: Build
run: cargo build --release --features llvm${{ matrix.llvm-version[1] }} --verbose
- name: Run tests
Expand Down
73 changes: 72 additions & 1 deletion src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,70 @@ use crate::types::AnyTypeEnum;
// REVIEW: Should Attributes have a 'ctx lifetime?
/// Functions, function parameters, and return types can have `Attribute`s to indicate
/// how they should be treated by optimizations and code generation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy)]
pub struct Attribute {
pub(crate) attribute: LLVMAttributeRef,
}

impl std::fmt::Debug for Attribute {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the Debug and PartialEq instance during debugging. I can remove them again, but then when something fails you only see an address, which is not helpful at all.

fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_string() {
return f
.debug_struct("Attribute::String")
.field("ptr", &self.attribute)
.field("kind_id", &self.get_string_kind_id())
.field("value", &self.get_string_value())
.finish();
}

if self.is_enum() {
return f
.debug_struct("Attribute::Enum")
.field("ptr", &self.attribute)
.field("kind_id", &self.get_enum_kind_id())
.field("value", &self.get_enum_value())
.finish();
}

if self.is_type() {
return f
.debug_struct("Attribute::Type")
.field("ptr", &self.attribute)
.field("kind_id", &self.get_enum_kind_id())
.field("value", &self.get_type_value())
.finish();
}

unreachable!(
"attribute at {:?} is not a string, enum or type attribute",
self.attribute
);
}
}

impl Eq for Attribute {}

impl PartialEq<Self> for Attribute {
fn eq(&self, other: &Self) -> bool {
if self.is_enum() && other.is_enum() {
return self.get_enum_kind_id() == other.get_enum_kind_id()
&& self.get_enum_value() == other.get_enum_value();
}

if self.is_string() && other.is_string() {
return self.get_string_kind_id() == other.get_string_kind_id()
&& self.get_string_value() == other.get_string_value();
}

if self.is_type() && other.is_type() {
return self.get_enum_kind_id() == other.get_enum_kind_id()
&& self.get_type_value() == other.get_type_value();
}

self.attribute == other.attribute
}
}

impl Attribute {
/// Creates a new `Attribute` from a raw pointer.
pub unsafe fn new(attribute: LLVMAttributeRef) -> Self {
Expand Down Expand Up @@ -95,6 +154,12 @@ impl Attribute {
unsafe { LLVMIsTypeAttribute(self.attribute) == 1 }
}

// private function to make code elsewhere easier
#[llvm_versions(4.0..12.0)]
fn is_type(self) -> bool {
false
}
Comment on lines +157 to +161
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a bit cheaty, but it's the simplest way I saw to keep code duplication to a minimum. Suggestions are welcome.


/// Gets the enum kind id associated with a builtin name.
///
/// # Example
Expand Down Expand Up @@ -280,6 +345,12 @@ impl Attribute {

unsafe { AnyTypeEnum::new(LLVMGetTypeAttributeValue(self.attribute)) }
}

// private function to make code elsewhere easier
#[llvm_versions(4.0..12.0)]
fn get_type_value(&self) {
unreachable!("not implemented in this version")
}
}

/// An `AttributeLoc` determines where on a function an attribute is assigned to.
Expand Down
56 changes: 40 additions & 16 deletions tests/all/test_attributes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::context::Context;
use inkwell::values::BasicValueEnum;
use inkwell::AddressSpace;

#[test]
fn test_enum_attribute_kinds() {
Expand Down Expand Up @@ -78,56 +80,78 @@ fn test_attributes_on_function_values() {
let context = Context::create();
let builder = context.create_builder();
let module = context.create_module("my_mod");
let void_type = context.void_type();
let i32_type = context.i32_type();
let fn_type = void_type.fn_type(&[i32_type.into()], false);
let i32_ptr_type = context.i32_type().ptr_type(AddressSpace::default());
let fn_type = i32_ptr_type.fn_type(&[i32_ptr_type.into()], false);
let fn_value = module.add_function("my_fn", fn_type, None);
let entry_bb = context.append_basic_block(fn_value, "entry");
let string_attribute = context.create_string_attribute("my_key", "my_val");
let alignstack_attribute = Attribute::get_named_enum_kind_id("alignstack");
let enum_attribute = context.create_enum_attribute(alignstack_attribute, 1);
Comment on lines -87 to -88
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alignstack is not a valid attibute on a function return type. At least for llvm 15, any value beside 0 (1 on line 88 here) leads to issues. The attribute is still applied when that value is 0. I think the value is only useful for some attributes (e.g. dereferencable(n), where n is the enum value).

to apply the noalias attribute instead, the function return type must be a pointer type.


let alwaysinline_attribute = Attribute::get_named_enum_kind_id("alwaysinline");
let function_enum_attribute = context.create_enum_attribute(alwaysinline_attribute, 0);

let inreg_attribute = Attribute::get_named_enum_kind_id("noalias");
let return_enum_attribute = context.create_enum_attribute(inreg_attribute, 0);

builder.position_at_end(entry_bb);
builder.build_return(None);
let null: BasicValueEnum = i32_ptr_type.const_null().into();
builder.build_return(Some(&null));

assert_eq!(fn_value.count_attributes(AttributeLoc::Return), 0);
assert_eq!(fn_value.count_attributes(AttributeLoc::Param(0)), 0);
assert_eq!(fn_value.attributes(AttributeLoc::Return), vec![]);
assert_eq!(fn_value.attributes(AttributeLoc::Param(0)), vec![]);

fn_value.remove_string_attribute(AttributeLoc::Return, "my_key"); // Noop
fn_value.remove_enum_attribute(AttributeLoc::Return, alignstack_attribute); // Noop

// define align 1 "my_key"="my_val" void @my_fn()
fn_value.remove_enum_attribute(AttributeLoc::Return, inreg_attribute); // Noop
fn_value.remove_enum_attribute(AttributeLoc::Function, alwaysinline_attribute); // Noop

// ; Function Attrs: alwaysinline
// define inreg "my_key"="my_val" void @my_fn(i32 "my_key"="my_val" %0) #0 {
// entry:
// ret void
// }
//
// attributes #0 = { alwaysinline }
fn_value.add_attribute(AttributeLoc::Return, string_attribute);
fn_value.add_attribute(AttributeLoc::Param(0), string_attribute); // Applied to 1st param
fn_value.add_attribute(AttributeLoc::Return, enum_attribute);
fn_value.add_attribute(AttributeLoc::Return, return_enum_attribute);
fn_value.add_attribute(AttributeLoc::Function, function_enum_attribute);

module.print_to_stderr();

module.verify().unwrap();

assert_eq!(fn_value.count_attributes(AttributeLoc::Return), 2);
assert_eq!(
fn_value.get_enum_attribute(AttributeLoc::Return, alignstack_attribute),
Some(enum_attribute)
fn_value.get_enum_attribute(AttributeLoc::Return, inreg_attribute),
Some(return_enum_attribute)
);
assert_eq!(
fn_value.get_string_attribute(AttributeLoc::Return, "my_key"),
Some(string_attribute)
);
assert_eq!(
fn_value.attributes(AttributeLoc::Return),
vec![enum_attribute, string_attribute]
vec![return_enum_attribute, string_attribute]
);

fn_value.remove_string_attribute(AttributeLoc::Return, "my_key");

assert_eq!(fn_value.count_attributes(AttributeLoc::Return), 1);
assert_eq!(fn_value.attributes(AttributeLoc::Return), vec![enum_attribute]);
assert_eq!(fn_value.attributes(AttributeLoc::Return), vec![return_enum_attribute]);
fn_value.remove_enum_attribute(AttributeLoc::Return, inreg_attribute);

fn_value.remove_enum_attribute(AttributeLoc::Return, alignstack_attribute);
assert_eq!(fn_value.count_attributes(AttributeLoc::Function), 1);
assert_eq!(
fn_value.attributes(AttributeLoc::Function),
vec![function_enum_attribute]
);
fn_value.remove_enum_attribute(AttributeLoc::Function, alwaysinline_attribute);

assert_eq!(fn_value.count_attributes(AttributeLoc::Function), 0);
assert_eq!(fn_value.count_attributes(AttributeLoc::Return), 0);
assert!(fn_value
.get_enum_attribute(AttributeLoc::Return, alignstack_attribute)
.get_enum_attribute(AttributeLoc::Return, inreg_attribute)
.is_none());
assert!(fn_value.get_string_attribute(AttributeLoc::Return, "my_key").is_none());
assert_eq!(fn_value.attributes(AttributeLoc::Function), vec![]);
Expand Down