NY: Infer widths for loop-carried variables and bound constant shifts - #615
Merged
Merged
Conversation
No integration contract had loop-carried variables narrower than a word, so the integration tests never exercised masked 32-bit values rewritten by a round loop. The copied sha-256-hashing workload from resolc-compiler-tests covers that shape.
The For arm widened every loop variable, post input and output to I256, so the loop header reset every width the body had proved. For instance, the 32-bit working variables of a sha 256 round loop were emitted as 256-bit values. Joining the forward widths of the control edges that reach each phi, and building the phis at that width, keeps masked loop-carried variables narrow through the loop.
A shr by a constant was only bounded by the 256-bit word and a shl was always I256, so a shifted narrow value re-widened even when its magnitude was known, and codegen emitted every shl at word width unless the consumer demanded at most 64 bits. Bounding shr by the operand width minus the shift and shl by the width plus the shift, and emitting a constant shl at 64 bits when that bound fits, keeps rotate chains over narrow loop-carried variables narrow at the source instead of leaving the fold to LLVM.
elle-j
marked this pull request as ready for review
September 23, 2026 12:45
xermicus
approved these changes
Sep 24, 2026
xermicus
left a comment
Member
There was a problem hiding this comment.
Nit: Looks like there's no unit test for a break or continue that carries fewer values than the loop has variables.
The join forwards the loop variable for positions a break or continue leaves out. Only the continue half is observable, since outputs already join the loop variable through the exit edge, so the test yields a masked copy of the variable from the body and lets a continue skip that position.
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.
Description
The newyork optimizer's width inference did not prove values narrower than 256 bits in the
forhandler (unlike e.g.ifandswitch) and forced every loop-carried variable toI256. Everything computed from those loop-carried variables re-widened every iteration.Changes summary:
forjoins the forward widths of every control edge reaching each loop phi.shr(C, x)was bounded by256 - C, andshl(C, x)by nothing, so a shift of a narrow value re-widened it even when the amount was a literal.shr(C, x)is bounded bywidth(x) - C, andshl(C, x)bymin(width(x) + C, 256).SHA-256 example
The sha-256-hashing workload has been copied into this repo as an integration fixture.
The eight
uint32localsatohhare rewritten on every iteration of the 64-round loop, androtris a pair of shifts:In Yul each of these is a loop variable masked to 32 bits. Before, the initializers and the masked results were inferred as
I32, but the loop header reset the variables toI256on every backedge. newyork IR:LLVM recovered some of the narrowing afterwards (but unreliably). Each surviving 256-bit shift became a multi-limb sequence in the machine code, with the operand's second limb reloaded from the stack.
RISC-V for
rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25):RISC-V instructions
Measurements (
-Oz --newyork)llvm.fshl.i32(of 10rotrcalls)lshr i256surviving optimization**The eight remaining
lshr i256are outside the round loop.