Skip to content

Conversation

@djolertrk
Copy link
Collaborator

@djolertrk djolertrk commented Dec 8, 2025

This demonstrates that simple "assert-based debugging" does not work.
The reason is that there is no source locations attached to ub.unreachable.

It can be confirmed by using following commands:

  # Build the example
  cargo build --release --target wasm32-unknown-unknown \
    --manifest-path examples/assert-debug-test/Cargo.toml

  # Check HIR with source locations  
  ./bin/midenc examples/assert-debug-test/target/wasm32-unknown-unknown/release/assert_debug_test.wasm \
    --entrypoint=assert_debug_test::test_assert \
    -Ztrim-path-prefix=examples/assert-debug-test \
    -Zprint-hir-source-locations \
    --debug full --emit=hir=-

And to run the integration test:

cargo test --release -p miden-integration-tests debug_source_locations -- --nocapture

This addresses #806.

@djolertrk djolertrk marked this pull request as draft December 8, 2025 22:46
@djolertrk
Copy link
Collaborator Author

Similar situation is when an assert_eq is being used, since in that case an intrinsic is being used that also has a default/unknown source location attached, a solution for that case can be inlining of the call to intrinsic itself, so we can find a reasonable span for it,.

@djolertrk
Copy link
Collaborator Author

cc @greenhat @bitwalker

@greenhat
Copy link
Contributor

greenhat commented Dec 10, 2025

This demonstrates that simple "assert-based debugging" does not work. The reason is that there is no source locations attached to ub.unreachable.

Interesting. I see we set the passed span for the unreachable at

builder.unreachable(span);

I wonder how it ends up being lost. Or do we pass an empty span here? Any ideas?

EDIT: Oh, I'm just slow. #812 (comment) fixes it, right?

@djolertrk
Copy link
Collaborator Author

I double checked now, without changes from #812 (comment) (so basically with this branch only), we have this situation:

        public builtin.function @test_assert(v1: i32) -> i32 {
        ^block8(v1: i32):
            v3 = arith.constant 100 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":23:1);
            v4 = hir.bitcast v1 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v5 = hir.bitcast v3 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v6 = arith.gt v4, v5 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v7 = arith.zext v6 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v8 = hir.bitcast v7 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v9 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            v10 = arith.neq v8, v9 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
            cf.cond_br v10 ^block10, ^block11 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:13);
        ^block9(v2: i32):
            builtin.ret v2 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":27:1);
        ^block10:
            cf.br ^block9(v1) #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":27:1);
        ^block11:
            v11 = arith.constant 1048576 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:5);
            v12 = arith.constant 25 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:5);
            v13 = arith.constant 1048612 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:5);
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v11, v12, v13)
            ub.unreachable  #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":24:5);
        };
        
         public builtin.function @test_multiple_asserts(v14: i32, v15: i32) -> i32 {
        ^block12(v14: i32, v15: i32):
            v17 = arith.constant 0 : i32;
            v18 = arith.eq v14, v17 : i1;
            v19 = arith.zext v18 : u32;
            v20 = hir.bitcast v19 : i32;
            v21 = arith.constant 0 : i32;
            v22 = arith.neq v20, v21 : i1;
            cf.cond_br v22 ^block16, ^block17;
        ^block13(v16: i32):

        ^block14:
            v41 = arith.constant 1048708 : i32;
            v42 = arith.constant 24 : i32;
            v43 = arith.constant 1048732 : i32;
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v41, v42, v43)
            ub.unreachable ;
        ^block15:
            v38 = arith.constant 1048668 : i32;
            v39 = arith.constant 23 : i32;
            v40 = arith.constant 1048692 : i32;
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v38, v39, v40)
            ub.unreachable ;
        ^block16:
            v35 = arith.constant 1048628 : i32;
            v36 = arith.constant 23 : i32;
            v37 = arith.constant 1048652 : i32;
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v35, v36, v37)
            ub.unreachable ;
        ^block17:
            v23 = arith.constant 0 : i32;
            v24 = arith.eq v15, v23 : i1;
            v25 = arith.zext v24 : u32;
            v26 = hir.bitcast v25 : i32;
            v27 = arith.constant 0 : i32;
            v28 = arith.neq v26, v27 : i1;
            cf.cond_br v28 ^block15, ^block18;
        ^block18:
            v29 = arith.eq v14, v15 : i1;
            v30 = arith.zext v29 : u32;
            v31 = hir.bitcast v30 : i32;
            v32 = arith.constant 0 : i32;
            v33 = arith.neq v31, v32 : i1;
            cf.cond_br v33 ^block14, ^block19;
        ^block19:
            v34 = arith.add v15, v14 : i32 #[overflow = wrapping];
            builtin.ret v34;
        };

As you can see test_assert has valid source location attachments, but test_multiple_asserts has no location at all -- all of them are UNKNOWN.
However, I was able to find the root cause of the issue -- the problem is caused by the comments in the rust example (https://github.com/walnuthq/compiler/blob/pr/assert-debugging/examples/assert-debug-test/src/lib.rs#L4)... The compute_line_starts() incorrectly parses those (in a C-like style), causing source manager to incorrectly compute source locations, and that is why we ended up with no locations at all in the test_multiple_asserts.

Here is the fix 0xMiden/miden-vm#2449. So, only with this fix (we can completely forget about #812 for now), we see:

        public builtin.function @test_multiple_asserts(v14: i32, v15: i32) -> i32 {
        ^block12(v14: i32, v15: i32):
            v17 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            v18 = arith.eq v14, v17 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            v19 = arith.zext v18 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            v20 = hir.bitcast v19 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            v21 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            v22 = arith.neq v20, v21 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
            cf.cond_br v22 ^block16, ^block17 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:13);
        ^block13(v16: i32):

        ^block14:
            v41 = arith.constant 1048708 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:5);
            v42 = arith.constant 24 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:5);
            v43 = arith.constant 1048732 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:5);
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v41, v42, v43)
            ub.unreachable  #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:5);
        ^block15:
            v38 = arith.constant 1048668 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:5);
            v39 = arith.constant 23 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:5);
            v40 = arith.constant 1048692 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:5);
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v38, v39, v40)
            ub.unreachable  #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:5);
        ^block16:
            v35 = arith.constant 1048628 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:5);
            v36 = arith.constant 23 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:5);
            v37 = arith.constant 1048652 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:5);
            hir.exec @root_ns:[email protected]/assert_debug_test/_ZN4core9panicking5panic17h4cab4a0cdc8b5cb0E(v35, v36, v37)
            ub.unreachable  #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":30:5);
        ^block17:
            v23 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            v24 = arith.eq v15, v23 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            v25 = arith.zext v24 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            v26 = hir.bitcast v25 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            v27 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            v28 = arith.neq v26, v27 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
            cf.cond_br v28 ^block15, ^block18 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":32:13);
        ^block18:
            v29 = arith.eq v14, v15 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
            v30 = arith.zext v29 : u32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
            v31 = hir.bitcast v30 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
            v32 = arith.constant 0 : i32 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
            v33 = arith.neq v31, v32 : i1 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
            cf.cond_br v33 ^block14, ^block19 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":34:13);
        ^block19:
            v34 = arith.add v15, v14 : i32 #[overflow = wrapping] #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":36:5);
            builtin.ret v34 #loc("/path/to/compiler/examples/assert-debug-test/src/lib.rs":37:2);
        };

But, for the test I added here, the miden-debug still prints:

Stack Trace:
 |-> root_ns:[email protected]::init in <unavailable>
 |-> root_ns:[email protected]::assert_debug_test::test_assert in <unavailable>
 |-> root_ns:[email protected]::assert_debug_test::core::panicking::panic in <unavailable>
 `-> root_ns:[email protected]::assert_debug_test::core::panicking::panic_fmt in <unavailable>:

Last 3 Instructions (of current frame):
 |   drop
 |   drop
 |   pad
 `-> <error occured here>


Last 5 Instructions (any frame):
 |   u32add
 |   drop
 |   noop
 |   drop
 |   pad
 `-> <error occured here>

@greenhat
Copy link
Contributor

So we have source location for the panic function call but no source code for the unreachable op inside it. I wonder if miden-debug could go up the stack and find the first source span. But it would not help when we're running in the VM through the miden-client. So whatever solution we find needs to be implemented in the VM.
@bitwalker thoughts?

@greenhat
Copy link
Contributor

@djolertrk good catch! Great job!

@greenhat
Copy link
Contributor

I think we should also print the source info in the call stack trace for each frame where it is known. @bitwalker Do we have a call stack trace or plans for it in the fast processor VM?

@djolertrk djolertrk marked this pull request as ready for review December 15, 2025 16:20
@djolertrk djolertrk force-pushed the pr/assert-debugging branch 3 times, most recently from 42033a4 to fd57be2 Compare December 15, 2025 16:41
Copy link
Contributor

@greenhat greenhat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Thank you! Please check my comments.

&[
"--debug",
"full",
&format!("-Ztrim-path-prefix={example_path_str}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make cargo-miden use the -Ztrim-path-prefix option. I mean the test should call cargo_miden::run rather than use cargo with midenc.

The cargo-miden is the advised way of compiling the Miden package. We're not advertising the cargo -> midenc workflow that much, leaving it for people who want to tinker with the compiler. So cargo-miden should produce a Miden package with proper source location info.

.unwrap()
.parent()
.unwrap()
.join("examples")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

examples folder is for user-facing examples, ones that might be interesting to the users. For internal test fixtures like this one the tests/rust-apps-wasm/rust-sdk/ is the best fit.

@djolertrk djolertrk force-pushed the pr/assert-debugging branch 2 times, most recently from b125e5b to 66b0426 Compare December 16, 2025 11:50
@bitwalker
Copy link
Collaborator

I think we should also print the source info in the call stack trace for each frame where it is known. @bitwalker Do we have a call stack trace or plans for it in the fast processor VM?

We have all the necessary information available, but we only display sources for a frame if explicitly requested in the debugger today. In the TUI that's ideal, but we may want to introduce functionality in the executor itself that lets us print the source snippet for each frame when printing an exception in our test suite (as an example).

@djolertrk djolertrk force-pushed the pr/assert-debugging branch 2 times, most recently from b31bbd4 to ad91208 Compare December 22, 2025 10:02
@greenhat greenhat self-requested a review December 22, 2025 14:03
Copy link
Contributor

@greenhat greenhat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Only cargo-miden fix left.

[
"--debug".to_string(),
"full".to_string(),
format!("-Ztrim-path-prefix={fixture_path_str}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #810 (comment) I meant that cargo-miden should calculate and pass -Ztrim-path-prefix under the hood without asking the user. So that user runs cargo miden build --debug full and gets the Miden package with correct source locations.

Copy link
Collaborator Author

@djolertrk djolertrk Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, thanks! It should be addressed in the latest commit.

Copy link
Contributor

@greenhat greenhat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Thank you! One last thing, please rebase it on the latest ⁣next branch.

@greenhat
Copy link
Contributor

@djolertrk Oh, I cannot merge it due to missing commit signatures. Please sign the commit.

@greenhat greenhat merged commit a28d27e into 0xMiden:next Dec 29, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants