diff --git a/.gitignore b/.gitignore index c663bcca4..3547a9b09 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,12 @@ env/ *.out node_modules/ *DS_Store +._* *.iml book/ # Ignore Cargo.lock in test projects examples/**/Cargo.lock tests/**/Cargo.lock +**/src/bindings.rs +*.lit_test_times.txt* diff --git a/Cargo.lock b/Cargo.lock index b6cc6bc6d..980ec5f0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1734,7 +1734,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -2376,9 +2376,9 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6013b3a390e0dcb29242f4480a7727965887bbf0903466c88f362b4cb20c0e" +checksum = "1d2094e2b943f7bf955a2bc3b44b0ad7c4f45a286f170eaa7e5060871c44847a" dependencies = [ "env_logger", "log", @@ -2393,9 +2393,9 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996156b8f7c5fe6be17dea71089c6d7985c2dec1e3a4fec068b1dfc690e25df5" +checksum = "b5a3212614ad28399612f39024c1e321dc8cebc8998def06058e60462ddc3856" dependencies = [ "aho-corasick", "env_logger", @@ -2501,9 +2501,9 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdec54a321cdf3d23e9ef615e91cb858038c6b4d4202507bdec048fc6d7763e4" +checksum = "39a4a2e2de49213ec899e88fe399d4ec568c8eb9e8c747d6ed58938c40031daa" dependencies = [ "derive_more", "itertools 0.14.0", @@ -2523,9 +2523,9 @@ dependencies = [ [[package]] name = "miden-core-lib" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621e8fa911a790bcf3cd3aedce80bc10922a19d6181f08ff3ca078f955cff70b" +checksum = "2d2ea7e17c4382255c6e0cb1e4b90693449dcf5a286a844e2918af66b371c0ab" dependencies = [ "env_logger", "fs-err", @@ -2672,9 +2672,9 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e50274d11c80b901cf6c90362de8c98c8c8ad6030c80624d683b63d899a0fb" +checksum = "16570786d938b7f795921b3a84890708a7d72708442c622eb58c2fb5480821e9" dependencies = [ "memchr", "miden-crypto", @@ -2688,6 +2688,16 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "miden-debugdump" +version = "0.8.1" +dependencies = [ + "clap", + "miden-core", + "miden-mast-package", + "miden-thiserror", +] + [[package]] name = "miden-field" version = "0.22.6" @@ -2807,9 +2817,9 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8b2e3447fcde1f0e6b76e5219f129517639772cb02ca543177f0584e315288" +checksum = "0953396dc5e575b79bccb8b7da6e0d18ce71bcde899901bb4293a433f9003b94" dependencies = [ "derive_more", "miden-assembly-syntax", @@ -2892,9 +2902,9 @@ dependencies = [ [[package]] name = "miden-package-registry" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969ba3942052e52b3968e34dbd1c52c707e75777ee42ebdae2c8f57af56cf6cf" +checksum = "e07af92dc184a71132a34d89ad15e69633435bfd36fb5af4ce18b200bd1952e5" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -2907,9 +2917,9 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec6cecbf22bd92b73a931ee80b424e46b8b7cdf4f2f3c364c25c5c15d2840da" +checksum = "340c424f9f62b56a808c9a479cef016f25478e227555ce39cb2684e8baf26542" dependencies = [ "itertools 0.14.0", "miden-air", @@ -2926,9 +2936,9 @@ dependencies = [ [[package]] name = "miden-project" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3840520c01881534fbbceb6b3687ec1c407fbaf310a35ce415fd3510abc52fdb" +checksum = "541619ccdf566c2fac0d24bfc3806bc36e1d57a698a937621f1874ceb36a55d4" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -3139,9 +3149,9 @@ dependencies = [ [[package]] name = "miden-utils-core-derive" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3846c8674ccec0c37005f99c1a599a24790ba2a5e5f4e1c7aec5f456821df835" +checksum = "cdd5103e9b6527ad396dce12c135cea1984dfd77ebbffa76f260f4e139906cc4" dependencies = [ "proc-macro2", "quote", @@ -3150,9 +3160,9 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397f5d1e8679cf17cf7713ffd9654840791a6ed5818b025bbc2fbfdce846579a" +checksum = "72226906c968c2e7c37435d67be9e29aeba05336db30c4e57d290cc6efb1da9d" dependencies = [ "miden-crypto", "miden-debug-types", @@ -3163,9 +3173,9 @@ dependencies = [ [[package]] name = "miden-utils-indexing" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8834e76299686bcce3de1685158aa4cff49b7fa5e0e00a6cc811e8f2cf5775f" +checksum = "5cc2e62161113179a370ae0bf1fd33eb8d20b6131e8559d2dc0bead5cffae586" dependencies = [ "miden-crypto", "serde", @@ -3174,9 +3184,9 @@ dependencies = [ [[package]] name = "miden-utils-sync" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9e9747e9664c1a0997bb040ae291306ea0a1c74a572141ec66cec855c1b0e8" +checksum = "4e9210b3592b577843710daf68293087c68b53d8482c82f6875ad83d578cb51e" dependencies = [ "lock_api", "loom", @@ -3234,6 +3244,7 @@ dependencies = [ "miden-thiserror", "midenc-dialect-arith", "midenc-dialect-cf", + "midenc-dialect-debuginfo", "midenc-dialect-hir", "midenc-dialect-scf", "midenc-dialect-ub", @@ -3257,9 +3268,12 @@ dependencies = [ "inventory", "log", "miden-assembly", + "miden-core", + "miden-debug-types", "miden-mast-package", "miden-thiserror", "midenc-codegen-masm", + "midenc-dialect-debuginfo", "midenc-dialect-hir", "midenc-dialect-scf", "midenc-frontend-wasm", @@ -3286,6 +3300,15 @@ dependencies = [ "midenc-hir", ] +[[package]] +name = "midenc-dialect-debuginfo" +version = "0.8.1" +dependencies = [ + "log", + "midenc-hir", + "paste", +] + [[package]] name = "midenc-dialect-hir" version = "0.8.1" @@ -3307,9 +3330,11 @@ name = "midenc-dialect-scf" version = "0.8.1" dependencies = [ "bitvec", + "env_logger", "log", "midenc-dialect-arith", "midenc-dialect-cf", + "midenc-dialect-debuginfo", "midenc-dialect-ub", "midenc-expect-test", "midenc-hir", @@ -3366,6 +3391,7 @@ dependencies = [ "miden-thiserror", "midenc-dialect-arith", "midenc-dialect-cf", + "midenc-dialect-debuginfo", "midenc-dialect-hir", "midenc-dialect-ub", "midenc-dialect-wasm", @@ -3443,6 +3469,7 @@ dependencies = [ "miden-thiserror", "midenc-dialect-arith", "midenc-dialect-cf", + "midenc-dialect-debuginfo", "midenc-dialect-hir", "midenc-dialect-scf", "midenc-dialect-ub", diff --git a/Cargo.toml b/Cargo.toml index 58043fd9a..6ec2ab198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } @@ -140,6 +141,7 @@ midenc-dialect-arith = { version = "0.8.1", path = "dialects/arith" } midenc-dialect-hir = { version = "0.8.1", path = "dialects/hir" } midenc-dialect-scf = { version = "0.8.1", path = "dialects/scf" } midenc-dialect-cf = { version = "0.8.1", path = "dialects/cf" } +midenc-dialect-debuginfo = { version = "0.8.1", path = "dialects/debuginfo" } midenc-dialect-ub = { version = "0.8.1", path = "dialects/ub" } midenc-dialect-wasm = { version = "0.8.1", path = "dialects/wasm" } midenc-hir = { version = "0.8.1", path = "hir" } @@ -159,23 +161,6 @@ miden-integration-tests = { path = "tests/integration" } midenc-expect-test = { path = "tools/expect-test" } miden-field = { version = "0.22" } -[patch.crates-io] -#miden-assembly = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } -#miden-assembly = { path = "../miden-vm/assembly" } -#miden-assembly-syntax = { path = "../miden-vm/assembly-syntax" } -#miden-core = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } -#miden-core = { path = "../miden-vm/core" } -# miden-client = { git = "https://github.com/0xMiden/miden-client", rev = "0a5add565d1388f77cd182f3639c16aa8f7ec674" } -# miden-debug = { git = "https://github.com/0xMiden/miden-debug", branch = "main" } -#miden-debug-types = { path = "../miden-vm/crates/debug/types" } -#miden-processor = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } -#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" } - [profile.dev] lto = false # Needed for 'inventory' to work diff --git a/Makefile.toml b/Makefile.toml index f1f5c4eb8..17a26321d 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -235,6 +235,20 @@ args = [ "${MIDENC_BIN_DIR}", ] +[tasks.miden-debugdump] +category = "Build" +description = "Builds miden-debugdump and installs it to the bin folder" +command = "cargo" +args = [ + "-Z", + "unstable-options", + "build", + "-p", + "miden-debugdump", + "--artifact-dir", + "${MIDENC_BIN_DIR}", +] + [tasks.miden-objtool] category = "Build" description = "Builds miden-objtool and installs it to the bin folder" @@ -442,7 +456,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-debugdump"] [tasks.lit] category = "Test" @@ -453,7 +467,7 @@ args = [ "lit", "${@}", ] -dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt"] +dependencies = ["litcheck", "midenc", "cargo-miden", "hir-opt", "miden-debugdump"] [tasks.litcheck] diff --git a/codegen/masm/Cargo.toml b/codegen/masm/Cargo.toml index 50ecdda21..bbdab77b2 100644 --- a/codegen/masm/Cargo.toml +++ b/codegen/masm/Cargo.toml @@ -29,6 +29,7 @@ midenc-hir.workspace = true midenc-hir-analysis.workspace = true midenc-dialect-arith.workspace = true midenc-dialect-cf.workspace = true +midenc-dialect-debuginfo.workspace = true midenc-dialect-hir.workspace = true midenc-dialect-scf.workspace = true midenc-dialect-ub.workspace = true diff --git a/codegen/masm/src/emitter.rs b/codegen/masm/src/emitter.rs index 12db31481..14c2071ab 100644 --- a/codegen/masm/src/emitter.rs +++ b/codegen/masm/src/emitter.rs @@ -115,9 +115,16 @@ impl BlockEmitter<'_> { // operand stack space on operands that will never be used. //self.drop_unused_operands_at(op); - let lowering = op.as_trait::().unwrap_or_else(|| { - panic!("illegal operation: no lowering has been defined for '{}'", op.name()) - }); + let Some(lowering) = op.as_trait::() else { + // Skip debug info ops that have no lowering (e.g. debuginfo.kill, + // debuginfo.declare) rather than panicking. These ops carry no + // semantic meaning for code generation. + if op.name().dialect().as_str() == "debuginfo" { + log::trace!(target: "codegen", "skipping debug info op with no lowering: {}", op.name()); + return; + } + panic!("illegal operation: no lowering has been defined for '{}'", op.name()); + }; // Schedule operands for this instruction lowering diff --git a/codegen/masm/src/lib.rs b/codegen/masm/src/lib.rs index f3a1e7ab3..27ada6d0f 100644 --- a/codegen/masm/src/lib.rs +++ b/codegen/masm/src/lib.rs @@ -29,6 +29,7 @@ pub mod masm { use midenc_dialect_arith as arith; use midenc_dialect_cf as cf; +use midenc_dialect_debuginfo as debuginfo; use midenc_dialect_hir as hir; use midenc_dialect_scf as scf; use midenc_dialect_ub as ub; @@ -46,6 +47,9 @@ pub use self::{ inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::( lower_builtin_ops )); +inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::( + lower_debuginfo_ops +)); inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::( lower_arith_ops )); @@ -71,6 +75,10 @@ fn lower_builtin_ops(info: &mut midenc_hir::DialectInfo) { info.register_operation_trait::(); } +fn lower_debuginfo_ops(info: &mut midenc_hir::DialectInfo) { + info.register_operation_trait::(); +} + fn lower_arith_ops(info: &mut midenc_hir::DialectInfo) { info.register_operation_trait::(); info.register_operation_trait::(); diff --git a/codegen/masm/src/lower/component.rs b/codegen/masm/src/lower/component.rs index c2d5566d7..4817cf8b2 100644 --- a/codegen/masm/src/lower/component.rs +++ b/codegen/masm/src/lower/component.rs @@ -1,10 +1,12 @@ -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, + decode_frame_base_local_index, diagnostics::IntoDiagnostic, dialects::builtin, + encode_frame_base_local_offset, pass::AnalysisManager, }; use midenc_hir_analysis::analyses::LivenessAnalysis; use midenc_session::{ @@ -646,6 +648,33 @@ 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); + + // Strip DebugVar-only procedure bodies. + // The Miden assembler rejects procedures whose bodies contain only decorators + // (like DebugVar) and no real instructions, because decorators don't affect + // MAST digests — two empty procedures with different decorators would be + // indistinguishable. If there are no real instructions, the debug info is + // meaningless anyway, so just drop it. + if !block_has_real_instructions(&body) { + body = masm::Block::new(body.span(), vec![]); + } + let mut procedure = masm::Procedure::new(span, visibility, name, num_locals, body); procedure.set_signature(signature); for attribute in ["auth_script", "note_script"] { @@ -660,3 +689,91 @@ 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) => inst.has_textual_representation(), + 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: `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, +) { + 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, + ); + } + } + } +} diff --git a/codegen/masm/src/lower/lowering.rs b/codegen/masm/src/lower/lowering.rs index 044973b5f..cdbba0836 100644 --- a/codegen/masm/src/lower/lowering.rs +++ b/codegen/masm/src/lower/lowering.rs @@ -1,5 +1,6 @@ use midenc_dialect_arith as arith; use midenc_dialect_cf as cf; +use midenc_dialect_debuginfo as debuginfo; use midenc_dialect_hir as hir; use midenc_dialect_scf as scf; use midenc_dialect_ub as ub; @@ -1262,6 +1263,113 @@ impl HirLowering for arith::Split { } } +impl HirLowering for debuginfo::DebugValue { + fn schedule_operands(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + // Debug value operations are purely observational — they do not consume their + // operand from the stack. Skip operand scheduling entirely; the emit() method + // will look up the value's current stack position (if any) on its own. + Ok(()) + } + + fn required_operands(&self) -> ValueRange<'_, 4> { + // No operands need to be scheduled on the stack for debug ops. + ValueRange::Empty + } + + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + use miden_core::{ + Felt, + operations::{DebugVarInfo, DebugVarLocation}, + }; + use midenc_hir::DIExpressionOp; + + // Get the variable info + let var = self.variable(); + + // Build the DebugVarLocation from DIExpression + let expr = self.expression(); + let value = self.value().as_value_ref(); + + // If the value is not on the stack and there's no expression info, + // skip emitting this debug info (the value has been optimized away) + let has_location_expr = expr.operations.first().is_some_and(|op| { + matches!( + op, + DIExpressionOp::WasmStack(_) + | DIExpressionOp::WasmLocal(_) + | DIExpressionOp::ConstU64(_) + | DIExpressionOp::ConstS64(_) + | DIExpressionOp::FrameBase { .. } + ) + }); + if !has_location_expr && emitter.stack.find(&value).is_none() { + // Value has been dropped and we have no other location info, skip + return Ok(()); + } + // Resolve the runtime location. Returns None when the location cannot + // be determined (value dropped and no expression info), in which case + // we skip emitting the decorator entirely rather than emitting a + // placeholder — the debugger would have nothing useful to show. + let value_location = if let Some(first_op) = expr.operations.first() { + match first_op { + DIExpressionOp::WasmStack(offset) => Some(DebugVarLocation::Stack(*offset as u8)), + DIExpressionOp::WasmLocal(idx) => { + // WASM locals are always stored in memory via FMP in Miden. + // Store raw WASM local index; the FMP offset will be computed + // later in MasmFunctionBuilder::build() when num_locals is known. + i16::try_from(*idx).ok().map(DebugVarLocation::Local) + } + DIExpressionOp::WasmGlobal(_) | DIExpressionOp::Deref => { + emitter.stack.find(&value).map(|pos| DebugVarLocation::Stack(pos as u8)) + } + DIExpressionOp::ConstU64(val) => Some(DebugVarLocation::Const(Felt::new(*val))), + DIExpressionOp::ConstS64(val) => { + Some(DebugVarLocation::Const(Felt::new(*val as u64))) + } + DIExpressionOp::FrameBase { + global_index, + byte_offset, + } => Some(DebugVarLocation::FrameBase { + global_index: *global_index, + byte_offset: *byte_offset, + }), + _ => emitter.stack.find(&value).map(|pos| DebugVarLocation::Stack(pos as u8)), + } + } else { + emitter.stack.find(&value).map(|pos| DebugVarLocation::Stack(pos as u8)) + }; + + let Some(value_location) = value_location else { + return Ok(()); + }; + + let mut debug_var = DebugVarInfo::new(var.name.to_string(), value_location); + + // Set arg_index if this is a parameter + if let Some(arg_index) = var.arg_index { + debug_var.set_arg_index(arg_index + 1); // Convert to 1-based + } + + // Set source location + if let Some(line) = core::num::NonZeroU32::new(var.line) { + use miden_assembly::debuginfo::{ColumnNumber, FileLineCol, LineNumber, Uri}; + let uri = Uri::new(var.file.as_str()); + let file_line_col = FileLineCol::new( + uri, + LineNumber::new(line.get()).unwrap_or_default(), + var.column.and_then(ColumnNumber::new).unwrap_or_default(), + ); + debug_var.set_location(file_line_col); + } + + // Emit the instruction + let inst = masm::Instruction::DebugVar(debug_var); + emitter.emit_op(masm::Op::Inst(Span::new(self.span(), inst))); + + Ok(()) + } +} + impl HirLowering for builtin::GlobalSymbol { fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { let context = self.as_operation().context(); diff --git a/dialects/debuginfo/Cargo.toml b/dialects/debuginfo/Cargo.toml new file mode 100644 index 000000000..8c89f4d1d --- /dev/null +++ b/dialects/debuginfo/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "midenc-dialect-debuginfo" +description = "Miden IR Debug Info Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +midenc-hir.workspace = true +paste.workspace = true +log.workspace = true diff --git a/dialects/debuginfo/src/builders.rs b/dialects/debuginfo/src/builders.rs new file mode 100644 index 000000000..298493efc --- /dev/null +++ b/dialects/debuginfo/src/builders.rs @@ -0,0 +1,99 @@ +use midenc_hir::{ + Builder, BuilderExt, DIExpression, DILocalVariable, Report, SourceSpan, ValueRef, +}; + +use super::ops::*; + +/// Builder trait for creating debug info operations. +/// +/// This trait follows the same pattern as other dialect builders +/// (`ArithOpBuilder`, `HirOpBuilder`, etc.) and can be implemented +/// for any type that wraps a `Builder`. +/// +/// # Usage +/// +/// ```ignore +/// // Emit a debug value tracking where variable 'x' lives: +/// builder.debug_value(ssa_value, variable_attr, span)?; +/// +/// // With a custom expression (e.g., value needs a deref): +/// builder.debug_value_with_expr(ssa_value, variable_attr, Some(expr), span)?; +/// +/// // Mark a variable as dead: +/// builder.debug_kill(variable_attr, span)?; +/// ``` +pub trait DebugInfoOpBuilder<'f, B: ?Sized + Builder> { + /// Emit a `debuginfo.value` operation that records the current value of a + /// source-level variable. + /// + /// This creates an SSA use of `value`, ensuring that transforms cannot + /// silently drop the value without updating the debug info. + fn debug_value( + &mut self, + value: ValueRef, + variable: DILocalVariable, + span: SourceSpan, + ) -> Result { + self.debug_value_with_expr(value, variable, None, span) + } + + /// Emit a `debuginfo.value` operation with an optional expression that + /// describes how to recover the source-level value from the IR value. + /// + /// The expression encodes the *inverse* of whatever transformation was + /// applied to the value. For example, if a value was promoted to a stack + /// allocation (pointer), the expression would contain a `deref` operation + /// to recover the original value. + fn debug_value_with_expr( + &mut self, + value: ValueRef, + variable: DILocalVariable, + expression: Option, + span: SourceSpan, + ) -> Result { + let expr = expression.unwrap_or_default(); + let op_builder = self.builder_mut().create::(span); + op_builder(value, variable, expr) + } + + /// Emit a `debuginfo.declare` operation that records the storage address + /// of a source-level variable. + fn debug_declare( + &mut self, + address: ValueRef, + variable: DILocalVariable, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + op_builder(address, variable) + } + + /// Emit a `debuginfo.kill` operation that marks a variable as dead. + /// + /// After this point, the debugger should report the variable as unavailable + /// until the next `debug_value` or `debug_declare` for the same variable. + fn debug_kill( + &mut self, + variable: DILocalVariable, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + op_builder(variable) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +/// Blanket implementation: any `Builder` can use `DebugInfoOpBuilder` directly. +impl DebugInfoOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/debuginfo/src/lib.rs b/dialects/debuginfo/src/lib.rs new file mode 100644 index 000000000..441cefe1e --- /dev/null +++ b/dialects/debuginfo/src/lib.rs @@ -0,0 +1,125 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![deny(warnings)] + +//! # DebugInfo Dialect +//! +//! A first-class dialect for tracking source-level debug information through +//! compiler transformations. Inspired by [Mojo's DebugInfo dialect], this +//! dialect makes debug variable tracking a first-class citizen of the IR, +//! using SSA use-def chains to enforce correctness. +//! +//! ## Motivation +//! +//! Traditional approaches to debug info in MLIR-like compilers (e.g., Flang/FIR) +//! treat debug information as metadata or attributes — second-class citizens that +//! transforms are free to silently drop. The consequences: +//! +//! - Transforms can silently lose debug info with no verifier catching it +//! - No mechanism forces transform authors to update debug info +//! - Debug info quality degrades as the optimizer gets more aggressive +//! +//! ## Approach: SSA-Based Debug Info +//! +//! This dialect defines debug operations as real IR operations with SSA operands: +//! +//! - **`debuginfo.value`** — Records the current value of a source variable. +//! Uses an SSA value operand, so deleting the value without updating debug +//! uses is a hard error. +//! +//! - **`debuginfo.declare`** — Records the storage address of a source variable. +//! Similarly uses an SSA operand for the address. +//! +//! - **`debuginfo.kill`** — Marks a variable as dead, giving the debugger precise +//! lifetime boundaries instead of scope-based heuristics. +//! +//! ## Transform Hooks +//! +//! The [`transform`] module provides utilities that make it easy for transform +//! authors to maintain debug info: +//! +//! - **Simple replacements** are handled automatically via `replace_all_uses_with` +//! - **Complex transforms** use [`salvage_debug_info`](transform::salvage_debug_info) +//! where the transform author only describes the *inverse* of their transformation +//! - **Value deletion** without a replacement emits `debuginfo.kill` automatically +//! +//! ## Design Pillars (from Mojo) +//! +//! 1. **SSA use-def chains** — debug values participate in standard use-def tracking +//! 2. **Expression trees** — `DIExpressionAttr` describes how to recover source values +//! from transformed IR values (encode the inverse transformation) +//! 3. **Explicit lifetimes** — `debuginfo.kill` for precise variable death points +//! +//! [Mojo's DebugInfo dialect]: https://llvm.org/devmtg/2024-04/slides/TechnicalTalks/MojoDebugging.pdf + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +mod builders; +mod ops; +pub mod transform; + +use midenc_hir::{ + AttributeRef, Builder, Dialect, DialectInfo, DialectRegistration, OperationRef, SourceSpan, + Type, +}; + +pub use self::{builders::DebugInfoOpBuilder, ops::*}; + +/// The DebugInfo dialect — first-class debug variable tracking. +/// +/// This dialect provides operations for tracking source-level variables through +/// compiler transformations using SSA semantics. Unlike metadata-based approaches, +/// debug info here participates in standard use-def chains, making it impossible +/// for transforms to silently drop debug information. +#[derive(Debug)] +pub struct DebugInfoDialect { + info: DialectInfo, +} + +impl DebugInfoDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl DialectRegistration for DebugInfoDialect { + const NAMESPACE: &'static str = "debuginfo"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} + +impl Dialect for DebugInfoDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + _builder: &mut dyn Builder, + _attr: AttributeRef, + _ty: &Type, + _span: SourceSpan, + ) -> Option { + // Debug info operations don't produce values that can be constants + None + } +} diff --git a/dialects/debuginfo/src/ops.rs b/dialects/debuginfo/src/ops.rs new file mode 100644 index 000000000..f60a4dcd9 --- /dev/null +++ b/dialects/debuginfo/src/ops.rs @@ -0,0 +1,141 @@ +use midenc_hir::{ + DIExpressionAttr, DILocalVariableAttr, UnsafeIntrusiveEntityRef, derive::operation, + traits::AnyType, +}; + +// Note: DILocalVariableAttr and DIExpressionAttr are now the generated wrapper +// types from #[derive(DialectAttribute)] on DILocalVariable and DIExpression. +use crate::DebugInfoDialect; + +pub type DebugValueRef = UnsafeIntrusiveEntityRef; +pub type DebugDeclareRef = UnsafeIntrusiveEntityRef; +pub type DebugKillRef = UnsafeIntrusiveEntityRef; + +/// Records the current value of a source-level variable. +/// +/// This is the core operation of the debuginfo dialect. It creates a first-class +/// SSA use of the value, which means: +/// +/// - If a transform deletes the value without updating its debug uses, that's a +/// hard error (not a silent drop like with metadata-based approaches). +/// - Standard MLIR-style use-def tracking automatically enforces this — transforms +/// must call `replace_all_uses_with` or explicitly handle debug uses. +/// +/// The `variable` attribute identifies the source variable, and the `expression` +/// attribute describes how to recover the source-level value from the IR value +/// (e.g., "dereference this pointer" if the value was promoted to an alloca). +/// +/// # Example +/// +/// ```text +/// debuginfo.value %0 #[variable = di.local_variable(name = x, ...)] +/// #[expression = di.expression(DW_OP_WASM_local 0)] +/// ``` +#[operation(dialect = DebugInfoDialect)] +pub struct DebugValue { + #[operand] + value: AnyType, + #[attr] + variable: DILocalVariableAttr, + #[attr] + expression: DIExpressionAttr, +} + +/// Records the storage location (address) of a source-level variable. +/// +/// Unlike `DebugValue` which tracks values, `DebugDeclare` tracks the address +/// where a variable is stored. This is useful for variables that live in memory +/// (e.g., stack allocations) where the address itself doesn't change, but the +/// value at that address may be updated through stores. +/// +/// Like `DebugValue`, this creates a real SSA use of the address value, +/// preventing silent drops during transforms. +#[operation(dialect = DebugInfoDialect)] +pub struct DebugDeclare { + #[operand] + address: AnyType, + #[attr] + variable: DILocalVariableAttr, +} + +/// Marks a source-level variable as dead at this program point. +/// +/// This provides explicit lifetime boundaries for variables, giving the debugger +/// precise information about when a variable is no longer valid. Without this, +/// debuggers must rely on scope-based heuristics which can be inaccurate after +/// optimizations. +/// +/// After a `debuginfo.kill`, the debugger should report the variable as +/// "optimized out" or "not available" until the next `debuginfo.value` or +/// `debuginfo.declare` for the same variable. +/// +/// # Example +/// +/// ```text +/// debuginfo.kill #[variable = di.local_variable(name = x, ...)] +/// ``` +#[operation(dialect = DebugInfoDialect)] +pub struct DebugKill { + #[attr] + variable: DILocalVariableAttr, +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use midenc_hir::{Builder, Context, DILocalVariable, SourceSpan, Type, interner::Symbol}; + + use crate::{DebugInfoDialect, DebugInfoOpBuilder}; + + fn make_variable() -> DILocalVariable { + let mut variable = + DILocalVariable::new(Symbol::intern("x"), Symbol::intern("main.rs"), 12, Some(7)); + variable.arg_index = Some(0); + variable.ty = Some(Type::I32); + variable + } + + #[test] + fn debug_value_carries_metadata() { + let context = Rc::new(Context::default()); + context.get_or_register_dialect::(); + + let block = context.create_block_with_params([Type::I32]); + let arg = block.borrow().arguments()[0]; + let value = arg.borrow().as_value_ref(); + + let mut builder = context.clone().builder(); + builder.set_insertion_point_to_end(block); + + let variable = make_variable(); + let debug_value = builder + .debug_value(value, variable.clone(), SourceSpan::UNKNOWN) + .expect("failed to create debuginfo.value op"); + + assert_eq!(debug_value.borrow().variable().as_value(), &variable); + assert_eq!(block.borrow().back(), Some(debug_value.as_operation_ref())); + + let op = debug_value.as_operation_ref(); + let printed = alloc::string::ToString::to_string(&*op.borrow()); + assert!(printed.contains("di.local_variable")); + } + + #[test] + fn debug_kill_carries_variable() { + let context = Rc::new(Context::default()); + context.get_or_register_dialect::(); + + let block = context.create_block_with_params([Type::I32]); + + let mut builder = context.clone().builder(); + builder.set_insertion_point_to_end(block); + + let variable = make_variable(); + let debug_kill = builder + .debug_kill(variable.clone(), SourceSpan::UNKNOWN) + .expect("failed to create debuginfo.kill op"); + + assert_eq!(debug_kill.borrow().variable().as_value(), &variable); + } +} diff --git a/dialects/debuginfo/src/transform.rs b/dialects/debuginfo/src/transform.rs new file mode 100644 index 000000000..b172bee09 --- /dev/null +++ b/dialects/debuginfo/src/transform.rs @@ -0,0 +1,268 @@ +//! Transform utilities for maintaining debug info across IR transformations. +//! +//! This module provides the "transformation hooks" that make the debuginfo dialect +//! practical. Following Mojo's approach, these utilities make it easy for transform +//! authors to keep debug info valid — they only need to describe the *inverse* of +//! their transformation. +//! +//! # Design Philosophy +//! +//! The debuginfo dialect uses SSA use-def chains for debug values, which means +//! transforms *cannot* silently drop debug info. When a transform replaces or +//! deletes a value, any `debuginfo.value` operations using that value must be +//! updated. The standard `replace_all_uses_with` already handles this correctly +//! for simple value replacements. +//! +//! For more complex transforms (e.g., promoting a value to memory, splitting a +//! value into pieces), the transform author uses `salvage_debug_info` to describe +//! how the debug expression should be updated to recover the source-level value +//! from the new representation. +//! +//! # Examples +//! +//! ## Simple value replacement (handled automatically) +//! +//! When CSE replaces `%1 = add %a, %b` with an existing `%0 = add %a, %b`: +//! ```text +//! // Before: debuginfo.value %1 #[variable = x] +//! // rewriter.replace_all_uses_with(%1, %0) +//! // After: debuginfo.value %0 #[variable = x] -- automatic! +//! ``` +//! +//! ## Value promoted to memory (use salvage_debug_info) +//! +//! When a transform promotes a value to a stack allocation: +//! ```text +//! // Before: debuginfo.value %val #[variable = x] +//! // Transform creates: %ptr = alloca T +//! // store %val, %ptr +//! // Call: salvage_debug_info(%val, SalvageAction::Deref { new_value: %ptr }) +//! // After: debuginfo.value %ptr #[variable = x, expression = di.expression(DW_OP_deref)] +//! ``` + +use alloc::vec::Vec; + +use midenc_hir::{Builder, DIExpressionOp, Op, OperationRef, Spanned, ValueRef}; + +use crate::{DebugInfoOpBuilder, ops::DebugValue}; + +/// Describes how to recover the original source-level value after a transformation. +/// +/// When a transform changes a value's representation, it creates a `SalvageAction` +/// describing the inverse operation. The debuginfo framework then updates the +/// `DIExpressionAttr` accordingly so the debugger can still find the variable's value. +/// +/// Transform authors only need to pick the right variant — the framework handles +/// updating all affected `debuginfo.value` operations. +#[derive(Clone, Debug)] +pub enum SalvageAction { + /// The value is now behind a pointer; dereference to recover the original. + /// + /// Use this when a value is promoted to a stack allocation. + /// The expression will have `DW_OP_deref` prepended. + Deref { + /// The new pointer value that replaces the original. + new_value: ValueRef, + }, + + /// A constant offset was added to the value. + /// + /// Use this when a value is relocated by a fixed amount (e.g., frame + /// pointer adjustments). The expression will encode the inverse subtraction. + OffsetBy { + /// The new value (original + offset). + new_value: ValueRef, + /// The offset that was added. + offset: u64, + }, + + /// The value was replaced by a new value with an arbitrary expression. + /// + /// Use this for complex transformations where the simple patterns don't apply. + /// The caller provides the full expression describing how to recover the + /// source-level value from the new IR value. + WithExpression { + /// The new value replacing the original. + new_value: ValueRef, + /// Expression operations describing the inverse transform. + ops: Vec, + }, + + /// The value is now a constant. + /// + /// Use this when constant propagation determines the value at this point. + Constant { + /// The constant value. + value: u64, + }, + + /// The value was completely removed with no recovery possible. + /// + /// Use this as a last resort when the value cannot be recovered. + /// This will emit a `debuginfo.kill` for the affected variable. + Undef, +} + +/// Salvage debug info for all `debuginfo.value` operations that use `old_value`. +/// +/// When a transform is about to delete or replace a value, call this function +/// to update all debug uses. The `action` describes how the debugger can recover +/// the original source-level value from the new representation. +/// +/// This is the main entry point for transform authors who need to update debug +/// info beyond simple `replace_all_uses_with` scenarios. +/// +/// # Example +/// +/// ```ignore +/// // Value was promoted to memory: +/// let ptr = builder.alloca(ty, span)?; +/// builder.store(old_val, ptr, span)?; +/// salvage_debug_info( +/// &old_val, +/// &SalvageAction::Deref { new_value: ptr }, +/// &mut builder, +/// ); +/// ``` +pub fn salvage_debug_info( + old_value: &ValueRef, + action: &SalvageAction, + builder: &mut B, +) { + // Collect all debug value ops that use the old value + let debug_ops: Vec = debug_value_users(old_value); + + for mut debug_op in debug_ops { + apply_salvage_action(&mut debug_op, action, builder); + } +} + +/// Apply a salvage action to a single debug value operation. +fn apply_salvage_action( + debug_op: &mut OperationRef, + action: &SalvageAction, + builder: &mut B, +) { + let span = debug_op.borrow().span(); + + match action { + SalvageAction::Deref { new_value } => { + // Get existing expression and prepend deref + let (variable, mut expr) = { + let op = debug_op.borrow(); + let dv = op.downcast_ref::().unwrap(); + (dv.variable().as_value().clone(), dv.expression().as_value().clone()) + }; + expr.operations.insert(0, DIExpressionOp::Deref); + + // Erase old op and create new one with updated value and expression + debug_op.borrow_mut().erase(); + let _ = builder.debug_value_with_expr(*new_value, variable, Some(expr), span); + } + + SalvageAction::OffsetBy { new_value, offset } => { + let (variable, mut expr) = { + let op = debug_op.borrow(); + let dv = op.downcast_ref::().unwrap(); + (dv.variable().as_value().clone(), dv.expression().as_value().clone()) + }; + // To recover: subtract the offset that was added + expr.operations.push(DIExpressionOp::ConstU64(*offset)); + expr.operations.push(DIExpressionOp::Minus); + + debug_op.borrow_mut().erase(); + let _ = builder.debug_value_with_expr(*new_value, variable, Some(expr), span); + } + + SalvageAction::WithExpression { new_value, ops } => { + let (variable, mut expr) = { + let op = debug_op.borrow(); + let dv = op.downcast_ref::().unwrap(); + (dv.variable().as_value().clone(), dv.expression().as_value().clone()) + }; + expr.operations.extend(ops.iter().cloned()); + + debug_op.borrow_mut().erase(); + let _ = builder.debug_value_with_expr(*new_value, variable, Some(expr), span); + } + + SalvageAction::Constant { value } => { + let variable = { + let op = debug_op.borrow(); + let dv = op.downcast_ref::().unwrap(); + dv.variable().as_value().clone() + }; + + debug_op.borrow_mut().erase(); + // Emit a kill since we can't create a debuginfo.value without a live SSA operand + // for constants — the constant value is encoded in the expression + let _ = builder.debug_kill(variable, span); + // TODO: in the future, could emit a debuginfo.value with a materialized constant + // and a ConstU64/StackValue expression pair + let _ = value; + } + + SalvageAction::Undef => { + let variable = { + let op = debug_op.borrow(); + let dv = op.downcast_ref::().unwrap(); + dv.variable().as_value().clone() + }; + + debug_op.borrow_mut().erase(); + let _ = builder.debug_kill(variable, span); + } + } +} + +/// Check if an operation is a debug info operation. +/// +/// This is useful for transforms that need to skip or handle debug ops +/// differently (e.g., DCE should not consider debug uses as "real" uses +/// that keep a value alive). +pub fn is_debug_info_op(op: &dyn Op) -> bool { + op.as_operation().is::() + || op.as_operation().is::() + || op.as_operation().is::() +} + +/// Collect all `debuginfo.value` operations that reference the given value. +/// +/// Useful for transforms that need to inspect or update debug info for a +/// specific value. +pub fn debug_value_users(value: &ValueRef) -> Vec { + let value = value.borrow(); + let mut ops = Vec::new(); + for use_ in value.iter_uses() { + if use_.owner.borrow().is::() { + ops.push(use_.owner); + } + } + ops +} + +/// Recursively collect all debug info operations within an operation's regions. +pub fn collect_debug_ops(op: &OperationRef) -> Vec { + let mut debug_ops = Vec::new(); + collect_debug_ops_recursive(op, &mut debug_ops); + debug_ops +} + +fn collect_debug_ops_recursive(op: &OperationRef, debug_ops: &mut Vec) { + let op = op.borrow(); + + if op.is::() + || op.is::() + || op.is::() + { + debug_ops.push(op.as_operation_ref()); + } + + for region in op.regions() { + for block in region.body() { + for inner_op in block.body() { + collect_debug_ops_recursive(&inner_op.as_operation_ref(), debug_ops); + } + } + } +} diff --git a/dialects/scf/Cargo.toml b/dialects/scf/Cargo.toml index 1adab716c..19985c6ae 100644 --- a/dialects/scf/Cargo.toml +++ b/dialects/scf/Cargo.toml @@ -28,3 +28,5 @@ bitvec.workspace = true # NOTE: Use local paths for dev-only dependency to avoid relying on crates.io during packaging midenc-expect-test = { path = "../../tools/expect-test" } midenc-hir = { path = "../../hir", features = ["logging"] } +midenc-dialect-debuginfo = { path = "../debuginfo" } +env_logger = "0.11" diff --git a/dialects/scf/src/transforms/cfg_to_scf.rs b/dialects/scf/src/transforms/cfg_to_scf.rs index d029ad68c..26b4180e9 100644 --- a/dialects/scf/src/transforms/cfg_to_scf.rs +++ b/dialects/scf/src/transforms/cfg_to_scf.rs @@ -837,4 +837,67 @@ mod tests { Ok(()) } + + /// This test verifies that `debuginfo.debug_value` operations are preserved through the + /// CF-to-SCF transformation. The key behavior being tested is that `replace_all_uses_with` + /// (used internally by the transform to replace block arguments with `scf.if` results) + /// automatically updates the SSA operands of debug value ops. + #[test] + fn cfg_to_scf_debug_value_preservation() -> Result<(), Report> { + use midenc_dialect_debuginfo::{DebugInfoDialect, DebugInfoOpBuilder}; + use midenc_hir::{DILocalVariable, interner::Symbol}; + + let mut test = Test::new("cfg_to_scf_debug_value_preservation", &[Type::U32], &[Type::U32]); + test.context().get_or_register_dialect::(); + + let span = SourceSpan::default(); + let mut builder = test.function_builder(); + + let if_is_zero = builder.create_block(); + let if_is_nonzero = builder.create_block(); + let exit_block = builder.create_block(); + let return_val = builder.append_block_param(exit_block, Type::U32, span); + + let block = builder.current_block(); + let input = block.borrow().arguments()[0].upcast(); + + let input_var = + DILocalVariable::new(Symbol::intern("input"), Symbol::intern("test.rs"), 1, Some(1)); + let result_var = + DILocalVariable::new(Symbol::intern("result"), Symbol::intern("test.rs"), 2, Some(1)); + + let zero = builder.u32(0, span); + let is_zero = builder.eq(input, zero, span)?; + builder.builder_mut().debug_value(input, input_var.clone(), span)?; + builder.cond_br(is_zero, if_is_zero, [], if_is_nonzero, [], span)?; + + builder.switch_to_block(if_is_zero); + let a = builder.incr(input, span)?; + builder.builder_mut().debug_value(a, result_var.clone(), span)?; + builder.br(exit_block, [a], span)?; + + builder.switch_to_block(if_is_nonzero); + let b = builder.mul(input, input, span)?; + builder.builder_mut().debug_value(b, result_var.clone(), span)?; + builder.br(exit_block, [b], span)?; + + builder.switch_to_block(exit_block); + // KEY: this debug_value uses the block argument `return_val`, which will be + // replaced by the scf.if result via replace_all_uses_with + builder.builder_mut().debug_value(return_val, result_var.clone(), span)?; + builder.ret(Some(return_val), span)?; + + let operation = test.function().as_operation_ref(); + + let input_ir = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_debug_value_preservation_before.hir"] + .assert_eq(&input_ir); + + test.apply_pass::(true)?; + + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_debug_value_preservation_after.hir"].assert_eq(&output); + + Ok(()) + } } diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_after.hir new file mode 100644 index 000000000..78268a0b0 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_after.hir @@ -0,0 +1,16 @@ +builtin.function public extern("C") @cfg_to_scf_debug_value_preservation(%0: u32) -> u32 { + %2 = arith.constant 0 : u32; + %3 = arith.eq %0, %2; + "debuginfo.debug_value"(%0) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + %8 = scf.if %3 then { + %4 = arith.incr %0; + "debuginfo.debug_value"(%4) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + scf.yield %4 : (u32); + } else { + %5 = arith.mul %0, %0 <{ overflow = #builtin.overflow }>; + "debuginfo.debug_value"(%5) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + scf.yield %5 : (u32); + } : (i1) -> (u32); + "debuginfo.debug_value"(%8) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + builtin.ret %8 : (u32); +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_before.hir new file mode 100644 index 000000000..8b5c2c435 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_debug_value_preservation_before.hir @@ -0,0 +1,17 @@ +builtin.function public extern("C") @cfg_to_scf_debug_value_preservation(%0: u32) -> u32 { + %2 = arith.constant 0 : u32; + %3 = arith.eq %0, %2; + "debuginfo.debug_value"(%0) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + cf.cond_br %3 ^block1, ^block2 : (i1); +^block1: + %4 = arith.incr %0; + "debuginfo.debug_value"(%4) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + cf.br ^block3:(%4); +^block2: + %5 = arith.mul %0, %0 <{ overflow = #builtin.overflow }>; + "debuginfo.debug_value"(%5) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + cf.br ^block3:(%5); +^block3(%1: u32): + "debuginfo.debug_value"(%1) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : u32 -> (); + builtin.ret %1 : (u32); +}; \ No newline at end of file diff --git a/eval/Cargo.toml b/eval/Cargo.toml index 9f3b7b231..c65b69cb6 100644 --- a/eval/Cargo.toml +++ b/eval/Cargo.toml @@ -22,6 +22,7 @@ log.workspace = true miden-core.workspace = true midenc-dialect-arith.workspace = true midenc-dialect-cf.workspace = true +midenc-dialect-debuginfo.workspace = true midenc-dialect-scf.workspace = true midenc-dialect-hir.workspace = true midenc-dialect-ub.workspace = true diff --git a/eval/src/eval.rs b/eval/src/eval.rs index 44e10cc21..38aceaaa8 100644 --- a/eval/src/eval.rs +++ b/eval/src/eval.rs @@ -5,6 +5,7 @@ use alloc::{ use midenc_dialect_arith as arith; use midenc_dialect_cf as cf; +use midenc_dialect_debuginfo as debuginfo; use midenc_dialect_hir as hir; use midenc_dialect_scf as scf; use midenc_dialect_ub as ub; @@ -106,6 +107,13 @@ impl Eval for ub::Unreachable { } } +// Debug info operations are purely observational and have no runtime semantics. +impl Eval for debuginfo::DebugValue { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + Ok(ControlFlowEffect::None) + } +} + impl Eval for ub::Poison { fn eval(&self, evaluator: &mut HirEvaluator) -> Result { let value = match self.value().as_immediate() { diff --git a/eval/src/lib.rs b/eval/src/lib.rs index 03e37cdd0..6f242de64 100644 --- a/eval/src/lib.rs +++ b/eval/src/lib.rs @@ -15,6 +15,7 @@ mod value; use midenc_dialect_arith as arith; use midenc_dialect_cf as cf; +use midenc_dialect_debuginfo as debuginfo; use midenc_dialect_hir as hir; use midenc_dialect_scf as scf; use midenc_dialect_ub as ub; @@ -48,6 +49,9 @@ inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::( eval_wasm_dialect )); +inventory::submit!(::midenc_hir::DialectRegistrationHookInfo::new::( + eval_debuginfo_dialect +)); fn eval_builtin_dialect(info: &mut ::midenc_hir::DialectInfo) { info.register_operation_trait::(); @@ -154,3 +158,7 @@ fn eval_wasm_dialect(info: &mut ::midenc_hir::DialectInfo) { info.register_operation_trait::(); info.register_operation_trait::(); } + +fn eval_debuginfo_dialect(info: &mut ::midenc_hir::DialectInfo) { + info.register_operation_trait::(); +} diff --git a/examples/counter-contract/Cargo.lock b/examples/counter-contract/Cargo.lock new file mode 100644 index 000000000..2fc5b4042 --- /dev/null +++ b/examples/counter-contract/Cargo.lock @@ -0,0 +1,3470 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "counter-contract" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fs-err" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" +dependencies = [ + "autocfg", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.12.0" +dependencies = [ + "miden-base", + "miden-base-macros", + "miden-base-sys", + "miden-field 0.22.6", + "miden-field-repr", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5322d00bef8b19f4cd3415da2533a87c8860c7d9b80043d6cce0f184b40c5fff" +dependencies = [ + "miden-core", + "miden-crypto", + "miden-utils-indexing", + "thiserror", + "tracing", +] + +[[package]] +name = "miden-assembly" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ece22da0cbf350e4a2939a07eaa3200445e42e47ce1b1ee6538723b6b40a4d4" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84a0e14ce66e76497a6771f3e360eb85557f2417ea22db279d54c1238ffafde" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "proptest", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.12.0" +dependencies = [ + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.12.0" +dependencies = [ + "heck", + "miden-formatting", + "miden-protocol", + "midenc-frontend-wasm-metadata", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn 2.0.106", + "toml 0.8.23", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "miden-base-sys" +version = "0.12.0" +dependencies = [ + "miden-field-repr", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf4f5601b0d669aa125cce3bba4b98f2c8df729e2d53e66777429ac5f53e228" +dependencies = [ + "derive_more", + "itertools", + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "miden-utils-core-derive", + "miden-utils-indexing", + "miden-utils-sync", + "num-derive", + "num-traits", + "thiserror", +] + +[[package]] +name = "miden-core-lib" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82595fabb062315c32f6fc11c31755d3e5c6f8bc8c67d35154a067397d65b1de" +dependencies = [ + "env_logger", + "fs-err", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "thiserror", +] + +[[package]] +name = "miden-crypto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0a034a460e27723dcfdf25effffab84331c3b46b13e7a1bd674197cc71bfe" +dependencies = [ + "blake3", + "cc", + "chacha20poly1305", + "curve25519-dalek", + "ed25519-dalek", + "flume", + "glob", + "hkdf", + "k256", + "miden-crypto-derive", + "miden-field 0.23.0", + "miden-serde-utils 0.23.0", + "num", + "num-complex", + "p3-blake3", + "p3-challenger 0.5.2", + "p3-dft 0.5.2", + "p3-goldilocks 0.5.2", + "p3-keccak", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-miden-lifted-stark", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "rand 0.9.2", + "rand_chacha", + "rand_core 0.9.3", + "rand_hc", + "serde", + "sha2", + "sha3", + "subtle", + "thiserror", + "x25519-dalek", +] + +[[package]] +name = "miden-crypto-derive" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8bf6ebde028e79bcc61a3632d2f375a5cc64caa17d014459f75015238cb1e08" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "miden-debug-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9ef08bafef275f0d6a15108108b3f6df6642772e0a1c05e102cb7e96841e888" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-indexing", + "miden-utils-sync", + "paste", + "serde", + "serde_spanned 1.0.0", + "thiserror", +] + +[[package]] +name = "miden-field" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "546ac41444d6f1ab015daa0bf420759405d3cf0a5cb2b552140ad60443ddf39c" +dependencies = [ + "miden-serde-utils 0.22.6", + "num-bigint", + "p3-challenger 0.4.2", + "p3-field 0.4.2", + "p3-goldilocks 0.4.2", + "paste", + "rand 0.9.2", + "serde", + "thiserror", +] + +[[package]] +name = "miden-field" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38011348f4fb4c9e5ce1f471203d024721c00e3b60a91aa91aaefe6738d8b5ea" +dependencies = [ + "miden-serde-utils 0.23.0", + "num-bigint", + "p3-challenger 0.5.2", + "p3-field 0.5.2", + "p3-goldilocks 0.5.2", + "paste", + "rand 0.10.0", + "serde", + "subtle", + "thiserror", +] + +[[package]] +name = "miden-field-repr" +version = "0.12.0" +dependencies = [ + "miden-core", + "miden-field 0.22.6", + "miden-field-repr-derive", +] + +[[package]] +name = "miden-field-repr-derive" +version = "0.12.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b24d09fda64e0751f943ac616643342b05a47d626e2ee0040b902eff3c924e" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", + "miden-debug-types", + "thiserror", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin 0.9.8", + "strip-ansi-escapes", + "syn 2.0.106", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "miden-processor" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba53ff06ef0affa0c3fb13e7e2ef5bde99f96eebcec8c360c6658050480ef676" +dependencies = [ + "itertools", + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "miden-utils-indexing", + "paste", + "rayon", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "miden-protocol" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a05834c821538ce55f28e9696d26e34cc5b768cb89847e561c171f00893fdd" +dependencies = [ + "bech32", + "fs-err", + "getrandom 0.3.3", + "miden-assembly", + "miden-assembly-syntax", + "miden-core", + "miden-core-lib", + "miden-crypto", + "miden-mast-package", + "miden-processor", + "miden-protocol-macros", + "miden-utils-sync", + "miden-verifier", + "rand 0.9.2", + "regex", + "semver 1.0.26", + "thiserror", + "walkdir", +] + +[[package]] +name = "miden-protocol-macros" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebadea5a01fead52f86f3f0f6966a3e5222c17e6c044ffaff539b320f7fde628" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.12.0" + +[[package]] +name = "miden-serde-utils" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bedaf94fb4bb6806e4af99fadce74e4cdd0e8664c571107b255f86b00b3149b" +dependencies = [ + "p3-field 0.4.2", + "p3-goldilocks 0.4.2", +] + +[[package]] +name = "miden-serde-utils" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff78082e9b4ca89863e68da01b35f8a4029ee6fd912e39fa41fde4273a7debab" +dependencies = [ + "p3-field 0.5.2", + "p3-goldilocks 0.5.2", +] + +[[package]] +name = "miden-stdlib-sys" +version = "0.12.0" +dependencies = [ + "miden-field 0.22.6", +] + +[[package]] +name = "miden-utils-core-derive" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "477db426fc31f666d7e65b0cc907fe431d36d88d611a0594cf266104eb168b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "miden-utils-diagnostics" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "785c1ec4ad9994100b117b8eab8c453dcc35d3d168e4f72ac818efb700abe7b1" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-indexing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46cec00c8cf32ec46df7542fb9ea15fbe7a5149920ef97776a4f4bc3a563e8de" +dependencies = [ + "miden-crypto", + "thiserror", +] + +[[package]] +name = "miden-utils-sync" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529c1c173506f30d3949f7a54b65f1eb318098e37ed5730a1bb9027eee2fa4b" +dependencies = [ + "lock_api", + "loom", + "once_cell", + "parking_lot", +] + +[[package]] +name = "miden-verifier" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997c842047ffa2d011eb65bf638a3135b2d52bce5b20770fcc6040f1b48c624a" +dependencies = [ + "bincode", + "miden-air", + "miden-core", + "miden-crypto", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "midenc-frontend-wasm-metadata" +version = "0.12.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "midenc-hir-type" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb29d7c049fb69373c7e775e3d4411e63e4ee608bc43826282ba62c6ec9f891" +dependencies = [ + "miden-formatting", + "miden-serde-utils 0.23.0", + "serde", + "serde_repr", + "smallvec", + "thiserror", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "p3-air" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2ec9cbfc642fc5173817287c3f8b789d07743b5f7e812d058b7a03e344f9ab" +dependencies = [ + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "tracing", +] + +[[package]] +name = "p3-blake3" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b667f43b19499dd939c9e2553aa95688936a88360d50117dae3c8848d07dbc70" +dependencies = [ + "blake3", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", +] + +[[package]] +name = "p3-challenger" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e42ba74a49c08c6e99f74cd9b343bfa31aa5721fea55079b18e3fd65f1dcbc" +dependencies = [ + "p3-field 0.4.2", + "p3-maybe-rayon 0.4.2", + "p3-monty-31 0.4.2", + "p3-symmetric 0.4.2", + "p3-util 0.4.2", + "tracing", +] + +[[package]] +name = "p3-challenger" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0b490c745a7d2adeeafff06411814c8078c432740162332b3cd71be0158a76" +dependencies = [ + "p3-field 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-monty-31 0.5.2", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "tracing", +] + +[[package]] +name = "p3-commit" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916ae7989d5c3b49f887f5c55b2f9826bdbb81aaebf834503c4145d8b267c829" +dependencies = [ + "itertools", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-util 0.5.2", + "serde", +] + +[[package]] +name = "p3-dft" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63fa5eb1bd12a240089e72ae3fe10350944d9c166d00a3bfd2a1794db65cf5c" +dependencies = [ + "itertools", + "p3-field 0.4.2", + "p3-matrix 0.4.2", + "p3-maybe-rayon 0.4.2", + "p3-util 0.4.2", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55301e91544440254977108b85c32c09d7ea05f2f0dd61092a2825339906a4a7" +dependencies = [ + "itertools", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-util 0.5.2", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebfdb6ef992ae64e9e8f449ac46516ffa584f11afbdf9ee244288c2a633cdf4" +dependencies = [ + "itertools", + "num-bigint", + "p3-maybe-rayon 0.4.2", + "p3-util 0.4.2", + "paste", + "rand 0.9.2", + "serde", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85affca7fc983889f260655c4cf74163eebb94605f702e4b6809ead707cba54f" +dependencies = [ + "itertools", + "num-bigint", + "p3-maybe-rayon 0.5.2", + "p3-util 0.5.2", + "paste", + "rand 0.10.0", + "serde", + "tracing", +] + +[[package]] +name = "p3-goldilocks" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64716244b5612622d4e78a4f48b74f6d3bb7b4085b7b6b25364b1dfca7198c66" +dependencies = [ + "num-bigint", + "p3-challenger 0.4.2", + "p3-dft 0.4.2", + "p3-field 0.4.2", + "p3-mds 0.4.2", + "p3-poseidon2 0.4.2", + "p3-symmetric 0.4.2", + "p3-util 0.4.2", + "paste", + "rand 0.9.2", + "serde", +] + +[[package]] +name = "p3-goldilocks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca1081f5c47b940f2d75a11c04f62ea1cc58a5d480dd465fef3861c045c63cd" +dependencies = [ + "num-bigint", + "p3-challenger 0.5.2", + "p3-dft 0.5.2", + "p3-field 0.5.2", + "p3-mds 0.5.2", + "p3-poseidon1", + "p3-poseidon2 0.5.2", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "paste", + "rand 0.10.0", + "serde", +] + +[[package]] +name = "p3-keccak" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcf27615ece1995e4fcf4c69740f1cf515d1481367a20b4b3ce7f4f1b8d70f7" +dependencies = [ + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "tiny-keccak", +] + +[[package]] +name = "p3-matrix" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5542f96504dae8100c91398fb1e3f5ec669eb9c73d9e0b018a93b5fe32bad230" +dependencies = [ + "itertools", + "p3-field 0.4.2", + "p3-maybe-rayon 0.4.2", + "p3-util 0.4.2", + "rand 0.9.2", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-matrix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53428126b009071563d1d07305a9de8be0d21de00b57d2475289ee32ffca6577" +dependencies = [ + "itertools", + "p3-field 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-util 0.5.2", + "rand 0.10.0", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5669ca75645f99cd001e9d0289a4eeff2bc2cd9dc3c6c3aaf22643966e83df" + +[[package]] +name = "p3-maybe-rayon" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082bf467011c06c768c579ec6eb9accb5e1e62108891634cc770396e917f978a" + +[[package]] +name = "p3-mds" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038763af23df9da653065867fd85b38626079031576c86fd537097e5be6a0da0" +dependencies = [ + "p3-dft 0.4.2", + "p3-field 0.4.2", + "p3-symmetric 0.4.2", + "p3-util 0.4.2", + "rand 0.9.2", +] + +[[package]] +name = "p3-mds" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35209e6214102ea6ec6b8cb1b9c15a9b8e597a39f9173597c957f123bced81b3" +dependencies = [ + "p3-dft 0.5.2", + "p3-field 0.5.2", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "rand 0.10.0", +] + +[[package]] +name = "p3-miden-lifted-air" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c31c65fdc88952d7b301546add9670676e5b878aa0066dd929f107c203b006" +dependencies = [ + "p3-air", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-util 0.5.2", + "thiserror", +] + +[[package]] +name = "p3-miden-lifted-fri" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9932f1b0a16609a45cd4ee10a4d35412728bc4b38837c7979d7c85d8dcc9fc" +dependencies = [ + "p3-challenger 0.5.2", + "p3-commit", + "p3-dft 0.5.2", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-miden-lmcs", + "p3-miden-transcript", + "p3-util 0.5.2", + "rand 0.10.0", + "thiserror", + "tracing", +] + +[[package]] +name = "p3-miden-lifted-stark" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3956ab7270c3cdd53ca9796d39ae1821984eb977415b0672110f9666bff5d8" +dependencies = [ + "p3-challenger 0.5.2", + "p3-dft 0.5.2", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-miden-lifted-air", + "p3-miden-lifted-fri", + "p3-miden-lmcs", + "p3-miden-stateful-hasher", + "p3-miden-transcript", + "p3-util 0.5.2", + "thiserror", + "tracing", +] + +[[package]] +name = "p3-miden-lmcs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c46791c983e772136db3d48f102431457451447abb9087deb6c8ce3c1efc86" +dependencies = [ + "p3-commit", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-miden-stateful-hasher", + "p3-miden-transcript", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "rand 0.10.0", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "p3-miden-stateful-hasher" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec47a9d9615eb3d9d2a59b00d19751d9ad85384b55886827913d680d912eac6a" +dependencies = [ + "p3-field 0.5.2", + "p3-symmetric 0.5.2", +] + +[[package]] +name = "p3-miden-transcript" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c565647487e4a949f67e6f115b0391d6cb82ac8e561165789939bab23d0ae7" +dependencies = [ + "p3-challenger 0.5.2", + "p3-field 0.5.2", + "serde", + "thiserror", +] + +[[package]] +name = "p3-monty-31" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a981d60da3d8cbf8561014e2c186068578405fd69098fa75b43d4afb364a47" +dependencies = [ + "itertools", + "num-bigint", + "p3-dft 0.4.2", + "p3-field 0.4.2", + "p3-matrix 0.4.2", + "p3-maybe-rayon 0.4.2", + "p3-mds 0.4.2", + "p3-poseidon2 0.4.2", + "p3-symmetric 0.4.2", + "p3-util 0.4.2", + "paste", + "rand 0.9.2", + "serde", + "spin 0.10.0", + "tracing", + "transpose", +] + +[[package]] +name = "p3-monty-31" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa8c99ec50c035020bbf5457c6a729ba6a975719c1a8dd3f16421081e4f650c" +dependencies = [ + "itertools", + "num-bigint", + "p3-dft 0.5.2", + "p3-field 0.5.2", + "p3-matrix 0.5.2", + "p3-maybe-rayon 0.5.2", + "p3-mds 0.5.2", + "p3-poseidon1", + "p3-poseidon2 0.5.2", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "paste", + "rand 0.10.0", + "serde", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-poseidon1" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a018b618e3fa0aec8be933b1d8e404edd23f46991f6bf3f5c2f3f95e9413fe9" +dependencies = [ + "p3-field 0.5.2", + "p3-symmetric 0.5.2", + "rand 0.10.0", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903b73e4f9a7781a18561c74dc169cf03333497b57a8dd02aaeb130c0f386599" +dependencies = [ + "p3-field 0.4.2", + "p3-mds 0.4.2", + "p3-symmetric 0.4.2", + "p3-util 0.4.2", + "rand 0.9.2", +] + +[[package]] +name = "p3-poseidon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256a668a9ba916f8767552f13d0ba50d18968bc74a623bfdafa41e2970c944d0" +dependencies = [ + "p3-field 0.5.2", + "p3-mds 0.5.2", + "p3-symmetric 0.5.2", + "p3-util 0.5.2", + "rand 0.10.0", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd788f04e86dd5c35dd87cad29eefdb6371d2fd5f7664451382eeacae3c3ed0" +dependencies = [ + "itertools", + "p3-field 0.4.2", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c60a71a1507c13611b0f2b0b6e83669fd5b76f8e3115bcbced5ccfdf3ca7807" +dependencies = [ + "itertools", + "p3-field 0.5.2", + "p3-util 0.5.2", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663b16021930bc600ecada915c6c3965730a3b9d6a6c23434ccf70bfc29d6881" +dependencies = [ + "serde", +] + +[[package]] +name = "p3-util" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b766b9e9254bf3fa98d76e42cf8a5b30628c182dfd5272d270076ee12f0fc0" +dependencies = [ + "serde", + "transpose", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rand_hc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b363d4f6370f88d62bf586c80405657bde0f0e1b8945d47d2ad59b906cb4f54" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.13", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.106", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.106", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/examples/counter-contract/counter_contract.masm b/examples/counter-contract/counter_contract.masm new file mode 100644 index 000000000..fe56c288d --- /dev/null +++ b/examples/counter-contract/counter_contract.masm @@ -0,0 +1,1698 @@ +# mod miden:counter-contract/counter-contract@0.1.0 + +@callconv("canon-lift") +pub proc get-count( + +) -> felt + exec.::miden:counter-contract/counter-contract@0.1.0::init + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::miden:counter-contract/counter-contract@0.1.0#get-count + trace.252 + nop + exec.::std::sys::truncate_stack +end + +@callconv("canon-lift") +pub proc increment-count( + +) -> felt + exec.::miden:counter-contract/counter-contract@0.1.0::init + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::miden:counter-contract/counter-contract@0.1.0#increment-count + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:counter-contract/counter-contract@0.1.0::counter_contract + +@callconv("C") +proc __wasm_call_ctors( + +) + nop +end + +@callconv("C") +proc _RNvNtCs2bNbiPwbrt9_16counter_contract8bindings40___link_custom_section_describing_imports( + +) + nop +end + +@callconv("C") +proc _RNvXs2_Cs2bNbiPwbrt9_16counter_contractNtB5_15CounterContractNtNtNtNtNtNtB5_8bindings7exports5miden16counter_contract16counter_contract5Guest15increment_count( + +) -> felt + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.160 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.92 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.88 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.84 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.80 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.80 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB4_4WordINtNtCs3n4EX6Qeqp9_4core7convert4FromANtNtB6_4felt4Feltj4_E4from + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs6_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB5_4WordINtNtCs3n4EX6Qeqp9_4core7convert5AsRefBW_E6as_ref + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4feltNtB5_4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromhE4from + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.32 + dup.6 + u32wrapping_add + movup.2 + swap.3 + movdn.2 + swap.1 + swap.4 + swap.1 + swap.5 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::miden::active_account::get_map_item + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.88 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.80 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.1 + u32wrapping_add + push.144 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvMNtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB2_4Word7reverse + trace.252 + nop + push.144 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs3_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtNtB7_4felt4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromNtB5_4WordE4from + trace.252 + nop + push.1 + add + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.64 + dup.6 + u32wrapping_add + dup.5 + swap.1 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB5_4WordINtNtCs3n4EX6Qeqp9_4core7convert4FromNtNtB7_4felt4FeltE4from + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4feltNtB5_4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromhE4from + trace.252 + nop + push.76 + dup.7 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.72 + dup.8 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.68 + dup.9 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.64 + dup.10 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.80 + dup.11 + u32wrapping_add + swap.9 + swap.1 + swap.8 + swap.2 + swap.7 + swap.3 + swap.6 + swap.4 + swap.5 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::miden::native_account::set_map_item + trace.252 + nop + push.88 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.120 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.112 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.24 + push.80 + dup.3 + u32wrapping_add + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.136 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.96 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.128 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.112 + dup.2 + u32wrapping_add + push.32 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvMNtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB2_4Word7reverse + trace.252 + nop + push.128 + dup.2 + u32wrapping_add + push.144 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvMNtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB2_4Word7reverse + trace.252 + nop + push.152 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.24 + push.32 + dup.5 + u32wrapping_add + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.2 + dup.2 + movup.2 + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.144 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.48 + dup.6 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.2 + dup.2 + movup.2 + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.6 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.24 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs3_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtNtB7_4felt4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromNtB5_4WordE4from + trace.252 + nop + drop + push.160 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +@callconv("C") +proc _RNvXs2_Cs2bNbiPwbrt9_16counter_contractNtB5_15CounterContractNtNtNtNtNtNtB5_8bindings7exports5miden16counter_contract16counter_contract5Guest9get_count( + +) -> felt + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.64 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.60 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.56 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.52 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.48 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.48 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB4_4WordINtNtCs3n4EX6Qeqp9_4core7convert4FromANtNtB6_4felt4Feltj4_E4from + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs6_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB5_4WordINtNtCs3n4EX6Qeqp9_4core7convert5AsRefBW_E6as_ref + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4feltNtB5_4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromhE4from + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.32 + dup.6 + u32wrapping_add + movup.2 + swap.3 + movdn.2 + swap.1 + swap.4 + swap.1 + swap.5 + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::miden::active_account::get_map_item + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.56 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.48 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.1 + u32wrapping_add + push.16 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvMNtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB2_4Word7reverse + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs3_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtNtB7_4felt4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromNtB5_4WordE4from + trace.252 + nop + push.64 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +@callconv("C") +proc miden:counter-contract/counter-contract@0.1.0#get-count( + +) -> felt + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvNtCsjM1oOs60QyD_11wit_bindgen2rt14run_ctors_once + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_Cs2bNbiPwbrt9_16counter_contractNtB5_15CounterContractNtNtNtNtNtNtB5_8bindings7exports5miden16counter_contract16counter_contract5Guest9get_count + trace.252 + nop +end + +@callconv("C") +proc miden:counter-contract/counter-contract@0.1.0#increment-count( + +) -> felt + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvNtCsjM1oOs60QyD_11wit_bindgen2rt14run_ctors_once + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::_RNvXs2_Cs2bNbiPwbrt9_16counter_contractNtB5_15CounterContractNtNtNtNtNtNtB5_8bindings7exports5miden16counter_contract16counter_contract5Guest15increment_count + trace.252 + nop +end + +@callconv("C") +proc _RNvNtCsjM1oOs60QyD_11wit_bindgen2rt14run_ctors_once( + +) + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter-contract@0.1.0::counter_contract::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +@callconv("C") +proc _RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4feltNtB5_4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromhE4from( + i32 +) -> felt + push.255 + u32and +end + +@callconv("C") +proc _RNvXs3_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtNtB7_4felt4FeltINtNtCs3n4EX6Qeqp9_4core7convert4FromNtB5_4WordE4from( + i32 +) -> felt + push.12 + swap.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop +end + +@callconv("C") +proc _RNvMNtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB2_4Word7reverse( + i32, + i32 +) + dup.1 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + swap.2 + movup.2 + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 + movup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + swap.2 + movup.2 + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +@callconv("C") +proc _RNvXs2_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB5_4WordINtNtCs3n4EX6Qeqp9_4core7convert4FromNtNtB7_4felt4FeltE4from( + i32, + felt +) + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +@callconv("C") +proc _RNvXs6_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB5_4WordINtNtCs3n4EX6Qeqp9_4core7convert5AsRefBW_E6as_ref( + i32 +) -> i32 + nop +end + +@callconv("C") +proc _RNvXs_NtNtCsl8LGlN1ny35_16miden_stdlib_sys10intrinsics4wordNtB4_4WordINtNtCs3n4EX6Qeqp9_4core7convert4FromANtNtB6_4felt4Feltj4_E4from( + i32, + i32 +) + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + swap.1 + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + movup.2 + movdn.3 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +@callconv("C") +proc miden::active_account::get_map_item(felt, felt, felt, felt, felt, i32) + trace.240 + nop + exec.::miden::active_account::get_map_item + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +@callconv("C") +proc miden::native_account::set_map_item( + felt, + felt, + felt, + felt, + felt, + felt, + felt, + felt, + felt, + i32 +) + trace.240 + nop + exec.::miden::native_account::set_map_item + trace.252 + nop + movup.8 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.24 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.28 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index 2ba21ebd5..377d6c25c 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -24,6 +24,7 @@ indexmap = "2.7" log.workspace = true miden-core.workspace = true midenc-hir.workspace = true +midenc-dialect-debuginfo.workspace = true midenc-dialect-hir.workspace = true midenc-dialect-cf.workspace = true midenc-dialect-ub.workspace = true diff --git a/frontend/wasm/src/code_translator/mod.rs b/frontend/wasm/src/code_translator/mod.rs index c4526ec45..416226a35 100644 --- a/frontend/wasm/src/code_translator/mod.rs +++ b/frontend/wasm/src/code_translator/mod.rs @@ -57,6 +57,8 @@ pub fn translate_operator( diagnostics: &DiagnosticsHandler, span: SourceSpan, ) -> WasmResult<()> { + builder.record_debug_span(span); + if !state.reachable { translate_unreachable_operator(op, builder, state, mod_types, diagnostics, span)?; return Ok(()); @@ -94,6 +96,7 @@ pub fn translate_operator( val }; builder.store_local(local, val, span)?; + builder.emit_dbg_value_for_var(var, val, span); } Operator::LocalTee { local_index } => { let var = Variable::from_u32(*local_index); @@ -114,6 +117,7 @@ pub fn translate_operator( val }; builder.store_local(local, val, span)?; + builder.emit_dbg_value_for_var(var, val, span); } /********************************** Globals ****************************************/ Operator::GlobalGet { global_index } => { diff --git a/frontend/wasm/src/component/lift_exports.rs b/frontend/wasm/src/component/lift_exports.rs index 60ac3cb10..26f8e4b01 100644 --- a/frontend/wasm/src/component/lift_exports.rs +++ b/frontend/wasm/src/component/lift_exports.rs @@ -5,8 +5,8 @@ use midenc_dialect_cf::ControlFlowOpBuilder; use midenc_dialect_hir::HirOpBuilder; use midenc_frontend_wasm_metadata::ProtocolExportKind; use midenc_hir::{ - FunctionType, Ident, Op, OpExt, SmallVec, SourceSpan, SymbolPath, ValueRange, ValueRef, - Visibility, + DICompileUnit, DISubprogram, FunctionType, Ident, Op, OpExt, SmallVec, SourceSpan, SymbolPath, + ValueRange, ValueRef, Visibility, dialects::builtin::{ BuiltinOpBuilder, ComponentBuilder, ModuleBuilder, attributes::{Signature, UnitAttr}, @@ -25,11 +25,18 @@ use crate::{ }, }; +struct ComponentExportMetadata<'a> { + ty: &'a FunctionType, + param_names: &'a [String], + protocol_export_kind: Option, +} + /// Generates a lifted component export wrapper around a lowered core Wasm export. pub fn generate_export_lifting_function( component_builder: &mut ComponentBuilder, export_func_name: &str, export_func_ty: FunctionType, + export_param_names: &[String], core_export_func_path: SymbolPath, protocol_export_kind: Option, diagnostics: &DiagnosticsHandler, @@ -55,6 +62,11 @@ pub fn generate_export_lifting_function( } let export_func_ident = Ident::new(export_func_name.to_string().into(), SourceSpan::default()); + let export_metadata = ComponentExportMetadata { + ty: &export_func_ty, + param_names: export_param_names, + protocol_export_kind, + }; let core_export_module_path = core_export_func_path.without_leaf(); let core_module_ref = component_builder @@ -77,22 +89,21 @@ pub fn generate_export_lifting_function( generate_lifting_with_transformation( component_builder, export_func_ident, - &export_func_ty, + &export_metadata, cross_ctx_export_sig_flat, core_export_func_ref, core_export_func_sig, &core_export_func_path, - protocol_export_kind, diagnostics, )?; } else { generate_direct_lifting( component_builder, export_func_ident, + &export_metadata, core_export_func_ref, core_export_func_sig, cross_ctx_export_sig_flat, - protocol_export_kind, )?; } @@ -132,12 +143,11 @@ pub fn generate_export_lifting_function( fn generate_lifting_with_transformation( component_builder: &mut ComponentBuilder, export_func_ident: Ident, - export_func_ty: &FunctionType, + export_metadata: &ComponentExportMetadata<'_>, cross_ctx_export_sig_flat: Signature, core_export_func_ref: midenc_hir::dialects::builtin::FunctionRef, core_export_func_sig: Signature, core_export_func_path: &SymbolPath, - protocol_export_kind: Option, diagnostics: &DiagnosticsHandler, ) -> WasmResult<()> { assert_eq!( @@ -154,7 +164,7 @@ fn generate_lifting_with_transformation( // Extract flattened result types from the exported component-level function type let context = { core_export_func_ref.borrow().as_operation().context_rc() }; - let flattened_results = flatten_types(&context, &export_func_ty.results).map_err(|e| { + let flattened_results = flatten_types(&context, &export_metadata.ty.results).map_err(|e| { let message = format!( "Failed to flatten result types for exported function {core_export_func_path}: {e}" ); @@ -176,7 +186,13 @@ fn generate_lifting_with_transformation( }; let export_func_ref = component_builder.define_function(export_func_ident, Visibility::Public, new_func_sig)?; - annotate_protocol_export(export_func_ref, protocol_export_kind); + annotate_protocol_export(export_func_ref, export_metadata.protocol_export_kind); + annotate_component_export_debug_signature( + export_func_ref, + export_func_ident.name.as_str(), + export_metadata.ty, + export_metadata.param_names, + ); let (span, context) = { let export_func = export_func_ref.borrow(); @@ -216,11 +232,11 @@ fn generate_lifting_with_transformation( // Load results using the recursive function from canon_abi_utils assert_eq!( - export_func_ty.results.len(), + export_metadata.ty.results.len(), 1, "expected a single result in the component-level export function" ); - let result_type = &export_func_ty.results[0]; + let result_type = &export_metadata.ty.results[0]; load(&mut fb, result_ptr, result_type, &mut return_values, span)?; @@ -273,17 +289,23 @@ fn generate_lifting_with_transformation( fn generate_direct_lifting( component_builder: &mut ComponentBuilder, export_func_ident: Ident, + export_metadata: &ComponentExportMetadata<'_>, core_export_func_ref: midenc_hir::dialects::builtin::FunctionRef, core_export_func_sig: Signature, cross_ctx_export_sig_flat: Signature, - protocol_export_kind: Option, ) -> WasmResult<()> { let export_func_ref = component_builder.define_function( export_func_ident, Visibility::Public, cross_ctx_export_sig_flat.clone(), )?; - annotate_protocol_export(export_func_ref, protocol_export_kind); + annotate_protocol_export(export_func_ref, export_metadata.protocol_export_kind); + annotate_component_export_debug_signature( + export_func_ref, + export_func_ident.name.as_str(), + export_metadata.ty, + export_metadata.param_names, + ); let (span, context) = { let export_func = export_func_ref.borrow(); @@ -349,3 +371,43 @@ fn annotate_protocol_export( None => {} } } + +fn annotate_component_export_debug_signature( + mut export_func_ref: midenc_hir::dialects::builtin::FunctionRef, + export_func_name: &str, + export_func_ty: &FunctionType, + export_param_names: &[String], +) { + let context = { + let export_func = export_func_ref.borrow(); + export_func.as_operation().context_rc() + }; + + let file = midenc_hir::interner::Symbol::intern(""); + let mut compile_unit = DICompileUnit::new(midenc_hir::interner::Symbol::intern("wit"), file); + compile_unit.producer = Some(midenc_hir::interner::Symbol::intern("midenc-frontend-wasm")); + + let param_names = export_param_names + .iter() + .map(|name| midenc_hir::interner::Symbol::intern(name.as_str())); + let subprogram = + DISubprogram::new(midenc_hir::interner::Symbol::intern(export_func_name), file, 1, Some(1)) + .with_function_type(FunctionType { + abi: export_func_ty.abi, + params: export_func_ty.params.clone(), + results: export_func_ty.results.clone(), + }) + .with_param_names(param_names); + + let cu_attr = context + .create_attribute::(compile_unit) + .as_attribute_ref(); + let sp_attr = context + .create_attribute::(subprogram) + .as_attribute_ref(); + + let mut export_func = export_func_ref.borrow_mut(); + let op = export_func.as_operation_mut(); + op.set_attribute("di.compile_unit", cu_attr); + op.set_attribute("di.subprogram", sp_attr); +} diff --git a/frontend/wasm/src/component/translator.rs b/frontend/wasm/src/component/translator.rs index c65110229..82739fd60 100644 --- a/frontend/wasm/src/component/translator.rs +++ b/frontend/wasm/src/component/translator.rs @@ -479,6 +479,7 @@ impl<'a> ComponentTranslator<'a> { let type_func_idx = types.convert_component_func_type(frame.types, canon_lift.ty).unwrap(); let component_types = types.resources_mut_and_types().1; + let type_func = component_types[type_func_idx].clone(); let func_ty = convert_lifted_func_ty(CanonicalAbiMode::Export, &type_func_idx, component_types); let core_export_func_path = self.core_module_export_func_path(frame, canon_lift); @@ -491,6 +492,7 @@ impl<'a> ComponentTranslator<'a> { &mut self.result, name, func_ty, + &type_func.param_names, core_export_func_path, protocol_export_kind, self.context.diagnostics(), @@ -688,6 +690,7 @@ impl<'a> ComponentTranslator<'a> { TypeDef::ComponentInstance(type_component_instance_idx) => type_component_instance_idx, _ => panic!("expected component instance"), }; + types.register_component_instance_export_type_names(ty, Some(name.0)); frame .component_instances .push(ComponentInstanceDef::Import(ComponentInstanceImport { diff --git a/frontend/wasm/src/component/types/mod.rs b/frontend/wasm/src/component/types/mod.rs index 3d5371c54..44de3decb 100644 --- a/frontend/wasm/src/component/types/mod.rs +++ b/frontend/wasm/src/component/types/mod.rs @@ -282,6 +282,7 @@ pub struct ComponentTypes { options: PrimaryMap, results: PrimaryMap, resource_tables: PrimaryMap, + interface_type_names: FxHashMap, module_types: ModuleTypes, } @@ -325,6 +326,10 @@ impl ComponentTypes { InterfaceType::Result(i) => &self[*i].abi, } } + + pub fn interface_type_name(&self, ty: &InterfaceType) -> Option<&str> { + self.interface_type_names.get(ty).map(String::as_str) + } } macro_rules! impl_index { @@ -473,6 +478,7 @@ impl ComponentTypesBuilder { id: component_types::ComponentFuncTypeId, ) -> Result { let ty = &types[id]; + let param_names = ty.params.iter().map(|(name, _ty)| name.to_string()).collect(); let params = ty .params .iter() @@ -485,10 +491,53 @@ impl ComponentTypesBuilder { let ty = TypeFunc { params: self.new_tuple_type(params), results: self.new_tuple_type(results), + param_names, }; Ok(self.add_func_type(ty)) } + pub fn register_component_instance_export_type_names( + &mut self, + instance_idx: TypeComponentInstanceIndex, + namespace: Option<&str>, + ) { + let exports = self.component_types[instance_idx] + .exports + .iter() + .map(|(name, ty)| (name.clone(), *ty)) + .collect::>(); + + for (name, ty) in exports { + let qualified_name = namespace + .filter(|namespace| !namespace.is_empty()) + .map(|namespace| format!("{}/{}", namespace.trim_end_matches('/'), name)) + .unwrap_or(name); + self.register_type_name(ty, qualified_name); + } + } + + fn register_type_name(&mut self, ty: TypeDef, name: String) { + match ty { + TypeDef::Interface(interface_ty) => { + self.component_types.interface_type_names.entry(interface_ty).or_insert(name); + } + TypeDef::ComponentInstance(instance_idx) => { + self.register_component_instance_export_type_names(instance_idx, Some(&name)); + } + TypeDef::Component(component_idx) => { + let exports = self.component_types[component_idx] + .exports + .iter() + .map(|(export_name, ty)| (export_name.clone(), *ty)) + .collect::>(); + for (export_name, ty) in exports { + self.register_type_name(ty, format!("{}/{}", name, export_name)); + } + } + TypeDef::ComponentFunc(_) | TypeDef::Module(_) | TypeDef::Resource(_) => {} + } + } + /// Converts a wasmparser `ComponentEntityType` pub fn convert_component_entity_type( &mut self, @@ -1004,6 +1053,8 @@ pub struct TypeFunc { pub params: TypeTupleIndex, /// Results of the function represented as a tuple. pub results: TypeTupleIndex, + /// Source/component names of the parameters, in declaration order. + pub param_names: Box<[String]>, } /// All possible interface types that values can have. @@ -1755,11 +1806,15 @@ pub fn interface_type_to_ir( InterfaceType::String => todo!(), InterfaceType::ErrorContext => todo!("the async proposal is not currently supported"), InterfaceType::Record(idx) => { - let tys = component_types.records[*idx] - .fields - .iter() - .map(|f| interface_type_to_ir(&f.ty, component_types)); - midenc_hir::Type::from(midenc_hir::StructType::new(tys)) + let fields = component_types.records[*idx].fields.iter().map(|f| { + (Arc::::from(f.name.as_str()), interface_type_to_ir(&f.ty, component_types)) + }); + let struct_ty = if let Some(name) = component_types.interface_type_name(ty) { + midenc_hir::StructType::named(Arc::from(name), fields) + } else { + midenc_hir::StructType::new(fields) + }; + midenc_hir::Type::from(struct_ty) } // TODO: This is a stub to make `enum` in WIT generation work. Use proper type when ready. InterfaceType::Variant(_) => midenc_hir::Type::U32, diff --git a/frontend/wasm/src/module/build_ir.rs b/frontend/wasm/src/module/build_ir.rs index 1c055a288..b6aa8d39d 100644 --- a/frontend/wasm/src/module/build_ir.rs +++ b/frontend/wasm/src/module/build_ir.rs @@ -14,7 +14,8 @@ use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic, Severity, use wasmparser::Validator; use super::{ - MemoryIndex, module_translation_state::ModuleTranslationState, types::ModuleTypesBuilder, + MemoryIndex, debug_info::collect_function_debug_info, + module_translation_state::ModuleTranslationState, types::ModuleTypesBuilder, }; use crate::{ WasmTranslationConfig, @@ -116,6 +117,18 @@ pub fn build_ir_module( ..Default::default() }) .into_diagnostic()?; + parsed_module.function_debug = if context.session().options.emit_debug_decorators() { + collect_function_debug_info( + parsed_module, + module_types, + &parsed_module.module, + &addr2line, + context.diagnostics(), + ) + } else { + Default::default() + }; + let mut func_translator = FuncTranslator::new(context.clone()); // Although this renders this parsed module invalid(without function // bodies), we don't support multiple module instances. Thus, this @@ -188,8 +201,12 @@ pub fn build_ir_module( continue; } - let FunctionBodyData { validator, body } = body_data; + let FunctionBodyData { + validator, body, .. + } = body_data; let mut func_validator = validator.into_validator(Default::default()); + let debug_info = parsed_module.function_debug.get(&func_index).cloned(); + func_translator.translate_body( &body, function_ref, @@ -200,6 +217,7 @@ pub fn build_ir_module( context.session(), &mut func_validator, _config, + debug_info, )?; } Ok(()) diff --git a/frontend/wasm/src/module/debug_info.rs b/frontend/wasm/src/module/debug_info.rs new file mode 100644 index 000000000..1d9c0e43e --- /dev/null +++ b/frontend/wasm/src/module/debug_info.rs @@ -0,0 +1,906 @@ +use alloc::{rc::Rc, vec::Vec}; +use core::cell::RefCell; +use std::path::Path; + +use addr2line::Context; +use cranelift_entity::EntityRef; +use gimli::{self, AttributeValue, read::Operation}; +use log::debug; +use midenc_hir::{ + DICompileUnit, DIExpression, DIExpressionOp, DILocalVariable, DISubprogram, FxHashMap, + SourceSpan, encode_frame_base_local_index, interner::Symbol, +}; +use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic}; + +use super::{ + FuncIndex, Module, + module_env::{DwarfReader, FunctionBodyData, ParsedModule}, + types::{WasmFuncType, convert_valtype, ir_type}, +}; +use crate::module::types::ModuleTypesBuilder; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LocationDescriptor { + /// Inclusive start offset within the function's code, relative to the Wasm code section. + pub start: u64, + /// Exclusive end offset. `None` indicates the location is valid until the end of the function. + pub end: Option, + pub storage: VariableStorage, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VariableStorage { + Local(u32), + Global(u32), + Stack(u32), + ConstU64(u64), + /// Frame base + byte offset — from DW_OP_fbreg. + /// + /// For Wasm-global frame bases, `global_index` is the Wasm global index. + /// For Wasm-local frame bases, it is encoded with + /// `encode_frame_base_local_index`. + FrameBase { + global_index: u32, + byte_offset: i64, + }, + Unsupported, +} + +impl VariableStorage { + pub fn as_local(&self) -> Option { + match self { + VariableStorage::Local(index) => Some(*index), + _ => None, + } + } + + pub fn to_expression_op(&self) -> DIExpressionOp { + match self { + VariableStorage::Local(idx) => DIExpressionOp::WasmLocal(*idx), + VariableStorage::Global(idx) => DIExpressionOp::WasmGlobal(*idx), + VariableStorage::Stack(idx) => DIExpressionOp::WasmStack(*idx), + VariableStorage::ConstU64(val) => DIExpressionOp::ConstU64(*val), + VariableStorage::FrameBase { + global_index, + byte_offset, + } => DIExpressionOp::FrameBase { + global_index: *global_index, + byte_offset: *byte_offset, + }, + VariableStorage::Unsupported => { + DIExpressionOp::Unsupported(Symbol::intern("unsupported")) + } + } + } +} + +#[derive(Clone)] +pub struct LocalDebugInfo { + pub attr: DILocalVariable, + pub locations: Vec, + pub expression: Option, +} + +#[derive(Clone)] +pub struct FunctionDebugInfo { + pub compile_unit: DICompileUnit, + pub subprogram: DISubprogram, + pub locals: Vec>, + pub function_span: Option, + pub location_schedule: Vec, + pub next_location_event: usize, +} + +#[derive(Default, Clone)] +struct DwarfLocalData { + name: Option, + locations: Vec, + decl_line: Option, + decl_column: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LocationScheduleEntry { + pub offset: u64, + pub var_index: usize, + pub storage: VariableStorage, +} + +impl FunctionDebugInfo { + pub fn local_attr(&self, index: usize) -> Option<&DILocalVariable> { + self.locals.get(index).and_then(|info| info.as_ref().map(|data| &data.attr)) + } +} + +pub fn collect_function_debug_info( + parsed_module: &ParsedModule, + module_types: &ModuleTypesBuilder, + module: &Module, + addr2line: &Context>, + diagnostics: &DiagnosticsHandler, +) -> FxHashMap>> { + let mut map = FxHashMap::default(); + + let collected = collect_dwarf_local_data(parsed_module, module, diagnostics); + + debug!( + "Collecting function debug info for {} functions", + parsed_module.function_body_inputs.len() + ); + + for (defined_idx, body) in parsed_module.function_body_inputs.iter() { + let func_index = module.func_index(defined_idx); + let func_name = module.func_name(func_index); + if let Some(info) = build_function_debug_info( + parsed_module, + module_types, + module, + func_index, + body, + addr2line, + diagnostics, + collected.by_local.get(&func_index), + collected.frame_base.get(&func_index), + ) { + debug!( + "Collected debug info for function {}: {} locals", + func_name.as_str(), + info.locals.len() + ); + map.insert(func_index, Rc::new(RefCell::new(info))); + } else { + debug!("No debug info collected for function {}", func_name.as_str()); + } + } + + debug!("Collected debug info for {} functions total", map.len()); + map +} + +#[allow(clippy::too_many_arguments)] +fn build_function_debug_info( + parsed_module: &ParsedModule, + module_types: &ModuleTypesBuilder, + module: &Module, + func_index: FuncIndex, + body: &FunctionBodyData, + addr2line: &Context>, + diagnostics: &DiagnosticsHandler, + dwarf_locals: Option<&FxHashMap>, + frame_base_vars: Option<&Vec>, +) -> Option { + let func_name = module.func_name(func_index); + + let (file_symbol, directory_symbol) = determine_file_symbols(parsed_module, addr2line, body); + let (line, column) = determine_location(addr2line, body.body_offset); + + let mut compile_unit = DICompileUnit::new(Symbol::intern("wasm"), file_symbol); + compile_unit.directory = directory_symbol; + compile_unit.producer = Some(Symbol::intern("midenc-frontend-wasm")); + + let mut subprogram = DISubprogram::new(func_name, compile_unit.file, line, column); + subprogram.is_definition = true; + + let wasm_signature = module_types[module.functions[func_index].signature].clone(); + let locals = build_local_debug_info( + module, + func_index, + &wasm_signature, + body, + &subprogram, + diagnostics, + dwarf_locals, + frame_base_vars, + ); + let location_schedule = build_location_schedule(&locals); + + Some(FunctionDebugInfo { + compile_unit, + subprogram, + locals, + function_span: None, + location_schedule, + next_location_event: 0, + }) +} + +fn determine_file_symbols( + parsed_module: &ParsedModule, + addr2line: &Context>, + body: &FunctionBodyData, +) -> (Symbol, Option) { + if let Some(location) = addr2line + .find_location(body.body_offset) + .ok() + .flatten() + .and_then(|loc| loc.file.map(|file| file.to_owned())) + { + let path = Path::new(location.as_str()); + let directory_symbol = path.parent().and_then(|parent| parent.to_str()).map(Symbol::intern); + let file_symbol = Symbol::intern(location.as_str()); + (file_symbol, directory_symbol) + } else if let Some(path) = parsed_module.wasm_file.path.as_ref() { + let file_symbol = Symbol::intern(path.to_string_lossy().as_ref()); + let directory_symbol = path.parent().and_then(|parent| parent.to_str()).map(Symbol::intern); + (file_symbol, directory_symbol) + } else { + (Symbol::intern("unknown"), None) + } +} + +fn determine_location(addr2line: &Context>, offset: u64) -> (u32, Option) { + match addr2line.find_location(offset).ok().flatten() { + Some(location) => { + let line = location.line.unwrap_or_default(); + let column = location.column; + (line, column) + } + None => (0, None), + } +} + +#[allow(clippy::too_many_arguments)] +fn build_local_debug_info( + module: &Module, + func_index: FuncIndex, + wasm_signature: &WasmFuncType, + body: &FunctionBodyData, + subprogram: &DISubprogram, + diagnostics: &DiagnosticsHandler, + dwarf_locals: Option<&FxHashMap>, + frame_base_vars: Option<&Vec>, +) -> Vec> { + let param_count = wasm_signature.params().len(); + let mut local_entries = Vec::new(); + if let Ok(mut locals_reader) = body.body.get_locals_reader().into_diagnostic() { + let decl_count = locals_reader.get_count(); + for _ in 0..decl_count { + if let Ok((count, ty)) = locals_reader.read().into_diagnostic() { + local_entries.push((count, ty)); + } + } + } + let local_count: usize = local_entries.iter().map(|(count, _)| *count as usize).sum(); + + let total = param_count + local_count; + let mut locals = vec![None; total]; + let has_dwarf_locals = dwarf_locals.is_some_and(|locals| !locals.is_empty()) + || frame_base_vars.is_some_and(|locals| !locals.is_empty()); + + for (param_idx, wasm_ty) in wasm_signature.params().iter().enumerate() { + let index_u32 = param_idx as u32; + let dwarf_entry = dwarf_locals.and_then(|map| map.get(&index_u32)); + let mut name_symbol = module + .local_name(func_index, index_u32) + .unwrap_or_else(|| Symbol::intern(format!("arg{param_idx}"))); + if let Some(info) = dwarf_entry + && let Some(symbol) = info.name + { + name_symbol = symbol; + } + let mut attr = + DILocalVariable::new(name_symbol, subprogram.file, subprogram.line, subprogram.column); + attr.arg_index = Some(param_idx as u32); + if let Ok(ty) = ir_type(*wasm_ty, diagnostics) { + attr.ty = Some(ty); + } + let dwarf_info = dwarf_entry.cloned(); + if let Some(info) = dwarf_info.as_ref() { + if let Some(line) = info.decl_line + && line != 0 + { + attr.line = line; + } + if info.decl_column.is_some() { + attr.column = info.decl_column; + } + } + let locations = dwarf_info.as_ref().map(|info| info.locations.clone()).unwrap_or_default(); + + // Create expression from the first location if available + let expression = if !locations.is_empty() { + let ops = vec![locations[0].storage.to_expression_op()]; + Some(DIExpression::with_ops(ops)) + } else { + None + }; + + locals[param_idx] = Some(LocalDebugInfo { + attr, + locations, + expression, + }); + } + + let mut next_local_index = param_count; + for (count, ty) in local_entries { + for _ in 0..count { + let index_u32 = next_local_index as u32; + let dwarf_entry = dwarf_locals.and_then(|map| map.get(&index_u32)); + let local_name = module.local_name(func_index, index_u32); + if has_dwarf_locals && dwarf_entry.is_none() && local_name.is_none() { + next_local_index += 1; + continue; + } + + let mut name_symbol = + local_name.unwrap_or_else(|| Symbol::intern(format!("local{next_local_index}"))); + if let Some(info) = dwarf_entry + && let Some(symbol) = info.name + { + name_symbol = symbol; + } + let mut attr = DILocalVariable::new( + name_symbol, + subprogram.file, + subprogram.line, + subprogram.column, + ); + let wasm_ty = convert_valtype(ty); + if let Ok(ir_ty) = ir_type(wasm_ty, diagnostics) { + attr.ty = Some(ir_ty); + } + let dwarf_info = dwarf_entry.cloned(); + if let Some(info) = dwarf_info.as_ref() { + if let Some(line) = info.decl_line + && line != 0 + { + attr.line = line; + } + if info.decl_column.is_some() { + attr.column = info.decl_column; + } + } + let locations = + dwarf_info.as_ref().map(|info| info.locations.clone()).unwrap_or_default(); + + // Create expression from the first location if available + let expression = if !locations.is_empty() { + let ops = vec![locations[0].storage.to_expression_op()]; + Some(DIExpression::with_ops(ops)) + } else { + None + }; + + locals[next_local_index] = Some(LocalDebugInfo { + attr, + locations, + expression, + }); + next_local_index += 1; + } + } + + // Append FrameBase-only variables beyond normal WASM locals. + // These are variables like local `sum` in debug builds that live in + // linear memory via __stack_pointer and have no WASM local index. + if let Some(fb_vars) = frame_base_vars { + for fb_var in fb_vars { + let name = fb_var.name.unwrap_or_else(|| Symbol::intern("?")); + let mut attr = + DILocalVariable::new(name, subprogram.file, subprogram.line, subprogram.column); + if let Some(line) = fb_var.decl_line.filter(|l| *l != 0) { + attr.line = line; + } + attr.column = fb_var.decl_column; + let expression = if !fb_var.locations.is_empty() { + Some(DIExpression::with_ops(vec![fb_var.locations[0].storage.to_expression_op()])) + } else { + None + }; + locals.push(Some(LocalDebugInfo { + attr, + locations: fb_var.locations.clone(), + expression, + })); + } + } + + locals +} + +fn build_location_schedule(locals: &[Option]) -> Vec { + let mut schedule = Vec::new(); + for (var_index, info_opt) in locals.iter().enumerate() { + let Some(info) = info_opt else { + continue; + }; + for descriptor in &info.locations { + if descriptor.storage.as_local().is_none() + && !matches!(descriptor.storage, VariableStorage::FrameBase { .. }) + { + continue; + } + schedule.push(LocationScheduleEntry { + offset: descriptor.start, + var_index, + storage: descriptor.storage.clone(), + }); + } + } + schedule.sort_by(|a, b| a.offset.cmp(&b.offset)); + schedule +} + +/// Collected DWARF local data for all functions. +struct CollectedDwarfLocals { + /// Variables keyed by WASM local index (existing behavior). + by_local: FxHashMap>, + /// FrameBase-only variables that have no WASM local index (e.g. `sum` in debug builds). + frame_base: FxHashMap>, +} + +fn collect_dwarf_local_data( + parsed_module: &ParsedModule, + module: &Module, + diagnostics: &DiagnosticsHandler, +) -> CollectedDwarfLocals { + let _ = diagnostics; + let dwarf = &parsed_module.debuginfo.dwarf; + + let mut func_by_name = FxHashMap::default(); + for (func_index, _) in module.functions.iter() { + let name = module.func_name(func_index).as_str().to_owned(); + func_by_name.insert(name, func_index); + } + + let mut low_pc_map = FxHashMap::default(); + let code_section_offset = parsed_module.wasm_file.code_section_offset; + for (defined_idx, body) in parsed_module.function_body_inputs.iter() { + let func_index = module.func_index(defined_idx); + let adjusted = body.body_offset.saturating_sub(code_section_offset); + low_pc_map.insert(adjusted, func_index); + } + + let mut results: FxHashMap> = FxHashMap::default(); + let mut fb_results: FxHashMap> = FxHashMap::default(); + let mut units = dwarf.units(); + loop { + let header = match units.next() { + Ok(Some(header)) => header, + Ok(None) => break, + Err(err) => { + debug!("failed to iterate DWARF units: {err:?}"); + break; + } + }; + let unit = match dwarf.unit(header) { + Ok(unit) => unit, + Err(err) => { + debug!("failed to load DWARF unit: {err:?}"); + continue; + } + }; + + let mut entries = unit.entries(); + loop { + let next = match entries.next_dfs() { + Ok(Some(data)) => data, + Ok(None) => break, + Err(err) => { + debug!("error while traversing DWARF entries: {err:?}"); + break; + } + }; + let (delta, entry) = next; + let _ = delta; // we don't need depth deltas explicitly. + + if entry.tag() == gimli::DW_TAG_subprogram { + let Some(info) = + resolve_subprogram_target(dwarf, &unit, &func_by_name, &low_pc_map, entry) + else { + continue; + }; + + if let Err(err) = collect_subprogram_variables( + dwarf, + &unit, + entry.offset(), + info.func_index, + info.low_pc, + info.high_pc, + info.frame_base_global, + &mut results, + &mut fb_results, + ) { + debug!( + "failed to gather variables for function {:?}: {err:?}", + info.func_index + ); + } + } + } + } + + CollectedDwarfLocals { + by_local: results, + frame_base: fb_results, + } +} + +/// Result of resolving a DWARF subprogram to a WASM function. +struct SubprogramInfo { + func_index: FuncIndex, + low_pc: u64, + high_pc: Option, + /// The encoded WASM location used as the frame base (from DW_AT_frame_base). + /// Plain values are Wasm globals; values encoded with + /// `encode_frame_base_local_index` are Wasm locals. + frame_base_global: Option, +} + +fn resolve_subprogram_target>( + dwarf: &gimli::Dwarf, + unit: &gimli::Unit, + func_by_name: &FxHashMap, + low_pc_map: &FxHashMap, + entry: &gimli::DebuggingInformationEntry, +) -> Option { + let mut maybe_name: Option = None; + let mut low_pc = None; + let mut high_pc = None; + let mut frame_base_global = None; + + let mut attrs = entry.attrs(); + while let Ok(Some(attr)) = attrs.next() { + match attr.name() { + gimli::DW_AT_name => { + if let Ok(raw) = dwarf.attr_string(unit, attr.value()) + && let Ok(name) = raw.to_string_lossy() + { + maybe_name = Some(name.into_owned()); + } + } + gimli::DW_AT_linkage_name => { + if maybe_name.is_none() + && let Ok(raw) = dwarf.attr_string(unit, attr.value()) + && let Ok(name) = raw.to_string_lossy() + { + maybe_name = Some(name.into_owned()); + } + } + gimli::DW_AT_low_pc => match attr.value() { + AttributeValue::Addr(addr) => low_pc = Some(addr), + AttributeValue::Udata(val) => low_pc = Some(val), + _ => {} + }, + gimli::DW_AT_high_pc => match attr.value() { + AttributeValue::Addr(addr) => high_pc = Some(addr), + AttributeValue::Udata(size) => { + if let Some(base) = low_pc { + high_pc = Some(base.saturating_add(size)); + } + } + _ => {} + }, + gimli::DW_AT_frame_base => { + // Decode the frame base expression. Rust-generated Wasm commonly + // uses a generated Wasm local as the frame pointer; globals are + // still supported for producers that use __stack_pointer directly. + if let AttributeValue::Exprloc(expr) = attr.value() { + let mut ops = expr.operations(unit.encoding()); + while let Ok(Some(op)) = ops.next() { + match op { + Operation::WasmLocal { index } => { + frame_base_global = encode_frame_base_local_index(index); + } + Operation::WasmGlobal { index } => { + frame_base_global = Some(index); + } + _ => {} + } + } + } + } + _ => {} + } + } + + let make_info = |func_index, lp, hp| SubprogramInfo { + func_index, + low_pc: lp, + high_pc: hp, + frame_base_global, + }; + + if let Some(ref name) = maybe_name + && let Some(&func_index) = func_by_name.get(name) + { + return Some(make_info(func_index, low_pc.unwrap_or_default(), high_pc)); + } + + if let Some(base) = low_pc + && let Some(&func_index) = low_pc_map.get(&base) + { + return Some(make_info(func_index, base, high_pc)); + } + None +} + +#[allow(clippy::too_many_arguments)] +fn collect_subprogram_variables>( + dwarf: &gimli::Dwarf, + unit: &gimli::Unit, + offset: gimli::UnitOffset, + func_index: FuncIndex, + low_pc: u64, + high_pc: Option, + frame_base_global: Option, + results: &mut FxHashMap>, + fb_results: &mut FxHashMap>, +) -> gimli::Result<()> { + let mut tree = unit.entries_tree(Some(offset))?; + let root = tree.root()?; + let mut children = root.children(); + let mut param_counter: u32 = 0; + while let Some(child) = children.next()? { + walk_variable_nodes( + dwarf, + unit, + child, + func_index, + low_pc, + high_pc, + frame_base_global, + results, + fb_results, + &mut param_counter, + )?; + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn walk_variable_nodes>( + dwarf: &gimli::Dwarf, + unit: &gimli::Unit, + node: gimli::EntriesTreeNode, + func_index: FuncIndex, + low_pc: u64, + high_pc: Option, + frame_base_global: Option, + results: &mut FxHashMap>, + fb_results: &mut FxHashMap>, + param_counter: &mut u32, +) -> gimli::Result<()> { + let entry = node.entry(); + let tag = entry.tag(); + match tag { + gimli::DW_TAG_formal_parameter | gimli::DW_TAG_variable => { + // For formal parameters, the WASM local index equals the parameter + // order (params are always the first N WASM locals). + let fallback_index = if tag == gimli::DW_TAG_formal_parameter { + let idx = *param_counter; + *param_counter += 1; + Some(idx) + } else { + None + }; + let mut fb_vars = Vec::new(); + if let Some((local_index, mut data)) = decode_variable_entry( + dwarf, + unit, + entry, + low_pc, + high_pc, + frame_base_global, + fallback_index, + &mut fb_vars, + )? { + let local_map = results.entry(func_index).or_default(); + let entry = local_map.entry(local_index).or_insert_with(DwarfLocalData::default); + entry.name = entry.name.or(data.name); + entry.decl_line = entry.decl_line.or(data.decl_line); + entry.decl_column = entry.decl_column.or(data.decl_column); + if !data.locations.is_empty() { + entry.locations.append(&mut data.locations); + } + } + if !fb_vars.is_empty() { + fb_results.entry(func_index).or_default().extend(fb_vars); + } + } + _ => {} + } + + let mut children = node.children(); + while let Some(child) = children.next()? { + walk_variable_nodes( + dwarf, + unit, + child, + func_index, + low_pc, + high_pc, + frame_base_global, + results, + fb_results, + param_counter, + )?; + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn decode_variable_entry>( + dwarf: &gimli::Dwarf, + unit: &gimli::Unit, + entry: &gimli::DebuggingInformationEntry<'_, '_, R>, + low_pc: u64, + high_pc: Option, + frame_base_global: Option, + fallback_index: Option, + frame_base_vars: &mut Vec, +) -> gimli::Result> { + let mut name_symbol = None; + let mut location_attr = None; + let mut decl_line = None; + let mut decl_column = None; + + let mut attrs = entry.attrs(); + while let Some(attr) = attrs.next()? { + match attr.name() { + gimli::DW_AT_name => { + if let Ok(raw) = dwarf.attr_string(unit, attr.value()) + && let Ok(text) = raw.to_string_lossy() + { + name_symbol = Some(Symbol::intern(text.as_ref())); + } + } + gimli::DW_AT_location => location_attr = Some(attr.value()), + gimli::DW_AT_decl_line => { + if let Some(line) = attr.udata_value() { + decl_line = Some(line as u32); + } + } + gimli::DW_AT_decl_column => { + if let Some(column) = attr.udata_value() { + decl_column = Some(column as u32); + } + } + _ => {} + } + } + + let Some(location_value) = location_attr else { + return Ok(None); + }; + + let mut locations = Vec::new(); + + match location_value { + AttributeValue::Exprloc(ref expr) => { + let storage = decode_storage_from_expression(expr, unit, frame_base_global)?; + if let Some(storage) = storage { + // Determine the WASM local index for this variable. + // For WasmLocal storage, use the index directly. + // For FrameBase (DW_OP_fbreg), use the parameter order as + // fallback since formal params map to WASM locals 0..N. + let local_index = storage.as_local().or(fallback_index); + if let Some(local_index) = local_index { + locations.push(LocationDescriptor { + start: low_pc, + end: high_pc, + storage, + }); + let data = DwarfLocalData { + name: name_symbol, + locations, + decl_line, + decl_column, + }; + return Ok(Some((local_index, data))); + } else if matches!(&storage, VariableStorage::FrameBase { .. }) { + // FrameBase-only variable (no WASM local index, e.g. local `sum` + // in debug builds). Collect separately instead of dropping. + locations.push(LocationDescriptor { + start: low_pc, + end: high_pc, + storage, + }); + let data = DwarfLocalData { + name: name_symbol, + locations, + decl_line, + decl_column, + }; + frame_base_vars.push(data); + return Ok(None); + } + } + return Ok(None); + } + AttributeValue::LocationListsRef(offset) => { + let mut iter = dwarf.locations.locations( + offset, + unit.encoding(), + low_pc, + &dwarf.debug_addr, + unit.addr_base, + )?; + let mut has_frame_base = false; + while let Some(entry) = iter.next()? { + let storage_expr = entry.data; + if let Some(storage) = + decode_storage_from_expression(&storage_expr, unit, frame_base_global)? + && (storage.as_local().is_some() + || matches!(&storage, VariableStorage::FrameBase { .. })) + { + if matches!(&storage, VariableStorage::FrameBase { .. }) { + has_frame_base = true; + } + locations.push(LocationDescriptor { + start: entry.range.begin, + end: Some(entry.range.end), + storage, + }); + } + } + if locations.is_empty() { + return Ok(None); + } + // Try to find a WASM local index from any location descriptor + if let Some(local_index) = locations.iter().find_map(|desc| desc.storage.as_local()) { + let data = DwarfLocalData { + name: name_symbol, + locations, + decl_line, + decl_column, + }; + return Ok(Some((local_index, data))); + } else if has_frame_base { + // FrameBase-only location list variable + let data = DwarfLocalData { + name: name_symbol, + locations, + decl_line, + decl_column, + }; + frame_base_vars.push(data); + return Ok(None); + } + return Ok(None); + } + _ => {} + } + + Ok(None) +} + +fn decode_storage_from_expression>( + expr: &gimli::Expression, + unit: &gimli::Unit, + frame_base_global: Option, +) -> gimli::Result> { + let mut operations = expr.clone().operations(unit.encoding()); + let mut storage = None; + while let Some(op) = operations.next()? { + match op { + Operation::WasmLocal { index } => storage = Some(VariableStorage::Local(index)), + Operation::WasmGlobal { index } => storage = Some(VariableStorage::Global(index)), + Operation::WasmStack { index } => storage = Some(VariableStorage::Stack(index)), + Operation::UnsignedConstant { value } => { + storage = Some(VariableStorage::ConstU64(value)) + } + Operation::StackValue => {} + Operation::FrameOffset { offset } => { + // DW_OP_fbreg(offset): variable is at frame_base + offset in + // WASM linear memory. The frame base is a WASM global + // (typically __stack_pointer = global 0). + if let Some(global_index) = frame_base_global { + storage = Some(VariableStorage::FrameBase { + global_index, + byte_offset: offset, + }); + } + } + _ => {} + } + } + + Ok(storage) +} + +fn func_local_index(func_index: FuncIndex, module: &Module) -> Option { + module.defined_func_index(func_index).map(|idx| idx.index()) +} diff --git a/frontend/wasm/src/module/func_translation_state.rs b/frontend/wasm/src/module/func_translation_state.rs index bb0f96113..b0826a2e1 100644 --- a/frontend/wasm/src/module/func_translation_state.rs +++ b/frontend/wasm/src/module/func_translation_state.rs @@ -5,13 +5,16 @@ //! //! Based on Cranelift's Wasm -> CLIF translator v11.0.0 +use alloc::rc::Rc; +use core::cell::RefCell; + use midenc_dialect_hir::HirOpBuilder; use midenc_hir::{ BlockRef, Builder, OperationRef, SourceSpan, Type, ValueRef, dialects::builtin::attributes::Signature, }; -use super::function_builder_ext::FunctionBuilderExt; +use super::{debug_info::FunctionDebugInfo, function_builder_ext::FunctionBuilderExt}; use crate::{error::WasmResult, module::types::BlockType}; /// Information about the presence of an associated `else` for an `if`, or the @@ -232,6 +235,8 @@ pub struct FuncTranslationState { /// Is the current translation state still reachable? This is false when translating operators /// like End, Return, or Unreachable. pub(crate) reachable: bool, + /// Optional debug metadata for the current function. + pub(crate) debug_info: Option>>, } impl FuncTranslationState { @@ -241,6 +246,7 @@ impl FuncTranslationState { stack: Vec::new(), control_stack: Vec::new(), reachable: true, + debug_info: None, } } @@ -248,6 +254,7 @@ impl FuncTranslationState { debug_assert!(self.stack.is_empty()); debug_assert!(self.control_stack.is_empty()); self.reachable = true; + self.debug_info = None; } /// Initialize the state for compiling a function with the given signature. @@ -259,6 +266,10 @@ impl FuncTranslationState { self.push_block(exit_block, 0, sig.results().len()); } + pub(crate) fn set_debug_info(&mut self, info: Option>>) { + self.debug_info = info; + } + /// Push a value. pub(crate) fn push1(&mut self, val: ValueRef) { self.stack.push(val); diff --git a/frontend/wasm/src/module/func_translator.rs b/frontend/wasm/src/module/func_translator.rs index f0ecd58e3..4a4d461f3 100644 --- a/frontend/wasm/src/module/func_translator.rs +++ b/frontend/wasm/src/module/func_translator.rs @@ -10,7 +10,7 @@ use std::{cell::RefCell, rc::Rc}; use cranelift_entity::EntityRef; use midenc_hir::{ - BlockRef, Builder, Context, Op, + BlockRef, Builder, Context, Op, Type, diagnostics::{ColumnNumber, LineNumber}, dialects::builtin::{BuiltinOpBuilder, FunctionRef}, }; @@ -21,8 +21,9 @@ use midenc_session::{ use wasmparser::{FuncValidator, FunctionBody, WasmModuleResources}; use super::{ - function_builder_ext::SSABuilderListener, module_env::ParsedModule, - module_translation_state::ModuleTranslationState, types::ModuleTypesBuilder, + debug_info::FunctionDebugInfo, function_builder_ext::SSABuilderListener, + module_env::ParsedModule, module_translation_state::ModuleTranslationState, + types::ModuleTypesBuilder, }; use crate::{ code_translator::translate_operator, @@ -69,12 +70,22 @@ impl FuncTranslator { session: &Session, func_validator: &mut FuncValidator, config: &crate::WasmTranslationConfig, + debug_info: Option>>, ) -> WasmResult<()> { let context = func.borrow().as_operation().context_rc(); let mut op_builder = midenc_hir::OpBuilder::new(context) .with_listener(SSABuilderListener::new(self.func_ctx.clone())); let mut builder = FunctionBuilderExt::new(func, &mut op_builder); + // Keep a clone for FrameBase variable declaration below + let debug_info_ref = debug_info.clone(); + + if let Some(info) = debug_info.clone() { + builder.set_debug_metadata(info); + } + + self.state.set_debug_info(debug_info); + let entry_block = builder.current_block(); builder.seal_block(entry_block); // Declare all predecessors known. @@ -91,7 +102,7 @@ impl FuncTranslator { let mut reader = body.get_locals_reader().into_diagnostic()?; - parse_local_decls( + let total_wasm_vars = parse_local_decls( &mut reader, &mut builder, num_params, @@ -99,6 +110,20 @@ impl FuncTranslator { &session.diagnostics, )?; + // Declare extra SSA variables for FrameBase-only debug entries (e.g. local `sum` + // in debug builds that lives in linear memory, not a WASM local). + // Use declare_var_only to avoid allocating HIR locals that would inflate + // num_locals and corrupt FMP offset calculations. + if let Some(info) = debug_info_ref.as_ref() { + let locals_len = info.borrow().locals.len(); + if locals_len > total_wasm_vars { + for idx in total_wasm_vars..locals_len { + let var = Variable::new(idx); + builder.declare_var_only(var, Type::I32); + } + } + } + let mut reader = body.get_operators_reader().into_diagnostic()?; parse_function_body( &mut reader, @@ -136,6 +161,7 @@ fn declare_parameters( let param_value = entry_block.borrow().arguments()[i]; builder.def_var(var, param_value); + builder.register_parameter(var, param_value); builder.store_local(local, param_value, SourceSpan::UNKNOWN).unwrap(); } next_local @@ -144,13 +170,14 @@ fn declare_parameters( /// Parse the local variable declarations that precede the function body. /// /// Declare local variables, starting from `num_params`. +/// Returns the total number of declared variables (params + locals). fn parse_local_decls( reader: &mut wasmparser::LocalsReader<'_>, builder: &mut FunctionBuilderExt<'_, B>, num_params: usize, validator: &mut FuncValidator, diagnostics: &DiagnosticsHandler, -) -> WasmResult<()> { +) -> WasmResult { let mut next_local = num_params; let local_count = reader.get_count(); @@ -161,7 +188,7 @@ fn parse_local_decls( declare_locals(builder, count, ty, &mut next_local, diagnostics)?; } - Ok(()) + Ok(next_local) } /// Declare `count` local variables of the same type, starting from `next_local`. @@ -313,6 +340,8 @@ fn parse_function_body( &session.diagnostics, effective_span, )?; + + builder.apply_location_schedule(offset, span); } let pos = reader.original_position(); func_validator.finish(pos).into_diagnostic()?; diff --git a/frontend/wasm/src/module/function_builder_ext.rs b/frontend/wasm/src/module/function_builder_ext.rs index eee1e55ce..4ed2a696a 100644 --- a/frontend/wasm/src/module/function_builder_ext.rs +++ b/frontend/wasm/src/module/function_builder_ext.rs @@ -1,23 +1,30 @@ -use alloc::rc::Rc; +use alloc::{rc::Rc, vec::Vec}; use core::cell::RefCell; +use std::path::Path; -use cranelift_entity::SecondaryMap; +use cranelift_entity::{EntityRef as _, SecondaryMap}; +use log::warn; use midenc_dialect_arith::ArithOpBuilder; use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_dialect_debuginfo::DebugInfoOpBuilder; use midenc_dialect_hir::HirOpBuilder; use midenc_dialect_ub::UndefinedBehaviorOpBuilder; use midenc_dialect_wasm::WasmOpBuilder; use midenc_hir::{ - BlockRef, Builder, Context, EntityRef, FxHashMap, FxHashSet, Ident, Listener, ListenerType, + BlockRef, Builder, Context, EntityRef, FxHashMap, FxHashSet, Ident, Listener, ListenerType, Op, OpBuilder, OperationRef, ProgramPoint, RegionRef, SmallVec, SourceSpan, Type, ValueRef, dialects::builtin::{ BuiltinOpBuilder, FunctionBuilder, FunctionRef, attributes::{LocalVariable, Signature}, }, + interner::Symbol, traits::{BranchOpInterface, Terminator}, }; -use crate::ssa::{SSABuilder, SideEffects, Variable}; +use crate::{ + module::debug_info::{FunctionDebugInfo, LocationScheduleEntry}, + ssa::{SSABuilder, SideEffects, Variable}, +}; /// Tracking variables and blocks for SSA construction. pub struct FunctionBuilderContext { @@ -127,6 +134,14 @@ impl Listener for SSABuilderListener { pub struct FunctionBuilderExt<'c, B: ?Sized + Builder> { inner: FunctionBuilder<'c, B>, func_ctx: Rc>, + debug_info: Option>>, + param_values: Vec<(Variable, ValueRef)>, + param_dbg_emitted: bool, + /// Set of variables that have been defined via def_var. Used by + /// apply_location_schedule to avoid calling try_use_var on undefined + /// variables, which would insert block parameters as a side effect and + /// corrupt the CFG. + defined_vars: alloc::collections::BTreeSet, } impl<'c> FunctionBuilderExt<'c, OpBuilder> { @@ -136,11 +151,193 @@ impl<'c> FunctionBuilderExt<'c, OpBuilder> { let inner = FunctionBuilder::new(func, builder); - Self { inner, func_ctx } + Self { + inner, + func_ctx, + debug_info: None, + param_values: Vec::new(), + param_dbg_emitted: false, + defined_vars: alloc::collections::BTreeSet::new(), + } } } impl FunctionBuilderExt<'_, B> { + const DI_COMPILE_UNIT_ATTR: &'static str = "di.compile_unit"; + const DI_SUBPROGRAM_ATTR: &'static str = "di.subprogram"; + + pub fn set_debug_metadata(&mut self, info: Rc>) { + self.debug_info = Some(info); + self.param_dbg_emitted = false; + self.refresh_function_debug_attrs(); + } + + pub fn emit_dbg_value_for_var(&mut self, var: Variable, value: ValueRef, span: SourceSpan) { + let Some(info) = self.debug_info.as_ref() else { + return; + }; + let idx = var.index(); + let (attr_opt, expr_opt) = { + let info = info.borrow(); + let local_info = info.locals.get(idx).and_then(|l| l.as_ref()); + match local_info { + Some(l) => (Some(l.attr.clone()), l.expression.clone()), + None => (None, None), + } + }; + let Some(mut attr) = attr_opt else { + return; + }; + + if let Some((file_symbol, _directory, line, column)) = self.span_to_location(span) { + attr.file = file_symbol; + if line != 0 { + attr.line = line; + } + attr.column = column; + } + + // If DWARF didn't provide a location expression, synthesize one from the + // wasm local index — we know this variable is stored as a wasm local. + let expr = expr_opt.or_else(|| { + let ops = vec![midenc_hir::DIExpressionOp::WasmLocal(idx as u32)]; + Some(midenc_hir::DIExpression::with_ops(ops)) + }); + + if let Err(err) = + DebugInfoOpBuilder::builder_mut(self).debug_value_with_expr(value, attr, expr, span) + { + warn!("failed to emit dbg.value for local {idx}: {err:?}"); + } + } + + pub fn def_var_with_dbg(&mut self, var: Variable, val: ValueRef, span: SourceSpan) { + self.def_var(var, val); + self.emit_dbg_value_for_var(var, val, span); + } + + pub fn register_parameter(&mut self, var: Variable, value: ValueRef) { + self.param_values.push((var, value)); + } + + pub fn record_debug_span(&mut self, span: SourceSpan) { + if span == SourceSpan::UNKNOWN { + return; + } + let Some(info_rc) = self.debug_info.as_ref() else { + return; + }; + + if let Some((file_symbol, directory_symbol, line, column)) = self.span_to_location(span) { + { + let mut info = info_rc.borrow_mut(); + info.compile_unit.file = file_symbol; + info.compile_unit.directory = directory_symbol; + info.subprogram.file = file_symbol; + info.subprogram.line = line; + info.subprogram.column = column; + info.function_span.get_or_insert(span); + } + self.refresh_function_debug_attrs(); + self.emit_parameter_dbg_if_needed(span); + } + } + + pub fn apply_location_schedule(&mut self, offset: u64, span: SourceSpan) { + let Some(info_rc) = self.debug_info.as_ref() else { + return; + }; + + let updates = { + let mut info = info_rc.borrow_mut(); + let mut pending = Vec::new(); + while info.next_location_event < info.location_schedule.len() { + let entry = &info.location_schedule[info.next_location_event]; + if entry.offset > offset { + break; + } + pending.push(entry.clone()); + info.next_location_event += 1; + } + pending + }; + + for entry in updates { + self.emit_scheduled_dbg_value(entry, span); + } + } + + fn emit_scheduled_dbg_value(&mut self, entry: LocationScheduleEntry, span: SourceSpan) { + use crate::module::debug_info::VariableStorage; + + // Skip variables already emitted as parameters to avoid duplicates. + if self.param_dbg_emitted + && self.param_values.iter().any(|(v, _)| v.index() == entry.var_index) + { + return; + } + + // Only emit debug values for variables that have already been defined. + // Calling try_use_var on an undefined variable would insert block + // parameters (phis) as a side effect, corrupting the CFG. + let is_frame_base = matches!(&entry.storage, VariableStorage::FrameBase { .. }); + if !is_frame_base && !self.defined_vars.contains(&(entry.var_index as u32)) { + return; + } + + let var = Variable::new(entry.var_index); + let value = match self.try_use_var(var) { + Ok(v) => v, + Err(_) => { + if is_frame_base { + // FrameBase-only variables have no WASM local, so no SSA value + // exists for them. The debuginfo.value op requires an SSA operand, + // so we attach an existing parameter value as an anchor. The MASM + // lowering ignores this operand when the DIExpression contains + // FrameBase — the location is fully described by the expression. + if let Some((_, v)) = self.param_values.first() { + let anchor = *v; + self.def_var(var, anchor); + anchor + } else { + warn!( + "cannot track FrameBase variable (index {}): no SSA value available \ + (function has no parameters)", + entry.var_index + ); + return; + } + } else { + return; + } + } + }; + + // Create expression from the scheduled location + let expression = { + let ops = vec![entry.storage.to_expression_op()]; + Some(midenc_hir::DIExpression::with_ops(ops)) + }; + + let Some(info) = self.debug_info.as_ref() else { + return; + }; + let idx = entry.var_index; + let attr_opt = { + let info = info.borrow(); + info.local_attr(idx).cloned() + }; + let Some(attr) = attr_opt else { + return; + }; + + if let Err(err) = DebugInfoOpBuilder::builder_mut(self) + .debug_value_with_expr(value, attr, expression, span) + { + warn!("failed to emit scheduled dbg.value for local {idx}: {err:?}"); + } + } + pub fn name(&self) -> Ident { *self.inner.func.borrow().get_name() } @@ -308,6 +505,19 @@ impl FunctionBuilderExt<'_, B> { local } + /// Declare an SSA variable without allocating an HIR local. + /// + /// Used for FrameBase-only debug variables that live in linear memory + /// and don't need a real function-local storage slot. This avoids + /// inflating `num_locals` which would corrupt FMP offset calculations. + pub fn declare_var_only(&mut self, var: Variable, ty: Type) { + let mut ctx = self.func_ctx.borrow_mut(); + if ctx.types[var] != Type::Unknown { + return; // Already declared + } + ctx.types[var] = ty; + } + /// Declares the type of a variable, so that it can be used later (by calling /// [`FunctionBuilderExt::use_var`]). This function will return an error if the variable /// has been previously declared. @@ -367,12 +577,16 @@ impl FunctionBuilderExt<'_, B> { /// an error if the value supplied does not match the type the variable was /// declared to have. pub fn try_def_var(&mut self, var: Variable, val: ValueRef) -> Result<(), DefVariableError> { - let mut func_ctx = self.func_ctx.borrow_mut(); - let var_ty = func_ctx.types.get(var).ok_or(DefVariableError::DefinedBeforeDeclared(var))?; - if var_ty != val.borrow().ty() { - return Err(DefVariableError::TypeMismatch(var, val)); + { + let mut func_ctx = self.func_ctx.borrow_mut(); + let var_ty = + func_ctx.types.get(var).ok_or(DefVariableError::DefinedBeforeDeclared(var))?; + if var_ty != val.borrow().ty() { + return Err(DefVariableError::TypeMismatch(var, val)); + } + func_ctx.ssa.def_var(var, val, self.inner.current_block()); } - func_ctx.ssa.def_var(var, val, self.inner.current_block()); + self.defined_vars.insert(var.index() as u32); Ok(()) } @@ -437,6 +651,59 @@ impl FunctionBuilderExt<'_, B> { inst_branch.change_branch_destination(old_block, new_block); self.func_ctx.borrow_mut().ssa.declare_block_predecessor(new_block, branch_inst); } + + fn refresh_function_debug_attrs(&mut self) { + let Some(info) = self.debug_info.as_ref() else { + return; + }; + let info = info.borrow(); + let context = self.inner.builder().context_rc(); + let cu_attr = context + .create_attribute::(info.compile_unit.clone()) + .as_attribute_ref(); + let sp_attr = context + .create_attribute::(info.subprogram.clone()) + .as_attribute_ref(); + let mut func = self.inner.func.borrow_mut(); + let op = func.as_operation_mut(); + op.set_attribute(Self::DI_COMPILE_UNIT_ATTR, cu_attr); + op.set_attribute(Self::DI_SUBPROGRAM_ATTR, sp_attr); + } + + fn emit_parameter_dbg_if_needed(&mut self, span: SourceSpan) { + if self.param_dbg_emitted { + return; + } + self.param_dbg_emitted = true; + let params: Vec<_> = self.param_values.to_vec(); + for (var, value) in ¶ms { + self.emit_dbg_value_for_var(*var, *value, span); + } + // FrameBase-only variables (e.g. local `sum`) are emitted solely via + // the location schedule in apply_location_schedule/emit_scheduled_dbg_value, + // avoiding duplicate DebugVar emissions. + } + + fn span_to_location( + &self, + span: SourceSpan, + ) -> Option<(Symbol, Option, u32, Option)> { + if span == SourceSpan::UNKNOWN { + return None; + } + + let context = self.inner.builder().context(); + let session = context.session(); + let source_file = session.source_manager.get(span.source_id()).ok()?; + let uri = source_file.uri().as_str(); + let path = Path::new(uri); + let file_symbol = Symbol::intern(uri); + let directory_symbol = path.parent().and_then(|parent| parent.to_str()).map(Symbol::intern); + let location = source_file.location(span); + let line = location.line.to_u32(); + let column = location.column.to_u32(); + Some((file_symbol, directory_symbol, line, Some(column))) + } } impl<'f, B: ?Sized + Builder> ArithOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { @@ -499,6 +766,18 @@ impl<'f, B: ?Sized + Builder> BuiltinOpBuilder<'f, B> for FunctionBuilderExt<'f, } } +impl<'f, B: ?Sized + Builder> DebugInfoOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + impl<'f, B: ?Sized + Builder> HirOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { #[inline(always)] fn builder(&self) -> &B { diff --git a/frontend/wasm/src/module/mod.rs b/frontend/wasm/src/module/mod.rs index 69b6ca55d..7b97e6723 100644 --- a/frontend/wasm/src/module/mod.rs +++ b/frontend/wasm/src/module/mod.rs @@ -12,6 +12,7 @@ use self::types::*; use crate::{component::SignatureIndex, error::WasmResult, unsupported_diag}; pub mod build_ir; +pub mod debug_info; pub mod func_translation_state; pub mod func_translator; pub mod function_builder_ext; @@ -336,6 +337,14 @@ impl Module { .unwrap_or_else(|| Symbol::intern(format!("data{}", index.as_u32()))) } + /// Returns the name of the given local (including parameters) if available in the name section. + pub fn local_name(&self, func: FuncIndex, index: u32) -> Option { + self.name_section + .locals_names + .get(&func) + .and_then(|locals| locals.get(&index).copied()) + } + /// Sets the fallback name of this module, used if there is no module name in the name section pub fn set_name_fallback(&mut self, name_fallback: Cow<'static, str>) { self.name_fallback = Some(Ident::from(name_fallback.as_ref())); diff --git a/frontend/wasm/src/module/module_env.rs b/frontend/wasm/src/module/module_env.rs index d3c2d4206..9142a56e1 100644 --- a/frontend/wasm/src/module/module_env.rs +++ b/frontend/wasm/src/module/module_env.rs @@ -1,10 +1,10 @@ -use alloc::sync::Arc; +use alloc::{rc::Rc, sync::Arc}; use core::ops::Range; use std::path::PathBuf; use cranelift_entity::{PrimaryMap, packed_option::ReservedValue}; use midenc_frontend_wasm_metadata::{FrontendMetadata, WASM_FRONTEND_METADATA_CUSTOM_SECTION_NAME}; -use midenc_hir::{FxHashSet, Ident, interner::Symbol}; +use midenc_hir::{FxHashMap, FxHashSet, Ident, interner::Symbol}; use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic, Report, Severity}; use wasmparser::{ CustomSectionReader, DataKind, ElementItems, ElementKind, Encoding, ExternalKind, @@ -67,6 +67,10 @@ pub struct ParsedModule<'data> { /// DWARF debug information, if enabled, parsed from the module. pub debuginfo: DebugInfoData<'data>, + /// Precomputed debug metadata for functions + pub function_debug: + FxHashMap>>, + /// Set if debuginfo was found but it was not parsed due to `Tunables` /// configuration. pub has_unparsed_debuginfo: bool, @@ -186,6 +190,8 @@ pub struct FunctionBodyData<'a> { pub body: FunctionBody<'a>, /// Validator for the function body pub validator: FuncToValidate, + /// Offset in the original wasm binary where this function body starts + pub body_offset: u64, } #[cfg(test)] @@ -703,7 +709,12 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { params: sig.params().into(), }); } - self.result.function_body_inputs.push(FunctionBodyData { validator, body }); + let body_offset = body.range().start as u64; + self.result.function_body_inputs.push(FunctionBodyData { + validator, + body, + body_offset, + }); self.result.code_index += 1; Ok(()) } diff --git a/hir-analysis/src/analyses/liveness.rs b/hir-analysis/src/analyses/liveness.rs index 31baea14e..c328ed314 100644 --- a/hir-analysis/src/analyses/liveness.rs +++ b/hir-analysis/src/analyses/liveness.rs @@ -22,6 +22,14 @@ use crate::{ /// The distance penalty applied to an edge which exits a loop pub const LOOP_EXIT_DISTANCE: u32 = 100_000; +/// Returns `true` if the operation belongs to the debuginfo dialect. +/// +/// Debug info ops (debuginfo.debug_value, etc.) are purely observational — their +/// operands are not real uses and must not keep values alive. +fn is_debug_info_op(op: &Operation) -> bool { + op.name().dialect().as_str() == "debuginfo" +} + /// This analysis computes what values are live, and the distance to next use, for all program /// points in the given operation. It computes both live-in and live-out sets, in order to answer /// liveness questions about the state of the program at an operation, as well as questions about @@ -360,9 +368,15 @@ impl DenseBackwardDataFlowAnalysis for Liveness { temp_live_in.remove(result); } - // Set the next-use distance of any operands to 0 - for operand in op.operands().all().iter() { - temp_live_in.insert(operand.borrow().as_value_ref(), 0); + // Set the next-use distance of any operands to 0. + // Skip debug info ops: their operands are observational metadata and must + // not keep values alive, otherwise scf.if branches can end up with + // mismatched operand-stack sizes when one branch has a real use and the + // other only a debug use. + if !is_debug_info_op(op) { + for operand in op.operands().all().iter() { + temp_live_in.insert(operand.borrow().as_value_ref(), 0); + } } // Determine if the state has changed, if so, then overwrite `live_in` with what we've diff --git a/hir-transform/src/sink.rs b/hir-transform/src/sink.rs index c14948b37..bf5f43bd8 100644 --- a/hir-transform/src/sink.rs +++ b/hir-transform/src/sink.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use midenc_hir::{ Backward, Builder, EntityMut, Forward, FxHashSet, OpBuilder, Operation, OperationName, OperationRef, ProgramPoint, RawWalk, Region, RegionBranchOpInterface, - RegionBranchTerminatorOpInterface, RegionRef, Report, SmallVec, Usable, ValueRef, + RegionBranchTerminatorOpInterface, RegionRef, Report, SmallVec, Usable, Value, ValueRef, adt::SmallDenseMap, dominance::DominanceInfo, matchers::{self, Matcher}, @@ -11,6 +11,49 @@ use midenc_hir::{ traits::{ConstantLike, Terminator}, }; +/// Returns `true` if the given operation belongs to the debuginfo dialect. +/// +/// Debug info operations (debuginfo.value, debuginfo.kill, etc.) are purely +/// observational and should not prevent optimizations such as sinking or DCE. +#[inline] +fn is_debug_info_op(op: &Operation) -> bool { + op.name().dialect().as_str() == "debuginfo" +} + +/// Check whether `operation` is the sole *non-debug-info* user of `value`. +/// +/// Debug uses are excluded because they are observational and should never +/// prevent value-producing operations from being moved or eliminated. +fn is_sole_non_debug_user(value: &dyn Value, operation: OperationRef) -> bool { + value + .iter_uses() + .all(|user| user.owner == operation || is_debug_info_op(&user.owner.borrow())) +} + +/// Returns `true` if the only remaining uses of the given value are debug info uses +/// (or the value is entirely unused). +fn has_only_debug_uses(value: &dyn Value) -> bool { + value.iter_uses().all(|user| is_debug_info_op(&user.owner.borrow())) +} + +/// Erase all debug info operations that reference the given value. +/// +/// This is used before erasing a defining op whose result is only kept alive by +/// debug uses. The debug ops are simply removed; the codegen emitter is also +/// hardened to skip orphaned debug ops, so this is a best-effort cleanup. +fn erase_debug_users(value: ValueRef) { + let debug_ops: Vec = { + let v = value.borrow(); + v.iter_uses() + .filter(|user| is_debug_info_op(&user.owner.borrow())) + .map(|user| user.owner) + .collect() + }; + for mut op in debug_ops { + op.borrow_mut().erase(); + } +} + /// This transformation sinks operations as close as possible to their uses, one of two ways: /// /// 1. If there exists only a single use of the operation, move it before it's use so that it is @@ -221,7 +264,9 @@ impl Pass for SinkOperandDefs { for operand in op.operands().iter().rev() { let value = operand.borrow(); let value = value.value(); - let is_sole_user = value.iter_uses().all(|user| user.owner == operation); + // Exclude debug info uses when determining whether this is the sole + // user — debug ops are observational and should not prevent sinking. + let is_sole_user = is_sole_non_debug_user(&*value, operation); let Some(defining_op) = value.get_defining_op() else { // Skip block arguments, nothing to move in that situation @@ -276,10 +321,13 @@ impl Pass for SinkOperandDefs { let mut operation = sink_state.operation; let op = operation.borrow(); - // If this operation is unused, remove it now if it has no side effects + // If this operation is unused (or only has debug info uses), remove it + // now if it has no side effects. let is_memory_effect_free = op.is_memory_effect_free() || op.implements::(); - if !op.is_used() + let only_debug_uses = + !op.is_used() || op.results().iter().all(|r| has_only_debug_uses(&*r.borrow())); + if only_debug_uses && is_memory_effect_free && !op.implements::() && !op.implements::() @@ -287,6 +335,10 @@ impl Pass for SinkOperandDefs { { log::debug!(target: Self::NAME, "erasing unused, effect-free, non-terminator op {op}"); drop(op); + // Erase any remaining debug uses before erasing the defining op + for result in operation.borrow().results().iter() { + erase_debug_users(result.borrow().as_value_ref()); + } operation.borrow_mut().erase(); continue; } @@ -323,10 +375,11 @@ impl Pass for SinkOperandDefs { operand.borrow_mut().set(replacement); changed = PostPassStatus::Changed; - // If no other uses of this value remain, then remove the original - // operation, as it is now dead. - if !operand_value.borrow().is_used() { + // If no other non-debug uses of this value remain, then remove + // the original operation, as it is now dead. + if has_only_debug_uses(&*operand_value.borrow()) { log::trace!(target: Self::NAME, " {operand_value} is no longer used, erasing definition"); + erase_debug_users(operand_value); // Replacements are only ever for op results let mut defining_op = operand_value.borrow().get_defining_op().unwrap(); defining_op.borrow_mut().erase(); @@ -336,7 +389,8 @@ impl Pass for SinkOperandDefs { } let value = operand_value.borrow(); - let is_sole_user = value.iter_uses().all(|user| user.owner == operation); + // Exclude debug info uses when determining sole-user status. + let is_sole_user = is_sole_non_debug_user(&*value, operation); let Some(mut defining_op) = value.get_defining_op() else { // Skip block arguments, nothing to move in that situation @@ -480,8 +534,11 @@ where } /// Given a region and an op which dominates the region, returns true if all - /// users of the given op are dominated by the entry block of the region, and - /// thus the operation can be sunk into the region. + /// *non-debug-info* users of the given op are dominated by the entry block + /// of the region, and thus the operation can be sunk into the region. + /// + /// Debug info uses are excluded because they are observational and should + /// not prevent control-flow sinking. fn all_users_dominated_by(&self, op: &Operation, region: &Region) -> bool { assert!( region.find_ancestor_op(op.as_operation_ref()).is_none(), @@ -491,6 +548,11 @@ where op.results().iter().all(|result| { let result = result.borrow(); result.iter_uses().all(|user| { + // Skip debug info users — they are observational and should not + // prevent sinking. + if is_debug_info_op(&user.owner.borrow()) { + return true; + } // The user is dominated by the region if its containing block is dominated // by the region's entry block. self.dominfo.dominates(®ion_entry, &user.owner.parent().unwrap()) @@ -531,6 +593,13 @@ where (all_users_dominated_by, should_move_into_region) }; if all_users_dominated_by && should_move_into_region { + // Before moving, erase any debug info ops outside the target region + // that reference results of this op — they would violate dominance + // after the move. + for result in op.borrow().results().iter() { + erase_debug_users(result.borrow().as_value_ref()); + } + (self.move_into_region)(op, region); self.num_sunk += 1; diff --git a/hir/src/attributes.rs b/hir/src/attributes.rs index 91fb82bdf..52924d175 100644 --- a/hir/src/attributes.rs +++ b/hir/src/attributes.rs @@ -1,5 +1,6 @@ mod attribute; +pub mod debug; mod named_attribute; mod traits; -pub use self::{attribute::*, named_attribute::*, traits::*}; +pub use self::{attribute::*, debug::*, named_attribute::*, traits::*}; diff --git a/hir/src/attributes/debug.rs b/hir/src/attributes/debug.rs new file mode 100644 index 000000000..44493f8f1 --- /dev/null +++ b/hir/src/attributes/debug.rs @@ -0,0 +1,383 @@ +use alloc::{format, sync::Arc, vec::Vec}; + +use crate::{ + AttrPrinter, Type, + derive::DialectAttribute, + dialects::builtin::BuiltinDialect, + formatter::{Document, PrettyPrint, const_text, text}, + interner::Symbol, + print::AsmPrinter, +}; + +/// Represents the compilation unit associated with debug information. +/// +/// The fields in this struct are intentionally aligned with the subset of +/// DWARF metadata we currently care about when tracking variable locations. +#[derive(DialectAttribute, Clone, Debug, PartialEq, Eq, Hash)] +#[attribute(dialect = BuiltinDialect, implements(AttrPrinter))] +pub struct DICompileUnit { + pub language: Symbol, + pub file: Symbol, + pub directory: Option, + pub producer: Option, + pub optimized: bool, +} + +impl Default for DICompileUnit { + fn default() -> Self { + Self { + language: crate::interner::symbols::Empty, + file: crate::interner::symbols::Empty, + directory: None, + producer: None, + optimized: false, + } + } +} + +impl DICompileUnit { + pub fn new(language: Symbol, file: Symbol) -> Self { + Self { + language, + file, + directory: None, + producer: None, + optimized: false, + } + } +} + +impl AttrPrinter for DICompileUnitAttr { + fn print(&self, printer: &mut AsmPrinter<'_>) { + *printer += self.value.render(); + } +} + +impl PrettyPrint for DICompileUnit { + fn render(&self) -> Document { + let mut doc = const_text("di.compile_unit(") + + text(format!("language = {}", self.language.as_str())) + + const_text(", file = ") + + text(self.file.as_str()); + + if let Some(directory) = self.directory { + doc = doc + const_text(", directory = ") + text(directory.as_str()); + } + if let Some(producer) = self.producer { + doc = doc + const_text(", producer = ") + text(producer.as_str()); + } + if self.optimized { + doc += const_text(", optimized"); + } + + doc + const_text(")") + } +} + +/// Represents a subprogram (function) scope for debug information. +/// The compile unit is not embedded but typically stored separately on the module. +#[derive(DialectAttribute, Clone, Debug, PartialEq, Eq, Hash)] +#[attribute(dialect = BuiltinDialect, implements(AttrPrinter))] +pub struct DISubprogram { + pub name: Symbol, + pub linkage_name: Option, + pub file: Symbol, + pub line: u32, + pub column: Option, + pub is_definition: bool, + pub is_local: bool, + pub ty: Option, + pub param_names: Vec, +} + +impl Default for DISubprogram { + fn default() -> Self { + Self { + name: crate::interner::symbols::Empty, + linkage_name: None, + file: crate::interner::symbols::Empty, + line: 0, + column: None, + is_definition: false, + is_local: false, + ty: None, + param_names: Vec::new(), + } + } +} + +impl DISubprogram { + pub fn new(name: Symbol, file: Symbol, line: u32, column: Option) -> Self { + Self { + name, + linkage_name: None, + file, + line, + column, + is_definition: true, + is_local: false, + ty: None, + param_names: Vec::new(), + } + } + + pub fn with_function_type(mut self, ty: crate::FunctionType) -> Self { + self.ty = Some(Type::Function(Arc::new(ty))); + self + } + + pub fn with_param_names(mut self, names: I) -> Self + where + I: IntoIterator, + { + self.param_names = names.into_iter().collect(); + self + } +} + +impl AttrPrinter for DISubprogramAttr { + fn print(&self, printer: &mut AsmPrinter<'_>) { + *printer += self.value.render(); + } +} + +impl PrettyPrint for DISubprogram { + fn render(&self) -> Document { + let mut doc = const_text("di.subprogram(") + + text(format!("name = {}", self.name.as_str())) + + const_text(", file = ") + + text(self.file.as_str()) + + const_text(", line = ") + + text(format!("{}", self.line)); + + if let Some(column) = self.column { + doc = doc + const_text(", column = ") + text(format!("{}", column)); + } + if let Some(linkage) = self.linkage_name { + doc = doc + const_text(", linkage = ") + text(linkage.as_str()); + } + if let Some(ty) = &self.ty { + doc = doc + const_text(", ty = ") + ty.render(); + } + if !self.param_names.is_empty() { + let names = + self.param_names.iter().map(|name| name.as_str()).collect::>().join(", "); + doc = doc + const_text(", params = [") + text(names) + const_text("]"); + } + if self.is_definition { + doc += const_text(", definition"); + } + if self.is_local { + doc += const_text(", local"); + } + + doc + const_text(")") + } +} + +/// Represents a local variable debug record. +/// The scope (DISubprogram) is not embedded but instead stored on the containing function. +#[derive(DialectAttribute, Clone, Debug, PartialEq, Eq, Hash)] +#[attribute(dialect = BuiltinDialect, implements(AttrPrinter))] +pub struct DILocalVariable { + pub name: Symbol, + pub arg_index: Option, + pub file: Symbol, + pub line: u32, + pub column: Option, + pub ty: Option, +} + +impl Default for DILocalVariable { + fn default() -> Self { + Self { + name: crate::interner::symbols::Empty, + arg_index: None, + file: crate::interner::symbols::Empty, + line: 0, + column: None, + ty: None, + } + } +} + +impl DILocalVariable { + pub fn new(name: Symbol, file: Symbol, line: u32, column: Option) -> Self { + Self { + name, + arg_index: None, + file, + line, + column, + ty: None, + } + } +} + +impl AttrPrinter for DILocalVariableAttr { + fn print(&self, printer: &mut AsmPrinter<'_>) { + *printer += self.value.render(); + } +} + +impl PrettyPrint for DILocalVariable { + fn render(&self) -> Document { + let mut doc = const_text("di.local_variable(") + + text(format!("name = {}", self.name.as_str())) + + const_text(", file = ") + + text(self.file.as_str()) + + const_text(", line = ") + + text(format!("{}", self.line)); + + if let Some(column) = self.column { + doc = doc + const_text(", column = ") + text(format!("{}", column)); + } + if let Some(arg_index) = self.arg_index { + doc = doc + const_text(", arg = ") + text(format!("{}", arg_index)); + } + if let Some(ty) = &self.ty { + doc = doc + const_text(", ty = ") + ty.render(); + } + + doc + const_text(")") + } +} + +/// Represents DWARF expression operations for describing variable locations +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum DIExpressionOp { + /// DW_OP_WASM_location 0x00 - Variable is in a WebAssembly local + WasmLocal(u32), + /// DW_OP_WASM_location 0x01 - Variable is in a WebAssembly global + WasmGlobal(u32), + /// DW_OP_WASM_location 0x02 - Variable is on the WebAssembly operand stack + WasmStack(u32), + /// DW_OP_constu - Unsigned constant value + ConstU64(u64), + /// DW_OP_consts - Signed constant value + ConstS64(i64), + /// DW_OP_plus_uconst - Add unsigned constant to top of stack + PlusUConst(u64), + /// DW_OP_minus - Subtract top two stack values + Minus, + /// DW_OP_plus - Add top two stack values + Plus, + /// DW_OP_deref - Dereference the address at top of stack + Deref, + /// DW_OP_stack_value - The value on the stack is the value of the variable + StackValue, + /// DW_OP_piece - Describes a piece of a variable + Piece(u64), + /// DW_OP_bit_piece - Describes a piece of a variable in bits + BitPiece { size: u64, offset: u64 }, + /// DW_OP_fbreg - Frame base register + offset. + /// The variable is in WASM linear memory at `value_of(global[global_index]) + byte_offset`. + FrameBase { global_index: u32, byte_offset: i64 }, + /// Placeholder for unsupported operations + Unsupported(Symbol), +} + +/// High-bit marker used to carry a Wasm-local frame base through the existing +/// `FrameBase { global_index, byte_offset }` debug-location shape without +/// changing the VM-facing `DebugVarLocation` ABI. +/// +/// Before MASM lowering completes, the low bits hold a raw Wasm local index. +/// After local patching, the low 16 bits hold the signed FMP-relative offset of +/// the Miden local containing the frame-base byte address. +pub const FRAME_BASE_LOCAL_MARKER: u32 = 1 << 31; + +pub fn encode_frame_base_local_index(local_index: u32) -> Option { + if local_index < FRAME_BASE_LOCAL_MARKER { + Some(FRAME_BASE_LOCAL_MARKER | local_index) + } else { + None + } +} + +pub fn decode_frame_base_local_index(encoded: u32) -> Option { + (encoded & FRAME_BASE_LOCAL_MARKER != 0).then_some(encoded & !FRAME_BASE_LOCAL_MARKER) +} + +pub fn encode_frame_base_local_offset(local_offset: i16) -> u32 { + FRAME_BASE_LOCAL_MARKER | u16::from_le_bytes(local_offset.to_le_bytes()) as u32 +} + +pub fn decode_frame_base_local_offset(encoded: u32) -> Option { + if encoded & FRAME_BASE_LOCAL_MARKER == 0 { + return None; + } + let low_bits = (encoded & 0xffff) as u16; + Some(i16::from_le_bytes(low_bits.to_le_bytes())) +} + +/// Represents a DWARF expression that describes how to compute or locate a variable's value +#[derive(DialectAttribute, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[attribute(dialect = BuiltinDialect, implements(AttrPrinter))] +pub struct DIExpression { + pub operations: Vec, +} + +impl DIExpression { + pub fn new() -> Self { + Self { + operations: Vec::new(), + } + } + + pub fn with_ops(operations: Vec) -> Self { + Self { operations } + } + + pub fn is_empty(&self) -> bool { + self.operations.is_empty() + } +} + +impl AttrPrinter for DIExpressionAttr { + fn print(&self, printer: &mut AsmPrinter<'_>) { + *printer += self.value.render(); + } +} + +impl PrettyPrint for DIExpression { + fn render(&self) -> Document { + if self.operations.is_empty() { + return const_text("di.expression()"); + } + + let mut doc = const_text("di.expression("); + for (i, op) in self.operations.iter().enumerate() { + if i > 0 { + doc += const_text(", "); + } + doc += match op { + DIExpressionOp::WasmLocal(idx) => text(format!("DW_OP_WASM_local {}", idx)), + DIExpressionOp::WasmGlobal(idx) => text(format!("DW_OP_WASM_global {}", idx)), + DIExpressionOp::WasmStack(idx) => text(format!("DW_OP_WASM_stack {}", idx)), + DIExpressionOp::ConstU64(val) => text(format!("DW_OP_constu {}", val)), + DIExpressionOp::ConstS64(val) => text(format!("DW_OP_consts {}", val)), + DIExpressionOp::PlusUConst(val) => text(format!("DW_OP_plus_uconst {}", val)), + DIExpressionOp::Minus => const_text("DW_OP_minus"), + DIExpressionOp::Plus => const_text("DW_OP_plus"), + DIExpressionOp::Deref => const_text("DW_OP_deref"), + DIExpressionOp::StackValue => const_text("DW_OP_stack_value"), + DIExpressionOp::Piece(size) => text(format!("DW_OP_piece {}", size)), + DIExpressionOp::BitPiece { size, offset } => { + text(format!("DW_OP_bit_piece {} {}", size, offset)) + } + DIExpressionOp::FrameBase { + global_index, + byte_offset, + } => { + if let Some(local_index) = decode_frame_base_local_index(*global_index) { + text(format!("DW_OP_fbreg local[{}]{:+}", local_index, byte_offset)) + } else { + text(format!("DW_OP_fbreg global[{}]{:+}", global_index, byte_offset)) + } + } + DIExpressionOp::Unsupported(name) => text(name.as_str()), + }; + } + doc + const_text(")") + } +} diff --git a/hir/src/dialects/builtin/builders.rs b/hir/src/dialects/builtin/builders.rs index b9fbb36fd..44c2072f9 100644 --- a/hir/src/dialects/builtin/builders.rs +++ b/hir/src/dialects/builtin/builders.rs @@ -86,6 +86,9 @@ pub trait BuiltinOpBuilder<'f, B: ?Sized + Builder> { op_builder(arg) } + // Note: dbg_value / dbg_value_with_expr have moved to DebugInfoOpBuilder + // in the midenc-dialect-debuginfo crate. Use debug_value / debug_value_with_expr there. + fn builder(&self) -> &B; fn builder_mut(&mut self) -> &mut B; } diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 100c6db4f..9e48d8aa0 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -83,8 +83,11 @@ pub use midenc_session::diagnostics; pub use self::{ attributes::{ - Attribute, AttributeName, AttributeRef, AttributeRegistration, NamedAttribute, - NamedAttributeList, + Attribute, AttributeName, AttributeRef, AttributeRegistration, AttributeValue, + DICompileUnit, DICompileUnitAttr, DIExpression, DIExpressionAttr, DIExpressionOp, + DILocalVariable, DILocalVariableAttr, DISubprogram, DISubprogramAttr, NamedAttribute, + NamedAttributeList, decode_frame_base_local_index, decode_frame_base_local_offset, + encode_frame_base_local_index, encode_frame_base_local_offset, }, dialects::builtin::attributes::{Location, Overflow, Visibility, version}, direction::{Backward, Direction, Forward}, diff --git a/midenc-compile/Cargo.toml b/midenc-compile/Cargo.toml index 470c0bd0f..3173bc0ab 100644 --- a/midenc-compile/Cargo.toml +++ b/midenc-compile/Cargo.toml @@ -32,8 +32,11 @@ log.workspace = true inventory.workspace = true midenc-codegen-masm.workspace = true miden-assembly.workspace = true +miden-core.workspace = true +miden-debug-types.workspace = true miden-mast-package.workspace = true midenc-frontend-wasm.workspace = true +midenc-dialect-debuginfo.workspace = true midenc-dialect-scf.workspace = true midenc-dialect-hir.workspace = true midenc-hir.workspace = true diff --git a/midenc-compile/src/debug_info.rs b/midenc-compile/src/debug_info.rs new file mode 100644 index 000000000..8371efd7c --- /dev/null +++ b/midenc-compile/src/debug_info.rs @@ -0,0 +1,524 @@ +//! Debug info section builder for MASP packages. +//! +//! This module provides utilities for collecting debug information from the HIR +//! and building debug sections that can be serialized into the MASP package. + +use alloc::{collections::BTreeMap, format, string::ToString, sync::Arc, vec::Vec}; + +use miden_debug_types::{ColumnNumber, LineNumber}; +use miden_mast_package::debug_info::{ + DebugFieldInfo, DebugFileInfo, DebugFunctionInfo, DebugFunctionsSection, DebugPrimitiveType, + DebugSourcesSection, DebugTypeIdx, DebugTypeInfo, DebugTypesSection, DebugVariableInfo, +}; +use midenc_dialect_debuginfo as debuginfo; +use midenc_hir::{DILocalVariable, DISubprogramAttr, OpExt, Type, dialects::builtin}; + +/// The output of the debug info collection pass: three separate sections. +pub struct DebugInfoSections { + pub types: DebugTypesSection, + pub sources: DebugSourcesSection, + pub functions: DebugFunctionsSection, +} + +/// Builder for constructing debug info sections from HIR components. +pub struct DebugInfoBuilder { + types: DebugTypesSection, + sources: DebugSourcesSection, + functions: DebugFunctionsSection, + /// Maps source file paths to their indices in the file table + file_indices: BTreeMap, + /// Maps type keys to their indices in the type table + type_indices: BTreeMap, +} + +/// A key for deduplicating types (uses u32 since DebugTypeIdx lacks Ord) +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum TypeKey { + Primitive(u8), // Use discriminant instead of the enum directly + Pointer(u32), + Array(u32, Option), + Struct(u32, u32, Vec<(u32, u32, u32)>), + Function(Option, Vec), + Unknown, +} + +impl Default for DebugInfoBuilder { + fn default() -> Self { + Self::new() + } +} + +impl DebugInfoBuilder { + /// Creates a new debug info builder. + pub fn new() -> Self { + Self { + types: DebugTypesSection::new(), + sources: DebugSourcesSection::new(), + functions: DebugFunctionsSection::new(), + file_indices: BTreeMap::new(), + type_indices: BTreeMap::new(), + } + } + + /// Adds a file to the file table and returns its index. + /// + /// The `directory` parameter, if provided, is joined with the path to create + /// a full path. The debug info section stores full paths only. + pub fn add_file(&mut self, path: &str, directory: Option<&str>) -> u32 { + // Build the full path + let full_path = if let Some(dir) = directory { + if path.starts_with('/') || path.starts_with("\\\\") { + // Already absolute + path.to_string() + } else { + format!("{}/{}", dir.trim_end_matches('/'), path) + } + } else { + path.to_string() + }; + + if let Some(&idx) = self.file_indices.get(&full_path) { + return idx; + } + + let path_idx = self.sources.add_string(Arc::from(full_path.as_str())); + let file = DebugFileInfo::new(path_idx); + + let idx = self.sources.add_file(file); + self.file_indices.insert(full_path, idx); + idx + } + + /// Adds a type to the type table and returns its index. + pub fn add_type(&mut self, ty: &Type) -> DebugTypeIdx { + let debug_type = hir_type_to_debug_type(ty, self); + let key = type_to_key(&debug_type); + + if let Some(&idx) = self.type_indices.get(&key) { + return idx; + } + + let idx = self.types.add_type(debug_type); + self.type_indices.insert(key, idx); + idx + } + + /// Adds a primitive type and returns its index. + pub fn add_primitive_type(&mut self, prim: DebugPrimitiveType) -> DebugTypeIdx { + let key = TypeKey::Primitive(prim as u8); + if let Some(&idx) = self.type_indices.get(&key) { + return idx; + } + + let idx = self.types.add_type(DebugTypeInfo::Primitive(prim)); + self.type_indices.insert(key, idx); + idx + } + + /// Collects debug information from an HIR component. + pub fn collect_from_component(&mut self, component: &builtin::Component) { + // Traverse the component and collect debug info from all functions + let region = component.body(); + let block = region.entry(); + + for op in block.body() { + if let Some(module) = op.downcast_ref::() { + self.collect_from_module(module); + } else if let Some(interface) = op.downcast_ref::() { + self.collect_from_interface(interface); + } else if let Some(function) = op.downcast_ref::() { + self.collect_from_function(function); + } + } + } + + fn collect_from_module(&mut self, module: &builtin::Module) { + let region = module.body(); + let block = region.entry(); + + for op in block.body() { + if let Some(function) = op.downcast_ref::() { + self.collect_from_function(function); + } + } + } + + fn collect_from_interface(&mut self, interface: &builtin::Interface) { + let region = interface.body(); + let block = region.entry(); + + for op in block.body() { + if let Some(function) = op.downcast_ref::() { + self.collect_from_function(function); + } + } + } + + fn collect_from_function(&mut self, function: &builtin::Function) { + // Try to get DISubprogram from the function's attributes + let subprogram_attr = + function.get_attribute(midenc_hir::interner::Symbol::intern("di.subprogram")); + + let subprogram = subprogram_attr.and_then(|attr| { + let borrowed = attr.borrow(); + borrowed.downcast_ref::().map(|sp| sp.as_value().clone()) + }); + + let Some(subprogram) = subprogram else { + // No debug info for this function, just collect from body + self.collect_variables_from_function_body(function, None); + return; + }; + + // Add file + let file_idx = self.add_file(subprogram.file.as_str(), None); + + // Add function name + let name_idx = self.functions.add_string(Arc::from(subprogram.name.as_str())); + let linkage_name_idx = subprogram + .linkage_name + .map(|s| self.functions.add_string(Arc::from(s.as_str()))); + + // Create function info + let line = LineNumber::new(subprogram.line).unwrap_or_default(); + let column = ColumnNumber::new(subprogram.column.unwrap_or(1)).unwrap_or_default(); + + let mut func_info = DebugFunctionInfo::new(name_idx, file_idx, line, column); + if let Some(linkage_idx) = linkage_name_idx { + func_info = func_info.with_linkage_name(linkage_idx); + } + if let Some(ref ty) = subprogram.ty { + let type_idx = self.add_type(ty); + func_info = func_info.with_type(type_idx); + self.collect_subprogram_parameters(&subprogram, ty, &mut func_info); + } + + // Collect local variables from function body + self.collect_variables_from_function_body(function, Some(&mut func_info)); + + self.functions.add_function(func_info); + } + + fn collect_variables_from_function_body( + &mut self, + function: &builtin::Function, + func_info: Option<&mut DebugFunctionInfo>, + ) { + // Walk through the function body to find DbgValue operations + let entry = function.entry_block(); + let entry_block = entry.borrow(); + + if let Some(func_info) = func_info { + self.collect_variables_from_block(&entry_block, func_info); + } + } + + fn collect_variables_from_block( + &mut self, + block: &midenc_hir::Block, + func_info: &mut DebugFunctionInfo, + ) { + for op in block.body() { + // Check if this is a DbgValue operation + if let Some(dbg_value) = op.downcast_ref::() + && let Some(var_info) = self.extract_variable_info(dbg_value.variable().as_value()) + { + func_info.add_variable(var_info); + } + + // Recursively process nested regions + for region_idx in 0..op.num_regions() { + let region = op.region(region_idx); + let entry = region.entry(); + self.collect_variables_from_block(&entry, func_info); + } + } + } + + fn extract_variable_info(&mut self, var: &DILocalVariable) -> Option { + let name_idx = self.functions.add_string(Arc::from(var.name.as_str())); + + // Add type if available + let type_idx = if let Some(ref ty) = var.ty { + self.add_type(ty) + } else { + self.add_primitive_type(DebugPrimitiveType::Felt) // Default to felt + }; + + let line = LineNumber::new(var.line).unwrap_or_default(); + let column = ColumnNumber::new(var.column.unwrap_or(1)).unwrap_or_default(); + + let mut var_info = DebugVariableInfo::new(name_idx, type_idx, line, column); + + if let Some(arg_index) = var.arg_index { + var_info = var_info.with_arg_index(arg_index + 1); + } + + Some(var_info) + } + + fn collect_subprogram_parameters( + &mut self, + subprogram: &midenc_hir::DISubprogram, + ty: &Type, + func_info: &mut DebugFunctionInfo, + ) { + let Type::Function(func_ty) = ty else { + return; + }; + + for (idx, param_ty) in func_ty.params().iter().enumerate() { + let name = subprogram + .param_names + .get(idx) + .copied() + .unwrap_or_else(|| midenc_hir::interner::Symbol::intern(format!("arg{idx}"))); + let name_idx = self.functions.add_string(Arc::from(name.as_str())); + let type_idx = self.add_type(param_ty); + let line = LineNumber::new(subprogram.line).unwrap_or_default(); + let column = ColumnNumber::new(subprogram.column.unwrap_or(1)).unwrap_or_default(); + let var_info = DebugVariableInfo::new(name_idx, type_idx, line, column) + .with_arg_index((idx as u32) + 1); + func_info.add_variable(var_info); + } + } + + /// Builds and returns the final debug info sections. + pub fn build(self) -> DebugInfoSections { + DebugInfoSections { + types: self.types, + sources: self.sources, + functions: self.functions, + } + } + + /// Returns whether any debug info has been collected. + pub fn is_empty(&self) -> bool { + self.functions.is_empty() && self.types.is_empty() && self.sources.is_empty() + } +} + +/// Converts an HIR Type to a DebugTypeInfo. +fn hir_type_to_debug_type(ty: &Type, builder: &mut DebugInfoBuilder) -> DebugTypeInfo { + match ty { + Type::Unknown => DebugTypeInfo::Unknown, + Type::Never => DebugTypeInfo::Primitive(DebugPrimitiveType::Void), + Type::I1 => DebugTypeInfo::Primitive(DebugPrimitiveType::Bool), + Type::I8 => DebugTypeInfo::Primitive(DebugPrimitiveType::I8), + Type::U8 => DebugTypeInfo::Primitive(DebugPrimitiveType::U8), + Type::I16 => DebugTypeInfo::Primitive(DebugPrimitiveType::I16), + Type::U16 => DebugTypeInfo::Primitive(DebugPrimitiveType::U16), + Type::I32 => DebugTypeInfo::Primitive(DebugPrimitiveType::I32), + Type::U32 => DebugTypeInfo::Primitive(DebugPrimitiveType::U32), + Type::I64 => DebugTypeInfo::Primitive(DebugPrimitiveType::I64), + Type::U64 => DebugTypeInfo::Primitive(DebugPrimitiveType::U64), + Type::I128 => DebugTypeInfo::Primitive(DebugPrimitiveType::I128), + Type::U128 => DebugTypeInfo::Primitive(DebugPrimitiveType::U128), + Type::U256 => DebugTypeInfo::Unknown, // No direct mapping for U256 + Type::F64 => DebugTypeInfo::Primitive(DebugPrimitiveType::F64), + Type::Felt => DebugTypeInfo::Primitive(DebugPrimitiveType::Felt), + Type::Ptr(ptr_type) => { + let pointee_idx = builder.add_type(ptr_type.pointee()); + DebugTypeInfo::Pointer { + pointee_type_idx: pointee_idx, + } + } + Type::Array(array_type) => { + let element_idx = builder.add_type(array_type.element_type()); + DebugTypeInfo::Array { + element_type_idx: element_idx, + count: Some(array_type.len() as u32), + } + } + // For types we don't have direct mappings for, use Unknown + Type::Struct(struct_ty) => { + let name = struct_ty.name(); + if name.as_deref().is_some_and(is_component_felt_type_name) { + return DebugTypeInfo::Primitive(DebugPrimitiveType::Felt); + } + if name.as_deref().is_some_and(is_component_word_type_name) { + return DebugTypeInfo::Primitive(DebugPrimitiveType::Word); + } + + let name_idx = + builder.types.add_string(Arc::from(name.as_deref().unwrap_or(""))); + let use_debug_layout = name.is_some(); + let mut next_offset = 0u32; + let fields: Vec = struct_ty + .fields() + .iter() + .enumerate() + .map(|(idx, field)| { + let field_name = field + .name + .as_deref() + .map(Arc::::from) + .unwrap_or_else(|| Arc::from(format!("field{idx}").as_str())); + let name_idx = builder.types.add_string(field_name); + let type_idx = builder.add_type(&field.ty); + let offset = if use_debug_layout { + let offset = next_offset; + next_offset = next_offset.saturating_add( + builder + .types + .get_type(type_idx) + .map(|ty| debug_type_size(ty, builder)) + .unwrap_or(0), + ); + offset + } else { + field.offset + }; + DebugFieldInfo { + name_idx, + type_idx, + offset, + } + }) + .collect(); + + DebugTypeInfo::Struct { + name_idx, + size: if use_debug_layout { + fields_size(fields.as_slice(), builder) + } else { + struct_ty.size() as u32 + }, + fields, + } + } + Type::Function(func_ty) => { + let return_type_idx = match func_ty.results().len() { + 0 => None, + 1 => Some(builder.add_type(&func_ty.results()[0])), + _ => Some(builder.add_tuple_type("return", func_ty.results())), + }; + let param_type_indices = + func_ty.params().iter().map(|ty| builder.add_type(ty)).collect(); + DebugTypeInfo::Function { + return_type_idx, + param_type_indices, + } + } + Type::List(_) | Type::Enum(_) => DebugTypeInfo::Unknown, + } +} + +impl DebugInfoBuilder { + fn add_tuple_type(&mut self, name: &str, fields: &[Type]) -> DebugTypeIdx { + let name_idx = self.types.add_string(Arc::from(name)); + let mut offset = 0u32; + let fields: Vec = fields + .iter() + .enumerate() + .map(|(idx, ty)| { + let name_idx = self.types.add_string(Arc::from(format!("field{idx}").as_str())); + let type_idx = self.add_type(ty); + let field = DebugFieldInfo { + name_idx, + type_idx, + offset, + }; + offset = offset.saturating_add( + self.types.get_type(type_idx).map(|ty| debug_type_size(ty, self)).unwrap_or(0), + ); + field + }) + .collect(); + self.types.add_type(DebugTypeInfo::Struct { + name_idx, + size: fields_size(fields.as_slice(), self), + fields, + }) + } +} + +fn fields_size(fields: &[DebugFieldInfo], builder: &DebugInfoBuilder) -> u32 { + fields + .iter() + .filter_map(|field| builder.types.get_type(field.type_idx).map(|ty| (field.offset, ty))) + .map(|(offset, ty)| offset.saturating_add(debug_type_size(ty, builder))) + .max() + .unwrap_or_default() +} + +fn debug_type_size(ty: &DebugTypeInfo, builder: &DebugInfoBuilder) -> u32 { + match ty { + DebugTypeInfo::Primitive(prim) => prim.size_in_bytes(), + DebugTypeInfo::Pointer { .. } => 4, + DebugTypeInfo::Array { + element_type_idx, + count, + } => { + let Some(count) = count else { + return 0; + }; + let Some(element_type) = builder.types.get_type(*element_type_idx) else { + return 0; + }; + count.saturating_mul(debug_type_size(element_type, builder)) + } + DebugTypeInfo::Struct { size, .. } => *size, + DebugTypeInfo::Function { .. } => 4, + DebugTypeInfo::Unknown => 0, + } +} + +fn is_component_felt_type_name(name: &str) -> bool { + name == "felt" || name.ends_with("/felt") || name.ends_with("::felt") +} + +fn is_component_word_type_name(name: &str) -> bool { + name == "word" || name.ends_with("/word") || name.ends_with("::word") +} + +/// Creates a key for type deduplication. +fn type_to_key(ty: &DebugTypeInfo) -> TypeKey { + match ty { + DebugTypeInfo::Primitive(p) => TypeKey::Primitive(*p as u8), + DebugTypeInfo::Pointer { pointee_type_idx } => TypeKey::Pointer(pointee_type_idx.as_u32()), + DebugTypeInfo::Array { + element_type_idx, + count, + } => TypeKey::Array(element_type_idx.as_u32(), *count), + DebugTypeInfo::Struct { + name_idx, + size, + fields, + } => TypeKey::Struct( + *name_idx, + *size, + fields + .iter() + .map(|field| (field.name_idx, field.type_idx.as_u32(), field.offset)) + .collect(), + ), + DebugTypeInfo::Function { + return_type_idx, + param_type_indices, + } => TypeKey::Function( + return_type_idx.map(DebugTypeIdx::as_u32), + param_type_indices.iter().map(|idx| idx.as_u32()).collect(), + ), + DebugTypeInfo::Unknown => TypeKey::Unknown, + } +} + +/// Builds debug info sections from an HIR component if debug info is enabled. +pub fn build_debug_info_sections( + component: &builtin::Component, + emit_debug_decorators: bool, +) -> Option { + if !emit_debug_decorators { + return None; + } + + let mut builder = DebugInfoBuilder::new(); + builder.collect_from_component(component); + + if builder.is_empty() { + None + } else { + Some(builder.build()) + } +} diff --git a/midenc-compile/src/lib.rs b/midenc-compile/src/lib.rs index b08d01d34..4ddbffa4a 100644 --- a/midenc-compile/src/lib.rs +++ b/midenc-compile/src/lib.rs @@ -6,6 +6,7 @@ extern crate alloc; extern crate std; mod compiler; +pub mod debug_info; mod stage; mod stages; diff --git a/midenc-compile/src/stages/assemble.rs b/midenc-compile/src/stages/assemble.rs index eb9dfcb27..1ee21d1b0 100644 --- a/midenc-compile/src/stages/assemble.rs +++ b/midenc-compile/src/stages/assemble.rs @@ -1,4 +1,4 @@ -use alloc::{string::ToString, vec, vec::Vec}; +use alloc::{string::ToString, vec::Vec}; use miden_mast_package::{ Dependency, Package, PackageManifest, Section, SectionId, TargetType, Version, @@ -81,13 +81,25 @@ fn build_package( .expect("package dependencies should be unique"); let account_component_metadata_bytes = outputs.account_component_metadata_bytes.clone(); + let debug_info_bytes = outputs.debug_info_bytes.clone(); - let sections = match account_component_metadata_bytes { - Some(bytes) => { - vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, bytes)] - } - None => vec![], - }; + let mut sections = Vec::new(); + + if let Some(bytes) = account_component_metadata_bytes { + sections.push(Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, bytes)); + } + + if let Some((types_bytes, sources_bytes, functions_bytes)) = debug_info_bytes { + log::debug!( + "adding debug sections to package (types={} sources={} functions={} bytes)", + types_bytes.len(), + sources_bytes.len(), + functions_bytes.len(), + ); + sections.push(Section::new(SectionId::DEBUG_TYPES, types_bytes)); + sections.push(Section::new(SectionId::DEBUG_SOURCES, sources_bytes)); + sections.push(Section::new(SectionId::DEBUG_FUNCTIONS, functions_bytes)); + } Package { name, diff --git a/midenc-compile/src/stages/codegen.rs b/midenc-compile/src/stages/codegen.rs index 909b24140..4f920ad22 100644 --- a/midenc-compile/src/stages/codegen.rs +++ b/midenc-compile/src/stages/codegen.rs @@ -20,6 +20,8 @@ pub struct CodegenOutput { pub link_packages: BTreeMap>, /// The serialized AccountComponentMetadata (name, description, storage layout, etc.) pub account_component_metadata_bytes: Option>, + /// The serialized debug sections (types, sources, functions) + pub debug_info_bytes: Option<(Vec, Vec, Vec)>, } /// Perform code generation on the possibly-linked output of previous stages @@ -73,11 +75,38 @@ impl Stage for CodegenStage { session.emit(OutputMode::Text, masm_component.as_ref()).into_diagnostic()?; } + // Build debug info sections if debug decorators are enabled + let debug_info_bytes = if session.options.emit_debug_decorators() { + use miden_core::serde::Serializable; + + log::debug!("collecting debug info for debug sections"); + let debug_sections = + crate::debug_info::build_debug_info_sections(&component.borrow(), true); + debug_sections.map(|sections| { + let mut types_bytes = alloc::vec::Vec::new(); + sections.types.write_into(&mut types_bytes); + let mut sources_bytes = alloc::vec::Vec::new(); + sections.sources.write_into(&mut sources_bytes); + let mut functions_bytes = alloc::vec::Vec::new(); + sections.functions.write_into(&mut functions_bytes); + log::debug!( + "built debug sections: types={} sources={} functions={} bytes", + types_bytes.len(), + sources_bytes.len(), + functions_bytes.len(), + ); + (types_bytes, sources_bytes, functions_bytes) + }) + } else { + None + }; + Ok(CodegenOutput { component: Arc::from(masm_component), link_libraries, link_packages, account_component_metadata_bytes: linker_output.account_component_metadata_bytes, + debug_info_bytes, }) } } diff --git a/midenc-compile/src/stages/rewrite.rs b/midenc-compile/src/stages/rewrite.rs index 637d3529e..6b9161070 100644 --- a/midenc-compile/src/stages/rewrite.rs +++ b/midenc-compile/src/stages/rewrite.rs @@ -108,6 +108,18 @@ impl Stage for ApplyRewritesStage { log::trace!(target: "driver", "after rewrites: {}", input.world.borrow().as_operation()); log::debug!(target: "driver", "rewrites successful"); + // Emit HIR if requested + let session = context.session(); + if session.should_emit(midenc_session::OutputType::Hir) { + log::debug!(target: "driver", "emitting HIR component"); + session + .emit(midenc_session::OutputMode::Text, &*input.component.borrow()) + .into_diagnostic()?; + log::debug!(target: "driver", "HIR component emitted successfully"); + } else { + log::debug!(target: "driver", "HIR emission not requested"); + } + if context.session().rewrite_only() { log::debug!(target: "driver", "stopping compiler early (rewrite-only=true)"); Err(CompilerStopped.into()) diff --git a/tests/integration-network/src/mockchain/basic_wallet.rs b/tests/integration-network/src/mockchain/basic_wallet.rs index fec3d1519..b55b62897 100644 --- a/tests/integration-network/src/mockchain/basic_wallet.rs +++ b/tests/integration-network/src/mockchain/basic_wallet.rs @@ -107,7 +107,7 @@ pub fn test_basic_wallet_p2id() { chain.build_tx_context(alice_id, &[p2id_note_mint.id()], &[]).unwrap(); let tx_measurements = execute_tx(&mut chain, consume_tx_context_builder); expect!["3216"].assert_eq(prologue_cycles(&tx_measurements)); - expect!["20211"].assert_eq(note_cycles(&tx_measurements, p2id_note_mint.id())); + expect!["20094"].assert_eq(note_cycles(&tx_measurements, p2id_note_mint.id())); eprintln!("\n=== Checking Alice's account has the minted asset ==="); let alice_account = chain.committed_account(alice_id).unwrap(); @@ -127,12 +127,12 @@ pub fn test_basic_wallet_p2id() { &mut note_rng, ); let tx_measurements = execute_tx(&mut chain, alice_tx_context_builder); - expect!["25232"].assert_eq(tx_script_processing_cycles(&tx_measurements)); + expect!["25009"].assert_eq(tx_script_processing_cycles(&tx_measurements)); eprintln!("\n=== Step 4: Bob consumes p2id note ==="); let consume_tx_context_builder = chain.build_tx_context(bob_id, &[bob_note.id()], &[]).unwrap(); let tx_measurements = execute_tx(&mut chain, consume_tx_context_builder); - expect!["20211"].assert_eq(note_cycles(&tx_measurements, bob_note.id())); + expect!["20094"].assert_eq(note_cycles(&tx_measurements, bob_note.id())); eprintln!("\n=== Checking Bob's account has the transferred asset ==="); let bob_account = chain.committed_account(bob_id).unwrap(); @@ -257,7 +257,7 @@ pub fn test_basic_wallet_p2ide() { let consume_tx_context_builder = chain.build_tx_context(bob_id, &[p2ide_note.id()], &[]).unwrap(); let tx_measurements = execute_tx(&mut chain, consume_tx_context_builder); - expect!["20686"].assert_eq(note_cycles(&tx_measurements, p2ide_note.id())); + expect!["20569"].assert_eq(note_cycles(&tx_measurements, p2ide_note.id())); // Step 5: verify balances let bob_account = chain.committed_account(bob_id).unwrap(); @@ -382,7 +382,7 @@ pub fn test_basic_wallet_p2ide_reclaim() { let reclaim_tx_context_builder = chain.build_tx_context(alice_id, &[p2ide_note.id()], &[]).unwrap(); let tx_measurements = execute_tx(&mut chain, reclaim_tx_context_builder); - expect!["21699"].assert_eq(note_cycles(&tx_measurements, p2ide_note.id())); + expect!["21582"].assert_eq(note_cycles(&tx_measurements, p2ide_note.id())); // Step 5: verify Alice has her original amount back let alice_account = chain.committed_account(alice_id).unwrap(); diff --git a/tests/integration-network/src/mockchain/counter_contract.rs b/tests/integration-network/src/mockchain/counter_contract.rs index 359e8e6bc..c5bf35447 100644 --- a/tests/integration-network/src/mockchain/counter_contract.rs +++ b/tests/integration-network/src/mockchain/counter_contract.rs @@ -68,7 +68,7 @@ pub fn test_counter_contract() { .build_tx_context(counter_account.clone(), &[counter_note.id()], &[]) .unwrap(); let tx_measurements = execute_tx(&mut chain, tx_context_builder); - expect!["24863"].assert_eq(note_cycles(&tx_measurements, counter_note.id())); + expect!["24294"].assert_eq(note_cycles(&tx_measurements, counter_note.id())); // The counter contract storage value should be 2 after the note is consumed (incremented by 1). assert_counter_storage( diff --git a/tests/integration-network/src/mockchain/counter_contract_no_auth.rs b/tests/integration-network/src/mockchain/counter_contract_no_auth.rs index 02ff78ac4..00909db45 100644 --- a/tests/integration-network/src/mockchain/counter_contract_no_auth.rs +++ b/tests/integration-network/src/mockchain/counter_contract_no_auth.rs @@ -104,8 +104,8 @@ pub fn test_counter_contract_no_auth() { .build_tx_context(counter_account.clone(), &[counter_note.id()], &[]) .unwrap(); let tx_measurements = execute_tx(&mut chain, tx_context_builder); - expect!["1824"].assert_eq(auth_procedure_cycles(&tx_measurements)); - expect!["24863"].assert_eq(note_cycles(&tx_measurements, counter_note.id())); + expect!["1803"].assert_eq(auth_procedure_cycles(&tx_measurements)); + expect!["24294"].assert_eq(note_cycles(&tx_measurements, counter_note.id())); // The counter contract storage value should be 2 after the note is consumed assert_counter_storage( diff --git a/tests/integration-network/src/mockchain/counter_contract_rust_auth.rs b/tests/integration-network/src/mockchain/counter_contract_rust_auth.rs index e2a699633..eef14a217 100644 --- a/tests/integration-network/src/mockchain/counter_contract_rust_auth.rs +++ b/tests/integration-network/src/mockchain/counter_contract_rust_auth.rs @@ -72,7 +72,7 @@ pub fn test_counter_contract_rust_auth_blocks_unauthorized_note_creation() { let tx_context = tx_context_builder.build().unwrap(); let executed_tx = block_on(tx_context.execute()).expect("authorized client should be able to create a note"); - expect!["83038"].assert_eq(auth_procedure_cycles(executed_tx.measurements())); + expect!["82697"].assert_eq(auth_procedure_cycles(executed_tx.measurements())); assert_eq!(executed_tx.output_notes().num_notes(), 1); assert_eq!(executed_tx.output_notes().get_note(0).id(), own_note.id()); diff --git a/tests/integration/expected/debug_conditional_assignment.hir b/tests/integration/expected/debug_conditional_assignment.hir new file mode 100644 index 000000000..09aaab61a --- /dev/null +++ b/tests/integration/expected/debug_conditional_assignment.hir @@ -0,0 +1,36 @@ +builtin.component private @root_ns:root@1.0.0 { + builtin.module private @test_rust_6a38c85fac04c065bce04eac12569a730e53b0bf392ff986bec40561d4ead6b0 { + builtin.function public extern("C") @entrypoint(%0: i32) -> i32 { + hir.store_local %0 <{ local = #builtin.local_variable<0, i32> }> : (i32); + %2 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %3 = arith.constant 1 : i32; + %4 = hir.bitcast %3 <{ ty = #builtin.type }>; + %5 = arith.shl %2, %4; + %6 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %7 = arith.constant 1 : i32; + %8 = arith.add %6, %7 <{ overflow = #builtin.overflow }>; + %9 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %10 = arith.constant 10 : i32; + %11 = hir.bitcast %9 <{ ty = #builtin.type }>; + %12 = hir.bitcast %10 <{ ty = #builtin.type }>; + %13 = arith.gt %11, %12; + %14 = arith.zext %13 <{ ty = #builtin.type }>; + %15 = hir.bitcast %14 <{ ty = #builtin.type }>; + %16 = arith.constant 0 : i32; + %17 = arith.neq %15, %16; + %18 = cf.select %17, %5, %8; + cf.br ^block7:(%18); + ^block7(%1: i32): + builtin.ret %1 : (i32); + }; + builtin.global_variable private @__stack_pointer : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global1 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global2 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/debug_multiple_locals.hir b/tests/integration/expected/debug_multiple_locals.hir new file mode 100644 index 000000000..c2bc22eef --- /dev/null +++ b/tests/integration/expected/debug_multiple_locals.hir @@ -0,0 +1,27 @@ +builtin.component private @root_ns:root@1.0.0 { + builtin.module private @test_rust_4a512383b70b366e158ab1775d5a9229476df2d2f66794836c3f1deff13c59ae { + builtin.function public extern("C") @entrypoint(%0: i32) -> i32 { + hir.store_local %0 <{ local = #builtin.local_variable<0, i32> }> : (i32); + %2 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %3 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %4 = arith.constant 1 : i32; + %5 = hir.bitcast %4 <{ ty = #builtin.type }>; + %6 = arith.shl %3, %5; + %7 = arith.add %2, %6 <{ overflow = #builtin.overflow }>; + %8 = arith.constant 1 : i32; + %9 = arith.add %7, %8 <{ overflow = #builtin.overflow }>; + cf.br ^block7:(%9); + ^block7(%1: i32): + builtin.ret %1 : (i32); + }; + builtin.global_variable private @__stack_pointer : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global1 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global2 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/debug_nested_loops.hir b/tests/integration/expected/debug_nested_loops.hir new file mode 100644 index 000000000..391c3c774 --- /dev/null +++ b/tests/integration/expected/debug_nested_loops.hir @@ -0,0 +1,36 @@ +builtin.component private @root_ns:root@1.0.0 { + builtin.module private @test_rust_0c3ae86ec9024ba37eabb5e0f732abe49b77e739f7696fe0d9cb0b5d1f72d1c1 { + builtin.function public extern("C") @entrypoint(%0: i32) -> i32 { + hir.store_local %0 <{ local = #builtin.local_variable<0, i32> }> : (i32); + %2 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %3 = arith.constant -1 : i32; + %4 = arith.add %2, %3 <{ overflow = #builtin.overflow }>; + %5 = hir.bitcast %4 <{ ty = #builtin.type }>; + %6 = arith.zext %5 <{ ty = #builtin.type }>; + %7 = hir.bitcast %6 <{ ty = #builtin.type }>; + %8 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %9 = hir.bitcast %8 <{ ty = #builtin.type }>; + %10 = arith.zext %9 <{ ty = #builtin.type }>; + %11 = hir.bitcast %10 <{ ty = #builtin.type }>; + %12 = arith.mul %7, %11 <{ overflow = #builtin.overflow }>; + %13 = arith.constant 1 : i64; + %14 = hir.bitcast %12 <{ ty = #builtin.type }>; + %15 = hir.cast %13 <{ ty = #builtin.type }>; + %16 = arith.shr %14, %15; + %17 = hir.bitcast %16 <{ ty = #builtin.type }>; + %18 = arith.trunc %17 <{ ty = #builtin.type }>; + cf.br ^block7:(%18); + ^block7(%1: i32): + builtin.ret %1 : (i32); + }; + builtin.global_variable private @__stack_pointer : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global1 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global2 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/debug_simple_params.hir b/tests/integration/expected/debug_simple_params.hir new file mode 100644 index 000000000..3f128228d --- /dev/null +++ b/tests/integration/expected/debug_simple_params.hir @@ -0,0 +1,23 @@ +builtin.component private @root_ns:root@1.0.0 { + builtin.module private @test_rust_f87bb1f3934b1e6844c1c9b6ceccb97d68349d13274693606c56673a28b6e537 { + builtin.function public extern("C") @entrypoint(%0: i32, %1: i32) -> i32 { + hir.store_local %0 <{ local = #builtin.local_variable<0, i32> }> : (i32); + hir.store_local %1 <{ local = #builtin.local_variable<1, i32> }> : (i32); + %3 = hir.load_local <{ local = #builtin.local_variable<1, i32> }>; + %4 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %5 = arith.add %3, %4 <{ overflow = #builtin.overflow }>; + cf.br ^block7:(%5); + ^block7(%2: i32): + builtin.ret %2 : (i32); + }; + builtin.global_variable private @__stack_pointer : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global1 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global2 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/debug_variable_locations.hir b/tests/integration/expected/debug_variable_locations.hir new file mode 100644 index 000000000..9828234f9 --- /dev/null +++ b/tests/integration/expected/debug_variable_locations.hir @@ -0,0 +1,53 @@ +builtin.component private @root_ns:root@1.0.0 { + builtin.module private @test_rust_abc71f0181dc4e2804cd5c8e0cf50175a2515fac3096a395915f9b0604eb287b { + builtin.function public extern("C") @entrypoint(%0: i32) -> i32 { + hir.store_local %0 <{ local = #builtin.local_variable<0, i32> }> : (i32); + %2 = arith.constant 0 : i32; + hir.store_local %2 <{ local = #builtin.local_variable<1, i32> }> : (i32); + "debuginfo.debug_value"(%2) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : i32 -> (); + %3 = arith.constant 0 : i32; + hir.store_local %3 <{ local = #builtin.local_variable<2, i32> }> : (i32); + "debuginfo.debug_value"(%3) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : i32 -> (); + cf.br ^block8; + ^block7(%1: i32): + + ^block8: + %5 = hir.load_local <{ local = #builtin.local_variable<1, i32> }>; + %6 = hir.load_local <{ local = #builtin.local_variable<0, i32> }>; + %7 = hir.bitcast %5 <{ ty = #builtin.type }>; + %8 = hir.bitcast %6 <{ ty = #builtin.type }>; + %9 = arith.lte %7, %8; + %10 = arith.zext %9 <{ ty = #builtin.type }>; + %11 = hir.bitcast %10 <{ ty = #builtin.type }>; + %12 = arith.constant 0 : i32; + %13 = arith.neq %11, %12; + cf.cond_br %13 ^block10, ^block11 : (i1); + ^block9(%4: i32): + + ^block10: + %15 = hir.load_local <{ local = #builtin.local_variable<2, i32> }>; + %16 = hir.load_local <{ local = #builtin.local_variable<1, i32> }>; + %17 = arith.add %15, %16 <{ overflow = #builtin.overflow }>; + hir.store_local %17 <{ local = #builtin.local_variable<2, i32> }> : (i32); + "debuginfo.debug_value"(%17) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : i32 -> (); + %18 = hir.load_local <{ local = #builtin.local_variable<1, i32> }>; + %19 = arith.constant 1 : i32; + %20 = arith.add %18, %19 <{ overflow = #builtin.overflow }>; + hir.store_local %20 <{ local = #builtin.local_variable<1, i32> }> : (i32); + "debuginfo.debug_value"(%20) <{ variable = #builtin.di_local_variable, expression = #builtin.di_expression }> : i32 -> (); + cf.br ^block8; + ^block11: + %14 = hir.load_local <{ local = #builtin.local_variable<2, i32> }>; + builtin.ret %14 : (i32); + }; + builtin.global_variable private @__stack_pointer : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global1 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + builtin.global_variable public @global2 : i32 { + builtin.ret_imm #builtin.number<1048576>; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/src/rust_masm_tests/debug.rs b/tests/integration/src/rust_masm_tests/debug.rs new file mode 100644 index 000000000..0999e420e --- /dev/null +++ b/tests/integration/src/rust_masm_tests/debug.rs @@ -0,0 +1,105 @@ +use std::borrow::Cow; + +use midenc_expect_test::expect_file; + +use crate::{CompilerTestBuilder, testing::setup}; + +#[test] +fn variable_locations_schedule() { + setup::enable_compiler_instrumentation(); + + let source = r#" + (n: u32) -> u32 { + let mut sum = 0u32; + let mut i = 0u32; + while i <= n { + sum += i; + i += 1; + } + sum + } + "#; + + let mut builder = CompilerTestBuilder::rust_fn_body(source, []); + builder.with_rustflags([Cow::Borrowed("-C"), Cow::Borrowed("debuginfo=2")]); + let mut test = builder.build(); + test.expect_ir_unoptimized(expect_file!["../../expected/debug_variable_locations.hir"]); +} + +#[test] +fn debug_simple_params() { + setup::enable_compiler_instrumentation(); + + let source = r#" + (a: u32, b: u32) -> u32 { + a + b + } + "#; + + let mut builder = CompilerTestBuilder::rust_fn_body(source, []); + builder.with_rustflags([Cow::Borrowed("-C"), Cow::Borrowed("debuginfo=2")]); + let mut test = builder.build(); + test.expect_ir_unoptimized(expect_file!["../../expected/debug_simple_params.hir"]); +} + +#[test] +fn debug_conditional_assignment() { + setup::enable_compiler_instrumentation(); + + let source = r#" + (x: u32) -> u32 { + let result = if x > 10 { x * 2 } else { x + 1 }; + result + } + "#; + + let mut builder = CompilerTestBuilder::rust_fn_body(source, []); + builder.with_rustflags([Cow::Borrowed("-C"), Cow::Borrowed("debuginfo=2")]); + let mut test = builder.build(); + test.expect_ir_unoptimized(expect_file!["../../expected/debug_conditional_assignment.hir"]); +} + +#[test] +fn debug_multiple_locals() { + setup::enable_compiler_instrumentation(); + + let source = r#" + (n: u32) -> u32 { + let a: u32 = n + 1; + let b: u32 = n * 2; + let c: u32 = a + b; + c + } + "#; + + let mut builder = CompilerTestBuilder::rust_fn_body(source, []); + builder.with_rustflags([Cow::Borrowed("-C"), Cow::Borrowed("debuginfo=2")]); + let mut test = builder.build(); + test.expect_ir_unoptimized(expect_file!["../../expected/debug_multiple_locals.hir"]); +} + +#[test] +fn debug_nested_loops() { + setup::enable_compiler_instrumentation(); + + let source = r#" + (n: u32) -> u32 { + let mut total = 0u32; + let mut i = 0u32; + while i < n { + let mut j = 0u32; + while j < i { + total += 1; + j += 1; + } + i += 1; + } + total + } + "#; + + let mut builder = CompilerTestBuilder::rust_fn_body(source, []); + builder.with_rustflags([Cow::Borrowed("-C"), Cow::Borrowed("debuginfo=2")]); + let mut test = builder.build(); + test.expect_ir_unoptimized(expect_file!["../../expected/debug_nested_loops.hir"]); +} diff --git a/tests/integration/src/rust_masm_tests/examples.rs b/tests/integration/src/rust_masm_tests/examples.rs index 2b17ae042..4fc975c54 100644 --- a/tests/integration/src/rust_masm_tests/examples.rs +++ b/tests/integration/src/rust_masm_tests/examples.rs @@ -329,7 +329,7 @@ fn basic_wallet_and_p2id() { CompilerTest::rust_source_cargo_miden("../../examples/basic-wallet", config.clone(), []); let account_package = account_test.compile_package(); assert!(account_package.is_library(), "expected library"); - expect!["29016"].assert_eq(stripped_mast_size_str(&account_package)); + expect!["36630"].assert_eq(stripped_mast_size_str(&account_package)); let mut tx_script_test = CompilerTest::rust_source_cargo_miden( "../../examples/basic-wallet-tx-script", @@ -338,19 +338,19 @@ fn basic_wallet_and_p2id() { ); let tx_script_package = tx_script_test.compile_package(); assert!(tx_script_package.is_program(), "expected program"); - expect!["45884"].assert_eq(stripped_mast_size_str(&tx_script_package)); + expect!["56999"].assert_eq(stripped_mast_size_str(&tx_script_package)); let mut p2id_test = CompilerTest::rust_source_cargo_miden("../../examples/p2id-note", config.clone(), []); let note_package = p2id_test.compile_package(); assert!(note_package.is_library(), "expected library"); - expect!["38430"].assert_eq(stripped_mast_size_str(¬e_package)); + expect!["55262"].assert_eq(stripped_mast_size_str(¬e_package)); let mut p2ide_test = CompilerTest::rust_source_cargo_miden("../../examples/p2ide-note", config, []); let p2ide_package = p2ide_test.compile_package(); assert!(p2ide_package.is_library(), "expected library"); - expect!["43003"].assert_eq(stripped_mast_size_str(&p2ide_package)); + expect!["61528"].assert_eq(stripped_mast_size_str(&p2ide_package)); } #[test] diff --git a/tests/integration/src/rust_masm_tests/mod.rs b/tests/integration/src/rust_masm_tests/mod.rs index b4283e4fb..89bc1e43b 100644 --- a/tests/integration/src/rust_masm_tests/mod.rs +++ b/tests/integration/src/rust_masm_tests/mod.rs @@ -12,6 +12,7 @@ use crate::testing::eval_package; mod abi_transform; mod apps; +mod debug; mod debug_source_locations; mod examples; mod instructions; diff --git a/tests/lit/debug/function_metadata.rs b/tests/lit/debug/function_metadata.rs new file mode 100644 index 000000000..7bf99b7dc --- /dev/null +++ b/tests/lit/debug/function_metadata.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { core::arch::wasm32::unreachable() } +} + +#[no_mangle] +pub extern "C" fn multiply(x: u32, y: u32) -> u32 { + x * y +} diff --git a/tests/lit/debug/function_metadata.shtest b/tests/lit/debug/function_metadata.shtest new file mode 100644 index 000000000..5909e6eea --- /dev/null +++ b/tests/lit/debug/function_metadata.shtest @@ -0,0 +1,6 @@ +# Test that HIR includes source locations for function parameters +# RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && rustc 'tests/lit/debug/function_metadata.rs' --crate-type=cdylib --target wasm32-unknown-unknown -C opt-level=0 -C panic=abort -C debuginfo=2 -o \"\$TMPDIR/function_metadata.wasm\" && bin/midenc \"\$TMPDIR/function_metadata.wasm\" --entrypoint=function_metadata::multiply -Z print-hir-source-locations --emit hir=- 2>&1" | filecheck %s + +# Check that function has source location annotations +# CHECK-LABEL: builtin.function{{.*}}@multiply +# CHECK: loc({{.*}}function_metadata.rs:{{[0-9]+}} diff --git a/tests/lit/debug/lit.suite.toml b/tests/lit/debug/lit.suite.toml new file mode 100644 index 000000000..f95cc52c0 --- /dev/null +++ b/tests/lit/debug/lit.suite.toml @@ -0,0 +1,5 @@ +name = "debug" +patterns = ["*.shtest"] +working_dir = "../../../" + +[format.shtest] diff --git a/tests/lit/debug/location_expressions.rs b/tests/lit/debug/location_expressions.rs new file mode 100644 index 000000000..c899970e3 --- /dev/null +++ b/tests/lit/debug/location_expressions.rs @@ -0,0 +1,25 @@ +// Test file to verify location expressions in debug info +// Using no_std to avoid runtime overhead + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +pub extern "C" fn test_expressions(p0: i32, p1: i32, p2: i32, p3: i32) -> i32 { + // These parameters should be in WASM locals 0, 1, 2, 3 + // The debug info expressions should show: + // p0 -> DW_OP_WASM_local 0 + // p1 -> DW_OP_WASM_local 1 + // p2 -> DW_OP_WASM_local 2 + // p3 -> DW_OP_WASM_local 3 + + // Simple arithmetic using all parameters + let sum1 = p0.wrapping_add(p1); + let sum2 = p2.wrapping_add(p3); + sum1.wrapping_add(sum2) +} \ No newline at end of file diff --git a/tests/lit/debug/location_expressions.shtest b/tests/lit/debug/location_expressions.shtest new file mode 100644 index 000000000..0d7ca8088 --- /dev/null +++ b/tests/lit/debug/location_expressions.shtest @@ -0,0 +1,9 @@ +# Test that debug info with source locations is properly represented in HIR +# This test verifies that operations include source location annotations +# RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && rustc 'tests/lit/debug/location_expressions.rs' --crate-type=cdylib --target wasm32-unknown-unknown -C opt-level=0 -C panic=abort -C debuginfo=2 -o \"\$TMPDIR/location_expressions.wasm\" && bin/midenc \"\$TMPDIR/location_expressions.wasm\" --entrypoint=location_expressions::test_expressions -Z print-hir-source-locations --emit hir=- 2>&1" | filecheck %s + +# Test that the function exists with 4 parameters +# CHECK-LABEL: builtin.function{{.*}}@test_expressions({{.*}}: i32, {{.*}}: i32, {{.*}}: i32, {{.*}}: i32) -> i32 + +# Test that operations have source location annotations +# CHECK: loc({{.*}}location_expressions.rs:{{[0-9]+}} diff --git a/tests/lit/debug/simple_debug.rs b/tests/lit/debug/simple_debug.rs new file mode 100644 index 000000000..342241ecb --- /dev/null +++ b/tests/lit/debug/simple_debug.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { core::arch::wasm32::unreachable() } +} + +#[no_mangle] +pub extern "C" fn add(a: u32, b: u32) -> u32 { + a + b +} diff --git a/tests/lit/debug/simple_debug.shtest b/tests/lit/debug/simple_debug.shtest new file mode 100644 index 000000000..073ec66e3 --- /dev/null +++ b/tests/lit/debug/simple_debug.shtest @@ -0,0 +1,6 @@ +# Test that basic debug info source locations are emitted for a simple function +# RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && rustc 'tests/lit/debug/simple_debug.rs' --crate-type=cdylib --target wasm32-unknown-unknown -C opt-level=0 -C panic=abort -C debuginfo=2 -o \"\$TMPDIR/simple_debug.wasm\" && bin/midenc \"\$TMPDIR/simple_debug.wasm\" --entrypoint=simple_debug::add -Z print-hir-source-locations --emit hir=- 2>&1" | filecheck %s + +# Check that function has source location annotations +# CHECK-LABEL: builtin.function{{.*}}@add +# CHECK: loc({{.*}}simple_debug.rs:{{[0-9]+}} diff --git a/tests/lit/debug/variable_locations.rs b/tests/lit/debug/variable_locations.rs new file mode 100644 index 000000000..623d268de --- /dev/null +++ b/tests/lit/debug/variable_locations.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] +#![allow(unused_unsafe)] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { core::arch::wasm32::unreachable() } +} + +#[no_mangle] +pub extern "C" fn entrypoint(n: u32) -> u32 { + let mut sum = 0u32; + let mut i = 0u32; + while i <= n { + sum = sum + i; + i = i + 1; + } + sum +} diff --git a/tests/lit/debug/variable_locations.shtest b/tests/lit/debug/variable_locations.shtest new file mode 100644 index 000000000..2ddf07a5a --- /dev/null +++ b/tests/lit/debug/variable_locations.shtest @@ -0,0 +1,6 @@ +# Test that debug info tracks source locations in a loop +# RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && rustc 'tests/lit/debug/variable_locations.rs' --crate-type=cdylib --target wasm32-unknown-unknown -C opt-level=0 -C panic=abort -C debuginfo=2 -o \"\$TMPDIR/variable_locations.wasm\" && bin/midenc \"\$TMPDIR/variable_locations.wasm\" --entrypoint=variable_locations::entrypoint -Z print-hir-source-locations --emit hir=- 2>&1" | filecheck %s + +# Check that function has source location annotations +# CHECK-LABEL: builtin.function{{.*}}@entrypoint +# CHECK: loc({{.*}}variable_locations.rs:{{[0-9]+}} diff --git a/tests/lit/debugdump/lit.suite.toml b/tests/lit/debugdump/lit.suite.toml new file mode 100644 index 000000000..a0ec4334b --- /dev/null +++ b/tests/lit/debugdump/lit.suite.toml @@ -0,0 +1,11 @@ +name = "debugdump" +patterns = ["*.wat"] +working_dir = "../../../" + +[substitutions] +"midenc" = "$$MIDENC_BIN_DIR/midenc" +"miden-debugdump" = "$$MIDENC_BIN_DIR/miden-debugdump" +"%cargo" = "cargo +$$CARGO_MAKE_RUSTUP_TOOLCHAIN_NAME" +"%target_dir" = "$$CARGO_TARGET_DIR" + +[format.shtest] diff --git a/tests/lit/debugdump/locations-source-loc.wat b/tests/lit/debugdump/locations-source-loc.wat new file mode 100644 index 000000000..6df3ceeb8 --- /dev/null +++ b/tests/lit/debugdump/locations-source-loc.wat @@ -0,0 +1,25 @@ +;; Test that .debug_loc section shows DebugVar entries with source locations +;; from a real Rust project compiled with debug info. +;; +;; RUN: %cargo build --target-dir %target_dir/debugdump-source-location --release --target wasm32-unknown-unknown --manifest-path tests/lit/source-location/test-project/Cargo.toml 2>&1 +;; RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && TMPFILE=\"\$TMPDIR/out.masp\" && midenc '%target_dir/debugdump-source-location/wasm32-unknown-unknown/release/source_location_test.wasm' --lib --debug full -o \"\$TMPFILE\" && miden-debugdump \"\$TMPFILE\" --section locations" | filecheck %s + +;; Check header +;; CHECK: .debug_loc contents (DebugVar entries from MAST): +;; CHECK: Total DebugVar entries: 4 +;; CHECK: Unique variable names: 3 + +;; Check variable "arg0" - parameter from test_assertion function +;; CHECK: Variable: "arg0" +;; CHECK: 1 location entries: +;; CHECK: FMP-4 (param #1) + +;; Check variable "local3" - from panic handler +;; CHECK: Variable: "local3" +;; CHECK: 1 location entries: +;; CHECK: FMP-1 + +;; Check variable "x" - parameter from entrypoint function +;; CHECK: Variable: "x" +;; CHECK: 2 location entries: +;; CHECK: FMP-4 (param #1) diff --git a/tests/lit/debugdump/locations.wat b/tests/lit/debugdump/locations.wat new file mode 100644 index 000000000..3ba06eb57 --- /dev/null +++ b/tests/lit/debugdump/locations.wat @@ -0,0 +1,22 @@ +;; Test that .debug_loc section is present and handles empty case +;; RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && TMPFILE=\"\$TMPDIR/out.masp\" && midenc '%s' --exe --debug full -o \"\$TMPFILE\" && miden-debugdump \"\$TMPFILE\" --section locations" | filecheck %s + +;; Check header for .debug_loc section +;; CHECK: .debug_loc contents (DebugVar entries from MAST): +;; For raw WAT files without debug info, we expect no decorators +;; CHECK: (no DebugVar entries found) + +(module + (func $add (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add + ) + + (func $entrypoint (export "entrypoint") + i32.const 5 + i32.const 3 + call $add + drop + ) +) diff --git a/tests/lit/debugdump/simple.wat b/tests/lit/debugdump/simple.wat new file mode 100644 index 000000000..0d2d9903a --- /dev/null +++ b/tests/lit/debugdump/simple.wat @@ -0,0 +1,32 @@ +;; Test that miden-debugdump correctly parses and displays debug info from a .masp file +;; RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && TMPFILE=\"\$TMPDIR/out.masp\" && midenc '%s' --exe --debug full -o \"\$TMPFILE\" && miden-debugdump \"\$TMPFILE\"" | filecheck %s + +;; Check header +;; CHECK: DEBUG INFO DUMP: +;; CHECK: Debug info versions: + +;; Check summary section is present +;; CHECK: .debug_info summary: +;; CHECK: Strings: +;; CHECK: Types: +;; CHECK: Files: +;; CHECK: Functions: + +;; Check that we have functions from the WAT +;; CHECK: .debug_functions contents: +;; CHECK: FUNCTION: add +;; CHECK: FUNCTION: multiply + +(module + (func $add (export "add") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.add + ) + + (func $multiply (export "multiply") (param $x i32) (param $y i32) (result i32) + local.get $x + local.get $y + i32.mul + ) +) diff --git a/tests/lit/debugdump/summary.wat b/tests/lit/debugdump/summary.wat new file mode 100644 index 000000000..8498a9778 --- /dev/null +++ b/tests/lit/debugdump/summary.wat @@ -0,0 +1,21 @@ +;; Test that miden-debugdump --summary shows only summary output +;; RUN: /bin/sh -c "TMPDIR=$(mktemp -d) && TMPFILE=\"\$TMPDIR/out.masp\" && midenc '%s' --exe --debug full -o \"\$TMPFILE\" && miden-debugdump \"\$TMPFILE\" --summary" | filecheck %s + +;; Check summary is present +;; CHECK: .debug_info summary: +;; CHECK: Strings: +;; CHECK: Types:{{.*}}entries +;; CHECK: Files:{{.*}}entries +;; CHECK: Functions:{{.*}}entries + +;; Make sure full dump sections are NOT present with --summary +;; CHECK-NOT: .debug_str contents: +;; CHECK-NOT: .debug_types contents: +;; CHECK-NOT: .debug_files contents: +;; CHECK-NOT: .debug_functions contents: + +(module + (func $test (export "test") (param i32) (result i32) + local.get 0 + ) +) diff --git a/tests/lit/lit.suite.toml b/tests/lit/lit.suite.toml index 826f0e25a..e8b52875d 100644 --- a/tests/lit/lit.suite.toml +++ b/tests/lit/lit.suite.toml @@ -4,6 +4,7 @@ patterns = ["*.wat", "*.masm", "*.stderr"] [substitutions] "midenc" = "$$MIDENC_BIN_DIR/midenc" "hir-opt" = "$$MIDENC_BIN_DIR/hir-opt" +"miden-debugdump" = "$$MIDENC_BIN_DIR/miden-debugdump" "%cargo" = "cargo +$$CARGO_MAKE_RUSTUP_TOOLCHAIN_NAME" "%target_dir" = "$$CARGO_TARGET_DIR" diff --git a/tests/lit/source-location/test-project/Cargo.toml b/tests/lit/source-location/test-project/Cargo.toml new file mode 100644 index 000000000..9abe2f178 --- /dev/null +++ b/tests/lit/source-location/test-project/Cargo.toml @@ -0,0 +1,17 @@ +cargo-features = ["trim-paths"] + +[package] +name = "source_location_test" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[profile.release] +debug = true +trim-paths = ["diagnostics", "object"] + +[profile.dev] +debug = true +trim-paths = ["diagnostics", "object"] diff --git a/tests/lit/source-location/test-project/src/lib.rs b/tests/lit/source-location/test-project/src/lib.rs new file mode 100644 index 000000000..35082cd10 --- /dev/null +++ b/tests/lit/source-location/test-project/src/lib.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +#[unsafe(no_mangle)] +pub extern "C" fn test_assertion(x: u32) -> u32 { + assert!(x > 100, "x should be greater than 100"); + + x +} + +#[unsafe(no_mangle)] +#[inline(never)] +pub fn entrypoint(x: u32) -> u32 { + test_assertion(x) +} diff --git a/tests/lit/variable_locations.rs b/tests/lit/variable_locations.rs new file mode 100644 index 000000000..623d268de --- /dev/null +++ b/tests/lit/variable_locations.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] +#![allow(unused_unsafe)] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { core::arch::wasm32::unreachable() } +} + +#[no_mangle] +pub extern "C" fn entrypoint(n: u32) -> u32 { + let mut sum = 0u32; + let mut i = 0u32; + while i <= n { + sum = sum + i; + i = i + 1; + } + sum +} diff --git a/tools/cargo-miden/tests/build.rs b/tools/cargo-miden/tests/build.rs index 96faec42f..8cb349d57 100644 --- a/tools/cargo-miden/tests/build.rs +++ b/tools/cargo-miden/tests/build.rs @@ -205,12 +205,13 @@ fn new_project_integration_tests_pass() { let output = std::process::Command::new("cargo") .arg("test") + .arg("--tests") .current_dir(&integration_dir) .output() - .expect("failed to spawn `cargo test` inside integration directory"); + .expect("failed to spawn `cargo test --tests` inside integration directory"); if !output.status.success() { panic!( - "`cargo test` failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", + "`cargo test --tests` failed in {} with status {:?}\nstdout:\n{}\nstderr:\n{}", integration_dir.display(), output.status.code(), String::from_utf8_lossy(&output.stdout), diff --git a/tools/debugdump/Cargo.toml b/tools/debugdump/Cargo.toml new file mode 100644 index 000000000..930d9812a --- /dev/null +++ b/tools/debugdump/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "miden-debugdump" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +description = "A tool to dump debug information from MASP packages" +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +categories = ["development-tools", "command-line-utilities"] +keywords = ["debug", "miden", "dwarfdump"] +license.workspace = true +readme.workspace = true +edition.workspace = true +publish.workspace = true + +[[bin]] +name = "miden-debugdump" +path = "src/main.rs" + +[dependencies] +miden-mast-package.workspace = true +miden-core.workspace = true +clap.workspace = true +thiserror.workspace = true diff --git a/tools/debugdump/src/main.rs b/tools/debugdump/src/main.rs new file mode 100644 index 000000000..db969dda9 --- /dev/null +++ b/tools/debugdump/src/main.rs @@ -0,0 +1,725 @@ +//! miden-debugdump - A tool to dump debug information from MASP packages +//! +//! Similar to llvm-dwarfdump, this tool parses the `.debug_info` section +//! from compiled MASP packages and displays the debug metadata in a +//! human-readable format. + +use std::{ + collections::BTreeMap, + fs::File, + io::{BufReader, Read}, + path::PathBuf, +}; + +use clap::{Parser, ValueEnum}; +use miden_core::{ + mast::MastForest, + operations::{DebugVarInfo, DebugVarLocation}, + serde::{Deserializable, SliceReader}, +}; +use miden_mast_package::{ + Package, SectionId, + debug_info::{ + DebugFileInfo, DebugFunctionInfo, DebugFunctionsSection, DebugPrimitiveType, + DebugSourcesSection, DebugTypeIdx, DebugTypeInfo, DebugTypesSection, DebugVariableInfo, + }, +}; + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("failed to read file: {0}")] + Io(#[from] std::io::Error), + #[error("failed to parse package: {0}")] + Parse(String), + #[error("no debug_info section found in package")] + NoDebugInfo, +} + +const FRAME_BASE_LOCAL_MARKER: u32 = 1 << 31; + +fn decode_frame_base_local_offset(encoded: u32) -> Option { + if encoded & FRAME_BASE_LOCAL_MARKER == 0 { + return None; + } + + let low_bits = (encoded & 0xffff) as u16; + Some(i16::from_le_bytes(low_bits.to_le_bytes())) +} + +fn format_debug_var_location(location: &DebugVarLocation) -> String { + if let DebugVarLocation::FrameBase { + global_index, + byte_offset, + } = location + && let Some(offset) = decode_frame_base_local_offset(*global_index) + { + format!("frame_base(FMP{offset:+}){byte_offset:+}") + } else { + location.to_string() + } +} + +/// Holds the three debug info sections with helper accessors. +struct DebugSections { + types: DebugTypesSection, + sources: DebugSourcesSection, + functions: DebugFunctionsSection, +} + +impl DebugSections { + /// Look up a string in the types section's string table. + fn get_type_string(&self, idx: u32) -> Option { + self.types.get_string(idx).map(|s| s.to_string()) + } + + /// Look up a string in the sources section's string table. + fn get_source_string(&self, idx: u32) -> Option { + self.sources.get_string(idx).map(|s| s.to_string()) + } + + /// Look up a string in the functions section's string table. + fn get_func_string(&self, idx: u32) -> Option { + self.functions.get_string(idx).map(|s| s.to_string()) + } + + /// Look up a type by index. + fn get_type(&self, idx: DebugTypeIdx) -> Option<&DebugTypeInfo> { + self.types.get_type(idx) + } + + /// Look up a file by index. + fn get_file(&self, idx: u32) -> Option<&DebugFileInfo> { + self.sources.get_file(idx) + } +} + +/// A tool to dump debug information from MASP packages +#[derive(Parser, Debug)] +#[command( + name = "miden-debugdump", + about = "Dump debug information from MASP packages", + version, + rename_all = "kebab-case" +)] +struct Cli { + /// Input MASP file to analyze + #[arg(required = true)] + input: PathBuf, + + /// Filter output to specific section + #[arg(short, long, value_enum)] + section: Option, + + /// Show all available information (verbose) + #[arg(short, long)] + verbose: bool, + + /// Show raw indices instead of resolved names + #[arg(long)] + raw: bool, + + /// Only show summary statistics + #[arg(long)] + summary: bool, +} + +#[derive(Debug, Clone, Copy, ValueEnum)] +enum DumpSection { + /// Show string table + Strings, + /// Show type information + Types, + /// Show source file information + Files, + /// Show function debug information + Functions, + /// Show variable information within functions + Variables, + /// Show variable location decorators from MAST (similar to DWARF .debug_loc) + Locations, +} + +fn main() { + if let Err(e) = run() { + eprintln!("error: {e}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), Error> { + let cli = Cli::parse(); + + // Read the MASP file + let file = File::open(&cli.input)?; + let mut reader = BufReader::new(file); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes)?; + + // Parse the package + let package: Package = Package::read_from(&mut SliceReader::new(&bytes)) + .map_err(|e| Error::Parse(e.to_string()))?; + + // Get the MAST forest for location decorators + let mast_forest = package.mast.mast_forest(); + + // Find the three debug sections + let types_section = package.sections.iter().find(|s| s.id == SectionId::DEBUG_TYPES); + let sources_section = package.sections.iter().find(|s| s.id == SectionId::DEBUG_SOURCES); + let functions_section = package.sections.iter().find(|s| s.id == SectionId::DEBUG_FUNCTIONS); + + // We need at least one section to proceed + if types_section.is_none() && sources_section.is_none() && functions_section.is_none() { + return Err(Error::NoDebugInfo); + } + + // Parse each section (use empty defaults if missing) + let types: DebugTypesSection = match types_section { + Some(s) => DebugTypesSection::read_from(&mut SliceReader::new(&s.data)) + .map_err(|e| Error::Parse(e.to_string()))?, + None => DebugTypesSection::new(), + }; + let sources: DebugSourcesSection = match sources_section { + Some(s) => DebugSourcesSection::read_from(&mut SliceReader::new(&s.data)) + .map_err(|e| Error::Parse(e.to_string()))?, + None => DebugSourcesSection::new(), + }; + let functions: DebugFunctionsSection = match functions_section { + Some(s) => DebugFunctionsSection::read_from(&mut SliceReader::new(&s.data)) + .map_err(|e| Error::Parse(e.to_string()))?, + None => DebugFunctionsSection::new(), + }; + + let debug_sections = DebugSections { + types, + sources, + functions, + }; + + // Print header + println!("{}", "=".repeat(80)); + println!("DEBUG INFO DUMP: {}", cli.input.display()); + println!("Package: {} (version: {})", package.name, package.version); + println!( + "Debug info versions: types={}, sources={}, functions={}", + debug_sections.types.version, + debug_sections.sources.version, + debug_sections.functions.version, + ); + println!("{}", "=".repeat(80)); + println!(); + + if cli.summary { + print_summary(&debug_sections, mast_forest); + return Ok(()); + } + + match cli.section { + Some(DumpSection::Strings) => print_strings(&debug_sections), + Some(DumpSection::Types) => print_types(&debug_sections, cli.raw), + Some(DumpSection::Files) => print_files(&debug_sections, cli.raw), + Some(DumpSection::Functions) => print_functions(&debug_sections, cli.raw, cli.verbose), + Some(DumpSection::Variables) => print_variables(&debug_sections, cli.raw), + Some(DumpSection::Locations) => print_locations(mast_forest, &debug_sections, cli.verbose), + None => { + // Print everything + print_summary(&debug_sections, mast_forest); + println!(); + print_strings(&debug_sections); + println!(); + print_types(&debug_sections, cli.raw); + println!(); + print_files(&debug_sections, cli.raw); + println!(); + print_functions(&debug_sections, cli.raw, cli.verbose); + println!(); + print_locations(mast_forest, &debug_sections, cli.verbose); + } + } + + Ok(()) +} + +fn print_summary(debug_sections: &DebugSections, mast_forest: &MastForest) { + println!(".debug_info summary:"); + println!( + " Strings: {} (types) + {} (sources) + {} (functions)", + debug_sections.types.strings.len(), + debug_sections.sources.strings.len(), + debug_sections.functions.strings.len(), + ); + println!(" Types: {} entries", debug_sections.types.types.len()); + println!(" Files: {} entries", debug_sections.sources.files.len()); + println!(" Functions: {} entries", debug_sections.functions.functions.len()); + + let total_vars: usize = + debug_sections.functions.functions.iter().map(|f| f.variables.len()).sum(); + let total_inlined: usize = + debug_sections.functions.functions.iter().map(|f| f.inlined_calls.len()).sum(); + println!(" Variables: {} total (across all functions)", total_vars); + println!(" Inlined: {} call sites", total_inlined); + + // Count debug vars in MAST + let debug_var_count = mast_forest.debug_info().debug_vars().len(); + println!(" DebugVar entries: {} in MAST", debug_var_count); +} + +fn print_strings(debug_sections: &DebugSections) { + println!(".debug_str contents:"); + println!("{:-<80}", ""); + + println!(" [types string table]"); + for (idx, s) in debug_sections.types.strings.iter().enumerate() { + println!(" [{:4}] \"{}\"", idx, s); + } + println!(); + println!(" [sources string table]"); + for (idx, s) in debug_sections.sources.strings.iter().enumerate() { + println!(" [{:4}] \"{}\"", idx, s); + } + println!(); + println!(" [functions string table]"); + for (idx, s) in debug_sections.functions.strings.iter().enumerate() { + println!(" [{:4}] \"{}\"", idx, s); + } +} + +fn print_types(debug_sections: &DebugSections, raw: bool) { + println!(".debug_types contents:"); + println!("{:-<80}", ""); + for (idx, ty) in debug_sections.types.types.iter().enumerate() { + print!(" [{:4}] ", idx); + print_type(ty, debug_sections, raw, 0); + println!(); + } +} + +fn print_type(ty: &DebugTypeInfo, debug_sections: &DebugSections, raw: bool, indent: usize) { + let pad = " ".repeat(indent); + match ty { + DebugTypeInfo::Primitive(prim) => { + print!("{}PRIMITIVE: {}", pad, primitive_name(*prim)); + print!(" (size: {} bytes, {} felts)", prim.size_in_bytes(), prim.size_in_felts()); + } + DebugTypeInfo::Pointer { pointee_type_idx } => { + if raw { + print!("{}POINTER -> type[{}]", pad, pointee_type_idx.as_u32()); + } else { + print!("{}POINTER -> ", pad); + if let Some(pointee) = debug_sections.get_type(*pointee_type_idx) { + print_type_brief(pointee, debug_sections); + } else { + print!("", pointee_type_idx.as_u32()); + } + } + } + DebugTypeInfo::Array { + element_type_idx, + count, + } => { + if raw { + print!("{}ARRAY [{}; {:?}]", pad, element_type_idx.as_u32(), count); + } else { + print!("{}ARRAY [", pad); + if let Some(elem) = debug_sections.get_type(*element_type_idx) { + print_type_brief(elem, debug_sections); + } else { + print!(""); + } + match count { + Some(n) => print!("; {}]", n), + None => print!("; ?]"), + } + } + } + DebugTypeInfo::Struct { + name_idx, + size, + fields, + } => { + let name = if raw { + format!("str[{}]", name_idx) + } else { + debug_sections.get_type_string(*name_idx).unwrap_or_else(|| "".into()) + }; + print!("{}STRUCT {} (size: {} bytes)", pad, name, size); + if !fields.is_empty() { + println!(); + for field in fields { + let field_name = if raw { + format!("str[{}]", field.name_idx) + } else { + debug_sections + .get_type_string(field.name_idx) + .unwrap_or_else(|| "".into()) + }; + print!("{} +{:4}: {} : ", pad, field.offset, field_name); + if let Some(fty) = debug_sections.get_type(field.type_idx) { + print_type_brief(fty, debug_sections); + } else { + print!(""); + } + println!(); + } + } + } + DebugTypeInfo::Function { + return_type_idx, + param_type_indices, + } => { + print!("{}FUNCTION (", pad); + for (i, param_idx) in param_type_indices.iter().enumerate() { + if i > 0 { + print!(", "); + } + if raw { + print!("type[{}]", param_idx.as_u32()); + } else if let Some(pty) = debug_sections.get_type(*param_idx) { + print_type_brief(pty, debug_sections); + } else { + print!(""); + } + } + print!(") -> "); + match return_type_idx { + Some(idx) => { + if raw { + print!("type[{}]", idx.as_u32()); + } else if let Some(rty) = debug_sections.get_type(*idx) { + print_type_brief(rty, debug_sections); + } else { + print!(""); + } + } + None => print!("void"), + } + } + DebugTypeInfo::Unknown => { + print!("{}UNKNOWN", pad); + } + } +} + +fn print_type_brief(ty: &DebugTypeInfo, debug_sections: &DebugSections) { + match ty { + DebugTypeInfo::Primitive(prim) => print!("{}", primitive_name(*prim)), + DebugTypeInfo::Pointer { pointee_type_idx } => { + print!("*"); + if let Some(p) = debug_sections.get_type(*pointee_type_idx) { + print_type_brief(p, debug_sections); + } + } + DebugTypeInfo::Array { + element_type_idx, + count, + } => { + print!("["); + if let Some(e) = debug_sections.get_type(*element_type_idx) { + print_type_brief(e, debug_sections); + } + match count { + Some(n) => print!("; {}]", n), + None => print!("]"), + } + } + DebugTypeInfo::Struct { name_idx, .. } => { + print!( + "struct {}", + debug_sections.get_type_string(*name_idx).unwrap_or_else(|| "?".into()) + ); + } + DebugTypeInfo::Function { .. } => print!("fn(...)"), + DebugTypeInfo::Unknown => print!("?"), + } +} + +fn primitive_name(prim: DebugPrimitiveType) -> &'static str { + match prim { + DebugPrimitiveType::Void => "void", + DebugPrimitiveType::Bool => "bool", + DebugPrimitiveType::I8 => "i8", + DebugPrimitiveType::U8 => "u8", + DebugPrimitiveType::I16 => "i16", + DebugPrimitiveType::U16 => "u16", + DebugPrimitiveType::I32 => "i32", + DebugPrimitiveType::U32 => "u32", + DebugPrimitiveType::I64 => "i64", + DebugPrimitiveType::U64 => "u64", + DebugPrimitiveType::I128 => "i128", + DebugPrimitiveType::U128 => "u128", + DebugPrimitiveType::F32 => "f32", + DebugPrimitiveType::F64 => "f64", + DebugPrimitiveType::Felt => "felt", + DebugPrimitiveType::Word => "word", + } +} + +fn print_files(debug_sections: &DebugSections, raw: bool) { + println!(".debug_files contents:"); + println!("{:-<80}", ""); + for (idx, file) in debug_sections.sources.files.iter().enumerate() { + print_file(idx, file, debug_sections, raw); + } +} + +fn print_file(idx: usize, file: &DebugFileInfo, debug_sections: &DebugSections, raw: bool) { + let path = if raw { + format!("str[{}]", file.path_idx) + } else { + debug_sections + .get_source_string(file.path_idx) + .unwrap_or_else(|| "".into()) + }; + + print!(" [{:4}] {}", idx, path); + + if let Some(checksum) = &file.checksum { + print!(" [checksum: "); + for byte in &checksum[..4] { + print!("{:02x}", byte); + } + print!("...]"); + } + + println!(); +} + +fn print_functions(debug_sections: &DebugSections, raw: bool, verbose: bool) { + println!(".debug_functions contents:"); + println!("{:-<80}", ""); + for (idx, func) in debug_sections.functions.functions.iter().enumerate() { + print_function(idx, func, debug_sections, raw, verbose); + println!(); + } +} + +fn print_function( + idx: usize, + func: &DebugFunctionInfo, + debug_sections: &DebugSections, + raw: bool, + verbose: bool, +) { + let name = if raw { + format!("str[{}]", func.name_idx) + } else { + debug_sections + .get_func_string(func.name_idx) + .unwrap_or_else(|| "".into()) + }; + + println!(" [{:4}] FUNCTION: {}", idx, name); + + // Linkage name + if let Some(linkage_idx) = func.linkage_name_idx { + let linkage = if raw { + format!("str[{}]", linkage_idx) + } else { + debug_sections + .get_func_string(linkage_idx) + .unwrap_or_else(|| "".into()) + }; + println!(" Linkage name: {}", linkage); + } + + // Location + let file_path = if raw { + format!("file[{}]", func.file_idx) + } else { + debug_sections + .get_file(func.file_idx) + .and_then(|f| debug_sections.get_source_string(f.path_idx)) + .unwrap_or_else(|| "".into()) + }; + println!(" Location: {}:{}:{}", file_path, func.line, func.column); + + // Type + if let Some(type_idx) = func.type_idx { + print!(" Type: "); + if raw { + println!("type[{}]", type_idx.as_u32()); + } else if let Some(ty) = debug_sections.get_type(type_idx) { + print_type_brief(ty, debug_sections); + println!(); + } else { + println!(""); + } + } + + // MAST root + if let Some(root) = &func.mast_root { + print!(" MAST root: 0x"); + for byte in &root.as_bytes() { + print!("{:02x}", byte); + } + println!(); + } + + // Variables + if !func.variables.is_empty() { + println!(" Variables ({}):", func.variables.len()); + for var in &func.variables { + print_variable(var, debug_sections, raw, verbose); + } + } + + // Inlined calls + if !func.inlined_calls.is_empty() && verbose { + println!(" Inlined calls ({}):", func.inlined_calls.len()); + for call in &func.inlined_calls { + let callee = if raw { + format!("func[{}]", call.callee_idx) + } else { + debug_sections + .functions + .functions + .get(call.callee_idx as usize) + .and_then(|f| debug_sections.get_func_string(f.name_idx)) + .unwrap_or_else(|| "".into()) + }; + let call_file = if raw { + format!("file[{}]", call.file_idx) + } else { + debug_sections + .get_file(call.file_idx) + .and_then(|f| debug_sections.get_source_string(f.path_idx)) + .unwrap_or_else(|| "".into()) + }; + println!( + " - {} inlined at {}:{}:{}", + callee, call_file, call.line, call.column + ); + } + } +} + +fn print_variable( + var: &DebugVariableInfo, + debug_sections: &DebugSections, + raw: bool, + _verbose: bool, +) { + let name = if raw { + format!("str[{}]", var.name_idx) + } else { + debug_sections + .get_func_string(var.name_idx) + .unwrap_or_else(|| "".into()) + }; + + let kind = if var.is_parameter() { + format!("param #{}", var.arg_index) + } else { + "local".to_string() + }; + + print!(" - {} ({}): ", name, kind); + + if raw { + print!("type[{}]", var.type_idx.as_u32()); + } else if let Some(ty) = debug_sections.get_type(var.type_idx) { + print_type_brief(ty, debug_sections); + } else { + print!(""); + } + + print!(" @ {}:{}", var.line, var.column); + + if var.scope_depth > 0 { + print!(" [scope depth: {}]", var.scope_depth); + } + + println!(); +} + +fn print_variables(debug_sections: &DebugSections, raw: bool) { + println!(".debug_variables contents (all functions):"); + println!("{:-<80}", ""); + + for func in &debug_sections.functions.functions { + if func.variables.is_empty() { + continue; + } + + let func_name = debug_sections + .get_func_string(func.name_idx) + .unwrap_or_else(|| "".into()); + println!(" Function: {}", func_name); + + for var in &func.variables { + print_variable(var, debug_sections, raw, false); + } + println!(); + } +} + +/// Prints the .debug_loc section - variable location entries from MAST +/// +/// This is analogous to DWARF's .debug_loc section which contains location +/// lists describing where a variable's value can be found at runtime. +fn print_locations(mast_forest: &MastForest, debug_sections: &DebugSections, verbose: bool) { + println!(".debug_loc contents (DebugVar entries from MAST):"); + println!("{:-<80}", ""); + + // Collect all debug vars from the MastForest + let debug_vars = mast_forest.debug_info().debug_vars(); + + if debug_vars.is_empty() { + println!(" (no DebugVar entries found)"); + return; + } + + // Group by variable name for a cleaner view + let mut by_name: BTreeMap<&str, Vec<(usize, &DebugVarInfo)>> = BTreeMap::new(); + for (idx, info) in debug_vars.iter().enumerate() { + by_name.entry(info.name()).or_default().push((idx, info)); + } + + println!(" Total DebugVar entries: {}", debug_vars.len()); + println!(" Unique variable names: {}", by_name.len()); + println!(); + + for (name, entries) in &by_name { + println!(" Variable: \"{}\"", name); + println!(" {} location entries:", entries.len()); + + for (var_idx, info) in entries { + print!(" [var#{}] ", var_idx); + + // Print value location + print!("{}", format_debug_var_location(info.value_location())); + + // Print argument info if present + if let Some(arg_idx) = info.arg_index() { + print!(" (param #{})", arg_idx); + } + + // Print type info if present and we can resolve it + if let Some(type_id) = info.type_id() { + let type_idx = DebugTypeIdx::from(type_id); + if let Some(ty) = debug_sections.get_type(type_idx) { + print!(" : "); + print_type_brief(ty, debug_sections); + } else { + print!(" : type[{}]", type_id); + } + } + + // Print source location if present + if let Some(loc) = info.location() { + print!(" @ {}:{}:{}", loc.uri, loc.line, loc.column); + } + + println!(); + } + println!(); + } + + // In verbose mode, also show raw list + if verbose { + println!(" Raw debug var list (in order):"); + println!(" {:-<76}", ""); + for (idx, info) in debug_vars.iter().enumerate() { + println!(" [{:4}] {}", idx, info); + } + } +}