Skip to main content

pliron_llvm/
metadata_conversions.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Conversion of [LLVM metadata](crate::metadata) to and from LLVM-IR.
5
6/// Conversion of LLVM metadata from LLVM-IR, a companion to [crate::from_llvm_ir].
7pub mod from_llvm_ir {
8    use alloc::{
9        string::{String, ToString},
10        vec,
11        vec::Vec,
12    };
13
14    use llvm_sys::debuginfo::LLVMMetadataKind;
15    use pliron::{
16        builtin::ops::ModuleOp,
17        context::{Context, Ptr},
18        input_error_noloc,
19        operation::Operation,
20        result::Result,
21        utils::table::{HMap, HSet},
22    };
23    use thiserror::Error;
24
25    use crate::{
26        from_llvm_ir::{ConversionContext, const_llvm_value_to_attr},
27        llvm_sys::core::{
28            LLVMMetadata, LLVMModule, LLVMValue, llvm_get_md_kind_id_in_module,
29            llvm_get_md_node_operands, llvm_get_md_string, llvm_get_metadata_kind,
30            llvm_get_named_metadata_operands, llvm_global_copy_all_metadata,
31            llvm_instruction_get_all_metadata_other_than_debug_loc, llvm_md_node_in_module,
32            llvm_metadata_as_value_in_module, llvm_named_metadata_names,
33            llvm_print_module_to_string, llvm_print_value_to_string, llvm_value_as_metadata,
34        },
35        metadata::{
36            MdAttachmentsAttr, MdNodeAttr, MdNodeId, MdOperandAttr, MdTableAttr, NamedMdAttr,
37            set_attachments, set_metadata_table, set_named_metadata,
38        },
39    };
40
41    /// State for converting LLVM metadata
42    #[derive(Default)]
43    pub(crate) struct MdConversionContext {
44        /// Already converted metadata nodes, mapped to their entry in [Self::table],
45        /// or to `None` if the node is one we cannot represent and dropped.
46        node_map: HMap<LLVMMetadata, Option<MdNodeId>>,
47        /// The module's metadata table, built up as metadata is converted.
48        table: MdTableAttr,
49        /// Metadata kind ids mapped to their [names](md_kind_name).
50        kind_names: HMap<u32, String>,
51        /// Whether the module's textual form has been scanned for metadata kind names.
52        kind_names_scraped: bool,
53    }
54
55    /// Metadata conversion errors.
56    #[derive(Error, Debug)]
57    pub enum MdConversionErr {
58        #[error("Cannot determine the name of metadata kind id {0}")]
59        UnknownKind(u32),
60    }
61
62    /// LLVM's metadata kind for an instruction's debug location.
63    pub(crate) const MD_KIND_DBG: &str = "dbg";
64
65    /// LLVM metadata kind names that LLVM pre-registers in every `LLVMContext`.
66    /// A stale list is not incorrect, it only costs a fallback to [scrape_md_kind_names].
67    const FIXED_MD_KIND_NAMES: &[&str] = &[
68        "dbg",
69        "tbaa",
70        "prof",
71        "fpmath",
72        "range",
73        "tbaa.struct",
74        "invariant.load",
75        "alias.scope",
76        "noalias",
77        "nontemporal",
78        "llvm.mem.parallel_loop_access",
79        "nonnull",
80        "dereferenceable",
81        "dereferenceable_or_null",
82        "make.implicit",
83        "unpredictable",
84        "invariant.group",
85        "align",
86        "llvm.loop",
87        "type",
88        "section_prefix",
89        "absolute_symbol",
90        "associated",
91        "callees",
92        "irr_loop",
93        "llvm.access.group",
94        "callback",
95        "llvm.preserve.access.index",
96        "vcall_visibility",
97        "noundef",
98        "annotation",
99        "nosanitize",
100        "func_sanitize",
101        "exclude",
102        "memprof",
103        "callsite",
104        "kcfi_type",
105        "pcsections",
106        "DIAssignID",
107        "coro.outside.frame",
108        "mmra",
109        "noalias.addrspace",
110        "callee_type",
111        "nofree",
112        "captures",
113        "alloc_token",
114        "implicit.ref",
115    ];
116
117    /// Collect every `!name` token that could be a metadata kind name from the textual
118    /// form of a module.
119    ///
120    /// The C-API maps a metadata kind name to its id but not the other way round,
121    /// So for kinds that LLVM doesn't pre-register the name can only be recovered
122    /// from the module's printed form.
123    ///
124    /// The scan over-approximates: a token that isn't a kind name just registers
125    /// an unused kind, which changes nothing about the module.
126    fn scrape_md_kind_names(module_text: &str) -> Vec<String> {
127        fn is_name_start(c: u8) -> bool {
128            c.is_ascii_alphabetic() || matches!(c, b'-' | b'$' | b'.' | b'_')
129        }
130        fn is_name_char(c: u8) -> bool {
131            c.is_ascii_alphanumeric() || matches!(c, b'-' | b'$' | b'.' | b'_')
132        }
133        fn hex_digit(c: Option<&u8>) -> Option<u8> {
134            c.and_then(|c| (*c as char).to_digit(16)).map(|d| d as u8)
135        }
136
137        let text = module_text.as_bytes();
138        let mut seen = HSet::default();
139        let mut names = vec![];
140        let mut idx = 0;
141        while idx < text.len() {
142            if text[idx] != b'!' {
143                idx += 1;
144                continue;
145            }
146            idx += 1;
147            // Undo the escaping LLVM's printer applies to a metadata name: a name
148            // character stands for itself and every other byte is printed as `\XX`.
149            let mut name = vec![];
150            while idx < text.len() {
151                let c = text[idx];
152                if is_name_char(c) && (!name.is_empty() || is_name_start(c)) {
153                    name.push(c);
154                    idx += 1;
155                } else if c == b'\\'
156                    && let (Some(hi), Some(lo)) =
157                        (hex_digit(text.get(idx + 1)), hex_digit(text.get(idx + 2)))
158                {
159                    name.push((hi << 4) | lo);
160                    idx += 3;
161                } else {
162                    break;
163                }
164            }
165            // A name whose bytes aren't UTF-8 has no [String] counterpart to register.
166            if let Ok(name) = String::from_utf8(name)
167                && !name.is_empty()
168                && seen.insert(name.clone())
169            {
170                names.push(name);
171            }
172        }
173        names
174    }
175
176    /// The name of the metadata kind `kind_id` in `module`'s context.
177    pub(crate) fn md_kind_name(
178        cctx: &mut ConversionContext,
179        module: &LLVMModule,
180        kind_id: u32,
181    ) -> Result<String> {
182        if cctx.md.kind_names.is_empty() {
183            for name in FIXED_MD_KIND_NAMES {
184                let id = llvm_get_md_kind_id_in_module(module, name);
185                cctx.md
186                    .kind_names
187                    .entry(id)
188                    .or_insert_with(|| name.to_string());
189            }
190        }
191
192        if let Some(name) = cctx.md.kind_names.get(&kind_id) {
193            return Ok(name.clone());
194        }
195
196        // An id we don't know: recover all names in use from the module's printed form.
197        if !cctx.md.kind_names_scraped {
198            cctx.md.kind_names_scraped = true;
199            let module_text = llvm_print_module_to_string(module)
200                .ok_or_else(|| input_error_noloc!(MdConversionErr::UnknownKind(kind_id)))?;
201            for name in scrape_md_kind_names(&module_text) {
202                let id = llvm_get_md_kind_id_in_module(module, &name);
203                cctx.md.kind_names.entry(id).or_insert(name);
204            }
205        }
206
207        cctx.md
208            .kind_names
209            .get(&kind_id)
210            .cloned()
211            .ok_or_else(|| input_error_noloc!(MdConversionErr::UnknownKind(kind_id)))
212    }
213
214    /// Convert an LLVM metadata node, and everything it refers to, into entries of the
215    /// module's metadata table, returning the [MdNodeId] of `md` itself.
216    fn convert_md_node(
217        ctx: &Context,
218        cctx: &mut ConversionContext,
219        module: &LLVMModule,
220        md: LLVMMetadata,
221    ) -> Result<Option<MdNodeId>> {
222        if let Some(id) = cctx.md.node_map.get(&md) {
223            return Ok(*id);
224        }
225
226        // Metadata we have no representation for is dropped.
227        let kind = llvm_get_metadata_kind(md);
228        if !matches!(kind, LLVMMetadataKind::LLVMMDTupleMetadataKind) {
229            log::warn!("Dropping unsupported metadata of kind {kind:?}");
230            cctx.md.node_map.insert(md, None);
231            return Ok(None);
232        }
233
234        // Reserve this node's id before converting its operands: metadata nodes are
235        // commonly self referential (`!0 = distinct !{!0, ...}`).
236        let id = cctx.md.table.reserve();
237        cctx.md.node_map.insert(md, Some(id));
238
239        let md_val = llvm_metadata_as_value_in_module(module, md);
240        let llvm_operands = llvm_get_md_node_operands(md_val);
241        let mut operands = Vec::with_capacity(llvm_operands.len());
242        for operand in &llvm_operands {
243            // If any operand cannot be represented, we drop the entire node.
244            // (missing operands may make the node inconsistent with its semantics).
245            let Some(operand) = convert_md_operand(ctx, cctx, module, *operand)? else {
246                // LLVM prints an unnamed node as `<0x...> = !{...}`.
247                // We only want its definition for warning.
248                let printed = llvm_print_value_to_string(md_val).unwrap_or_default();
249                let printed = printed.split_once(" = ").map_or(&*printed, |(_, def)| def);
250                log::warn!("Dropping metadata node {printed} with an operand we cannot represent");
251                cctx.md.node_map.insert(md, None);
252                // The id reserved above goes unused; its empty table entry is harmless.
253                return Ok(None);
254            };
255            operands.push(operand);
256        }
257
258        // The C-API can neither tell us whether a node is `distinct` nor create a distinct
259        // node directly. Uniquing a node with the same operands answers the question: for a
260        // uniqued node LLVM hands back the very same node, for a distinct one it cannot.
261        let llvm_md_operands: Vec<_> = llvm_operands
262            .iter()
263            .map(|operand| operand.map(llvm_value_as_metadata))
264            .collect();
265        let distinct = llvm_md_node_in_module(module, &llvm_md_operands) != md;
266
267        cctx.md.table.set(
268            id,
269            if distinct {
270                MdNodeAttr::new_distinct_tuple(operands)
271            } else {
272                MdNodeAttr::new_tuple(operands)
273            },
274        );
275
276        Ok(Some(id))
277    }
278
279    /// Convert one operand of an LLVM metadata node. An `operand` of `None` is LLVM's
280    /// `null` operand; a `None` result is an operand that cannot be represented.
281    fn convert_md_operand(
282        ctx: &Context,
283        cctx: &mut ConversionContext,
284        module: &LLVMModule,
285        operand: Option<LLVMValue>,
286    ) -> Result<Option<MdOperandAttr>> {
287        let Some(val) = operand else {
288            return Ok(Some(MdOperandAttr::Null));
289        };
290
291        // A constant operand comes back as the constant itself and anything else as a value
292        // wrapping metadata; going back to metadata classifies both uniformly.
293        let md = llvm_value_as_metadata(val);
294        match llvm_get_metadata_kind(md) {
295            LLVMMetadataKind::LLVMMDStringMetadataKind => match llvm_get_md_string(val) {
296                Some(s) => Ok(Some(MdOperandAttr::String(s))),
297                None => {
298                    log::warn!("Dropping metadata string operand whose contents aren't UTF-8");
299                    Ok(None)
300                }
301            },
302            LLVMMetadataKind::LLVMMDTupleMetadataKind => {
303                Ok(convert_md_node(ctx, cctx, module, md)?.map(MdOperandAttr::Node))
304            }
305            LLVMMetadataKind::LLVMConstantAsMetadataMetadataKind => {
306                match const_llvm_value_to_attr(ctx, cctx, val)? {
307                    Some(attr) => Ok(Some(MdOperandAttr::Constant(attr))),
308                    None => {
309                        log::warn!(
310                            "Dropping unsupported constant metadata operand {}",
311                            llvm_print_value_to_string(val).unwrap_or_default()
312                        );
313                        Ok(None)
314                    }
315                }
316            }
317            kind => {
318                log::warn!("Dropping unsupported metadata operand of kind {kind:?}");
319                Ok(None)
320            }
321        }
322    }
323
324    /// Attach the metadata in `entries` (kind id, node) to the pliron [Operation] `m_op`.
325    fn convert_md_attachments(
326        ctx: &Context,
327        cctx: &mut ConversionContext,
328        module: &LLVMModule,
329        entries: Vec<(u32, LLVMMetadata)>,
330        m_op: Ptr<Operation>,
331    ) -> Result<()> {
332        if entries.is_empty() {
333            return Ok(());
334        }
335        let mut attachments = MdAttachmentsAttr::new();
336        for (kind_id, md) in entries {
337            let kind = md_kind_name(cctx, module, kind_id)?;
338            if kind == MD_KIND_DBG {
339                // A debug location. pliron has its own [Location](pliron::location::Location).
340                continue;
341            }
342            let Some(node) = convert_md_node(ctx, cctx, module, md)? else {
343                log::warn!("Dropping metadata attached under kind \"{kind}\"");
344                continue;
345            };
346            attachments.set(kind, node);
347        }
348        if !attachments.is_empty() {
349            set_attachments(ctx, m_op, attachments);
350        }
351        Ok(())
352    }
353
354    /// Convert the metadata attached to the LLVM instruction `inst`.
355    pub(crate) fn convert_instruction_metadata(
356        ctx: &Context,
357        cctx: &mut ConversionContext,
358        module: &LLVMModule,
359        inst: LLVMValue,
360        m_inst: Ptr<Operation>,
361    ) -> Result<()> {
362        let entries = llvm_instruction_get_all_metadata_other_than_debug_loc(inst);
363        convert_md_attachments(ctx, cctx, module, entries, m_inst)
364    }
365
366    /// Convert the metadata attached to an LLVM global object.
367    pub(crate) fn convert_global_object_metadata(
368        ctx: &Context,
369        cctx: &mut ConversionContext,
370        module: &LLVMModule,
371        global: LLVMValue,
372        m_op: Ptr<Operation>,
373    ) -> Result<()> {
374        let entries = llvm_global_copy_all_metadata(global);
375        convert_md_attachments(ctx, cctx, module, entries, m_op)
376    }
377
378    /// Convert the module's named metadata (`!llvm.module.flags = !{!0, !1}`).
379    fn convert_named_metadata(
380        ctx: &Context,
381        cctx: &mut ConversionContext,
382        module: &LLVMModule,
383    ) -> Result<NamedMdAttr> {
384        let mut named = NamedMdAttr::new();
385        for name in llvm_named_metadata_names(module) {
386            for operand in llvm_get_named_metadata_operands(module, &name) {
387                let md = llvm_value_as_metadata(operand);
388                let Some(node) = convert_md_node(ctx, cctx, module, md)? else {
389                    log::warn!("Dropping an operand of named metadata \"{name}\"");
390                    continue;
391                };
392                named.push(name.clone(), node);
393            }
394        }
395        Ok(named)
396    }
397
398    /// Attach the module's metadata to `module_op`.
399    ///
400    /// Must be called after the module's functions have been converted, since their
401    /// instructions are what put most nodes in the table.
402    pub(crate) fn convert_module_metadata(
403        ctx: &Context,
404        cctx: &mut ConversionContext,
405        module: &LLVMModule,
406        module_op: ModuleOp,
407    ) -> Result<()> {
408        let named_md = convert_named_metadata(ctx, cctx, module)?;
409        if !named_md.is_empty() {
410            set_named_metadata(ctx, module_op, named_md);
411        }
412        if !cctx.md.table.is_empty() {
413            set_metadata_table(ctx, module_op, cctx.md.table.clone());
414        }
415        Ok(())
416    }
417}
418
419/// Conversion of LLVM metadata to LLVM-IR, a companion to [crate::to_llvm_ir].
420pub mod to_llvm_ir {
421    use alloc::{string::ToString, vec::Vec};
422
423    use pliron::{
424        attribute::attr_cast,
425        builtin::ops::ModuleOp,
426        context::{Context, Ptr},
427        input_err_noloc, input_error_noloc,
428        operation::Operation,
429        printable::Printable,
430        result::Result,
431        utils::table::{HMap, HSet},
432    };
433    use thiserror::Error;
434
435    use crate::{
436        llvm_sys::core::{
437            LLVMContext, LLVMMetadata, LLVMValue, llvm_add_named_metadata_operand,
438            llvm_get_md_kind_id_in_context, llvm_global_set_metadata, llvm_is_a,
439            llvm_md_node_in_context2, llvm_md_string_in_context2, llvm_metadata_as_value,
440            llvm_metadata_replace_all_uses_with, llvm_set_metadata, llvm_temporary_md_node,
441            llvm_value_as_metadata,
442        },
443        metadata::{
444            MdNodeId, MdOperandAttr, MdTableAttr, get_attachments, get_metadata_table,
445            get_named_metadata,
446        },
447        to_llvm_ir::{AttrToLLVMConst, ConversionContext},
448    };
449
450    /// State for converting metadata to LLVM.
451    #[derive(Default)]
452    pub(crate) struct MdConversionContext {
453        // The module's metadata table, that metadata references resolve against.
454        table: MdTableAttr,
455        // Metadata nodes that have already been built.
456        node_map: HMap<MdNodeId, LLVMMetadata>,
457        // Temporary nodes standing in for nodes that are still being built.
458        temporaries: HMap<MdNodeId, LLVMMetadata>,
459        // Metadata nodes currently being built, to detect cyclic references.
460        in_progress: HSet<MdNodeId>,
461    }
462
463    /// Metadata conversion errors.
464    #[derive(Error, Debug)]
465    pub enum MdToLLVMErr {
466        #[error("Metadata node #{0} is not in the module's metadata table")]
467        DanglingNodeRef(MdNodeId),
468        #[error("Metadata operand {0} is not convertible to an LLVM constant")]
469        OperandNotConst(String),
470        #[error(
471            "Metadata node #{0} is `distinct`, but the LLVM C-API can only create a distinct \
472             node that refers to itself"
473        )]
474        UnrepresentableDistinct(MdNodeId),
475    }
476
477    /// Build the LLVM metadata node for entry `id` of the module's metadata table,
478    /// building whatever it refers to along the way.
479    fn convert_md_node(
480        ctx: &Context,
481        llvm_ctx: &LLVMContext,
482        cctx: &mut ConversionContext,
483        id: MdNodeId,
484    ) -> Result<LLVMMetadata> {
485        if let Some(md) = cctx.md.node_map.get(&id) {
486            return Ok(*md);
487        }
488        // Metadata nodes may be cyclic (`!0 = distinct !{!0, ...}`), which the C-API can only
489        // express by creating a temporary node, referring to that, and then replacing it with
490        // the real node.
491        if cctx.md.in_progress.contains(&id) {
492            // A back edge: refer to a temporary that is replaced with the real node once
493            // that node has been built.
494            let temp = *cctx
495                .md
496                .temporaries
497                .entry(id)
498                .or_insert_with(|| llvm_temporary_md_node(llvm_ctx, &[]));
499            return Ok(temp);
500        }
501
502        let node = cctx
503            .md
504            .table
505            .get(id)
506            .cloned()
507            .ok_or_else(|| input_error_noloc!(MdToLLVMErr::DanglingNodeRef(id)))?;
508
509        cctx.md.in_progress.insert(id);
510        let mut operands = Vec::with_capacity(node.operands().len());
511        for operand in node.operands() {
512            operands.push(convert_md_operand(ctx, llvm_ctx, cctx, operand)?);
513        }
514        cctx.md.in_progress.remove(&id);
515
516        let md = llvm_md_node_in_context2(llvm_ctx, &operands);
517        if let Some(temp) = cctx.md.temporaries.remove(&id) {
518            llvm_metadata_replace_all_uses_with(temp, md);
519        }
520        cctx.md.node_map.insert(id, md);
521
522        Ok(md)
523    }
524
525    /// Build the LLVM metadata for one operand of a metadata node.
526    fn convert_md_operand(
527        ctx: &Context,
528        llvm_ctx: &LLVMContext,
529        cctx: &mut ConversionContext,
530        operand: &MdOperandAttr,
531    ) -> Result<Option<LLVMMetadata>> {
532        let md = match operand {
533            MdOperandAttr::Null => None,
534            MdOperandAttr::String(s) => Some(llvm_md_string_in_context2(llvm_ctx, s)),
535            MdOperandAttr::Node(id) => Some(convert_md_node(ctx, llvm_ctx, cctx, *id)?),
536            MdOperandAttr::Constant(attr) => {
537                let const_val = attr_cast::<dyn AttrToLLVMConst>(&**attr).ok_or_else(|| {
538                    input_error_noloc!(MdToLLVMErr::OperandNotConst(attr.disp(ctx).to_string()))
539                })?;
540                Some(llvm_value_as_metadata(
541                    const_val.convert(ctx, llvm_ctx, cctx)?,
542                ))
543            }
544        };
545        Ok(md)
546    }
547
548    /// Build every node of the module's metadata table, and add the module's named
549    /// metadata (`!llvm.module.flags = !{!0, !1}`).
550    pub(crate) fn convert_module_metadata(
551        ctx: &Context,
552        llvm_ctx: &LLVMContext,
553        cctx: &mut ConversionContext,
554        module: ModuleOp,
555    ) -> Result<()> {
556        let Some(table) = get_metadata_table(ctx, module) else {
557            return Ok(());
558        };
559        cctx.md.table = table;
560
561        for id in 0..cctx.md.table.len() as MdNodeId {
562            convert_md_node(ctx, llvm_ctx, cctx, id)?;
563        }
564
565        // A node that must not be uniqued with a structurally identical one can only be
566        // made `distinct` by being self referential.
567        for (id, node) in cctx.md.table.clone().iter() {
568            if !node.is_distinct() {
569                continue;
570            }
571            let md = cctx.md.node_map[&id];
572            let mut operands = Vec::with_capacity(node.operands().len());
573            for operand in node.operands() {
574                operands.push(convert_md_operand(ctx, llvm_ctx, cctx, operand)?);
575            }
576            if llvm_md_node_in_context2(llvm_ctx, &operands) == md {
577                return input_err_noloc!(MdToLLVMErr::UnrepresentableDistinct(id));
578            }
579        }
580
581        if let Some(named) = get_named_metadata(ctx, module) {
582            for (name, nodes) in named.iter() {
583                for node in nodes {
584                    let md = convert_md_node(ctx, llvm_ctx, cctx, *node)?;
585                    llvm_add_named_metadata_operand(
586                        cctx.cur_llvm_module,
587                        name,
588                        llvm_metadata_as_value(llvm_ctx, md),
589                    );
590                }
591            }
592        }
593
594        Ok(())
595    }
596
597    /// Attach the metadata of the pliron [Operation] `op` to the LLVM value it converted to.
598    pub(crate) fn convert_md_attachments(
599        ctx: &Context,
600        llvm_ctx: &LLVMContext,
601        cctx: &mut ConversionContext,
602        op: Ptr<Operation>,
603        op_llvm: LLVMValue,
604    ) -> Result<()> {
605        let Some(attachments) = get_attachments(ctx, op) else {
606            return Ok(());
607        };
608        let is_instruction = llvm_is_a::instruction(op_llvm);
609        for (kind, node) in attachments
610            .iter()
611            .map(|(kind, node)| (kind.to_string(), node))
612            .collect::<Vec<_>>()
613        {
614            let md = convert_md_node(ctx, llvm_ctx, cctx, node)?;
615            let kind_id = llvm_get_md_kind_id_in_context(llvm_ctx, &kind);
616            if is_instruction {
617                llvm_set_metadata(op_llvm, kind_id, llvm_metadata_as_value(llvm_ctx, md));
618            } else if llvm_is_a::global_object(op_llvm) {
619                llvm_global_set_metadata(op_llvm, kind_id, md);
620            } else {
621                // LLVM's builder constant folds, so an operation carrying metadata can
622                // convert to a constant, which has nowhere to hold it. LLVM drops metadata
623                // when it folds too.
624                log::warn!(
625                    "Dropping metadata \"{kind}\" of {}, which did not convert to an instruction",
626                    Operation::get_opid(op, ctx)
627                );
628            }
629        }
630        Ok(())
631    }
632}