Skip to main content

pliron_llvm/
llvm_attrs.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! LLVM [attributes](https://llvm.org/docs/LangRef.html#attributes).
5//! Not to be confused with pliron [Attribute][pliron::attribute::Attribute]s.
6
7use alloc::{string::String, vec::Vec};
8
9use pliron::{
10    builtin::attr_interfaces::OutlinedAttr,
11    combine::{self, Parser, choice, optional, token},
12    context::Context,
13    derive::{attr_interface_impl, pliron_attr},
14    irfmt::{
15        parsers::{delimited_list_parser, spaced, type_parser},
16        printers::quoted,
17    },
18    parsable::Parsable,
19    printable::{self, Printable},
20    r#type::TypeHandle,
21};
22
23/// The value of one LLVM attribute.
24#[derive(PartialEq, Eq, Clone, Debug, Hash)]
25pub enum LlvmAttrValue {
26    /// An enum attribute with no value, such as `nounwind`.
27    /// Printed as just `"nounwind"`.
28    Unit,
29    /// An enum attribute with an integer value, such as `alignstack(16)`.
30    /// Printed as `"alignstack" = 16`.
31    Int(u64),
32    /// An enum attribute with a type value, such as `byval(%struct.S)`.
33    /// Printed as `"byval" = builtin.integer i32`.
34    Type(TypeHandle),
35    /// A string attribute, such as `"target-cpu"="x86-64"`.
36    /// Printed as `"target-cpu" = "x86-64"`.
37    Str(String),
38}
39
40/// LLVM attributes at one attribute index.
41///
42/// An attribute index can be a parameter from 1 to N, or one of
43/// `LLVM_ATTRIBUTE_FUNCTION_INDEX` or `LLVM_ATTRIBUTE_RETURN_INDEX`.
44///
45/// Printed as `["nounwind", "alignstack" = 16, "target-cpu" = "x86-64"]`.
46#[pliron_attr(name = "llvm.attributes", verifier = "succ")]
47#[derive(PartialEq, Eq, Clone, Debug, Hash, Default)]
48pub struct LlvmAttributesAttr(Vec<(String, LlvmAttrValue)>);
49
50/// Always printed outlined.
51#[attr_interface_impl]
52impl OutlinedAttr for LlvmAttributesAttr {}
53
54impl LlvmAttributesAttr {
55    /// No attributes.
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// The value of the attribute named `name`, if it is present.
61    pub fn get(&self, name: &str) -> Option<&LlvmAttrValue> {
62        self.0.iter().find(|(n, _)| n == name).map(|(_, v)| v)
63    }
64
65    /// True if the attribute named `name` is present.
66    pub fn has(&self, name: &str) -> bool {
67        self.get(name).is_some()
68    }
69
70    /// Set the attribute named `name` to `value`, replacing any existing value.
71    pub fn set(&mut self, name: impl Into<String>, value: LlvmAttrValue) {
72        let name = name.into();
73        match self.0.iter_mut().find(|(n, _)| *n == name) {
74            Some(entry) => entry.1 = value,
75            None => self.0.push((name, value)),
76        }
77    }
78
79    /// Remove the attribute named `name`, if it is present.
80    pub fn remove(&mut self, name: &str) {
81        self.0.retain(|(n, _)| n != name);
82    }
83
84    /// True if there are no attributes.
85    pub fn is_empty(&self) -> bool {
86        self.0.is_empty()
87    }
88
89    /// Iterate over `(name, value)` pairs.
90    pub fn iter(&self) -> impl Iterator<Item = (&str, &LlvmAttrValue)> {
91        self.0.iter().map(|(name, value)| (name.as_str(), value))
92    }
93}
94
95impl Printable for LlvmAttributesAttr {
96    fn fmt(
97        &self,
98        ctx: &Context,
99        state: &printable::State,
100        f: &mut core::fmt::Formatter<'_>,
101    ) -> core::fmt::Result {
102        write!(f, "[")?;
103        for (idx, (name, value)) in self.0.iter().enumerate() {
104            if idx != 0 {
105                write!(f, ", ")?;
106            }
107            quoted(name).fmt(ctx, state, f)?;
108            match value {
109                LlvmAttrValue::Unit => (),
110                LlvmAttrValue::Int(i) => write!(f, " = {i}")?,
111                LlvmAttrValue::Type(ty) => write!(f, " = {}", ty.print(ctx, state))?,
112                LlvmAttrValue::Str(s) => {
113                    write!(f, " = ")?;
114                    quoted(s).fmt(ctx, state, f)?;
115                }
116            }
117        }
118        write!(f, "]")
119    }
120}
121
122impl Parsable for LlvmAttributesAttr {
123    type Arg = ();
124    type Parsed = Self;
125
126    fn parse<'a>(
127        state_stream: &mut pliron::parsable::StateStream<'a>,
128        _arg: Self::Arg,
129    ) -> pliron::parsable::ParseResult<'a, Self::Parsed> {
130        // No need to lookahead.
131        // A string value opens with `"`, an integer with a digit and a type with neither.
132        let value = choice!(
133            String::parser(()).map(LlvmAttrValue::Str),
134            u64::parser(()).map(LlvmAttrValue::Int),
135            type_parser().map(LlvmAttrValue::Type)
136        );
137        let entry = String::parser(()).and(
138            optional(combine::attempt(spaced(token('=')).with(value)))
139                .map(|value| value.unwrap_or(LlvmAttrValue::Unit)),
140        );
141
142        delimited_list_parser('[', ']', ',', entry)
143            .map(LlvmAttributesAttr)
144            .parse_stream(state_stream)
145            .into()
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use alloc::string::ToString;
152
153    use expect_test::expect;
154    use pliron::{
155        builtin::types::{IntegerType, Signedness},
156        parsable::parse_from_str,
157        result::ExpectOk,
158    };
159
160    use super::*;
161
162    /// A list with one entry of every value kind.
163    fn sample(ctx: &mut Context) -> LlvmAttributesAttr {
164        let i32_ty = IntegerType::get(ctx, 32, Signedness::Signless);
165        let mut attrs = LlvmAttributesAttr::new();
166        attrs.set("nounwind", LlvmAttrValue::Unit);
167        attrs.set("alignstack", LlvmAttrValue::Int(16));
168        attrs.set("byval", LlvmAttrValue::Type(i32_ty.into()));
169        attrs.set("target-cpu", LlvmAttrValue::Str("x86-64".to_string()));
170        attrs.set("no-jump-tables", LlvmAttrValue::Str(String::new()));
171        attrs
172    }
173
174    /// Print `attrs`, parse it back and check that nothing changed.
175    fn assert_roundtrips(ctx: &mut Context, attrs: LlvmAttributesAttr) {
176        let printed = attrs.disp(ctx).to_string();
177        let parsed = parse_from_str(LlvmAttributesAttr::parser(()), ctx, &printed).expect_ok(ctx);
178        assert_eq!(parsed, attrs, "round-trip mismatch for `{printed}`");
179    }
180
181    #[test]
182    fn test_llvm_attributes_attr_fmt() {
183        let ctx = &mut Context::default();
184        let attrs = sample(ctx);
185
186        expect![[
187            r#"["nounwind", "alignstack" = 16, "byval" = builtin.integer i32, "target-cpu" = "x86-64", "no-jump-tables" = ""]"#
188        ]]
189        .assert_eq(&attrs.disp(ctx).to_string());
190    }
191
192    #[test]
193    fn test_llvm_attributes_attr_roundtrip() {
194        let ctx = &mut Context::default();
195        assert_roundtrips(ctx, LlvmAttributesAttr::new());
196        let attrs = sample(ctx);
197        assert_roundtrips(ctx, attrs);
198    }
199
200    #[test]
201    fn test_llvm_attributes_attr_set_replaces_and_remove() {
202        let mut attrs = LlvmAttributesAttr::new();
203        attrs.set("alignstack", LlvmAttrValue::Int(8));
204        attrs.set("alignstack", LlvmAttrValue::Int(16));
205        assert_eq!(attrs.iter().count(), 1);
206        assert_eq!(attrs.get("alignstack"), Some(&LlvmAttrValue::Int(16)));
207
208        attrs.remove("alignstack");
209        assert!(attrs.is_empty());
210        assert!(!attrs.has("alignstack"));
211    }
212}