Skip to main content

pliron_llvm/
llvm_attrs_conversions.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Conversion of [LLVM attributes](crate::llvm_attrs) to and from LLVM-IR.
5
6use pliron::{std_deps::sync::LazyLock, utils::table::HMap};
7use thiserror::Error;
8
9use crate::llvm_sys::core::llvm_enum_attribute_kind;
10
11/// The kind of value held by an LLVM enum attribute.
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
13pub enum LlvmAttrValueKind {
14    /// No value, such as `nounwind`.
15    Enum,
16    /// An integer value, such as `alignstack(16)`.
17    Int,
18    /// A type value, such as `byval(%struct.S)`.
19    Type,
20    /// A constant range, such as `range(i32 0, 10)`. pliron does not model this.
21    ConstantRange,
22    /// A list of constant ranges, such as `initializes((0, 4))`. This isn't modelled yet.
23    ConstantRangeList,
24}
25
26/// Every enum attribute that LLVM defines in `llvm/IR/Attributes.td`, with its value kind.
27///
28/// LLVM's `Attribute::getNameFromAttrKind` is not in the C API.
29/// [llvm_enum_attribute_info] here provides the same functionality.
30///
31/// When this table does not match LLVM exactly:
32///   1. A name not defined by LLVM is left out of the reverse map.
33///      Since LLVM never reports that kind id, nothing is lost.
34///   2. A kind id not in this table makes [llvm_enum_attribute_info] return `None`.
35///      The attribute is later dropped with a warning.
36///
37/// Regenerate with:
38/// ```sh
39/// tr '\n' ' ' < llvm/include/llvm/IR/Attributes.td \
40///   | grep -oE 'def [A-Za-z0-9_]+ *: *(EnumAttr|IntAttr|TypeAttr|ConstantRangeAttr|ConstantRangeListAttr)<"[^"]+"' \
41///   | sed -E 's/def [A-Za-z0-9_]+ *: *([A-Za-z]+)Attr<"([^"]+)"/    ("\2", \1),/' \
42///   | LC_ALL=C sort -u
43/// ```
44const LLVM_ENUM_ATTRIBUTES: &[(&str, LlvmAttrValueKind)] = {
45    use LlvmAttrValueKind::*;
46    &[
47        ("align", Int),
48        ("alignstack", Int),
49        ("allocalign", Enum),
50        ("allockind", Int),
51        ("allocptr", Enum),
52        ("allocsize", Int),
53        ("alwaysinline", Enum),
54        ("builtin", Enum),
55        ("byref", Type),
56        ("byval", Type),
57        ("captures", Int),
58        ("cold", Enum),
59        ("convergent", Enum),
60        ("coro_elide_safe", Enum),
61        ("coro_only_destroy_when_complete", Enum),
62        ("dead_on_return", Int),
63        ("dead_on_unwind", Enum),
64        ("denormal_fpenv", Int),
65        ("dereferenceable", Int),
66        ("dereferenceable_or_null", Int),
67        ("disable_sanitizer_instrumentation", Enum),
68        ("elementtype", Type),
69        ("flatten", Enum),
70        ("fn_ret_thunk_extern", Enum),
71        ("hot", Enum),
72        ("hybrid_patchable", Enum),
73        ("immarg", Enum),
74        ("inalloca", Type),
75        ("initializes", ConstantRangeList),
76        ("inlinehint", Enum),
77        ("inreg", Enum),
78        ("jumptable", Enum),
79        ("memory", Int),
80        ("minsize", Enum),
81        ("mustprogress", Enum),
82        ("naked", Enum),
83        ("nest", Enum),
84        ("noalias", Enum),
85        ("nobuiltin", Enum),
86        ("nocallback", Enum),
87        ("nocf_check", Enum),
88        ("nocreateundeforpoison", Enum),
89        ("nodivergencesource", Enum),
90        ("noduplicate", Enum),
91        ("noext", Enum),
92        ("nofpclass", Int),
93        ("nofree", Enum),
94        ("noimplicitfloat", Enum),
95        ("noinline", Enum),
96        ("noipa", Enum),
97        ("nomerge", Enum),
98        ("nonlazybind", Enum),
99        ("nonnull", Enum),
100        ("nooutline", Enum),
101        ("noprofile", Enum),
102        ("norecurse", Enum),
103        ("noredzone", Enum),
104        ("noreturn", Enum),
105        ("nosanitize_bounds", Enum),
106        ("nosanitize_coverage", Enum),
107        ("nosync", Enum),
108        ("noundef", Enum),
109        ("nounwind", Enum),
110        ("null_pointer_is_valid", Enum),
111        ("optdebug", Enum),
112        ("optforfuzzing", Enum),
113        ("optnone", Enum),
114        ("optsize", Enum),
115        ("preallocated", Type),
116        ("presplitcoroutine", Enum),
117        ("range", ConstantRange),
118        ("readnone", Enum),
119        ("readonly", Enum),
120        ("returned", Enum),
121        ("returns_twice", Enum),
122        ("safestack", Enum),
123        ("sanitize_address", Enum),
124        ("sanitize_alloc_token", Enum),
125        ("sanitize_hwaddress", Enum),
126        ("sanitize_memory", Enum),
127        ("sanitize_memtag", Enum),
128        ("sanitize_numerical_stability", Enum),
129        ("sanitize_realtime", Enum),
130        ("sanitize_realtime_blocking", Enum),
131        ("sanitize_thread", Enum),
132        ("sanitize_type", Enum),
133        ("shadowcallstack", Enum),
134        ("signext", Enum),
135        ("skipprofile", Enum),
136        ("speculatable", Enum),
137        ("speculative_load_hardening", Enum),
138        ("sret", Type),
139        ("ssp", Enum),
140        ("sspreq", Enum),
141        ("sspstrong", Enum),
142        ("strictfp", Enum),
143        ("swiftasync", Enum),
144        ("swifterror", Enum),
145        ("swiftself", Enum),
146        ("uwtable", Int),
147        ("vscale_range", Int),
148        ("willreturn", Enum),
149        ("writable", Enum),
150        ("writeonly", Enum),
151        ("zeroext", Enum),
152    ]
153};
154
155/// Reverse of [llvm_enum_attribute_kind], over [LLVM_ENUM_ATTRIBUTES].
156static LLVM_ENUM_ATTRIBUTE_KIND_INFO: LazyLock<HMap<u32, (&'static str, LlvmAttrValueKind)>> =
157    LazyLock::new(|| {
158        LLVM_ENUM_ATTRIBUTES
159            .iter()
160            .filter_map(|(name, value_kind)| {
161                llvm_enum_attribute_kind(name).map(|kind| (kind, (*name, *value_kind)))
162            })
163            .collect()
164    });
165
166/// The name and value kind of the enum attribute `kind`.
167pub fn llvm_enum_attribute_info(kind: u32) -> Option<(&'static str, LlvmAttrValueKind)> {
168    LLVM_ENUM_ATTRIBUTE_KIND_INFO.get(&kind).copied()
169}
170
171/// Errors when LLVM attributes are converted to LLVM-IR.
172#[derive(Error, Debug)]
173pub enum ToLlvmAttrErr {
174    #[error(
175        "LLVM attribute \"{name}\" cannot hold the given value; expected value kind {value_kind:?}"
176    )]
177    ValueKindMismatch {
178        /// The name of the attribute.
179        name: String,
180        /// The kind of value that LLVM lets the attribute hold.
181        value_kind: LlvmAttrValueKind,
182    },
183}
184
185/// Conversion of LLVM attributes from LLVM-IR, a companion to [crate::from_llvm_ir].
186pub mod from_llvm_ir {
187    use pliron::{context::Context, result::Result};
188
189    use crate::{
190        from_llvm_ir::ConversionContext,
191        llvm_attrs::{LlvmAttrValue, LlvmAttributesAttr},
192        llvm_sys::core::{
193            LLVMAttribute, llvm_get_enum_attribute_kind, llvm_get_enum_attribute_value,
194            llvm_get_string_attribute_kind, llvm_get_string_attribute_value,
195            llvm_get_type_attribute_value, llvm_is_enum_attribute, llvm_is_string_attribute,
196            llvm_is_type_attribute,
197        },
198    };
199
200    use super::{LlvmAttrValueKind, llvm_enum_attribute_info};
201
202    /// Convert every LLVM attribute in `attrs` to an entry of an [LlvmAttributesAttr].
203    ///
204    /// An attribute with an unknown kind id is dropped with a log warning.
205    pub(crate) fn convert_llvm_attributes(
206        ctx: &mut Context,
207        cctx: &mut ConversionContext,
208        attrs: Vec<LLVMAttribute>,
209    ) -> Result<LlvmAttributesAttr> {
210        let mut converted = LlvmAttributesAttr::new();
211        for attr in attrs {
212            if llvm_is_string_attribute(attr) {
213                converted.set(
214                    llvm_get_string_attribute_kind(attr),
215                    LlvmAttrValue::Str(llvm_get_string_attribute_value(attr)),
216                );
217                continue;
218            }
219
220            let kind = llvm_get_enum_attribute_kind(attr);
221            let Some((name, value_kind)) = llvm_enum_attribute_info(kind) else {
222                log::warn!("Dropping LLVM attribute with unknown kind id {kind}");
223                continue;
224            };
225
226            let value = if llvm_is_type_attribute(attr) {
227                LlvmAttrValue::Type(crate::from_llvm_ir::convert_type(
228                    ctx,
229                    cctx,
230                    llvm_get_type_attribute_value(attr),
231                )?)
232            } else if llvm_is_enum_attribute(attr) {
233                if value_kind == LlvmAttrValueKind::Int {
234                    LlvmAttrValue::Int(llvm_get_enum_attribute_value(attr))
235                } else {
236                    LlvmAttrValue::Unit
237                }
238            } else {
239                // A constant-range attribute, such as `range` or `initializes`.
240                log::warn!("Dropping LLVM attribute \"{name}\", whose value is unsupported");
241                continue;
242            };
243            converted.set(name, value);
244        }
245        Ok(converted)
246    }
247}
248
249/// Conversion of LLVM attributes to LLVM-IR, a companion to [crate::to_llvm_ir].
250pub mod to_llvm_ir {
251    use pliron::{context::Context, input_err_noloc, result::Result};
252
253    use crate::{
254        llvm_attrs::{LlvmAttrValue, LlvmAttributesAttr},
255        llvm_sys::core::{
256            LLVM_ATTRIBUTE_FUNCTION_INDEX, LLVMAttribute, LLVMAttributeIndex, LLVMContext,
257            LLVMValue, llvm_create_enum_attribute, llvm_create_string_attribute,
258            llvm_create_type_attribute,
259        },
260        to_llvm_ir::TypeConversionContext,
261    };
262
263    use super::{
264        LlvmAttrValueKind, ToLlvmAttrErr, llvm_enum_attribute_info, llvm_enum_attribute_kind,
265    };
266
267    /// Add `attrs` to `value`, at `value`'s function index.
268    ///
269    /// `add_attribute` is used to select b/w global values and call sites.
270    ///
271    /// Drops (with a log warning) an attribute with a name that is not defined in LLVM.
272    pub(crate) fn add_function_attributes(
273        ctx: &Context,
274        llvm_ctx: &LLVMContext,
275        tcctx: &mut TypeConversionContext,
276        value: LLVMValue,
277        attrs: &LlvmAttributesAttr,
278        add_attribute: fn(LLVMValue, LLVMAttributeIndex, LLVMAttribute),
279    ) -> Result<()> {
280        for (name, attr_value) in attrs.iter() {
281            // A string attribute names itself, so it needs no enum attribute kind.
282            if let LlvmAttrValue::Str(s) = attr_value {
283                let attr = llvm_create_string_attribute(llvm_ctx, name, s);
284                add_attribute(value, LLVM_ATTRIBUTE_FUNCTION_INDEX, attr);
285                continue;
286            }
287
288            // Both LLVM and the value-kind table must know `name`.
289            let info = llvm_enum_attribute_kind(name).and_then(|kind| {
290                llvm_enum_attribute_info(kind).map(|(_, value_kind)| (kind, value_kind))
291            });
292            let Some((kind, value_kind)) = info else {
293                log::warn!("Dropping LLVM attribute \"{name}\", unknown to this LLVM version");
294                continue;
295            };
296
297            // Check the value kind to avoid LLVM abort on assertions-enabled builds.
298            let attr = match (value_kind, attr_value) {
299                (LlvmAttrValueKind::Enum, LlvmAttrValue::Unit) => {
300                    llvm_create_enum_attribute(llvm_ctx, kind, 0)
301                }
302                (LlvmAttrValueKind::Int, LlvmAttrValue::Int(i)) => {
303                    llvm_create_enum_attribute(llvm_ctx, kind, *i)
304                }
305                (LlvmAttrValueKind::Type, LlvmAttrValue::Type(ty)) => {
306                    let llvm_ty = crate::to_llvm_ir::convert_type(ctx, llvm_ctx, tcctx, *ty)?;
307                    llvm_create_type_attribute(llvm_ctx, kind, llvm_ty)
308                }
309                (LlvmAttrValueKind::ConstantRange | LlvmAttrValueKind::ConstantRangeList, _) => {
310                    log::warn!("Dropping LLVM attribute \"{name}\", whose value is unsupported");
311                    continue;
312                }
313                _ => {
314                    return input_err_noloc!(ToLlvmAttrErr::ValueKindMismatch {
315                        name: name.to_string(),
316                        value_kind,
317                    });
318                }
319            };
320            add_attribute(value, LLVM_ATTRIBUTE_FUNCTION_INDEX, attr);
321        }
322        Ok(())
323    }
324}