Skip to content

PPC soft-float: fix ipairs() returning corrupted values and pairs() yielding a spurious nil - #270

Open
BKPepe wants to merge 2 commits into
openresty:v2.1-agentzhfrom
BKPepe:ppc-softfp-iter-fixes
Open

PPC soft-float: fix ipairs() returning corrupted values and pairs() yielding a spurious nil#270
BKPepe wants to merge 2 commits into
openresty:v2.1-agentzhfrom
BKPepe:ppc-softfp-iter-fixes

Conversation

@BKPepe

@BKPepe BKPepe commented Aug 15, 2026

Copy link
Copy Markdown

Two independent bugs in src/vm_ppc.dasc, both confined to the .if not FPU branch and both introduced by 2763a42 ("Patch for PPC64 support"). Upstream LuaJIT is not affected — on the same board, the distro's upstream LuaJIT returns the correct results.

Reproducers on Turris 1.x (e500v2, 32-bit big-endian, musl, soft-float):

local sum = 0
for _, v in ipairs({10, 20, 30}) do
    sum = sum + v
end
assert(sum == 60)
local expected = {7, 8, 9}
local i = 0
for k, v in pairs(expected) do
    i = i + 1
    assert(k == i and v == expected[i])
end
assert(i == 3)

Before the fixes, the ipairs() test yields -42 instead of 60 and the pairs() test sees an extra (nil, nil) iteration. With the fixes, both pass, with and without the JIT.

ipairs() also has a memory-safety angle: only the payload half of the TValue is wrong while the itype stays correct, so for GC-typed elements the type tag ends up in the GCref and tostring() dereferences a fabricated pointer.

Verified on hardware, and through the OpenWrt package build in openwrt/packages#30280, where the resulting powerpc_8548 package was installed on the device and behaves correctly.

Copilot AI lite review requested due to automatic review settings August 15, 2026 09:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@BKPepe

BKPepe commented Aug 25, 2026

Copy link
Copy Markdown
Author

@zhuizhuhaomeng Can you take a look, please?

@zhuizhuhaomeng

Copy link
Copy Markdown
Contributor

[P1] PPC32LE soft-float ipairs() values are corrupted — src/vm_ppc.dasc (line 1836)
On little-endian PPC, the preceding raw offset-0 load already reads WORD_LO. The changed line reads WORD_LO again, so both registers contain the payload. The subsequent stores write the payload into the type word, producing malformed TValues for ordinary inputs such as ipairs({10, 20}). The load and stores need consistent endian-aware high/low-word handling.

[P2] pairs() checks the payload instead of the type on PPC32LE soft-float — src/vm_ppc.dasc (line 5813)
CARG1 comes from raw offset 0, which is the payload on little-endian systems; CARG2 holds the type. Consequently, the hash-less-table bug remains, while a valid hash value of integer -1 is mistaken for nil and skipped. The check should select the endian-correct register or explicitly load WORD_HI.

Would you please check the review issues from AI?

@BKPepe

BKPepe commented Aug 26, 2026

Copy link
Copy Markdown
Author

Thanks. Both of these come from 2763a42, and looking again the right fix in each case is to restore what that commit replaced.

For ipairs, the line was lwz TMP3, 4(TMP1) before 2763a42 and became lwz TMP3, WORD_HI(TMP1). The raw offset was correct on both endiannesses, because the stores at 1865-1866 put the two words back at the same offsets. WORD_HI is 0 on big-endian, which is what corrupts the value there. My WORD_LO version fixes big-endian but is not right on little-endian, so restoring 4(TMP1) is the better fix.

For BC_ITERN, 2763a42 removed the shared lwzx RB, TMP2, TMP3 and moved an equivalent load inside the FPU branch only, so on the soft-float path RB still holds the RC*8 left by slwi RB, RC, 3 at 5899 and the check can never match on any endianness. Rather than choose a register per endianness, the soft-float branch should load RB as well, with lwz RB, WORD_HI(CARG3), leaving checknil RB untouched.

That makes both hunks correct on either endianness. I will respin the PR accordingly. I can still only test on big-endian e500v2.

BKPepe added 2 commits August 26, 2026 10:04
The soft-float branch of the ipairs_aux fast function loads the second
word of the array slot from WORD_HI. On big-endian targets WORD_HI is
0, which is the itype the branch has already loaded into TMP2, so the
value word at offset 4 is never read.

Every element therefore comes back carrying the itype in its payload.
Numbers surface as -14, the LJ_TNUMX tag read as an int32. For GC
types the payload is the GCref, so the tag becomes a fabricated
pointer that tostring() then dereferences.

Restore the hardcoded 4(TMP1) that commit 2763a42 ("Patch for PPC64
support") rewrote as WORD_HI. The two words are stored straight back
to 8(RA) and 12(RA), so a raw offset preserves the slot layout and is
correct on either endianness. Upstream LuaJIT is unaffected.

Reproducer on Turris 1.x (e500v2, 32-bit big-endian, soft-float):

  $ luajit -e 'local s=0 for i,v in ipairs({10,20,30}) do s=s+v end print(s)'
  -42     -- expected 60

Signed-off-by: Josef Schlehofer <pepe.schlehofer@gmail.com>
BC_ITERN checks the itype of the node value in RB to skip empty slots
in the hash part. Upstream loads RB unconditionally before the FPU
split. Commit 2763a42 ("Patch for PPC64 support") moved that load
into the FPU branch only. On soft-float builds, RB still contains
RC*8 from the hash-part setup, so the nil check never succeeds and
iterating a table whose hash part is empty yields one extra
(nil, nil) pair.

Load RB in the soft-float branch as well, mirroring the FPU branch.
Taking it from WORD_HI keeps the check correct on either endianness
and leaves checknil untouched.

Reproducer on Turris 1.x (e500v2, 32-bit big-endian, soft-float):

  $ luajit -e 'for k,v in pairs({7,8,9}) do print(k,v) end'
  1    7
  2    8
  3    9
  nil  nil     -- spurious

Signed-off-by: Josef Schlehofer <pepe.schlehofer@gmail.com>
@BKPepe
BKPepe force-pushed the ppc-softfp-iter-fixes branch from ae896c3 to 60c3d8d Compare August 26, 2026 14:09
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.

3 participants