|
| 1 | +//! Debug tag storage. |
| 2 | +//! |
| 3 | +//! Cranelift permits the embedder to place "debug tags" on |
| 4 | +//! instructions in CLIF. These tags are sequences of items of various |
| 5 | +//! kinds, with no other meaning imposed by Cranelift. They are passed |
| 6 | +//! through to metadata provided alongside the compilation result. |
| 7 | +//! |
| 8 | +//! When Cranelift inlines a function, it will prepend any tags from |
| 9 | +//! the call instruction at the inlining callsite to tags on all |
| 10 | +//! inlined instructions. |
| 11 | +//! |
| 12 | +//! These tags can be used, for example, to identify stackslots that |
| 13 | +//! store user state, or to denote positions in user source. In |
| 14 | +//! general, the intent is to allow perfect reconstruction of original |
| 15 | +//! (source-level) program state in an instrumentation-based |
| 16 | +//! debug-info scheme, as long as the instruction(s) on which these |
| 17 | +//! tags are attached are preserved. This will be the case for any |
| 18 | +//! instructions with side-effects. |
| 19 | +//! |
| 20 | +//! A few answers to design questions that lead to this design: |
| 21 | +//! |
| 22 | +//! - Why not use the SourceLoc mechanism? Debug tags are richer than |
| 23 | +//! that infrastructure because they preserve inlining location and |
| 24 | +//! are interleaved properly with any other tags describing the |
| 25 | +//! frame. |
| 26 | +//! - Why not attach debug tags only to special sequence-point |
| 27 | +//! instructions? This is driven by inlining: we should have the |
| 28 | +//! semantic information about a callsite attached directly to the |
| 29 | +//! call and observe it there, not have a magic "look backward to |
| 30 | +//! find a sequence point" behavior in the inliner. |
| 31 | +//! |
| 32 | +//! In other words, the needs of preserving "virtual" frames across an |
| 33 | +//! inlining transform drive this design. |
| 34 | +
|
| 35 | +use crate::ir::{Inst, StackSlot}; |
| 36 | +use alloc::collections::BTreeMap; |
| 37 | +use alloc::vec::Vec; |
| 38 | +use core::ops::Range; |
| 39 | + |
| 40 | +/// Debug tags for instructions. |
| 41 | +#[derive(Clone, PartialEq, Hash, Default)] |
| 42 | +#[cfg_attr( |
| 43 | + feature = "enable-serde", |
| 44 | + derive(serde_derive::Serialize, serde_derive::Deserialize) |
| 45 | +)] |
| 46 | +pub struct DebugTags { |
| 47 | + /// Pool of tags, referred to by `insts` below. |
| 48 | + tags: Vec<DebugTag>, |
| 49 | + |
| 50 | + /// Per-instruction range for its list of tags in the tag pool (if |
| 51 | + /// any). |
| 52 | + /// |
| 53 | + /// Note: we don't use `PackedOption` and `EntityList` here |
| 54 | + /// because the values that we are storing are not entities. |
| 55 | + insts: BTreeMap<Inst, Range<u32>>, |
| 56 | +} |
| 57 | + |
| 58 | +/// One debug tag. |
| 59 | +#[derive(Clone, Debug, PartialEq, Hash)] |
| 60 | +#[cfg_attr( |
| 61 | + feature = "enable-serde", |
| 62 | + derive(serde_derive::Serialize, serde_derive::Deserialize) |
| 63 | +)] |
| 64 | +pub enum DebugTag { |
| 65 | + /// User-specified `u32` value, opaque to Cranelift. |
| 66 | + User(u32), |
| 67 | + |
| 68 | + /// A stack slot reference. |
| 69 | + StackSlot(StackSlot), |
| 70 | +} |
| 71 | + |
| 72 | +impl DebugTags { |
| 73 | + /// Set the tags on an instruction, overwriting existing tag list. |
| 74 | + pub fn set(&mut self, inst: Inst, tags: impl IntoIterator<Item = DebugTag>) { |
| 75 | + let start = u32::try_from(self.tags.len()).unwrap(); |
| 76 | + self.tags.extend(tags); |
| 77 | + let end = u32::try_from(self.tags.len()).unwrap(); |
| 78 | + if end > start { |
| 79 | + self.insts.insert(inst, start..end); |
| 80 | + } else { |
| 81 | + self.insts.remove(&inst); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + /// Get the tags associated with an instruction. |
| 86 | + pub fn get(&self, inst: Inst) -> &[DebugTag] { |
| 87 | + if let Some(range) = self.insts.get(&inst) { |
| 88 | + let start = usize::try_from(range.start).unwrap(); |
| 89 | + let end = usize::try_from(range.end).unwrap(); |
| 90 | + &self.tags[start..end] |
| 91 | + } else { |
| 92 | + &[] |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + /// Clone the tags from one instruction to another. |
| 97 | + /// |
| 98 | + /// This clone is cheap (references the same underlying storage) |
| 99 | + /// because the tag lists are immutable. |
| 100 | + pub fn clone_tags(&mut self, from: Inst, to: Inst) { |
| 101 | + if let Some(range) = self.insts.get(&from).cloned() { |
| 102 | + self.insts.insert(to, range); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + /// Are any debug tags present? |
| 107 | + /// |
| 108 | + /// This is used for adjusting margins when pretty-printing CLIF. |
| 109 | + pub fn is_empty(&self) -> bool { |
| 110 | + self.insts.is_empty() |
| 111 | + } |
| 112 | + |
| 113 | + /// Clear all tags. |
| 114 | + pub fn clear(&mut self) { |
| 115 | + self.insts.clear(); |
| 116 | + self.tags.clear(); |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +impl core::fmt::Display for DebugTag { |
| 121 | + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
| 122 | + match self { |
| 123 | + DebugTag::User(value) => write!(f, "{value}"), |
| 124 | + DebugTag::StackSlot(slot) => write!(f, "{slot}"), |
| 125 | + } |
| 126 | + } |
| 127 | +} |
0 commit comments