Support float modulo instead of miscompiling it - #32
Merged
Conversation
`f32 % f32` passed the type checker but no backend could compile it: the
Cranelift JIT hit `unreachable!("type Float32 not supported for modulo")`,
while the VM, stack, and asm backends emitted an integer `IRem` on float
registers and silently produced garbage. In Audulus that reads as a dead
DSP script — outputs stuck at zero with no error (#31).
Lower float `%` to a Lyte-level function rather than teaching four
backends a float remainder instruction:
- stdlib gains `__mod` overloads for f32/f64 implementing truncated
remainder (`x - y * trunc(x / y)`), matching C's fmod and Rust's `%`.
The existing `mod()` remains floored, so both conventions are available.
- The checker gives `%` its own builtin overload set (integers only), so
float `%` resolves through the operator-overload path to `__mod`.
`f32x4 % f32x4` now reports a type error instead of panicking the JIT.
- `rewrite_overloaded_binops` rewrites float `Binop::Mod` into a `__mod`
call, so no backend sees a float modulo.
- The three backends that silently emitted `IRem` for floats now
`unreachable!` like the JIT already did, making any regression loud.
Fixes #31
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JnL4SBHPzQ3rjj6VYmWrqb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #31.
f32 % f32passed the type checker but no backend could compile it:unreachable!("type Float32 not supported for modulo")— an internal compiler error.IRemon float registers, silently producing garbage.In Audulus the second case reads as a dead DSP script — outputs stuck at zero, controls unresponsive, no error anywhere.
Approach
Rather than reject
%on floats (the issue's alternative), this makes it work, by lowering it to a Lyte-level function instead of adding a float-remainder instruction to four backends.stdlib.lyte— new__mod(f32, f32)/__mod(f64, f64)overloads implementing truncated remainder (x - y * trunc(x / y), built from the existingfloor/ceilbuiltins).src/checker.rs—%now has its own builtin overload set (mod_overloads, i32/u32 only), so float%resolves through the existing operator-overload path to__mod.f32x4 % f32x4becomes a clean type error instead of a JIT panic.src/compiler.rs—rewrite_overloaded_binopsrewrites floatBinop::Modinto a__modcall, so no backend ever sees a float modulo.src/{jit,llvm_jit,vm_codegen,stack_codegen}.rs— the three backends that silently emittedIRemfor floats nowunreachable!like the JIT already did, so a future regression is loud rather than silent.Semantics
%follows C'sfmod/ Rust's%: the result takes the sign of the left operand, so-0.25 % 1.0 == -0.25. The stdlib's existingmod(x, y)stays floored (GLSL-style, sign ofy), so both conventions are available;docs/tutorial.mddocuments the difference.The alternative was making
%floored to agree withmod()— happy to switch if you'd rather they match.Testing
New golden test
tests/cases/arith/float_modulo.lytecovers both signs, f32 and f64, and thewrap01idiom from the issue. It passes on all five backends (jit, vm, asm, stack, llvm). Fullcargo test --workspaceis green, andcargo fmt --checkreports no new diffs.Notes
%is now a call rather than an instruction. Correct everywhere, but in a DSP hot loop it depends on inlining to be free. If%shows up in benchmark-sensitive code, realFRem/DRemopcodes would be the follow-up.%uses unsigned remainder in the Cranelift JIT (urem) while LLVM usessremfori32, so those two backends disagree on negative operands.🤖 Generated with Claude Code
https://claude.ai/code/session_01JnL4SBHPzQ3rjj6VYmWrqb