Skip to content

fix(gen): signed and float % lowers to @rem (Closes #5973) - #5993

Merged
gHashTag merged 2 commits into
masterfrom
claude/zig-signed-rem
Oct 4, 2026
Merged

gHashTag merged 2 commits into
masterfrom
claude/zig-signed-rem

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Summary

t27c gen (the Zig backend) wrote % as Zig's infix % whatever its operands. Zig accepts infix % only when both operands are unsigned and refuses everything else: "remainder division with 'i32' and 'i32': signed integers and floats must use @Rem or @mod". So fn rem(a: i32, b: i32) -> i32 { return a % b; } made the whole generated file fail to compile.

Changed function: Codegen::gen_expr, in the NodeKind::ExprBinary arm. The new branch sits directly after the W593 @divTrunc branch and uses the same test. When either operand is a known signed integer (is_signed_int_expr) or a float (is_float_expr), % becomes @rem(lhs, rhs).

  • @rem is the truncated remainder, so rem(-7, 2) == -1, the same answer C, Rust and t27b give. @mod would floor and give 1.
  • When both operands are unsigned, % stays %. That is valid Zig, so rewriting it would only churn seals.
  • / needed no change. W593 already writes @divTrunc for a signed /.

Before / after (reproducer from the t27b README)

test zig test before zig test after clang -O0 (gen-c, unchanged here) clang -O2 (gen-c, unchanged here)
rem_neg (rem(-7, 2) == -1) file does not compile pass pass pass
inc_max (inc(INT_MAX)) file does not compile panic: integer overflow (t27 trap) pass (UB) trap (UB)
shr_big (shr(1024, 40)) file does not compile panic: integer does not fit (t27 trap) trap (UB) pass (UB)

The C columns differ between -O0 and -O2 because of undefined behaviour in the generated C. That is #5974, fixed in a separate PR.

Corpus effect

  • gen: 8 of 1184 specs change output. No spec changes gen-c output.
  • Zig compile errors in each of the 8 went down or stayed the same, and no spec has a new error:
    • compiler/optimizer.t27: 1 -> 0, and its 14 tests now pass.
    • igla/race/backend.t27: 49 -> 48.
    • isa/tri27_machine.t27: 5 -> 4.
    • vsa/trinity_compat.t27: 6 -> 5.
    • isa/ternary_hash.t27, isa/ternary_pattern_matching.t27, isa/ternary_search.t27 and port/tools/verify_trainer_c.t27: unchanged.
  • Seals: 12 resealed with t27c seal <spec> --save. After rebasing on master, tools/check_seal_coverage.py reports OK: 1449 seals, 1325 hold, 124 known-broken (the baseline).

Tests

  • New file bootstrap/tests/zig_signed_rem.rs with 4 tests:
    • signed % becomes @rem, never @mod;
    • unsigned % stays infix;
    • float % becomes @rem;
    • a real zig test run (rem(-7,2) == -1, rem(7,-2) == 1), which skips if zig is not on PATH.
  • cargo test --release -p t27c (before the rebase): 2812 passed, 0 failed.
  • After the rebase onto current master, --test zig_signed_rem: 4/4.

Not established: what @rem does with a zero divisor or MIN % -1 in the generated code.

Closes #5973. Part of #5905.

🤖 Generated with Claude Code

Zig refuses infix `%` unless both operands are unsigned ("signed integers
and floats must use @Rem or @mod"), so `return a % b;` with `a: i32` made
the generated file fail to compile. The W593 `@divTrunc` arm for `/`
already had the right test; `%` now uses it and emits `@rem(a, b)`, the
truncated remainder (rem(-7, 2) == -1, as in C, Rust and t27b). Unsigned
`%` stays infix. `/` needed no change.

8 of 1184 specs change gen output and 0 change gen-c; none has a new zig
error, and compiler/optimizer.t27 now passes 14/14. 12 seals resealed.

Found by the t27b differential test. Part of #5905.

Closes #5973

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-04 10:26:03 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 42
PRs with All Checks Green 8
READY 2
FAILING 42
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 42 + 0 + 0 = 44, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=b44698686fab != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-04 13:24:02 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 39
PRs with All Checks Green 11
READY 10
FAILING 39
PENDING 0
NO CHECKS YET 0

These columns do not partition: 10 + 39 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=a47525693295 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag merged commit 2e852cf into master Oct 4, 2026
33 of 36 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.

gen (Zig): signed and float % emits plain %, which Zig refuses to compile

1 participant