Skip to main content

midenc_hir/dialects/debuginfo/attributes/
expression.rs

1use alloc::{format, string::ToString, vec::Vec};
2
3use crate::{
4    AttrPrinter, attributes::AttrParser, derive::DialectAttribute,
5    dialects::debuginfo::DebugInfoDialect, interner::Symbol, parse::ParserExt, print::AsmPrinter,
6};
7
8/// The Wasm location that supplies the base address for `DW_OP_fbreg`.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum FrameBase {
11    Local(u32),
12    Global(u32),
13}
14
15/// A frame base after MASM lowering has resolved Wasm locations to Miden locations.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17pub enum ResolvedFrameBase {
18    Local(i16),
19    Global(u32),
20}
21
22/// Represents DWARF expression operations for describing variable locations
23#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24#[repr(u8)]
25pub enum ExpressionOp {
26    /// DW_OP_WASM_location 0x00 - Variable is in a WebAssembly local
27    WasmLocal(u32) = 0,
28    /// DW_OP_WASM_location 0x01 - Variable is in a WebAssembly global
29    WasmGlobal(u32) = 1,
30    /// DW_OP_WASM_location 0x02 - Variable is on the WebAssembly operand stack
31    WasmStack(u32) = 2,
32    /// DW_OP_constu - Unsigned constant value
33    ConstU64(u64) = 3,
34    /// DW_OP_consts - Signed constant value
35    ConstS64(i64) = 4,
36    /// DW_OP_plus_uconst - Add unsigned constant to top of stack
37    PlusUConst(u64) = 5,
38    /// DW_OP_minus - Subtract top two stack values
39    Minus = 6,
40    /// DW_OP_plus - Add top two stack values
41    Plus = 7,
42    /// DW_OP_deref - Dereference the address at top of stack
43    Deref = 8,
44    /// DW_OP_stack_value - The value on the stack is the value of the variable
45    StackValue = 9,
46    /// DW_OP_piece - Describes a piece of a variable
47    Piece(u64) = 10,
48    /// DW_OP_bit_piece - Describes a piece of a variable in bits
49    BitPiece { size: u64, offset: u64 } = 11,
50    /// DW_OP_addr - pushes memory address `address` on the expression operand stack
51    Address { address: u64 } = 13,
52    /// DW_OP_fbreg - Wasm frame-base location + offset.
53    FrameBase { base: FrameBase, byte_offset: i64 } = 14,
54    /// A frame-base location resolved to the corresponding Miden local/global address.
55    ResolvedFrameBase {
56        base: ResolvedFrameBase,
57        byte_offset: i64,
58    } = 15,
59    /// Placeholder for unsupported operations
60    Unsupported(Symbol) = u8::MAX,
61}
62
63impl ExpressionOp {
64    const fn tag(&self) -> u8 {
65        // SAFETY: This is safe because we have given this enum a
66        // primitive representation with #[repr(u8)], with the first
67        // field of the underlying union-of-structs the discriminant
68        //
69        // See the section on "accessing the numeric value of the discriminant"
70        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
71        unsafe { *(self as *const Self).cast::<u8>() }
72    }
73}
74
75impl miden_core::serde::Serializable for ExpressionOp {
76    fn write_into<W: miden_core::serde::ByteWriter>(&self, target: &mut W) {
77        target.write_u8(self.tag());
78        match self {
79            Self::WasmLocal(idx) | Self::WasmGlobal(idx) | Self::WasmStack(idx) => {
80                target.write_u32(*idx);
81            }
82            Self::ConstU64(val) | Self::PlusUConst(val) | Self::Piece(val) => {
83                target.write_u64(*val);
84            }
85            Self::ConstS64(val) => {
86                target.write_u64(*val as u64);
87            }
88            Self::Minus | Self::Plus | Self::Deref | Self::StackValue => (),
89            Self::BitPiece { size, offset } => {
90                target.write_u64(*size);
91                target.write_u64(*offset);
92            }
93            Self::FrameBase { base, byte_offset } => {
94                match base {
95                    FrameBase::Local(index) => {
96                        target.write_u8(0);
97                        target.write_u32(*index);
98                    }
99                    FrameBase::Global(index) => {
100                        target.write_u8(1);
101                        target.write_u32(*index);
102                    }
103                }
104                target.write_u64(*byte_offset as u64);
105            }
106            Self::ResolvedFrameBase { base, byte_offset } => {
107                match base {
108                    ResolvedFrameBase::Local(offset) => {
109                        target.write_u8(0);
110                        target.write_bytes(&offset.to_le_bytes());
111                    }
112                    ResolvedFrameBase::Global(address) => {
113                        target.write_u8(1);
114                        target.write_u32(*address);
115                    }
116                }
117                target.write_u64(*byte_offset as u64);
118            }
119            Self::Address { address } => {
120                target.write_u64(*address);
121            }
122            Self::Unsupported(name) => {
123                target.write_usize(name.as_str().len());
124                target.write_bytes(name.as_str().as_bytes());
125            }
126        }
127    }
128}
129
130impl miden_core::serde::Deserializable for ExpressionOp {
131    fn read_from<R: miden_core::serde::ByteReader>(
132        source: &mut R,
133    ) -> Result<Self, miden_core::serde::DeserializationError> {
134        use miden_core::serde::DeserializationError;
135
136        Ok(match source.read_u8()? {
137            0 => Self::WasmLocal(u32::read_from(source)?),
138            1 => Self::WasmGlobal(u32::read_from(source)?),
139            2 => Self::WasmStack(u32::read_from(source)?),
140            3 => Self::ConstU64(u64::read_from(source)?),
141            4 => Self::ConstS64(u64::read_from(source)? as i64),
142            5 => Self::PlusUConst(u64::read_from(source)?),
143            6 => Self::Minus,
144            7 => Self::Plus,
145            8 => Self::Deref,
146            9 => Self::StackValue,
147            10 => Self::Piece(u64::read_from(source)?),
148            11 => {
149                let size = u64::read_from(source)?;
150                let offset = u64::read_from(source)?;
151                Self::BitPiece { size, offset }
152            }
153            12 => {
154                // Legacy expressions only represented global frame bases.
155                let global_index = u32::read_from(source)?;
156                let byte_offset = u64::read_from(source)? as i64;
157                Self::FrameBase {
158                    base: FrameBase::Global(global_index),
159                    byte_offset,
160                }
161            }
162            13 => {
163                let address = u64::read_from(source)?;
164                Self::Address { address }
165            }
166            14 => {
167                let base = match source.read_u8()? {
168                    0 => FrameBase::Local(u32::read_from(source)?),
169                    1 => FrameBase::Global(u32::read_from(source)?),
170                    tag => {
171                        return Err(DeserializationError::InvalidValue(format!(
172                            "invalid frame-base tag '{tag}'"
173                        )));
174                    }
175                };
176                let byte_offset = u64::read_from(source)? as i64;
177                Self::FrameBase { base, byte_offset }
178            }
179            15 => {
180                let base = match source.read_u8()? {
181                    0 => {
182                        let bytes = source.read_array::<2>()?;
183                        ResolvedFrameBase::Local(i16::from_le_bytes(bytes))
184                    }
185                    1 => ResolvedFrameBase::Global(u32::read_from(source)?),
186                    tag => {
187                        return Err(DeserializationError::InvalidValue(format!(
188                            "invalid resolved frame-base tag '{tag}'"
189                        )));
190                    }
191                };
192                let byte_offset = u64::read_from(source)? as i64;
193                Self::ResolvedFrameBase { base, byte_offset }
194            }
195            u8::MAX => {
196                let len = usize::read_from(source)?;
197                let bytes = source.read_slice(len)?;
198                let s = core::str::from_utf8(bytes)
199                    .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
200                Self::Unsupported(Symbol::intern(s))
201            }
202            invalid => {
203                return Err(DeserializationError::InvalidValue(format!(
204                    "unknown DIExpressionOp tag '{invalid}'"
205                )));
206            }
207        })
208    }
209
210    fn min_serialized_size() -> usize {
211        1
212    }
213}
214
215impl crate::formatter::PrettyPrint for ExpressionOp {
216    fn render(&self) -> crate::formatter::Document {
217        use crate::formatter::*;
218        match self {
219            Self::WasmLocal(idx) => {
220                const_text("DW_OP_WASM_local") + const_text("(") + display(idx) + const_text(")")
221            }
222            Self::WasmGlobal(idx) => {
223                const_text("DW_OP_WASM_global") + const_text("(") + display(idx) + const_text(")")
224            }
225            Self::WasmStack(idx) => {
226                const_text("DW_OP_WASM_stack") + const_text("(") + display(idx) + const_text(")")
227            }
228            Self::ConstU64(val) => {
229                const_text("DW_OP_constu") + const_text("(") + display(val) + const_text(")")
230            }
231            Self::ConstS64(val) => {
232                const_text("DW_OP_consts") + const_text("(") + display(val) + const_text(")")
233            }
234            Self::PlusUConst(val) => {
235                const_text("DW_OP_plus_uconst") + const_text("(") + display(val) + const_text(")")
236            }
237            Self::Minus => const_text("DW_OP_minus"),
238            Self::Plus => const_text("DW_OP_plus"),
239            Self::Deref => const_text("DW_OP_deref"),
240            Self::StackValue => const_text("DW_OP_stack_value"),
241            Self::Piece(size) => {
242                const_text("DW_OP_piece") + const_text("(") + display(*size) + const_text(")")
243            }
244            Self::BitPiece { size, offset } => {
245                const_text("DW_OP_bit_piece")
246                    + const_text("(")
247                    + display(*size)
248                    + const_text(",")
249                    + display(*offset)
250                    + const_text(")")
251            }
252            Self::FrameBase { base, byte_offset } => match base {
253                FrameBase::Local(index) => {
254                    const_text("DW_OP_fbreg(local, ")
255                        + text(format!("{index}{byte_offset:+}"))
256                        + const_text(")")
257                }
258                FrameBase::Global(index) => {
259                    const_text("DW_OP_fbreg(global, ")
260                        + text(format!("{index}{byte_offset:+}"))
261                        + const_text(")")
262                }
263            },
264            Self::ResolvedFrameBase { base, byte_offset } => match base {
265                ResolvedFrameBase::Local(offset) => {
266                    const_text("MIDEN_OP_fbreg(local, ")
267                        + text(format!("{offset}{byte_offset:+}"))
268                        + const_text(")")
269                }
270                ResolvedFrameBase::Global(address) => {
271                    const_text("MIDEN_OP_fbreg(global, ")
272                        + text(format!("{address}{byte_offset:+}"))
273                        + const_text(")")
274                }
275            },
276            Self::Address { address } => {
277                const_text("DW_OP_addr") + const_text("(") + display(*address) + const_text(")")
278            }
279            Self::Unsupported(name) => const_text(name.as_str()),
280        }
281    }
282}
283
284impl ExpressionOp {
285    fn parse(parser: &mut dyn crate::parse::Parser<'_>) -> crate::parse::ParseResult<Self> {
286        use crate::parse::Token;
287
288        let mut op = parser
289            .token_stream_mut()
290            .expect_map("DIExpression operator", |tok| match tok {
291                Token::BareIdent(id) => match id {
292                    "DW_OP_WASM_local" => Some(ExpressionOp::WasmLocal(0)),
293                    "DW_OP_WASM_global" => Some(ExpressionOp::WasmGlobal(0)),
294                    "DW_OP_WASM_stack" => Some(ExpressionOp::WasmStack(0)),
295                    "DW_OP_constu" => Some(ExpressionOp::ConstU64(0)),
296                    "DW_OP_consts" => Some(ExpressionOp::ConstS64(0)),
297                    "DW_OP_plus_uconst" => Some(ExpressionOp::PlusUConst(0)),
298                    "DW_OP_minus" => Some(ExpressionOp::Minus),
299                    "DW_OP_plus" => Some(ExpressionOp::Plus),
300                    "DW_OP_deref" => Some(ExpressionOp::Deref),
301                    "DW_OP_stack_value" => Some(ExpressionOp::StackValue),
302                    "DW_OP_piece" => Some(ExpressionOp::Piece(0)),
303                    "DW_OP_bit_piece" => Some(ExpressionOp::BitPiece { size: 0, offset: 0 }),
304                    "DW_OP_fbreg" => Some(ExpressionOp::FrameBase {
305                        base: FrameBase::Global(0),
306                        byte_offset: 0,
307                    }),
308                    "DW_OP_addr" => Some(ExpressionOp::Address { address: 0 }),
309                    other => Some(ExpressionOp::Unsupported(Symbol::intern(other))),
310                },
311                _ => None,
312            })?
313            .into_inner();
314        match &mut op {
315            ExpressionOp::WasmLocal(idx)
316            | ExpressionOp::WasmGlobal(idx)
317            | ExpressionOp::WasmStack(idx) => {
318                parser.parse_lparen()?;
319                *idx = parser.parse_decimal_integer::<u32>()?.into_inner();
320                parser.parse_rparen()?;
321            }
322            ExpressionOp::ConstU64(val)
323            | ExpressionOp::PlusUConst(val)
324            | ExpressionOp::Piece(val)
325            | ExpressionOp::Address { address: val } => {
326                parser.parse_lparen()?;
327                *val = parser.parse_decimal_integer::<u64>()?.into_inner();
328                parser.parse_rparen()?;
329            }
330            ExpressionOp::ConstS64(val) => {
331                parser.parse_lparen()?;
332                *val = parser.parse_decimal_integer::<i64>()?.into_inner();
333                parser.parse_rparen()?;
334            }
335            ExpressionOp::Minus
336            | ExpressionOp::Plus
337            | ExpressionOp::Deref
338            | ExpressionOp::StackValue
339            | ExpressionOp::Unsupported(_) => (),
340            ExpressionOp::BitPiece { size, offset } => {
341                parser.parse_lparen()?;
342                *size = parser.parse_decimal_integer::<u64>()?.into_inner();
343                parser.parse_comma()?;
344                *offset = parser.parse_decimal_integer::<u64>()?.into_inner();
345                parser.parse_rparen()?;
346            }
347            ExpressionOp::FrameBase { base, byte_offset } => {
348                parser.parse_lparen()?;
349                let is_local = parser
350                    .token_stream_mut()
351                    .expect_map("'local' or 'global' modifier", |tok| match tok {
352                        Token::BareIdent("local") => Some(true),
353                        Token::BareIdent("global") => Some(false),
354                        _ => None,
355                    })?
356                    .into_inner();
357                parser.parse_comma()?;
358                let index = parser.parse_decimal_integer::<u32>()?.into_inner();
359                // The printed form is `INDEX{+|-}OFFSET`, e.g. `DW_OP_fbreg(local, 2+8)`
360                let negative = parser
361                    .token_stream_mut()
362                    .expect_map("'+' or '-' offset sign", |tok| match tok {
363                        Token::Plus => Some(false),
364                        Token::Minus => Some(true),
365                        _ => None,
366                    })?
367                    .into_inner();
368                let (offset_span, magnitude) = parser.parse_decimal_integer::<u64>()?.into_parts();
369                let signed = if negative {
370                    -(magnitude as i128)
371                } else {
372                    magnitude as i128
373                };
374                *byte_offset = i64::try_from(signed).map_err(|_| {
375                    crate::parse::ParserError::InvalidIntegerLiteral {
376                        span: offset_span,
377                        reason: format!("byte offset '{signed}' is out of range for i64"),
378                    }
379                })?;
380                *base = if is_local {
381                    FrameBase::Local(index)
382                } else {
383                    FrameBase::Global(index)
384                };
385                parser.parse_rparen()?;
386            }
387            ExpressionOp::ResolvedFrameBase { .. } => unreachable!(
388                "resolved frame-base expressions are produced only during MASM lowering"
389            ),
390        }
391
392        Ok(op)
393    }
394}
395
396/// Represents a DWARF expression that describes how to compute or locate a variable's value
397#[derive(DialectAttribute, Clone, Debug, Default, PartialEq, Eq, Hash)]
398#[attribute(dialect = DebugInfoDialect, implements(AttrPrinter))]
399pub struct Expression {
400    pub operations: Vec<ExpressionOp>,
401}
402
403impl Expression {
404    pub fn new() -> Self {
405        Self {
406            operations: Vec::new(),
407        }
408    }
409
410    pub fn with_ops(operations: Vec<ExpressionOp>) -> Self {
411        Self { operations }
412    }
413
414    pub fn is_empty(&self) -> bool {
415        self.operations.is_empty()
416    }
417}
418
419impl miden_core::serde::Serializable for Expression {
420    fn write_into<W: miden_core::serde::ByteWriter>(&self, target: &mut W) {
421        target.write_usize(self.operations.len());
422        for op in self.operations.iter() {
423            target.write(op);
424        }
425    }
426}
427
428impl miden_core::serde::Deserializable for Expression {
429    fn read_from<R: miden_core::serde::ByteReader>(
430        source: &mut R,
431    ) -> Result<Self, miden_core::serde::DeserializationError> {
432        let len = usize::read_from(source)?;
433        let operations = source.read_many_iter(len)?.collect::<Result<Vec<_>, _>>()?;
434        Ok(Self::with_ops(operations))
435    }
436}
437
438impl AttrPrinter for ExpressionAttr {
439    fn print(&self, printer: &mut AsmPrinter<'_>) {
440        use crate::formatter::*;
441
442        if self.operations.is_empty() {
443            *printer += const_text("[]");
444            return;
445        }
446
447        *printer += const_text("[");
448        for (i, op) in self.operations.iter().enumerate() {
449            if i > 0 {
450                *printer += const_text(", ");
451            }
452            match op {
453                ExpressionOp::WasmLocal(idx) => {
454                    *printer += const_text("DW_OP_WASM_local");
455                    *printer += const_text("(") + display(*idx) + const_text(")");
456                }
457                ExpressionOp::WasmGlobal(idx) => {
458                    *printer += const_text("DW_OP_WASM_global");
459                    *printer += const_text("(") + display(*idx) + const_text(")");
460                }
461                ExpressionOp::WasmStack(idx) => {
462                    *printer += const_text("DW_OP_WASM_stack");
463                    *printer += const_text("(") + display(*idx) + const_text(")");
464                }
465                ExpressionOp::ConstU64(val) => {
466                    *printer += const_text("DW_OP_constu");
467                    *printer += const_text("(") + display(*val) + const_text(")");
468                }
469                ExpressionOp::ConstS64(val) => {
470                    *printer += const_text("DW_OP_consts");
471                    *printer += const_text("(") + display(*val) + const_text(")");
472                }
473                ExpressionOp::PlusUConst(val) => {
474                    *printer += const_text("DW_OP_plus_uconst");
475                    *printer += const_text("(") + display(*val) + const_text(")");
476                }
477                ExpressionOp::Minus => *printer += const_text("DW_OP_minus"),
478                ExpressionOp::Plus => *printer += const_text("DW_OP_plus"),
479                ExpressionOp::Deref => *printer += const_text("DW_OP_deref"),
480                ExpressionOp::StackValue => *printer += const_text("DW_OP_stack_value"),
481                ExpressionOp::Piece(size) => {
482                    *printer += const_text("DW_OP_piece");
483                    *printer += const_text("(") + display(*size) + const_text(")");
484                }
485                ExpressionOp::BitPiece { size, offset } => {
486                    *printer += const_text("DW_OP_bit_piece");
487                    *printer += const_text("(")
488                        + display(*size)
489                        + const_text(",")
490                        + display(*offset)
491                        + const_text(")");
492                }
493                ExpressionOp::FrameBase { base, byte_offset } => match base {
494                    FrameBase::Local(index) => {
495                        *printer += const_text("DW_OP_fbreg(local, ");
496                        *printer += text(format!("{}{:+}", index, byte_offset));
497                        *printer += const_text(")");
498                    }
499                    FrameBase::Global(index) => {
500                        *printer += const_text("DW_OP_fbreg(global, ");
501                        *printer += text(format!("{}{:+}", index, byte_offset));
502                        *printer += const_text(")");
503                    }
504                },
505                ExpressionOp::ResolvedFrameBase { base, byte_offset } => match base {
506                    ResolvedFrameBase::Local(offset) => {
507                        *printer += const_text("MIDEN_OP_fbreg(local, ");
508                        *printer += text(format!("{}{:+}", offset, byte_offset));
509                        *printer += const_text(")");
510                    }
511                    ResolvedFrameBase::Global(address) => {
512                        *printer += const_text("MIDEN_OP_fbreg(global, ");
513                        *printer += text(format!("{}{:+}", address, byte_offset));
514                        *printer += const_text(")");
515                    }
516                },
517                ExpressionOp::Address { address } => {
518                    *printer += const_text("DW_OP_addr");
519                    *printer += const_text("(") + display(*address) + const_text(")");
520                }
521                ExpressionOp::Unsupported(name) => *printer += const_text(name.as_str()),
522            }
523        }
524        *printer += const_text("]");
525    }
526}
527
528impl AttrParser for ExpressionAttr {
529    fn parse(
530        parser: &mut dyn crate::parse::Parser<'_>,
531    ) -> crate::parse::ParseResult<crate::AttributeRef> {
532        use crate::parse::Delimiter;
533
534        let mut ops = Vec::default();
535        parser.parse_comma_separated_list(
536            Delimiter::OptionalBracket,
537            Some("DIExpression"),
538            |parser| {
539                ops.push(ExpressionOp::parse(parser)?);
540
541                Ok(true)
542            },
543        )?;
544
545        let attr = parser
546            .context_rc()
547            .create_attribute::<ExpressionAttr, _>(Expression::with_ops(ops));
548
549        Ok(attr.as_attribute_ref())
550    }
551}