Skip to content

Add IR printing functionality to the PassManager #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 15, 2025

Conversation

igaray
Copy link

@igaray igaray commented May 1, 2025

Usage

This PR implements the IR printing functionality, which can enabled via the use of flags passed to the compiler. The flags it introduces are:
-Z print-ir-after-all, -Z print-ir-after-pass and -Z print-ir-after-modified.

  • print-ir-after-all: This enables IR printing for every pass.
  • print-ir-after-pass: This enables IR printing for a selected number passes.
    • Those passes have to be passed as a comma separated string after an equal sign, like so:
    • -Z print-ir-after-pass=canonicalizer,lift-control-flow
  • print-if-after-modified: This will only allow a pass to print the IR if it modifies the IR it received as input.

Here's an example output of the usage of these flags, these CLI examples are using the fib.rs available in the Miden book. Do note that the output here is excluding all the non-Print output, in reality the compiler is way more verbose.

Print after every pass

print-ir-after-all will enable IR printing after every compiler pass.

Click here to see the CLI command
midenc compile  --emit masm=wasm_fib.masm,masp fib.wasm -Z print-ir-after-all


[TRACE midenc_hir::pass] Before the Pipeline Collection: [builtin.component] pass
[TRACE printer] builtin.world  {
      builtin.component root_ns:[email protected] {
          builtin.module public @wasm_fib {
              public builtin.function @fib(v0: i32) -> i32 {
              ^block4(v0: i32):
                  v2 = arith.constant 0 : i32;
                  v3 = arith.constant 0 : i32;
                  v4 = arith.constant 1 : i32;
                  cf.br ^block6(v4, v0, v3);
              ^block5(v1: i32):
  
              ^block6(v6: i32, v7: i32, v10: i32):
                  v8 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v8 : i1;
                  cf.cond_br v9 ^block8, ^block9;
              ^block7(v5: i32):
  
              ^block8:
                  v11 = arith.constant -1 : i32;
                  v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                  v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                  cf.br ^block6(v13, v12, v6);
              ^block9:
                  builtin.ret v10;
              };
  
              builtin.global_variable private @#__stack_pointer : i32 {
                  builtin.ret_imm 1048576;
              };
          };
      };
  };
[TRACE midenc_hir::pass] Before the Pipeline Collection: [builtin.module] pass
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v2 = arith.constant 0 : i32;
              v3 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              cf.br ^block6(v4, v0, v3);
          ^block5(v1: i32):
  
          ^block6(v6: i32, v7: i32, v10: i32):
              v8 = arith.constant 0 : i32;
              v9 = arith.neq v7, v8 : i1;
              cf.cond_br v9 ^block8, ^block9;
          ^block7(v5: i32):
  
          ^block8:
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              cf.br ^block6(v13, v12, v6);
          ^block9:
              builtin.ret v10;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] Before the Pipeline Collection: [builtin.function] pass
[TRACE printer] builtin.module public @wasm_fib {
      public builtin.function @fib(v0: i32) -> i32 {
      ^block4(v0: i32):
          v2 = arith.constant 0 : i32;
          v3 = arith.constant 0 : i32;
          v4 = arith.constant 1 : i32;
          cf.br ^block6(v4, v0, v3);
      ^block5(v1: i32):
  
      ^block6(v6: i32, v7: i32, v10: i32):
          v8 = arith.constant 0 : i32;
          v9 = arith.neq v7, v8 : i1;
          cf.cond_br v9 ^block8, ^block9;
      ^block7(v5: i32):
  
      ^block8:
          v11 = arith.constant -1 : i32;
          v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
          v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
          cf.br ^block6(v13, v12, v6);
      ^block9:
          builtin.ret v10;
      };
  
      builtin.global_variable private @#__stack_pointer : i32 {
          builtin.ret_imm 1048576;
      };
  };
[TRACE midenc_hir::pass] Before the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v2 = arith.constant 0 : i32;
      v3 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v3);
  ^block5(v1: i32):
  
  ^block6(v6: i32, v7: i32, v10: i32):
      v8 = arith.constant 0 : i32;
      v9 = arith.neq v7, v8 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block7(v5: i32):
  
  ^block8:
      v11 = arith.constant -1 : i32;
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] Before the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the sink-operand-defs pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the sink-operand-defs pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the control-flow-sink pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the control-flow-sink pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the transform-spills pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the transform-spills pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the Pipeline Collection: [builtin.function] pass
[TRACE printer] builtin.module public @wasm_fib {
      public builtin.function @fib(v0: i32) -> i32 {
      ^block4(v0: i32):
          v22 = ub.poison i32 : i32;
          v2 = arith.constant 0 : i32;
          v4 = arith.constant 1 : i32;
          v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
          ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
              v53 = arith.constant 0 : i32;
              v9 = arith.neq v7, v53 : i1;
              v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
              ^block8:
                  v21 = arith.constant 1 : u32;
                  v15 = arith.constant 0 : u32;
                  v11 = arith.constant -1 : i32;
                  v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                  v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                  scf.yield v13, v12, v6, v15, v21;
              } else {
              ^block16:
                  v50 = arith.constant 0 : u32;
                  v51 = arith.constant 1 : u32;
                  v52 = ub.poison i32 : i32;
                  scf.yield v52, v52, v52, v51, v50;
              };
              v44 = arith.trunc v49 : i1;
              scf.condition v44, v45, v46, v47, v10;
          } do {
          ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
              scf.yield v40, v41, v42, v43;
          };
          builtin.ret v35;
      };
  
      builtin.global_variable private @#__stack_pointer : i32 {
          builtin.ret_imm 1048576;
      };
  };
[TRACE midenc_hir::pass] After the Pipeline Collection: [builtin.module] pass
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v22 = ub.poison i32 : i32;
              v2 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
              ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
                  v53 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v53 : i1;
                  v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
                  ^block8:
                      v21 = arith.constant 1 : u32;
                      v15 = arith.constant 0 : u32;
                      v11 = arith.constant -1 : i32;
                      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                      scf.yield v13, v12, v6, v15, v21;
                  } else {
                  ^block16:
                      v50 = arith.constant 0 : u32;
                      v51 = arith.constant 1 : u32;
                      v52 = ub.poison i32 : i32;
                      scf.yield v52, v52, v52, v51, v50;
                  };
                  v44 = arith.trunc v49 : i1;
                  scf.condition v44, v45, v46, v47, v10;
              } do {
              ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
                  scf.yield v40, v41, v42, v43;
              };
              builtin.ret v35;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] Before the Pipeline Collection: [builtin.function] pass
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v22 = ub.poison i32 : i32;
              v2 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
              ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
                  v53 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v53 : i1;
                  v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
                  ^block8:
                      v21 = arith.constant 1 : u32;
                      v15 = arith.constant 0 : u32;
                      v11 = arith.constant -1 : i32;
                      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                      scf.yield v13, v12, v6, v15, v21;
                  } else {
                  ^block16:
                      v50 = arith.constant 0 : u32;
                      v51 = arith.constant 1 : u32;
                      v52 = ub.poison i32 : i32;
                      scf.yield v52, v52, v52, v51, v50;
                  };
                  v44 = arith.trunc v49 : i1;
                  scf.condition v44, v45, v46, v47, v10;
              } do {
              ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
                  scf.yield v40, v41, v42, v43;
              };
              builtin.ret v35;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] After the Pipeline Collection: [builtin.function] pass
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v22 = ub.poison i32 : i32;
              v2 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
              ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
                  v53 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v53 : i1;
                  v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
                  ^block8:
                      v21 = arith.constant 1 : u32;
                      v15 = arith.constant 0 : u32;
                      v11 = arith.constant -1 : i32;
                      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                      scf.yield v13, v12, v6, v15, v21;
                  } else {
                  ^block16:
                      v50 = arith.constant 0 : u32;
                      v51 = arith.constant 1 : u32;
                      v52 = ub.poison i32 : i32;
                      scf.yield v52, v52, v52, v51, v50;
                  };
                  v44 = arith.trunc v49 : i1;
                  scf.condition v44, v45, v46, v47, v10;
              } do {
              ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
                  scf.yield v40, v41, v42, v43;
              };
              builtin.ret v35;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] After the Pipeline Collection: [builtin.component] pass
[TRACE printer] builtin.world  {
      builtin.component root_ns:[email protected] {
          builtin.module public @wasm_fib {
              public builtin.function @fib(v0: i32) -> i32 {
              ^block4(v0: i32):
                  v22 = ub.poison i32 : i32;
                  v2 = arith.constant 0 : i32;
                  v4 = arith.constant 1 : i32;
                  v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
                  ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
                      v53 = arith.constant 0 : i32;
                      v9 = arith.neq v7, v53 : i1;
                      v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
                      ^block8:
                          v21 = arith.constant 1 : u32;
                          v15 = arith.constant 0 : u32;
                          v11 = arith.constant -1 : i32;
                          v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                          v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                          scf.yield v13, v12, v6, v15, v21;
                      } else {
                      ^block16:
                          v50 = arith.constant 0 : u32;
                          v51 = arith.constant 1 : u32;
                          v52 = ub.poison i32 : i32;
                          scf.yield v52, v52, v52, v51, v50;
                      };
                      v44 = arith.trunc v49 : i1;
                      scf.condition v44, v45, v46, v47, v10;
                  } do {
                  ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
                      scf.yield v40, v41, v42, v43;
                  };
                  builtin.ret v35;
              };
  
              builtin.global_variable private @#__stack_pointer : i32 {
                  builtin.ret_imm 1048576;
              };
          };
      };
  };
This output, by itself, can be *quite* verbose, since every pass will print the IR *even if* no modification has been made.

In order to reduce noise, the -Z print-ir-after-modified can be used:

Click here to see the CLI command
midenc compile  --emit masm=wasm_fib.masm,masp fib.wasm -Z print-ir-after-all -Z print-ir-after-modified

[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.world  {
      builtin.component root_ns:[email protected] {
          builtin.module public @wasm_fib {
              public builtin.function @fib(v0: i32) -> i32 {
              ^block4(v0: i32):
                  v2 = arith.constant 0 : i32;
                  v3 = arith.constant 0 : i32;
                  v4 = arith.constant 1 : i32;
                  cf.br ^block6(v4, v0, v3);
              ^block5(v1: i32):
  
              ^block6(v6: i32, v7: i32, v10: i32):
                  v8 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v8 : i1;
                  cf.cond_br v9 ^block8, ^block9;
              ^block7(v5: i32):
  
              ^block8:
                  v11 = arith.constant -1 : i32;
                  v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                  v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                  cf.br ^block6(v13, v12, v6);
              ^block9:
                  builtin.ret v10;
              };
  
              builtin.global_variable private @#__stack_pointer : i32 {
                  builtin.ret_imm 1048576;
              };
          };
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v2 = arith.constant 0 : i32;
              v3 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              cf.br ^block6(v4, v0, v3);
          ^block5(v1: i32):
  
          ^block6(v6: i32, v7: i32, v10: i32):
              v8 = arith.constant 0 : i32;
              v9 = arith.neq v7, v8 : i1;
              cf.cond_br v9 ^block8, ^block9;
          ^block7(v5: i32):
  
          ^block8:
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              cf.br ^block6(v13, v12, v6);
          ^block9:
              builtin.ret v10;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.module public @wasm_fib {
      public builtin.function @fib(v0: i32) -> i32 {
      ^block4(v0: i32):
          v2 = arith.constant 0 : i32;
          v3 = arith.constant 0 : i32;
          v4 = arith.constant 1 : i32;
          cf.br ^block6(v4, v0, v3);
      ^block5(v1: i32):
  
      ^block6(v6: i32, v7: i32, v10: i32):
          v8 = arith.constant 0 : i32;
          v9 = arith.neq v7, v8 : i1;
          cf.cond_br v9 ^block8, ^block9;
      ^block7(v5: i32):
  
      ^block8:
          v11 = arith.constant -1 : i32;
          v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
          v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
          cf.br ^block6(v13, v12, v6);
      ^block9:
          builtin.ret v10;
      };
  
      builtin.global_variable private @#__stack_pointer : i32 {
          builtin.ret_imm 1048576;
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v2 = arith.constant 0 : i32;
      v3 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v3);
  ^block5(v1: i32):
  
  ^block6(v6: i32, v7: i32, v10: i32):
      v8 = arith.constant 0 : i32;
      v9 = arith.neq v7, v8 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block7(v5: i32):
  
  ^block8:
      v11 = arith.constant -1 : i32;
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the sink-operand-defs pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };

Now, only passes that modify the IR will actually print. In the case of fib.rs those pases are: canonicalizer, lift-control-flow and sink-operand-defs.

Print after a subset of passes

And let's say that one is only interested in a subset of passes. For this, the print-ir-after-pass is added.
The following will only print the IR after passes: control-flow-sink, canonicalizer and lift-control-flow-pass.

Click here to see the CLI command
midenc compile  --emit masm=wasm_fib.masm,masp fib.wasm -Z print-ir-after-pass=canonicalizer, lift-control-flow-pass, control-flow-sink

[TRACE midenc_hir::pass] Before the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v2 = arith.constant 0 : i32;
      v3 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v3);
  ^block5(v1: i32):
  
  ^block6(v6: i32, v7: i32, v10: i32):
      v8 = arith.constant 0 : i32;
      v9 = arith.neq v7, v8 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block7(v5: i32):
  
  ^block8:
      v11 = arith.constant -1 : i32;
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] Before the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] Before the control-flow-sink pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };
[TRACE midenc_hir::pass] After the control-flow-sink pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v53 = arith.constant 0 : i32;
          v9 = arith.neq v7, v53 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v21 = arith.constant 1 : u32;
              v15 = arith.constant 0 : u32;
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              v50 = arith.constant 0 : u32;
              v51 = arith.constant 1 : u32;
              v52 = ub.poison i32 : i32;
              scf.yield v52, v52, v52, v51, v50;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };

Furthermore, this last flag can also be combined with the print-ir-after-modified flag. This combination will only print IR if one of the selected passes modifies the IR.

Click here to see the CLI command
midenc compile  --emit masm=wasm_fib.masm,masp fib.wasm -Z print-ir-after-pass=canonicalizer,lift-control-flow,control-flow-sink -Z print-ir-after-modified

[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.world  {
      builtin.component root_ns:[email protected] {
          builtin.module public @wasm_fib {
              public builtin.function @fib(v0: i32) -> i32 {
              ^block4(v0: i32):
                  v2 = arith.constant 0 : i32;
                  v3 = arith.constant 0 : i32;
                  v4 = arith.constant 1 : i32;
                  cf.br ^block6(v4, v0, v3);
              ^block5(v1: i32):
  
              ^block6(v6: i32, v7: i32, v10: i32):
                  v8 = arith.constant 0 : i32;
                  v9 = arith.neq v7, v8 : i1;
                  cf.cond_br v9 ^block8, ^block9;
              ^block7(v5: i32):
  
              ^block8:
                  v11 = arith.constant -1 : i32;
                  v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
                  v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
                  cf.br ^block6(v13, v12, v6);
              ^block9:
                  builtin.ret v10;
              };
  
              builtin.global_variable private @#__stack_pointer : i32 {
                  builtin.ret_imm 1048576;
              };
          };
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.component root_ns:[email protected] {
      builtin.module public @wasm_fib {
          public builtin.function @fib(v0: i32) -> i32 {
          ^block4(v0: i32):
              v2 = arith.constant 0 : i32;
              v3 = arith.constant 0 : i32;
              v4 = arith.constant 1 : i32;
              cf.br ^block6(v4, v0, v3);
          ^block5(v1: i32):
  
          ^block6(v6: i32, v7: i32, v10: i32):
              v8 = arith.constant 0 : i32;
              v9 = arith.neq v7, v8 : i1;
              cf.cond_br v9 ^block8, ^block9;
          ^block7(v5: i32):
  
          ^block8:
              v11 = arith.constant -1 : i32;
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              cf.br ^block6(v13, v12, v6);
          ^block9:
              builtin.ret v10;
          };
  
          builtin.global_variable private @#__stack_pointer : i32 {
              builtin.ret_imm 1048576;
          };
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] builtin.module public @wasm_fib {
      public builtin.function @fib(v0: i32) -> i32 {
      ^block4(v0: i32):
          v2 = arith.constant 0 : i32;
          v3 = arith.constant 0 : i32;
          v4 = arith.constant 1 : i32;
          cf.br ^block6(v4, v0, v3);
      ^block5(v1: i32):
  
      ^block6(v6: i32, v7: i32, v10: i32):
          v8 = arith.constant 0 : i32;
          v9 = arith.neq v7, v8 : i1;
          cf.cond_br v9 ^block8, ^block9;
      ^block7(v5: i32):
  
      ^block8:
          v11 = arith.constant -1 : i32;
          v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
          v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
          cf.br ^block6(v13, v12, v6);
      ^block9:
          builtin.ret v10;
      };
  
      builtin.global_variable private @#__stack_pointer : i32 {
          builtin.ret_imm 1048576;
      };
  };
[TRACE midenc_hir::pass] IR before the pass pipeline
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v2 = arith.constant 0 : i32;
      v3 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v3);
  ^block5(v1: i32):
  
  ^block6(v6: i32, v7: i32, v10: i32):
      v8 = arith.constant 0 : i32;
      v9 = arith.neq v7, v8 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block7(v5: i32):
  
  ^block8:
      v11 = arith.constant -1 : i32;
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the canonicalizer pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      cf.br ^block6(v4, v0, v2);
  ^block6(v6: i32, v7: i32, v10: i32):
      v9 = arith.neq v7, v2 : i1;
      cf.cond_br v9 ^block8, ^block9;
  ^block8:
      v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
      v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
      cf.br ^block6(v13, v12, v6);
  ^block9:
      builtin.ret v10;
  };
[TRACE midenc_hir::pass] After the lift-control-flow pass
[TRACE printer] public builtin.function @fib(v0: i32) -> i32 {
  ^block4(v0: i32):
      v22 = ub.poison i32 : i32;
      v21 = arith.constant 1 : u32;
      v15 = arith.constant 0 : u32;
      v11 = arith.constant -1 : i32;
      v2 = arith.constant 0 : i32;
      v4 = arith.constant 1 : i32;
      v32, v33, v34, v35 = scf.while v4, v0, v2, v22 : i32, i32, i32, i32 {
      ^block6(v6: i32, v7: i32, v10: i32, v27: i32):
          v9 = arith.neq v7, v2 : i1;
          v45, v46, v47, v48, v49 = scf.if v9 : i32, i32, i32, u32, u32 {
          ^block8:
              v12 = arith.add v7, v11 : i32 #[overflow = wrapping];
              v13 = arith.add v10, v6 : i32 #[overflow = wrapping];
              scf.yield v13, v12, v6, v15, v21;
          } else {
          ^block16:
              scf.yield v22, v22, v22, v21, v15;
          };
          v44 = arith.trunc v49 : i1;
          scf.condition v44, v45, v46, v47, v10;
      } do {
      ^block15(v40: i32, v41: i32, v42: i32, v43: i32):
          scf.yield v40, v41, v42, v43;
      };
      builtin.ret v35;
  };

Like we saw in the second example, control-flow-sink didn't modify the IR. So even though it is enabled, since -Z print-ir-after-modifed was passed, it does not print IR.

Implementation

For this PR, I used the already existing Print struct. Originally, it implemented the Pass trait, which enabled Print to be added after each Pass, just as if it were any other compiler pass. Whilst this approach was straightforward to use for debugging a specific pass, it was not usable via the CLI and also required modifying the code manually.

So, Print got its functionality moved from Pass to PassInstrumentation. PassInstrumentation was an already present trait that allowed arbitray functions to be run before and/or after any pass. However, it was not implemented by any struct yet (If I'm not mistaken). The Pass trait was actually removed from Print, since if left implemented, it could lead to cases where IR printing was triggered twice: Once for PassInstrumentation and another time for Pass.

With this trait now in place, Print's methods could be invoked arbitrarily after every passed. The one thing that the PassInstrumentation trait was missing was an OperationRef, which actually holds the Display trait implementation for each operation.

Another thing that the issue required was the option to only print the IR after passes which modified its structure. That logic came in the form of the PostPassStatus enum, which was added to PassExecutionState. Now, every Pass::run_on_operation updates said value via the PassExecutionState::set_post_pass_status function to indicate whether the Pass modified the IR or not.

To actually enable IR printing, one has to pass one of the flags described in the [#Usage] section. These will get stored in the UnstableOptions struct and will later get mapped to a Print struct in the new() method. If no Print struct is created, no print is enabled.

@bitwalker
Copy link
Contributor

Thanks! I'll get this reviewed ASAP

@lima-limon-inc lima-limon-inc force-pushed the print-ir-passmanager branch from 2ea0339 to 8c51c14 Compare May 5, 2025 19:04
&mut self,
pass: &dyn OperationPass,
op: &OperationRef,
changed: PostPassStatus,
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it might not be better to pass a reference to the PassExecutionState here, and store the PostPassStatus in the state instead. That will allow us to extend the data available to after-pass instrumentations in the future in a backwards-compatible way (i.e. no changes required here or any callers of this method).

Copy link
Contributor

Choose a reason for hiding this comment

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

Great point! I incorporated this change in this commit: 54d3965

As a note, I avoided passing &mut PassExecutionState to fn transform_spills in order to minimize the places where PassExecutionState can be mutated.

@@ -136,6 +143,51 @@ where
}
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PassIdentifier {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need PassIdentifier at all. We should be able to use the existing pass names as an identifier, and if we want faster comparisons, we can obtain a Symbol from the raw string instead (i.e. crate::interner::Symbol, not the trait), but I don't think the overhead of the string comparisons is enough to matter for this anyway (or if it is, we can deal with that when we start identifying hotspots in the compiler).

Looking at how this type is used, it isn't clear to me that there is any other motivating use aside from cheaper comparisons, but maybe as this PR evolved the original motivation went away while this type remained. What do you think about removing it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Great point, I removed the enum all together in this commit: 0646833.

The original intent behind the enum was to add type checking when comparing passes; but in retrospect, using the name() method ended up being simpler.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it, makes sense! I think for me the calculus boils down to two things:

  • The set of available passes is not necessarily known to us, i.e. another compiler using our IR to lower to Miden might have their own set of passes, and we'd want any pass-related infrastructure to be used with those passes as well.
  • In general, we want to avoid referencing stuff that exists in downstream crates from upstream crates in the dependency graph (e.g. in the enum as it was defined in midenc_hir, we were referencing passes that are defined in downstream crates, e.g. midenc_codegen_masm).

So if you find yourself wanting to define a trait/enum/whatever and it violates one of those guidelines, we should probably discuss it, since we either need to do additional refactoring, or there might be a more suitable alternative. Doesn't mean we can't violate one of those rules, but we probably should have a really good reason to do so.


#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PostPassStatus {
IRUnchanged,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd simplify this to Unchanged and Changed, since there are no other variants. If there is ambiguity with future changes, we can deal with that then, otherwise it reads a bit unnecessarily verbose IMO.

Copy link
Contributor

@lima-limon-inc lima-limon-inc May 9, 2025

Choose a reason for hiding this comment

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

I completely agree; updated the enum variants here: 828a097

Copy link
Contributor

Choose a reason for hiding this comment

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

After further testing, the enum did provide a very straight-forward way to validate user input, mainly in this function.
The enum + match allowed for a "compile time" way to check if the user passed an invalid pass. Now after being removed, invalid passes passed to -Z print-ir-after-pass are simply ignored.

For example:

midenc compile file.wasm -Z print-ir-after-pass=invalid

In order to validate user input, I pushed this commit: 247b1f7

Now, an error is raised:

midenc compile file.wasm -Z print-ir-after-pass=invalid
Error:   x 'invalid' unrecognized pass.

However, I am hesistant about this implementation; since if any passes are added in the future, they would have to be added manually to that array.

What do you think? Does this situation merit the use of the enum? Or is this implementation sufficient?

Copy link
Contributor

Choose a reason for hiding this comment

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

The set of possible passes is open, and isn't necessarily static even, so I think it is perfectly acceptable to simply ignore an invalid pass name.

However, if we did want to provide some feedback for an unintentional typo or something, it would likely need to be done by visiting all the nodes of the pass pipeline registered with the PassManager to see if a pass with a given name is registered or not; and if not, raise a warning diagnostic. I'm not sure if the necessary APIs exist to do such a check right now, but I believe we could add them pretty easily. That doesn't need to happen as part of this PR though IMO.

Copy link
Contributor

Choose a reason for hiding this comment

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

The set of possible passes is open, and isn't necessarily static even, so I think it is perfectly acceptable to simply ignore an invalid pass name.

However, if we did want to provide some feedback for an unintentional typo or something, it would likely need to be done by visiting all the nodes of the pass pipeline registered with the PassManager to see if a pass with a given name is registered or not; and if not, raise a warning diagnostic. I'm not sure if the necessary APIs exist to do such a check right now, but I believe we could add them pretty easily. That doesn't need to happen as part of this PR though IMO.

Got it, I did not take that into consideration. Thank you very much for the thorough explanation.

Copy link
Contributor

@bitwalker bitwalker left a comment

Choose a reason for hiding this comment

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

Looks great! I have a few nits/questions/tweaks I've left comments for, but overall this is definitely a useful addition for debugging passes in the compiler!

lima-limon-inc and others added 6 commits May 9, 2025 18:13
Now to compare pass equality, the `pass.name()` method is used.
Signed-off-by: Tomas Fabrizio Orsi <[email protected]>
… never constructred

Signed-off-by: Tomas Fabrizio Orsi <[email protected]>
@lima-limon-inc lima-limon-inc force-pushed the print-ir-passmanager branch from 469fcc2 to e3365c2 Compare May 12, 2025 14:51
type Error = Report;

fn try_from(options: &Options) -> Result<Self, Self::Error> {
let available_passes = [
Copy link
Contributor

Choose a reason for hiding this comment

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

I would remove this bit for now, as the set of passes is open - what dictates whether the pass name is "invalid" is whether or not such a pass has been registered with a specific PassManager instance (and more precisely, somewhere in the pipeline being managed by that manager).

So if we want to have such a check, it will need to occur when the PassManager is instantiated (or rather, when enable_ir_printing is called). Since the IR printing is done via the trace infrastructure, we can simply emit a warning diagnostic when a pass name is not registered, rather than raising an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Noted, I reverted this commit here: 71cad3a

I also updated the PR description with the latest changes.

Copy link
Contributor

@bitwalker bitwalker left a comment

Choose a reason for hiding this comment

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

Looks good! I'm marking this approved, but will wait to merge until the invalid pass name validation is removed/modified based on my last comment. Nice work!

@bitwalker bitwalker merged commit 4837727 into 0xMiden:next May 15, 2025
7 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