Repository navigation
Fix two miscompiles of values of two to four longs passed and returned in registers - #519
Open
j-modernc-org wants to merge 2 commits into
Open
j-modernc-org wants to merge 2 commits into
j-modernc-org wants to merge 2 commits into
Conversation
added 2 commits
October 14, 2026 15:01
CheckFunctionCalls passes an argument of n > 1 longs through n _parm_ temporaries, set by the multiple assignment (_parm_0, _parm_1) = expr, and BuildExprlistFromObject splits an expression that is neither an identifier nor a call into one member reference per long, each of which evaluates the expression again. So take2(*get2()) called get2 twice for a struct of two longs, three times for three and four times for four, and an element indexed by a call, a member read through a call, a long long element indexed by a call and a call through a function pointer did the same. An assignment of such a value is right: hltransform.c extracts its side effects first. The side effects of what such an argument reads through, an index and a pointer, are now evaluated once into temporaries of one long ahead of the call, the base before the index, so the split reads storage that is already there. Test/cexec08.c prints each value and the number of calls computing it made: 2 to 4 calls on master, 1 with this change, as gcc prints. The bytecode backends pass such an argument their own way, which this does not change, and the test prints its expected text there.
A struct of two to four longs comes back from a call in registers. outasm.c compiles a member of it (AST_METHODREF) as the call's first result, copied to a temporary, at the member's offset, so a member past the first long was read from a register nothing wrote: mkP().b and mkS().len were garbage, mkP().a right. A member of a call whose struct comes back in registers is now the call's result of that index. Test/cexec09.c prints garbage for every member past the first on master and gcc's values with this change. The bytecode backends return such a struct their own way, which this does not change, and the test prints its expected text there.
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.
Two silent miscompiles of values of two to four longs, which the asm backends pass and return in registers. They are independent: one commit each, each with its own test.
An argument evaluated once per long. An argument of two to four longs whose expression has a side effect was evaluated once per long:
take2(*get2())calledget2twice for a struct of two longs, three times for three and four times for four.take2(arr[idx()]),take2(getq()->q), along longelement indexed by a call and a call through a function pointer did the same. CheckFunctionCalls passes such an argument through_parm_temporaries with a multiple assignment, and BuildExprlistFromObject splits an expression that is neither an identifier nor a call into one member reference per long, each of which evaluates the whole expression again. An assignment of the same value is right, because hltransform.c extracts its side effects first.The fix evaluates the side effects of what the argument reads through, an index and a pointer, into temporaries of one long ahead of the call, so the split reads storage that is already there. It does not reuse ExtractSideEffects: that copies an array whose base has a side effect into a temporary of the array's type, and with it an element of a 64-bit array read as
(*f()->words)[g()]came out wrong in one of our test programs.A member of a returned struct read from the wrong register. A member past the first long of a struct that a call returns in registers was read from a register nothing wrote:
mkP().bandmkS().lenprinted garbage, whilemkP().awas right. outasm.c compiles the member (AST_METHODREF) as the call's first result, copied to a temporary, at the member's offset. A member of such a call is now the call's result of that index.Tests. Test/cexec08.c and Test/cexec09.c fail on master and pass with this change, as P2 asm (on a P2-EDGE) and as P1 asm (spinsim). The bytecode backends pass and return these values their own way, and this change leaves them alone. P1 bytecode (spinsim) gets them wrong on master too: it evaluates such an argument once per long, reads
arr[idx()]and thelong longelement wrong, reads every member of a returned struct wrong, and cannot build cexec08 as written ("Cannot handle indices with large values"). nucode refuses both tests as written. So under__OUTPUT_BYTECODE__the tests only print their expected text.Checked with flexspin built from this branch (master b5993a8 plus these two commits):
make test_offline: the same tests pass as on master.make test_spinsim: all pass, cexec08 and cexec09 included.make "OPT=-O2 -Werror"builds.Test/runtests_p2.shruns, on a P2-EDGE: all 30 pass, cexec08 and cexec09 included.