Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5a5a093
Add debug variable location tracking to HIR and MASM
djolertrk Jan 9, 2026
02f9163
Align with latest debug_info in miden-vm
djolertrk Jan 16, 2026
781565d
fix: compute correct FMP offset for debug variable locations
djolertrk Jan 17, 2026
ac5faaf
Introduce Debug Info dialect
djolertrk Feb 3, 2026
e0c14a9
fix: handle debug info ops in transforms and MASM codegen
djolertrk Feb 4, 2026
67fc2e5
debuginfo: migrate DI types to DialectAttribute derive for v0.21.1 co…
djolertrk Mar 21, 2026
57cf2a6
feat: debug variable location tracking with dedup crash fix
djolertrk Mar 30, 2026
a57c499
fix: rebase recovery — entity alignment
djolertrk Mar 31, 2026
c403ea8
fixup: several bugfixes for var loc
djolertrk Mar 31, 2026
f89ecad
fixup: remove debug info docs
djolertrk Apr 1, 2026
98b6aee
fixup: debug var loc issues in frontend and codegen
djolertrk Apr 1, 2026
f3426e6
fix: update lit tests for current HIR output and debugdump formats
djolertrk Apr 1, 2026
3351d0f
fix: adapt debugdump to upstream Package.version API change
djolertrk Apr 1, 2026
188a574
chore: fix formatting
djolertrk Apr 1, 2026
5a372f0
fix: post-rebase cleanup for origin/next
djolertrk Apr 14, 2026
1e8f2af
fix: register DebugValue as no-op in HIR evaluator
djolertrk Apr 14, 2026
f19ef3b
fix: skip debug info ops in liveness analysis
djolertrk Apr 14, 2026
920db85
fix: skip location schedule emission for undefined variables
djolertrk Apr 14, 2026
577eb6f
fix: clippy for debug_info.rs
djolertrk Apr 14, 2026
9ed7a2e
fix: preserve DWARF frame-base variables
djolertrk Apr 14, 2026
615fee1
fix: run debugdump tests via litcheck
djolertrk Apr 28, 2026
206e283
fix: use released miden vm debug APIs
djolertrk Apr 29, 2026
e04048a
fixup: update tests
djolertrk Apr 29, 2026
ca53f38
fixup: improve debugdump help
djolertrk Apr 29, 2026
4874f47
fix(debug): emit component core type metadata
djolertrk Apr 29, 2026
a9f01da
chore: merge debugdump into objtool
bitwalker Apr 29, 2026
5ade168
chore: remove manual debug info section handling in driver
bitwalker Apr 29, 2026
551eb38
refactor(debuginfo): various improvements to variable tracking impl
bitwalker Apr 30, 2026
90ebb54
chore: rebase on latest toolchain and dependency versions
bitwalker May 1, 2026
ce54f0f
fix(hir): preserve debug variable ops through rewrites
djolertrk May 4, 2026
71a95b6
fix: emit Rust debug values in integration tests
djolertrk May 4, 2026
90206ab
fix: use di.debug_declare for variables without SSA value anchor
djolertrk May 4, 2026
121784e
chore: merge next and bitwalker/debug-variable-locations
bitwalker May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ env/
*.out
node_modules/
*DS_Store
._*
*.iml
book/

# Ignore Cargo.lock in test projects
examples/**/Cargo.lock
tests/**/Cargo.lock

*.lit_test_times.txt*
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ inventory = "0.3"
litcheck = { package = "litcheck-core", version = "0.4" }
litcheck-filecheck = "0.4"
log = { version = "0.4", features = ["kv"] }
env_logger = "0.11"

# Miden Dependencies
miden-assembly = { version = "0.22", default-features = false }
Expand Down Expand Up @@ -173,9 +174,9 @@ miden-field = { version = "^0.24" }
#miden-processor = { path = "../miden-vm/processor" }
#miden-mast-package = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" }
#miden-mast-package = { path = "../miden-vm/package" }
# miden-protocol = { git = "https://github.com/0xMiden/protocol", rev = "a53bbe2209f506df87876c8b9c9a1730214f456b" }
# miden-standards = { git = "https://github.com/0xMiden/protocol", rev = "a53bbe2209f506df87876c8b9c9a1730214f456b" }
# miden-tx = { tag = "v0.14.0-beta.4", git = "https://github.com/0xMiden/miden-base" }
#miden-protocol = { git = "https://github.com/0xMiden/protocol", rev = "a53bbe2209f506df87876c8b9c9a1730214f456b" }
#miden-standards = { git = "https://github.com/0xMiden/protocol", rev = "a53bbe2209f506df87876c8b9c9a1730214f456b" }
#miden-tx = { tag = "v0.14.0-beta.4", git = "https://github.com/0xMiden/miden-base" }

[profile.dev]
lto = false
Expand Down
5 changes: 3 additions & 2 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ dependencies = ["cargo-miden"]
category = "Test"
description = "Runs the lit/filecheck test suite"
command = "litcheck"
env = { MIDENC_BIN_DIR = "${MIDENC_BIN_DIR}" }
args = [
"lit",
"run",
Expand All @@ -444,7 +445,7 @@ args = [
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/bin",
"${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/tests/lit",
]
dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt"]
dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt", "miden-objtool"]

[tasks.lit]
category = "Test"
Expand All @@ -455,7 +456,7 @@ args = [
"lit",
"${@}",
]
dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt"]
dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt", "miden-objtool"]


[tasks.litcheck]
Expand Down
6 changes: 3 additions & 3 deletions codegen/masm/src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ impl BlockEmitter<'_> {
// operand stack space on operands that will never be used.
//self.drop_unused_operands_at(op);

let lowering = op.as_trait::<dyn HirLowering>().unwrap_or_else(|| {
panic!("illegal operation: no lowering has been defined for '{}'", op.name())
});
let Some(lowering) = op.as_trait::<dyn HirLowering>() else {
panic!("illegal operation: no lowering has been defined for '{}'", op.name());
};

// Schedule operands for this instruction
lowering
Expand Down
14 changes: 13 additions & 1 deletion codegen/masm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ use midenc_dialect_hir as hir;
use midenc_dialect_scf as scf;
use midenc_dialect_ub as ub;
use midenc_dialect_wasm as wasm;
use midenc_hir::{dialects::builtin, inventory};
use midenc_hir::{
dialects::{builtin, debuginfo},
inventory,
};

pub(crate) use self::lower::HirLowering;
pub use self::{
Expand Down Expand Up @@ -62,6 +65,9 @@ inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::<hir::HirDial
inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::<wasm::WasmDialect>(
lower_wasm_ops
));
inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::<debuginfo::DebugInfoDialect>(
lower_debuginfo_ops
));

fn lower_builtin_ops(info: &mut midenc_hir::DialectInfo) {
info.register_operation_trait::<builtin::Ret, dyn HirLowering>();
Expand Down Expand Up @@ -171,3 +177,9 @@ fn lower_wasm_ops(info: &mut midenc_hir::DialectInfo) {
info.register_operation_trait::<wasm::I64Load16S, dyn HirLowering>();
info.register_operation_trait::<wasm::I64Load32S, dyn HirLowering>();
}

fn lower_debuginfo_ops(info: &mut midenc_hir::DialectInfo) {
info.register_operation_trait::<debuginfo::DebugDeclare, dyn HirLowering>();
info.register_operation_trait::<debuginfo::DebugValue, dyn HirLowering>();
info.register_operation_trait::<debuginfo::DebugKill, dyn HirLowering>();
}
129 changes: 127 additions & 2 deletions codegen/masm/src/lower/component.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use alloc::{collections::BTreeSet, sync::Arc};
use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};

use miden_assembly::{PathBuf as LibraryPath, ast::InvocationTarget};
use miden_assembly_syntax::{ast::Attribute, parser::WordValue};
use miden_core::operations::DebugVarLocation;
use midenc_hir::{
FunctionIdent, Op, OpExt, SourceSpan, Span, Symbol, TraceTarget, ValueRef,
diagnostics::IntoDiagnostic, dialects::builtin, pass::AnalysisManager,
diagnostics::IntoDiagnostic,
dialects::{
builtin,
debuginfo::attributes::{decode_frame_base_local_index, encode_frame_base_local_offset},
},
pass::AnalysisManager,
};
use midenc_hir_analysis::analyses::LivenessAnalysis;
use midenc_session::{
Expand Down Expand Up @@ -646,6 +652,32 @@ impl MasmFunctionBuilder {
num_locals,
} = self;

// Align num_locals to WORD_SIZE, matching the assembler's FMP frame sizing.
// num_locals already counts all HIR locals (including those allocated for params).
// The assembler rounds up to next_multiple_of(WORD_SIZE) when advancing FMP
// (see fmp.rs fmp_start_frame_sequence and mem_ops.rs locaddr), so we must use
// the same alignment for debug var offset computation.
let aligned_num_locals = num_locals.next_multiple_of(miden_core::WORD_SIZE as u16);

// Resolve FrameBase global_index → Miden memory address.
// Use the stack pointer offset from the linker's global layout.
let stack_pointer_addr = link_info.globals_layout().stack_pointer_offset();

// Patch DebugVar Local locations to compute FMP offset.
// During lowering, Local(idx) stores the raw WASM local index.
// Now convert to FMP offset: idx - aligned_num_locals
// This matches locaddr.N which computes -(aligned_num_locals - N).
patch_debug_var_locals_in_block(&mut body, aligned_num_locals, stack_pointer_addr);

// If a function body after lowering produces a MASM procedure with an empty body aside
// from debug decorators, then we must emit a `nop` at the end of the block which will
// act as the anchor for those decorators. Such a procedure is basically useless, as it is
// just passing through arguments as results - but the assembler currently rejects empty
// procedures (not counting decorators), so we must handle this edge case.
if !block_has_real_instructions(&body) {
body.push(masm::Op::Inst(Span::unknown(masm::Instruction::Nop)));
}

let mut procedure = masm::Procedure::new(span, visibility, name, num_locals, body);
procedure.set_signature(signature);
for attribute in ["auth_script", "note_script"] {
Expand All @@ -660,3 +692,96 @@ impl MasmFunctionBuilder {
Ok(procedure)
}
}

/// Returns true if the block contains at least one real (non-decorator) instruction.
///
/// DebugVar instructions are decorator-only and don't produce MAST nodes. If a procedure
/// body contains only DebugVar ops, the assembler will reject it.
fn block_has_real_instructions(block: &masm::Block) -> bool {
block.iter().any(|op| match op {
masm::Op::Inst(inst) => !matches!(
inst.inner(),
masm::Instruction::Debug(_)
| masm::Instruction::DebugVar(_)
| masm::Instruction::Trace(_)
),
masm::Op::If {
then_blk, else_blk, ..
} => block_has_real_instructions(then_blk) || block_has_real_instructions(else_blk),
masm::Op::While { body, .. } => block_has_real_instructions(body),
masm::Op::Repeat { body, .. } => block_has_real_instructions(body),
})
}

/// Recursively patch DebugVar locations in a block.
///
/// Converts `Local(idx)` where idx is the raw WASM local index to `Local(offset)` where
/// `offset = idx - aligned_num_locals` (the FMP-relative offset, typically negative). This matches
/// the assembler's `locaddr.N` formula, i.e. `FMP - aligned_num_locals + N`.
///
/// Also resolves `FrameBase { global_index, byte_offset }` by replacing the WASM global index with
/// the resolved Miden memory address of the stack pointer.
fn patch_debug_var_locals_in_block(
block: &mut masm::Block,
aligned_num_locals: u16,
stack_pointer_addr: Option<u32>,
) {
for op in block.iter_mut() {
match op {
masm::Op::Inst(span_inst) => {
// Use DerefMut to get mutable access to the inner Instruction
if let masm::Instruction::DebugVar(info) = &mut **span_inst {
if let DebugVarLocation::Local(idx) = info.value_location() {
// Convert raw WASM local index to FMP offset
let fmp_offset = *idx - (aligned_num_locals as i16);
info.set_value_location(DebugVarLocation::Local(fmp_offset));
} else if let DebugVarLocation::FrameBase {
global_index,
byte_offset,
} = info.value_location()
{
let byte_offset = *byte_offset;
if let Some(local_index) = decode_frame_base_local_index(*global_index) {
if let Ok(local_index) = i16::try_from(local_index) {
let local_offset = local_index - (aligned_num_locals as i16);
info.set_value_location(DebugVarLocation::FrameBase {
global_index: encode_frame_base_local_offset(local_offset),
byte_offset,
});
}
} else {
// Resolve FrameBase: replace WASM global index with
// the Miden memory address of the stack pointer global.
if let Some(resolved_addr) = stack_pointer_addr {
info.set_value_location(DebugVarLocation::FrameBase {
global_index: resolved_addr,
byte_offset,
});
}
}
}
}
}
masm::Op::If {
then_blk, else_blk, ..
} => {
patch_debug_var_locals_in_block(then_blk, aligned_num_locals, stack_pointer_addr);
patch_debug_var_locals_in_block(else_blk, aligned_num_locals, stack_pointer_addr);
}
masm::Op::While {
body: while_body, ..
} => {
patch_debug_var_locals_in_block(while_body, aligned_num_locals, stack_pointer_addr);
}
masm::Op::Repeat {
body: repeat_body, ..
} => {
patch_debug_var_locals_in_block(
repeat_body,
aligned_num_locals,
stack_pointer_addr,
);
}
}
}
}
Loading
Loading