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
6pub mod from_llvm_ir {
7    use alloc::{
8        boxed::Box,
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        combine::stream::position::SourcePosition,
18        context::{Context, Ptr},
19        input_error_noloc,
20        location::{Location, Source},
21        operation::Operation,
22        result::Result,
23        std_deps::path::PathBuf,
24        utils::table::{HMap, HSet},
25    };
26    use thiserror::Error;
27
28    use crate::{
29        from_llvm_ir::{ConversionContext, const_llvm_value_to_attr},
30        llvm_sys::{
31            core::{
32                LLVMMetadata, LLVMModule, LLVMValue, llvm_get_md_kind_id_in_module,
33                llvm_get_md_node_operands, llvm_get_md_string, llvm_get_metadata_kind,
34                llvm_get_named_metadata_operands, llvm_global_copy_all_metadata,
35                llvm_instruction_get_all_metadata_other_than_debug_loc, llvm_is_a,
36                llvm_md_node_in_module, llvm_metadata_as_value_in_module,
37                llvm_named_metadata_names, llvm_print_module_to_string, llvm_print_value_to_string,
38                llvm_value_as_metadata,
39            },
40            debuginfo::{
41                llvm_di_file_get_directory, llvm_di_file_get_filename, llvm_di_location_get_column,
42                llvm_di_location_get_inlined_at, llvm_di_location_get_line,
43                llvm_di_location_get_scope, llvm_di_scope_get_file, llvm_di_subprogram_get_line,
44                llvm_get_subprogram, llvm_instruction_get_debug_loc,
45            },
46        },
47        metadata::{
48            MdAttachmentsAttr, MdNodeAttr, MdNodeId, MdOperandAttr, MdTableAttr, NamedMdAttr,
49            set_attachments, set_metadata_table, set_named_metadata,
50        },
51    };
52
53    /// State for converting LLVM metadata
54    #[derive(Default)]
55    pub(crate) struct MdConversionContext {
56        /// Already converted metadata nodes, mapped to their entry in [Self::table],
57        /// or to `None` if the node is one we cannot represent and dropped.
58        node_map: HMap<LLVMMetadata, Option<MdNodeId>>,
59        /// The module's metadata table, built up as metadata is converted.
60        table: MdTableAttr,
61        /// Metadata kind ids mapped to their [names](md_kind_name).
62        kind_names: HMap<u32, String>,
63        /// Whether the module's textual form has been scanned for metadata kind names.
64        kind_names_scraped: bool,
65    }
66
67    /// Metadata conversion errors.
68    #[derive(Error, Debug)]
69    pub enum MdConversionErr {
70        #[error("Cannot determine the name of metadata kind id {0}")]
71        UnknownKind(u32),
72    }
73
74    /// LLVM's metadata kind for an instruction's debug location.
75    pub(crate) const MD_KIND_DBG: &str = "dbg";
76
77    /// LLVM metadata kind names that LLVM pre-registers in every `LLVMContext`.
78    /// A stale list is not incorrect, it only costs a fallback to [scrape_md_kind_names].
79    const FIXED_MD_KIND_NAMES: &[&str] = &[
80        "dbg",
81        "tbaa",
82        "prof",
83        "fpmath",
84        "range",
85        "tbaa.struct",
86        "invariant.load",
87        "alias.scope",
88        "noalias",
89        "nontemporal",
90        "llvm.mem.parallel_loop_access",
91        "nonnull",
92        "dereferenceable",
93        "dereferenceable_or_null",
94        "make.implicit",
95        "unpredictable",
96        "invariant.group",
97        "align",
98        "llvm.loop",
99        "type",
100        "section_prefix",
101        "absolute_symbol",
102        "associated",
103        "callees",
104        "irr_loop",
105        "llvm.access.group",
106        "callback",
107        "llvm.preserve.access.index",
108        "vcall_visibility",
109        "noundef",
110        "annotation",
111        "nosanitize",
112        "func_sanitize",
113        "exclude",
114        "memprof",
115        "callsite",
116        "kcfi_type",
117        "pcsections",
118        "DIAssignID",
119        "coro.outside.frame",
120        "mmra",
121        "noalias.addrspace",
122        "callee_type",
123        "nofree",
124        "captures",
125        "alloc_token",
126        "implicit.ref",
127    ];
128
129    /// Collect every `!name` token that could be a metadata kind name from the textual
130    /// form of a module.
131    ///
132    /// The C-API maps a metadata kind name to its id but not the other way round,
133    /// So for kinds that LLVM doesn't pre-register the name can only be recovered
134    /// from the module's printed form.
135    ///
136    /// The scan over-approximates: a token that isn't a kind name just registers
137    /// an unused kind, which changes nothing about the module.
138    fn scrape_md_kind_names(module_text: &str) -> Vec<String> {
139        fn is_name_start(c: u8) -> bool {
140            c.is_ascii_alphabetic() || matches!(c, b'-' | b'$' | b'.' | b'_')
141        }
142        fn is_name_char(c: u8) -> bool {
143            c.is_ascii_alphanumeric() || matches!(c, b'-' | b'$' | b'.' | b'_')
144        }
145        fn hex_digit(c: Option<&u8>) -> Option<u8> {
146            c.and_then(|c| (*c as char).to_digit(16)).map(|d| d as u8)
147        }
148
149        let text = module_text.as_bytes();
150        let mut seen = HSet::default();
151        let mut names = vec![];
152        let mut idx = 0;
153        while idx < text.len() {
154            if text[idx] != b'!' {
155                idx += 1;
156                continue;
157            }
158            idx += 1;
159            // Undo the escaping LLVM's printer applies to a metadata name: a name
160            // character stands for itself and every other byte is printed as `\XX`.
161            let mut name = vec![];
162            while idx < text.len() {
163                let c = text[idx];
164                if is_name_char(c) && (!name.is_empty() || is_name_start(c)) {
165                    name.push(c);
166                    idx += 1;
167                } else if c == b'\\'
168                    && let (Some(hi), Some(lo)) =
169                        (hex_digit(text.get(idx + 1)), hex_digit(text.get(idx + 2)))
170                {
171                    name.push((hi << 4) | lo);
172                    idx += 3;
173                } else {
174                    break;
175                }
176            }
177            // A name whose bytes aren't UTF-8 has no [String] counterpart to register.
178            if let Ok(name) = String::from_utf8(name)
179                && !name.is_empty()
180                && seen.insert(name.clone())
181            {
182                names.push(name);
183            }
184        }
185        names
186    }
187
188    /// The name of the metadata kind `kind_id` in `module`'s context.
189    pub(crate) fn md_kind_name(
190        cctx: &mut ConversionContext,
191        module: &LLVMModule,
192        kind_id: u32,
193    ) -> Result<String> {
194        if cctx.md.kind_names.is_empty() {
195            for name in FIXED_MD_KIND_NAMES {
196                let id = llvm_get_md_kind_id_in_module(module, name);
197                cctx.md
198                    .kind_names
199                    .entry(id)
200                    .or_insert_with(|| name.to_string());
201            }
202        }
203
204        if let Some(name) = cctx.md.kind_names.get(&kind_id) {
205            return Ok(name.clone());
206        }
207
208        // An id we don't know: recover all names in use from the module's printed form.
209        if !cctx.md.kind_names_scraped {
210            cctx.md.kind_names_scraped = true;
211            let module_text = llvm_print_module_to_string(module)
212                .ok_or_else(|| input_error_noloc!(MdConversionErr::UnknownKind(kind_id)))?;
213            for name in scrape_md_kind_names(&module_text) {
214                let id = llvm_get_md_kind_id_in_module(module, &name);
215                cctx.md.kind_names.entry(id).or_insert(name);
216            }
217        }
218
219        cctx.md
220            .kind_names
221            .get(&kind_id)
222            .cloned()
223            .ok_or_else(|| input_error_noloc!(MdConversionErr::UnknownKind(kind_id)))
224    }
225
226    /// Convert an LLVM metadata node, and everything it refers to, into entries of the
227    /// module's metadata table, returning the [MdNodeId] of `md` itself.
228    fn convert_md_node(
229        ctx: &Context,
230        cctx: &mut ConversionContext,
231        module: &LLVMModule,
232        md: LLVMMetadata,
233    ) -> Result<Option<MdNodeId>> {
234        if let Some(id) = cctx.md.node_map.get(&md) {
235            return Ok(*id);
236        }
237
238        // Metadata we have no representation for is dropped.
239        let kind = llvm_get_metadata_kind(md);
240        if !matches!(kind, LLVMMetadataKind::LLVMMDTupleMetadataKind) {
241            log::warn!("Dropping unsupported metadata of kind {kind:?}");
242            cctx.md.node_map.insert(md, None);
243            return Ok(None);
244        }
245
246        // Reserve this node's id before converting its operands: metadata nodes are
247        // commonly self referential (`!0 = distinct !{!0, ...}`).
248        let id = cctx.md.table.reserve();
249        cctx.md.node_map.insert(md, Some(id));
250
251        let md_val = llvm_metadata_as_value_in_module(module, md);
252        let llvm_operands = llvm_get_md_node_operands(md_val);
253        let mut operands = Vec::with_capacity(llvm_operands.len());
254        for operand in &llvm_operands {
255            // If any operand cannot be represented, we drop the entire node.
256            // (missing operands may make the node inconsistent with its semantics).
257            let Some(operand) = convert_md_operand(ctx, cctx, module, *operand)? else {
258                // LLVM prints an unnamed node as `<0x...> = !{...}`.
259                // We only want its definition for warning.
260                let printed = llvm_print_value_to_string(md_val).unwrap_or_default();
261                let printed = printed.split_once(" = ").map_or(&*printed, |(_, def)| def);
262                log::warn!("Dropping metadata node {printed} with an operand we cannot represent");
263                cctx.md.node_map.insert(md, None);
264                // The id reserved above goes unused; its empty table entry is harmless.
265                return Ok(None);
266            };
267            operands.push(operand);
268        }
269
270        // The C-API can neither tell us whether a node is `distinct` nor create a distinct
271        // node directly. Uniquing a node with the same operands answers the question: for a
272        // uniqued node LLVM hands back the very same node, for a distinct one it cannot.
273        let llvm_md_operands: Vec<_> = llvm_operands
274            .iter()
275            .map(|operand| operand.map(llvm_value_as_metadata))
276            .collect();
277        let distinct = llvm_md_node_in_module(module, &llvm_md_operands) != md;
278
279        cctx.md.table.set(
280            id,
281            if distinct {
282                MdNodeAttr::new_distinct_tuple(operands)
283            } else {
284                MdNodeAttr::new_tuple(operands)
285            },
286        );
287
288        Ok(Some(id))
289    }
290
291    /// Convert one operand of an LLVM metadata node. An `operand` of `None` is LLVM's
292    /// `null` operand; a `None` result is an operand that cannot be represented.
293    fn convert_md_operand(
294        ctx: &Context,
295        cctx: &mut ConversionContext,
296        module: &LLVMModule,
297        operand: Option<LLVMValue>,
298    ) -> Result<Option<MdOperandAttr>> {
299        let Some(val) = operand else {
300            return Ok(Some(MdOperandAttr::Null));
301        };
302
303        // A constant operand comes back as the constant itself and anything else as a value
304        // wrapping metadata; going back to metadata classifies both uniformly.
305        let md = llvm_value_as_metadata(val);
306        match llvm_get_metadata_kind(md) {
307            LLVMMetadataKind::LLVMMDStringMetadataKind => match llvm_get_md_string(val) {
308                Some(s) => Ok(Some(MdOperandAttr::String(s))),
309                None => {
310                    log::warn!("Dropping metadata string operand whose contents aren't UTF-8");
311                    Ok(None)
312                }
313            },
314            LLVMMetadataKind::LLVMMDTupleMetadataKind => {
315                Ok(convert_md_node(ctx, cctx, module, md)?.map(MdOperandAttr::Node))
316            }
317            LLVMMetadataKind::LLVMConstantAsMetadataMetadataKind => {
318                match const_llvm_value_to_attr(ctx, cctx, val)? {
319                    Some(attr) => Ok(Some(MdOperandAttr::Constant(attr))),
320                    None => {
321                        log::warn!(
322                            "Dropping unsupported constant metadata operand {}",
323                            llvm_print_value_to_string(val).unwrap_or_default()
324                        );
325                        Ok(None)
326                    }
327                }
328            }
329            kind => {
330                log::warn!("Dropping unsupported metadata operand of kind {kind:?}");
331                Ok(None)
332            }
333        }
334    }
335
336    /// Attach the metadata in `entries` (kind id, node) to the pliron [Operation] `m_op`.
337    fn convert_md_attachments(
338        ctx: &Context,
339        cctx: &mut ConversionContext,
340        module: &LLVMModule,
341        entries: Vec<(u32, LLVMMetadata)>,
342        m_op: Ptr<Operation>,
343    ) -> Result<()> {
344        if entries.is_empty() {
345            return Ok(());
346        }
347        let mut attachments = MdAttachmentsAttr::new();
348        for (kind_id, md) in entries {
349            let kind = md_kind_name(cctx, module, kind_id)?;
350            if kind == MD_KIND_DBG {
351                // Debug data converts to [Location].
352                continue;
353            }
354            let Some(node) = convert_md_node(ctx, cctx, module, md)? else {
355                log::warn!("Dropping metadata attached under kind \"{kind}\"");
356                continue;
357            };
358            attachments.set(kind, node);
359        }
360        if !attachments.is_empty() {
361            set_attachments(ctx, m_op, attachments);
362        }
363        Ok(())
364    }
365
366    /// Convert the metadata attached to the LLVM instruction `inst`.
367    pub(crate) fn convert_instruction_metadata(
368        ctx: &Context,
369        cctx: &mut ConversionContext,
370        module: &LLVMModule,
371        inst: LLVMValue,
372        m_inst: Ptr<Operation>,
373    ) -> Result<()> {
374        convert_instruction_debug_loc(ctx, inst, m_inst);
375        let entries = llvm_instruction_get_all_metadata_other_than_debug_loc(inst);
376        convert_md_attachments(ctx, cctx, module, entries, m_inst)
377    }
378
379    /// Convert the metadata attached to an LLVM global object.
380    pub(crate) fn convert_global_object_metadata(
381        ctx: &Context,
382        cctx: &mut ConversionContext,
383        module: &LLVMModule,
384        global: LLVMValue,
385        m_op: Ptr<Operation>,
386    ) -> Result<()> {
387        if llvm_is_a::function(global) {
388            convert_function_debug_loc(ctx, global, m_op);
389        }
390        let entries = llvm_global_copy_all_metadata(global);
391        convert_md_attachments(ctx, cctx, module, entries, m_op)
392    }
393
394    /// A position at `line` and `column` in the file of the debug data `scope`.
395    fn src_pos(ctx: &Context, scope: LLVMMetadata, line: u32, column: u32) -> Location {
396        if line == 0 {
397            // Line 0 is LLVM's convention for no source position.
398            return Location::Unknown;
399        }
400        let src = llvm_di_scope_get_file(scope).map_or(Source::InMemory, |file| {
401            // A relative file name is relative to the directory of the file.
402            let path = PathBuf::from(llvm_di_file_get_directory(file))
403                .join(llvm_di_file_get_filename(file));
404            Source::new_from_file(ctx, path)
405        });
406        let pos = SourcePosition {
407            line: line.try_into().unwrap_or(i32::MAX),
408            column: column.try_into().unwrap_or(i32::MAX),
409        };
410        Location::SrcPos { src, pos }
411    }
412
413    /// The [Location] of the `DILocation` `di_loc`.
414    /// Inlined positions becomes [Location::CallSite].
415    fn convert_di_location(ctx: &Context, di_loc: LLVMMetadata) -> Location {
416        let loc = src_pos(
417            ctx,
418            llvm_di_location_get_scope(di_loc),
419            llvm_di_location_get_line(di_loc),
420            llvm_di_location_get_column(di_loc),
421        );
422        match llvm_di_location_get_inlined_at(di_loc) {
423            // TODO: Wrap `loc` in a [Location::Named] with the name of the subprogram
424            // of its scope. The C-API has no getter for this name.
425            Some(caller) => Location::CallSite {
426                callee: Box::new(loc),
427                caller: Box::new(convert_di_location(ctx, caller)),
428            },
429            None => loc,
430        }
431    }
432
433    /// Set the location of `m_inst` from the debug location of `inst`.
434    fn convert_instruction_debug_loc(ctx: &Context, inst: LLVMValue, m_inst: Ptr<Operation>) {
435        if let Some(di_loc) = llvm_instruction_get_debug_loc(inst) {
436            let loc = convert_di_location(ctx, di_loc);
437            m_inst.deref_mut(ctx).set_loc(loc);
438        }
439    }
440
441    /// Set the location of `m_func` from the subprogram of `function`.
442    fn convert_function_debug_loc(ctx: &Context, function: LLVMValue, m_func: Ptr<Operation>) {
443        if let Some(subprogram) = llvm_get_subprogram(function) {
444            let line = llvm_di_subprogram_get_line(subprogram);
445            // TODO: Wrap `loc` in a [Location::Named] with the name of `subprogram`.
446            // The C-API has no getter for this name.
447            let loc = src_pos(ctx, subprogram, line, 0);
448            m_func.deref_mut(ctx).set_loc(loc);
449        }
450    }
451
452    /// Convert the module's named metadata (`!llvm.module.flags = !{!0, !1}`).
453    fn convert_named_metadata(
454        ctx: &Context,
455        cctx: &mut ConversionContext,
456        module: &LLVMModule,
457    ) -> Result<NamedMdAttr> {
458        let mut named = NamedMdAttr::new();
459        for name in llvm_named_metadata_names(module) {
460            for operand in llvm_get_named_metadata_operands(module, &name) {
461                let md = llvm_value_as_metadata(operand);
462                let Some(node) = convert_md_node(ctx, cctx, module, md)? else {
463                    log::warn!("Dropping an operand of named metadata \"{name}\"");
464                    continue;
465                };
466                named.push(name.clone(), node);
467            }
468        }
469        Ok(named)
470    }
471
472    /// Attach the module's metadata to `module_op`.
473    ///
474    /// Must be called after the module's functions have been converted, since their
475    /// instructions are what put most nodes in the table.
476    pub(crate) fn convert_module_metadata(
477        ctx: &Context,
478        cctx: &mut ConversionContext,
479        module: &LLVMModule,
480        module_op: ModuleOp,
481    ) -> Result<()> {
482        let named_md = convert_named_metadata(ctx, cctx, module)?;
483        if !named_md.is_empty() {
484            set_named_metadata(ctx, module_op, named_md);
485        }
486        if !cctx.md.table.is_empty() {
487            set_metadata_table(ctx, module_op, cctx.md.table.clone());
488        }
489        Ok(())
490    }
491}
492
493/// Conversion of LLVM metadata (including debug-info) to LLVM-IR,
494/// a companion to [crate::to_llvm_ir].
495pub mod to_llvm_ir {
496    use alloc::{
497        string::{String, ToString},
498        vec::Vec,
499    };
500
501    use llvm_sys::{
502        LLVMModuleFlagBehavior,
503        debuginfo::{LLVMChecksumKind, LLVMDIFlagZero},
504    };
505    use pliron::{
506        attribute::attr_cast,
507        builtin::{op_interfaces::SymbolOpInterface, ops::ModuleOp},
508        context::{Context, Ptr},
509        graph::walkers::{
510            IRNode, WALKCONFIG_PREORDER_FORWARD,
511            interruptible::{WalkResult, immutable::walk_op, walk_advance, walk_break},
512        },
513        input_err_noloc, input_error_noloc,
514        location::{Location, Source},
515        op::Op,
516        operation::Operation,
517        printable::Printable,
518        result::Result,
519        uniqued_any,
520        utils::table::{HMap, HSet},
521    };
522    use thiserror::Error;
523
524    use crate::{
525        llvm_sys::{
526            core::{
527                LLVMContext, LLVMMetadata, LLVMModule, LLVMValue, llvm_add_module_flag,
528                llvm_add_named_metadata_operand, llvm_const_int, llvm_get_md_kind_id_in_context,
529                llvm_get_module_flag, llvm_global_set_metadata, llvm_int_type_in_context,
530                llvm_is_a, llvm_md_node_in_context2, llvm_md_string_in_context2,
531                llvm_metadata_as_value, llvm_metadata_replace_all_uses_with,
532                llvm_set_current_debug_location2, llvm_set_metadata, llvm_temporary_md_node,
533                llvm_value_as_metadata,
534            },
535            debuginfo::{
536                LLVMDIBuilder, llvm_debug_metadata_version, llvm_di_builder_create_compile_unit,
537                llvm_di_builder_create_debug_location, llvm_di_builder_create_file,
538                llvm_di_builder_create_file_with_checksum, llvm_di_builder_create_function,
539                llvm_di_builder_create_lexical_block_file, llvm_di_builder_create_subroutine_type,
540                llvm_di_scope_get_file, llvm_set_subprogram,
541            },
542        },
543        metadata::{
544            MdNodeId, MdOperandAttr, MdTableAttr, get_attachments, get_metadata_table,
545            get_named_metadata,
546        },
547        op_interfaces::LlvmSymbolName,
548        ops::FuncOp,
549        to_llvm_ir::{AttrToLLVMConst, ConversionContext},
550    };
551
552    /// State for converting metadata to LLVM.
553    pub(crate) struct MdConversionContext<'a> {
554        // The module's metadata table, that metadata references resolve against.
555        table: MdTableAttr,
556        // Metadata nodes that have already been built.
557        node_map: HMap<MdNodeId, LLVMMetadata>,
558        // Temporary nodes standing in for nodes that are still being built.
559        temporaries: HMap<MdNodeId, LLVMMetadata>,
560        // Metadata nodes currently being built, to detect cyclic references.
561        in_progress: HSet<MdNodeId>,
562        // State for debug info conversion.
563        di: Option<DiConversionContext<'a>>,
564    }
565
566    impl<'a> MdConversionContext<'a> {
567        pub(crate) fn new(
568            module: &'a LLVMModule,
569            debug_info: Option<DebugInfoOptions<'a>>,
570        ) -> Self {
571            Self {
572                table: MdTableAttr::default(),
573                node_map: HMap::default(),
574                temporaries: HMap::default(),
575                in_progress: HSet::default(),
576                di: debug_info.map(|options| DiConversionContext::new(module, options)),
577            }
578        }
579    }
580
581    /// Metadata conversion errors.
582    #[derive(Error, Debug)]
583    pub enum MdToLLVMErr {
584        #[error("Metadata node #{0} is not in the module's metadata table")]
585        DanglingNodeRef(MdNodeId),
586        #[error("Metadata operand {0} is not convertible to an LLVM constant")]
587        OperandNotConst(String),
588        #[error(
589            "Metadata node #{0} is `distinct`, but the LLVM C-API can only create a distinct \
590             node that refers to itself"
591        )]
592        UnrepresentableDistinct(MdNodeId),
593    }
594
595    /// Build the LLVM metadata node for entry `id` of the module's metadata table,
596    /// building whatever it refers to along the way.
597    fn convert_md_node(
598        ctx: &Context,
599        llvm_ctx: &LLVMContext,
600        cctx: &mut ConversionContext,
601        id: MdNodeId,
602    ) -> Result<LLVMMetadata> {
603        if let Some(md) = cctx.md.node_map.get(&id) {
604            return Ok(*md);
605        }
606        // Metadata nodes may be cyclic (`!0 = distinct !{!0, ...}`), which the C-API can only
607        // express by creating a temporary node, referring to that, and then replacing it with
608        // the real node.
609        if cctx.md.in_progress.contains(&id) {
610            // A back edge: refer to a temporary that is replaced with the real node once
611            // that node has been built.
612            let temp = *cctx
613                .md
614                .temporaries
615                .entry(id)
616                .or_insert_with(|| llvm_temporary_md_node(llvm_ctx, &[]));
617            return Ok(temp);
618        }
619
620        let node = cctx
621            .md
622            .table
623            .get(id)
624            .cloned()
625            .ok_or_else(|| input_error_noloc!(MdToLLVMErr::DanglingNodeRef(id)))?;
626
627        cctx.md.in_progress.insert(id);
628        let mut operands = Vec::with_capacity(node.operands().len());
629        for operand in node.operands() {
630            operands.push(convert_md_operand(ctx, llvm_ctx, cctx, operand)?);
631        }
632        cctx.md.in_progress.remove(&id);
633
634        let md = llvm_md_node_in_context2(llvm_ctx, &operands);
635        if let Some(temp) = cctx.md.temporaries.remove(&id) {
636            llvm_metadata_replace_all_uses_with(temp, md);
637        }
638        cctx.md.node_map.insert(id, md);
639
640        Ok(md)
641    }
642
643    /// Build the LLVM metadata for one operand of a metadata node.
644    fn convert_md_operand(
645        ctx: &Context,
646        llvm_ctx: &LLVMContext,
647        cctx: &mut ConversionContext,
648        operand: &MdOperandAttr,
649    ) -> Result<Option<LLVMMetadata>> {
650        let md = match operand {
651            MdOperandAttr::Null => None,
652            MdOperandAttr::String(s) => Some(llvm_md_string_in_context2(llvm_ctx, s)),
653            MdOperandAttr::Node(id) => Some(convert_md_node(ctx, llvm_ctx, cctx, *id)?),
654            MdOperandAttr::Constant(attr) => {
655                let const_val = attr_cast::<dyn AttrToLLVMConst>(&**attr).ok_or_else(|| {
656                    input_error_noloc!(MdToLLVMErr::OperandNotConst(attr.disp(ctx).to_string()))
657                })?;
658                Some(llvm_value_as_metadata(
659                    const_val.convert(ctx, llvm_ctx, cctx)?,
660                ))
661            }
662        };
663        Ok(md)
664    }
665
666    /// Build every node of the module's metadata table, and add the module's named
667    /// metadata (`!llvm.module.flags = !{!0, !1}`).
668    pub(crate) fn convert_module_metadata(
669        ctx: &Context,
670        llvm_ctx: &LLVMContext,
671        cctx: &mut ConversionContext,
672        module: ModuleOp,
673    ) -> Result<()> {
674        let Some(table) = get_metadata_table(ctx, module) else {
675            return Ok(());
676        };
677        cctx.md.table = table;
678
679        for id in 0..cctx.md.table.len() as MdNodeId {
680            convert_md_node(ctx, llvm_ctx, cctx, id)?;
681        }
682
683        // A node that must not be uniqued with a structurally identical one can only be
684        // made `distinct` by being self referential.
685        for (id, node) in cctx.md.table.clone().iter() {
686            if !node.is_distinct() {
687                continue;
688            }
689            let md = cctx.md.node_map[&id];
690            let mut operands = Vec::with_capacity(node.operands().len());
691            for operand in node.operands() {
692                operands.push(convert_md_operand(ctx, llvm_ctx, cctx, operand)?);
693            }
694            if llvm_md_node_in_context2(llvm_ctx, &operands) == md {
695                return input_err_noloc!(MdToLLVMErr::UnrepresentableDistinct(id));
696            }
697        }
698
699        if let Some(named) = get_named_metadata(ctx, module) {
700            for (name, nodes) in named.iter() {
701                for node in nodes {
702                    let md = convert_md_node(ctx, llvm_ctx, cctx, *node)?;
703                    llvm_add_named_metadata_operand(
704                        cctx.cur_llvm_module,
705                        name,
706                        llvm_metadata_as_value(llvm_ctx, md),
707                    );
708                }
709            }
710        }
711
712        Ok(())
713    }
714
715    /// Attach the metadata of the pliron [Operation] `op` to the LLVM value it converted to.
716    pub(crate) fn convert_md_attachments(
717        ctx: &Context,
718        llvm_ctx: &LLVMContext,
719        cctx: &mut ConversionContext,
720        op: Ptr<Operation>,
721        op_llvm: LLVMValue,
722    ) -> Result<()> {
723        let Some(attachments) = get_attachments(ctx, op) else {
724            return Ok(());
725        };
726        let is_instruction = llvm_is_a::instruction(op_llvm);
727        for (kind, node) in attachments
728            .iter()
729            .map(|(kind, node)| (kind.to_string(), node))
730            .collect::<Vec<_>>()
731        {
732            let md = convert_md_node(ctx, llvm_ctx, cctx, node)?;
733            let kind_id = llvm_get_md_kind_id_in_context(llvm_ctx, &kind);
734            if is_instruction {
735                llvm_set_metadata(op_llvm, kind_id, llvm_metadata_as_value(llvm_ctx, md));
736            } else if llvm_is_a::global_object(op_llvm) {
737                llvm_global_set_metadata(op_llvm, kind_id, md);
738            } else {
739                // LLVM's builder constant folds, so an operation carrying metadata can
740                // convert to a constant, which has nowhere to hold it. LLVM drops metadata
741                // when it folds too.
742                log::warn!(
743                    "Dropping metadata \"{kind}\" of {}, which did not convert to an instruction",
744                    Operation::get_opid(op, ctx)
745                );
746            }
747        }
748        Ok(())
749    }
750
751    /// The amount of debug data to emit.
752    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
753    pub enum DwarfEmissionKind {
754        /// No debug data.
755        None,
756        /// All the debug data.
757        Full,
758        /// Line tables and inlined frames only.
759        #[default]
760        LineTablesOnly,
761    }
762
763    /// The source language of a compile unit.
764    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
765    pub enum DwarfSourceLanguage {
766        /// C.
767        #[default]
768        C,
769        /// C++.
770        CPlusPlus,
771        /// Rust.
772        Rust,
773    }
774
775    /// The text of a source file, to embed in its `DIFile`.
776    #[derive(Clone, Debug)]
777    pub struct SourceText<'a> {
778        /// The contents of the file.
779        pub text: &'a str,
780        /// Hexadecimal MD5 of [text](Self::text). Not verified.
781        pub md5: String,
782    }
783
784    /// Debug data options of [ToLLVMOptions](crate::to_llvm_ir::ToLLVMOptions).
785    #[derive(Debug)]
786    pub struct DebugInfoOptions<'a> {
787        /// The amount of debug data to emit.
788        pub emission_kind: DwarfEmissionKind,
789        /// The source language of the compile unit.
790        pub language: DwarfSourceLanguage,
791        /// The producer of the compile unit.
792        pub producer: String,
793        /// Is the code optimized?
794        pub optimized: bool,
795        /// Base directory for relative file names.
796        pub directory: String,
797        /// Not set if the module already has one.
798        pub dwarf_version: u32,
799        /// Text to embed in the `DIFile` of each location path.
800        pub source_text: HMap<String, SourceText<'a>>,
801    }
802
803    impl Default for DebugInfoOptions<'_> {
804        fn default() -> Self {
805            Self {
806                emission_kind: DwarfEmissionKind::default(),
807                language: DwarfSourceLanguage::default(),
808                producer: "pliron".to_string(),
809                optimized: false,
810                directory: String::new(),
811                dwarf_version: 4,
812                source_text: HMap::default(),
813            }
814        }
815    }
816
817    /// State for converting op locations to debug data.
818    struct DiConversionContext<'a> {
819        options: DebugInfoOptions<'a>,
820        builder: LLVMDIBuilder<'a>,
821        // The compile unit. Created with the first subprogram.
822        unit: Option<LLVMMetadata>,
823        // The `DIFile` of each source.
824        files: HMap<Source, LLVMMetadata>,
825        // Lexical block files, by their scope and file.
826        block_files: HMap<(LLVMMetadata, LLVMMetadata), LLVMMetadata>,
827        // Subprograms of inlined functions, by file and name.
828        inlined: HMap<LLVMMetadata, HMap<String, LLVMMetadata>>,
829        // The subprogram of the function that is being converted.
830        subprogram: Option<LLVMMetadata>,
831    }
832
833    impl<'a> DiConversionContext<'a> {
834        fn new(module: &'a LLVMModule, options: DebugInfoOptions<'a>) -> Self {
835            Self {
836                options,
837                builder: LLVMDIBuilder::new(module),
838                unit: None,
839                files: HMap::default(),
840                block_files: HMap::default(),
841                inlined: HMap::default(),
842                subprogram: None,
843            }
844        }
845
846        fn file(&mut self, ctx: &Context, src: &Source) -> LLVMMetadata {
847            *self.files.entry(*src).or_insert_with(|| {
848                let name = match src {
849                    Source::File(key) => uniqued_any::get(ctx, *key).to_string_lossy().into_owned(),
850                    Source::InMemory => "<in-memory>".to_string(),
851                };
852                let directory = &self.options.directory;
853                let Some(source) = self.options.source_text.get(&name) else {
854                    return llvm_di_builder_create_file(&self.builder, &name, directory);
855                };
856                llvm_di_builder_create_file_with_checksum(
857                    &self.builder,
858                    &name,
859                    directory,
860                    LLVMChecksumKind::CSK_MD5,
861                    &source.md5,
862                    source.text,
863                )
864            })
865        }
866
867        fn ensure_unit(&mut self, file: LLVMMetadata) {
868            if self.unit.is_some() {
869                return;
870            }
871            self.unit = Some(llvm_di_builder_create_compile_unit(
872                &self.builder,
873                self.options.language.into(),
874                file,
875                &self.options.producer,
876                self.options.optimized,
877                "",
878                0,
879                "",
880                self.options.emission_kind.into(),
881                0,
882                false,
883                false,
884                "",
885                "",
886            ));
887        }
888
889        fn create_subprogram(
890            &mut self,
891            name: &str,
892            linkage_name: &str,
893            file: LLVMMetadata,
894            line: u32,
895            is_local_to_unit: bool,
896        ) -> LLVMMetadata {
897            self.ensure_unit(file);
898            let ty =
899                llvm_di_builder_create_subroutine_type(&self.builder, file, &[], LLVMDIFlagZero);
900            llvm_di_builder_create_function(
901                &self.builder,
902                file,
903                name,
904                linkage_name,
905                file,
906                line,
907                ty,
908                is_local_to_unit,
909                true,
910                line,
911                LLVMDIFlagZero,
912                self.options.optimized,
913            )
914        }
915
916        /// `scope`, or a lexical block of `scope` in the file of `src`.
917        fn scope_in_file(
918            &mut self,
919            ctx: &Context,
920            scope: LLVMMetadata,
921            src: &Source,
922        ) -> LLVMMetadata {
923            let file = self.file(ctx, src);
924            if llvm_di_scope_get_file(scope) == Some(file) {
925                return scope;
926            }
927            *self.block_files.entry((scope, file)).or_insert_with(|| {
928                llvm_di_builder_create_lexical_block_file(&self.builder, scope, file, 0)
929            })
930        }
931
932        /// Keyed by the outermost name of `callee`.
933        fn inlined_subprogram(&mut self, ctx: &Context, callee: &Location) -> Option<LLVMMetadata> {
934            let name = frame_name(callee)?;
935            let (src, _) = frame_src_pos(callee).unwrap_or((Source::InMemory, 0));
936            let file = self.file(ctx, &src);
937            if let Some(subprogram) = self.inlined.get(&file).and_then(|names| names.get(name)) {
938                return Some(*subprogram);
939            }
940            let subprogram = self.create_subprogram(name, "", file, 0, true);
941            self.inlined
942                .entry(file)
943                .or_default()
944                .insert(name.to_string(), subprogram);
945            Some(subprogram)
946        }
947
948        fn translate(
949            &mut self,
950            ctx: &Context,
951            llvm_ctx: &LLVMContext,
952            loc: &Location,
953            scope: LLVMMetadata,
954            inlined_at: Option<LLVMMetadata>,
955        ) -> Option<LLVMMetadata> {
956            match loc {
957                Location::SrcPos { src, pos } => {
958                    let scope = self.scope_in_file(ctx, scope, src);
959                    Some(llvm_di_builder_create_debug_location(
960                        llvm_ctx,
961                        non_negative(pos.line),
962                        non_negative(pos.column),
963                        scope,
964                        inlined_at,
965                    ))
966                }
967                Location::Named { child_loc, .. } => {
968                    self.translate(ctx, llvm_ctx, child_loc, scope, inlined_at)
969                }
970                Location::CallSite { callee, caller } => {
971                    let Some(caller) = self.translate(ctx, llvm_ctx, caller, scope, inlined_at)
972                    else {
973                        return self.translate(ctx, llvm_ctx, callee, scope, inlined_at);
974                    };
975                    let Some(callee_scope) = self.inlined_subprogram(ctx, callee) else {
976                        return Some(caller);
977                    };
978                    self.translate(ctx, llvm_ctx, callee, callee_scope, Some(caller))
979                        .or(Some(caller))
980                }
981                Location::Fused { locations, .. } => locations
982                    .iter()
983                    .find_map(|loc| self.translate(ctx, llvm_ctx, loc, scope, inlined_at)),
984                Location::Unknown => None,
985            }
986        }
987    }
988
989    fn non_negative(value: i32) -> u32 {
990        u32::try_from(value).unwrap_or(0)
991    }
992
993    /// The first name in the outermost frame of `loc`.
994    fn frame_name(loc: &Location) -> Option<&str> {
995        match loc {
996            Location::Named { name, .. } => Some(name),
997            Location::Fused { locations, .. } => locations.iter().find_map(frame_name),
998            Location::CallSite { caller, .. } => frame_name(caller),
999            Location::SrcPos { .. } | Location::Unknown => None,
1000        }
1001    }
1002
1003    /// The first source and line in the outermost frame of `loc`.
1004    fn frame_src_pos(loc: &Location) -> Option<(Source, u32)> {
1005        match loc {
1006            Location::SrcPos { src, pos } => Some((*src, non_negative(pos.line))),
1007            Location::Named { child_loc, .. } => frame_src_pos(child_loc),
1008            Location::Fused { locations, .. } => locations.iter().find_map(frame_src_pos),
1009            Location::CallSite { caller, .. } => frame_src_pos(caller),
1010            Location::Unknown => None,
1011        }
1012    }
1013
1014    /// The location of `func_op`, or else the first known location in its body.
1015    fn function_location(ctx: &Context, func_op: FuncOp) -> Option<Location> {
1016        let loc = func_op.loc(ctx);
1017        if !loc.is_unknown() {
1018            return Some(loc);
1019        }
1020        let result = walk_op(
1021            ctx,
1022            &mut (),
1023            &WALKCONFIG_PREORDER_FORWARD,
1024            func_op.get_operation(),
1025            |ctx: &Context, _state: &mut (), node: IRNode| -> WalkResult<Location> {
1026                let IRNode::Operation(op) = node else {
1027                    return walk_advance();
1028                };
1029                if op.deref(ctx).loc().is_unknown() {
1030                    walk_advance()
1031                } else {
1032                    walk_break(op.deref(ctx).loc().clone())
1033                }
1034            },
1035        );
1036        match result {
1037            WalkResult::Break(loc) => Some(loc),
1038            WalkResult::Continue(_) => None,
1039        }
1040    }
1041
1042    /// Create the subprogram of `func_op`, if debug data is requested.
1043    pub(crate) fn begin_function_debug_info(
1044        ctx: &Context,
1045        cctx: &mut ConversionContext,
1046        func_op: FuncOp,
1047        func_llvm: LLVMValue,
1048    ) {
1049        let Some(di) = cctx.md.di.as_mut() else {
1050            return;
1051        };
1052        di.subprogram = None;
1053        let Some(loc) = function_location(ctx, func_op) else {
1054            return;
1055        };
1056        let llvm_name = func_op
1057            .llvm_symbol_name(ctx)
1058            .unwrap_or_else(|| func_op.get_symbol_name(ctx).to_string());
1059        let name = frame_name(&loc).unwrap_or(&llvm_name);
1060        let linkage_name = if name == llvm_name { "" } else { &llvm_name };
1061        let (src, line) = frame_src_pos(&loc).unwrap_or((Source::InMemory, 0));
1062        let file = di.file(ctx, &src);
1063        let subprogram = di.create_subprogram(name, linkage_name, file, line, false);
1064        llvm_set_subprogram(func_llvm, subprogram);
1065        di.subprogram = Some(subprogram);
1066    }
1067
1068    /// Set the debug location of the instructions that `op` converts to,
1069    /// if debug data is requested.
1070    pub(crate) fn set_debug_location(
1071        ctx: &Context,
1072        llvm_ctx: &LLVMContext,
1073        cctx: &mut ConversionContext,
1074        op: Ptr<Operation>,
1075    ) {
1076        let Some(di) = cctx.md.di.as_mut() else {
1077            return;
1078        };
1079        let op_ref = op.deref(ctx);
1080        let di_loc = di.subprogram.map(|subprogram| {
1081            di.translate(ctx, llvm_ctx, op_ref.loc(), subprogram, None)
1082                .unwrap_or_else(|| {
1083                    llvm_di_builder_create_debug_location(llvm_ctx, 0, 0, subprogram, None)
1084                })
1085        });
1086        llvm_set_current_debug_location2(&cctx.builder, di_loc);
1087    }
1088
1089    /// Add the module flags that the debug data needs, and finalize it.
1090    pub(crate) fn finish_debug_info(llvm_ctx: &LLVMContext, cctx: &mut ConversionContext) {
1091        let Some(di) = cctx.md.di.take() else {
1092            return;
1093        };
1094        if di.unit.is_none() {
1095            return;
1096        }
1097        let int32 = llvm_int_type_in_context(llvm_ctx, 32);
1098        for (key, value) in [
1099            ("Debug Info Version", llvm_debug_metadata_version()),
1100            ("Dwarf Version", di.options.dwarf_version),
1101        ] {
1102            if llvm_get_module_flag(cctx.cur_llvm_module, key).is_none() {
1103                let value = llvm_value_as_metadata(llvm_const_int(int32, value.into(), false));
1104                llvm_add_module_flag(
1105                    cctx.cur_llvm_module,
1106                    LLVMModuleFlagBehavior::LLVMModuleFlagBehaviorWarning,
1107                    key,
1108                    value,
1109                );
1110            }
1111        }
1112    }
1113}