Repository navigation
Conversation
A range loop over a slice has the condition (index + 1) < len(slice) with a signed compare, while the bounds check on the element is unsigned. LLVM cannot merge the two without knowing that the length is not negative, so it keeps the check and adds a smax to the loop count. The index starts at -1 and goes up by one while it is below the length, and a slice length is never negative. An unsigned compare gives the same result. Use it for this loop condition so it matches the bounds check. In the standard library this removes the bounds check from 291 of the 292 range loops where gc removes it and LLVM did not. At -opt=z a loop with an early return goes from 21 to 15 instructions and loses its panic call. On a Cortex-M0+ a range loop with a continue statement takes 11% fewer cycles. Add a compiler test for the loop condition and runtime tests for range loop semantics.
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.
A range loop over a slice has the condition (index + 1) < len(slice) with a signed compare, while the bounds check on the element is unsigned. LLVM cannot merge the two without knowing that the length is not negative. It keeps the check and adds a smax to the loop count.
The index starts at -1 and goes up by one while it is below the length, and a slice length is never negative. An unsigned compare gives the same result, so this change uses it for that loop condition. Then it matches the bounds check and LLVM removes the check. Other loops, loops over arrays and loops over strings are not changed.
Results
In the standard library, building the test binaries of the packages in make/test.mk and comparing the surviving checks with the ones gc keeps, this removes the check from 291 of the 292 range loop sites where gc removes it and LLVM did not.
At -opt=z a range loop with an early return goes from 21 to 15 instructions and loses its panic call. At -opt=2 LLVM already removed it in that case.
On a Seeeduino XIAO (SAMD21, Cortex-M0+ at 48 MHz, -opt=s) I measured cycles with SysTick. A range loop with a continue statement takes 11.1% fewer cycles, from 18,368 to 16,320. Three other range loops are unchanged and so are two control loops. The cycle counts are exactly repeatable and do not depend on code layout.
On a desktop I did not find a consistent speedup. Over 8 code layouts, some benchmarks in compress/flate and strings.Replacer are 2 to 4% faster at -opt=z, but unchanged code varies by more than that. The compress/lzw compression benchmark is 4 to 6% slower on wasip1 and at -opt=z, in every layout, and I do not know why. The machine code of the affected function is smaller.
Code size changes by -0.01% to -0.33% at -opt=z, -0.05% to -0.16% at -opt=s and -0.21% to +0.50% at -opt=2 on the programs I tried.
Testing