1use 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#[derive(PartialEq, Eq, Clone, Debug, Hash)]
25pub enum LlvmAttrValue {
26 Unit,
29 Int(u64),
32 Type(TypeHandle),
35 Str(String),
38}
39
40#[pliron_attr(name = "llvm.attributes", verifier = "succ")]
47#[derive(PartialEq, Eq, Clone, Debug, Hash, Default)]
48pub struct LlvmAttributesAttr(Vec<(String, LlvmAttrValue)>);
49
50#[attr_interface_impl]
52impl OutlinedAttr for LlvmAttributesAttr {}
53
54impl LlvmAttributesAttr {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn get(&self, name: &str) -> Option<&LlvmAttrValue> {
62 self.0.iter().find(|(n, _)| n == name).map(|(_, v)| v)
63 }
64
65 pub fn has(&self, name: &str) -> bool {
67 self.get(name).is_some()
68 }
69
70 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 pub fn remove(&mut self, name: &str) {
81 self.0.retain(|(n, _)| n != name);
82 }
83
84 pub fn is_empty(&self) -> bool {
86 self.0.is_empty()
87 }
88
89 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 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 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 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}