Skip to content

feat(isel): lower llvm.trunc at every width up to 64 - #1560

Closed
regehr wants to merge 1 commit into
mainfrom
regehr/riscv-trunc
Closed

regehr wants to merge 1 commit into
mainfrom
regehr/riscv-trunc

Conversation

@regehr

@regehr regehr commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator

trunc_local only accepted operand and result widths in {8, 16, 32, 64}, so any trunc to i1 survived RISC-V instruction selection. legalize emits these itself when it widens an i1 add/sub/mul/and/or/xor to i64. In a census of 500 programs from vsmith --riscv, 458 had a leftover trunc … -> i1.

This PR accepts every operand width up to 64 (including odd ones like i52 -> i3). Operands wider than 64 bits are still left alone.

Why it's sound: the lowering is still just a pair of casts through !riscv.reg. The result's upper register bits are whatever the operand left there, and reconcile-cast zero-extends the value wherever it is used as a register again (slli/srli for i1). That matches the convention all the isel proofs assume, since LLVM.Int.toReg zero-extends. The RISC-V psABI only constrains how narrow integers are extended at function boundaries, which this doesn't touch.

Proof: the six per-width trunc_refinement_* theorems are replaced by a single trunc_refinement_le64, which covers every w₂ < w₁ ≤ 64, including the nsw/nuw poison cases. It uses pbv_decide.

Tests:

  • trunc.mlir gains i64 -> i1 and i52 -> i3 cases, plus a check that i65 -> i1 is not lowered.
  • New trunc_i1_exec.mlir: trunc 6 -> i1 is false even though the register still holds 6, and the i1 feeds both a select and a cond_br. It runs the full riscv pipeline and checks that the lowered program returns the same value as the source.

🤖 Generated with Claude Code

`trunc_local` only accepted operand and result widths in {8, 16, 32, 64},
so any `trunc` to `i1` (which `legalize` emits when it widens an `i1`
arithmetic op) survived instruction selection. Accept every operand width
up to 64 instead.

The lowering stays a pair of casts: the result's upper register bits are
whatever the operand left there, and `reconcile-cast` zero-extends the
value wherever it is used as a register again, which is the convention
all the isel proofs assume (`LLVM.Int.toReg` zero-extends). The psABI
only constrains values at function boundaries, which this does not touch.

The six per-width `trunc_refinement_*` theorems are replaced by one,
`trunc_refinement_le64`, covering every `w₂ < w₁ ≤ 64`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@regehr regehr closed this Sep 27, 2026
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.

1 participant