From cb726570d4bca9689ef96663c3ef306a7b09f97f Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Fri, 22 May 2026 22:00:27 +0200 Subject: [PATCH 1/3] egraph: peer through ireduce in brif (ctz/clz) Extends the simplify_skeleton fold from #13343 to peer through an intervening `ireduce`. The wasm pattern `i64.ctz; i32.wrap_i64; br_if` (and the `clz` analog) lowers to clif as `brif (ireduce (ctz X))`, which the existing 2-op rules didn't catch. `ireduce` is harmless on a ctz/clz result because the count is in [0, bitwidth] - always fits in the narrower type without loss. The new rules rewrite to the same bit-extract condition as the bare-form rules, using the wider source type for the band/sge. Source pattern: motoko/moc's EOP peephole emits exactly this shape for `(value & 1) == 0` as a branch condition. Test coverage: - cranelift filetest: brif_ireduce_ctz_i64 / brif_ireduce_clz_i64 - tests/disas: \$if_ctz_bare_i64 reblessed (testq \$1,%rdx; je) + new \$if_clz_bare_i64 (testq %rdx,%rdx; jge) --- cranelift/codegen/src/opts/icmp.isle | 13 ++++ .../filetests/egraph/brif-cnt-cond.clif | 68 +++++++++++++++++++ tests/disas/ctz-clz-bool-condition.wat | 41 +++++++---- 3 files changed, 109 insertions(+), 13 deletions(-) diff --git a/cranelift/codegen/src/opts/icmp.isle b/cranelift/codegen/src/opts/icmp.isle index e2d128b2dcb0..160c7e0075a3 100644 --- a/cranelift/codegen/src/opts/icmp.isle +++ b/cranelift/codegen/src/opts/icmp.isle @@ -465,3 +465,16 @@ ;; `brif (clz X) bt be` branches when `clz(X) != 0`, i.e. MSB(X) == 0. (rule (simplify_skeleton (brif (clz x_ty X) _ _)) (replace_branch_cond (sge $I8 X (iconst_u x_ty 0)))) + +;; Peer through `ireduce` when its operand is a `ctz`/`clz`. The wasm +;; shape is `i64.ctz; i32.wrap_i64; br_if` (motoko/moc's EOP peephole +;; emits this, and the same applies to clz). `ireduce` is harmless on a +;; ctz/clz result because the value is in [0, bitwidth] — always fits in +;; the smaller type without loss. We rewrite to the same bit-extract +;; condition as the bare-form rules above, using the wider source type. +(rule (simplify_skeleton (brif (ireduce _ (ctz x_ty X)) _ _)) + (replace_branch_cond + (eq $I8 (band x_ty X (iconst_u x_ty 1)) (iconst_u x_ty 0)))) + +(rule (simplify_skeleton (brif (ireduce _ (clz x_ty X)) _ _)) + (replace_branch_cond (sge $I8 X (iconst_u x_ty 0)))) diff --git a/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif b/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif index c2fc4b11ce5c..ffb0b242e947 100644 --- a/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif +++ b/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif @@ -130,3 +130,71 @@ block2: ; v3 = iconst.i32 200 ; return v3 ; v3 = 200 ; } + +;; `brif (ireduce (ctz X))` — 3-op form with an `i32.wrap_i64`-style +;; truncation between ctz and brif. The truncation is harmless because +;; ctz's output is in [0, 64]. Source pattern: motoko/moc's EOP LSB→ctz +;; peephole emits `i64.ctz; i32.wrap_i64; br_if` for the +;; `(value & 1) == 0`-as-branch-condition shape. +function %brif_ireduce_ctz_i64(i64) -> i32 { +block0(v0: i64): + v1 = ctz v0 + v2 = ireduce.i32 v1 + brif v2, block1, block2 + +block1: + v3 = iconst.i32 100 + return v3 + +block2: + v4 = iconst.i32 200 + return v4 +} + +; function %brif_ireduce_ctz_i64(i64) -> i32 fast { +; block0(v0: i64): +; v5 = iconst.i64 1 +; v6 = band v0, v5 ; v5 = 1 +; v7 = iconst.i64 0 +; v8 = icmp eq v6, v7 ; v7 = 0 +; brif v8, block1, block2 +; +; block1: +; v3 = iconst.i32 100 +; return v3 ; v3 = 100 +; +; block2: +; v4 = iconst.i32 200 +; return v4 ; v4 = 200 +; } + +;; Same shape with `clz` instead of `ctz`. +function %brif_ireduce_clz_i64(i64) -> i32 { +block0(v0: i64): + v1 = clz v0 + v2 = ireduce.i32 v1 + brif v2, block1, block2 + +block1: + v3 = iconst.i32 100 + return v3 + +block2: + v4 = iconst.i32 200 + return v4 +} + +; function %brif_ireduce_clz_i64(i64) -> i32 fast { +; block0(v0: i64): +; v5 = iconst.i64 0 +; v6 = icmp sge v0, v5 ; v5 = 0 +; brif v6, block1, block2 +; +; block1: +; v3 = iconst.i32 100 +; return v3 ; v3 = 100 +; +; block2: +; v4 = iconst.i32 200 +; return v4 ; v4 = 200 +; } diff --git a/tests/disas/ctz-clz-bool-condition.wat b/tests/disas/ctz-clz-bool-condition.wat index 78b79355b86d..b39feeddacab 100644 --- a/tests/disas/ctz-clz-bool-condition.wat +++ b/tests/disas/ctz-clz-bool-condition.wat @@ -73,6 +73,12 @@ (func $if_clz_ne0_i64 (param i64) (result i32) (i64.ne (i64.clz (local.get 0)) (i64.const 0)) if (result i32) i32.const 100 else i32.const 200 end) + ;; Wasm-natural shape mirroring `$if_ctz_bare_i64`: `i64.clz` produces + ;; i64, narrowed via `i32.wrap_i64` before `if`. Same `ireduce` peel as + ;; the ctz form; collapses to a sign-bit test on the wider input. + (func $if_clz_bare_i64 (param i64) (result i32) + (i64.clz (local.get 0)) i32.wrap_i64 + if (result i32) i32.const 100 else i32.const 200 end) ;; ----- negative test: numeric comparison must NOT collapse ------------ ;; `ctz(x) == 4` is an arithmetic test on the count, not a boolean @@ -164,14 +170,11 @@ ;; wasm[0]::function[7]::if_ctz_bare_i64: ;; pushq %rbp ;; movq %rsp, %rbp -;; movl $0x40, %esi -;; bsfq %rdx, %r9 -;; cmoveq %rsi, %r9 -;; testl %r9d, %r9d -;; jne 0x1a4 -;; 19a: movl $0xc8, %eax -;; jmp 0x1a9 -;; 1a4: movl $0x64, %eax +;; testq $1, %rdx +;; je 0x19b +;; 191: movl $0xc8, %eax +;; jmp 0x1a0 +;; 19b: movl $0x64, %eax ;; movq %rbp, %rsp ;; popq %rbp ;; retq @@ -256,17 +259,29 @@ ;; popq %rbp ;; retq ;; -;; wasm[0]::function[15]::if_ctz_eq4_i32: +;; wasm[0]::function[15]::if_clz_bare_i64: +;; pushq %rbp +;; movq %rsp, %rbp +;; testq %rdx, %rdx +;; jge 0x2f7 +;; 2ed: movl $0xc8, %eax +;; jmp 0x2fc +;; 2f7: movl $0x64, %eax +;; movq %rbp, %rsp +;; popq %rbp +;; retq +;; +;; wasm[0]::function[16]::if_ctz_eq4_i32: ;; pushq %rbp ;; movq %rsp, %rbp ;; movl $0x20, %esi ;; bsfl %edx, %r9d ;; cmovel %esi, %r9d ;; cmpl $4, %r9d -;; je 0x305 -;; 2fb: movl $0xc8, %eax -;; jmp 0x30a -;; 305: movl $0x64, %eax +;; je 0x345 +;; 33b: movl $0xc8, %eax +;; jmp 0x34a +;; 345: movl $0x64, %eax ;; movq %rbp, %rsp ;; popq %rbp ;; retq From c829d13ce3bd1bc6f36540d893b044e7f2ad8413 Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sat, 23 May 2026 00:22:56 +0200 Subject: [PATCH 2/3] egraph: address review comments - Shorten the inline comment on the ireduce-peer rules. - Rename the bound value `X` -> `x` to match the project's lowercase binding style. - Drop the verbose justification comment on the new filetest functions; match the one-liner style used by the existing tests in the file. No behavioural change; filetest still passes. --- cranelift/codegen/src/opts/icmp.isle | 16 ++++++---------- .../filetests/egraph/brif-cnt-cond.clif | 6 +----- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/cranelift/codegen/src/opts/icmp.isle b/cranelift/codegen/src/opts/icmp.isle index 160c7e0075a3..db6f2510b2d7 100644 --- a/cranelift/codegen/src/opts/icmp.isle +++ b/cranelift/codegen/src/opts/icmp.isle @@ -466,15 +466,11 @@ (rule (simplify_skeleton (brif (clz x_ty X) _ _)) (replace_branch_cond (sge $I8 X (iconst_u x_ty 0)))) -;; Peer through `ireduce` when its operand is a `ctz`/`clz`. The wasm -;; shape is `i64.ctz; i32.wrap_i64; br_if` (motoko/moc's EOP peephole -;; emits this, and the same applies to clz). `ireduce` is harmless on a -;; ctz/clz result because the value is in [0, bitwidth] — always fits in -;; the smaller type without loss. We rewrite to the same bit-extract -;; condition as the bare-form rules above, using the wider source type. -(rule (simplify_skeleton (brif (ireduce _ (ctz x_ty X)) _ _)) +;; Same when an `ireduce` (truncation) sits between the count and the +;; brif. The count is in [0, bitwidth] so the truncation is lossless. +(rule (simplify_skeleton (brif (ireduce _ (ctz x_ty x)) _ _)) (replace_branch_cond - (eq $I8 (band x_ty X (iconst_u x_ty 1)) (iconst_u x_ty 0)))) + (eq $I8 (band x_ty x (iconst_u x_ty 1)) (iconst_u x_ty 0)))) -(rule (simplify_skeleton (brif (ireduce _ (clz x_ty X)) _ _)) - (replace_branch_cond (sge $I8 X (iconst_u x_ty 0)))) +(rule (simplify_skeleton (brif (ireduce _ (clz x_ty x)) _ _)) + (replace_branch_cond (sge $I8 x (iconst_u x_ty 0)))) diff --git a/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif b/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif index ffb0b242e947..a08a998b84fb 100644 --- a/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif +++ b/cranelift/filetests/filetests/egraph/brif-cnt-cond.clif @@ -131,11 +131,7 @@ block2: ; return v3 ; v3 = 200 ; } -;; `brif (ireduce (ctz X))` — 3-op form with an `i32.wrap_i64`-style -;; truncation between ctz and brif. The truncation is harmless because -;; ctz's output is in [0, 64]. Source pattern: motoko/moc's EOP LSB→ctz -;; peephole emits `i64.ctz; i32.wrap_i64; br_if` for the -;; `(value & 1) == 0`-as-branch-condition shape. +;; Same with a truncating `ireduce` between ctz and brif. function %brif_ireduce_ctz_i64(i64) -> i32 { block0(v0: i64): v1 = ctz v0 From 99a572959f8b12a099bcc0b52ee7124608bd149f Mon Sep 17 00:00:00 2001 From: Gabor Greif Date: Sat, 23 May 2026 00:51:40 +0200 Subject: [PATCH 3/3] disas: drop justifying comment on $if_clz_bare_i64 Function name is self-explanatory; rest of the file leaves the analogous eq0/ne0/select variants uncommented. --- tests/disas/ctz-clz-bool-condition.wat | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/disas/ctz-clz-bool-condition.wat b/tests/disas/ctz-clz-bool-condition.wat index b39feeddacab..67e17de4ca23 100644 --- a/tests/disas/ctz-clz-bool-condition.wat +++ b/tests/disas/ctz-clz-bool-condition.wat @@ -73,9 +73,6 @@ (func $if_clz_ne0_i64 (param i64) (result i32) (i64.ne (i64.clz (local.get 0)) (i64.const 0)) if (result i32) i32.const 100 else i32.const 200 end) - ;; Wasm-natural shape mirroring `$if_ctz_bare_i64`: `i64.clz` produces - ;; i64, narrowed via `i32.wrap_i64` before `if`. Same `ireduce` peel as - ;; the ctz form; collapses to a sign-bit test on the wider input. (func $if_clz_bare_i64 (param i64) (result i32) (i64.clz (local.get 0)) i32.wrap_i64 if (result i32) i32.const 100 else i32.const 200 end)