Skip to content

PPC64: correct number conversions for the 64-bit ABI - #279

Open
neomantra wants to merge 1 commit into
openresty:v2.1-agentzhfrom
neomantra:ppc64le-num2int-fix
Open

neomantra wants to merge 1 commit into
openresty:v2.1-agentzhfrom
neomantra:ppc64le-num2int-fix

Conversation

@neomantra

Copy link
Copy Markdown
Member

In working this OpenResty issue #1152, the root cause was related to broken PPC64 number-to-integer conversions that cause errors in FFI operations and table lengths. Non-P64 code is unchanged.

I worked with Claude and Codex to come up with this, but I will be honest, that even in trying to understand it, I don't fully. I even have this nice Claude Artifact that walks through it, but I'm not sure it is accurate.

That said, QEMU tests passed over 1.5 million conversion checks and test-suite failures decreased from 39 to 2 existing failures. I only ran it in emulation, as I don't have access to anything native.

I will leave Docker-based instructions for those interested in testing on the OpenResty issue.

The rest is dense LLM stuff, which includes a description of why lj_tab_len needed to change. I am marking this as draft and for public information, or experts to handle better.


Commit f80b349 ("Unify Lua number to FFI integer conversions") moved double-to-integer conversions into four assembly functions in vm_ppc.dasc: lj_vm_num2int_check, lj_vm_num2i64, lj_vm_num2u64, and lj_vm_tobit. These functions use incorrect word order and integer return conventions for ppc64le. They read the low word of a double as the high word. Thus, they read an incorrect exponent and return incorrect results.

Since v2.1-20260114, ffi.cast("int64_t", 123456789) returns 0LL on ppc64le. The error also affects int32, uint64, and intptr casts, ffi.new(), 64-bit arithmetic with Lua numbers, and integer string formatting. Incorrect integer-key checks prevent correct use of table array parts. This causes errors in table lengths and table operations.

Add P64 implementations of the four functions. Use fctidz and fcfid for conversions. Return results in r3 as the 64-bit ABI requires. These leaf functions use the red zone below sp. They keep sp and the stack back chain unchanged for asynchronous stack walkers. Do not use fctiduz, to keep compatibility with older big-endian PPC64 CPUs. The non-P64 code paths do not change.

Remove the lj_tab_len workaround from PR #274 (1b3e6c0). The incorrect integer check kept keys with integer values in the hash part. The workaround always used the slow path, which found those keys. Thus, the workaround appeared to correct the problem. The slow path assumes that the last array slot is not nil. With correct conversions, keys go into the array part. This assumption then causes incorrect lengths. For example, #{1,2,3} returns 4.

Previous tests used QEMU user emulation on ppc64le with Debian bookworm and GCC 12. The results were as follows:

  • The corrected test harness passed all 125 assertions for 35 inputs. These assertions included the signed -2^63 boundary.
  • All 1,530,534 extended conversion checks passed.
  • During signal handling, 393 samples from the four functions showed no stack-pointer changes or invalid stack back chains.
  • Failures in luajit2-test-suite decreased from 39 to 2 of 172 tests. The two failures did not change: ffi_lib_z.lua requires the missing libz library, and debug_gc.lua has an error that occurred before this change.
  • The DynASM output was identical in all bytes for four non-P64 configurations.

Tests on native POWER hardware are still necessary.

Fixes openresty/openresty#1152.

Commit f80b349 ("Unify Lua number to FFI integer conversions") moved
double-to-integer conversions into four assembly functions in vm_ppc.dasc:
lj_vm_num2int_check, lj_vm_num2i64, lj_vm_num2u64, and lj_vm_tobit.
These functions use incorrect word order and integer return conventions
for ppc64le. They read the low word of a double as the high word.
Thus, they read an incorrect exponent and return incorrect results.

Since v2.1-20260114, ffi.cast("int64_t", 123456789) returns 0LL on
ppc64le. The error also affects int32, uint64, and intptr casts, ffi.new(),
64-bit arithmetic with Lua numbers, and integer string formatting.
Incorrect integer-key checks prevent correct use of table array parts.
This causes errors in table lengths and table operations.

Add P64 implementations of the four functions. Use fctidz and fcfid for
conversions. Return results in r3 as the 64-bit ABI requires.
These leaf functions use the red zone below sp. They keep sp and the
stack back chain unchanged for asynchronous stack walkers.
Do not use fctiduz, to keep compatibility with older big-endian PPC64 CPUs.
The non-P64 code paths do not change.

Remove the lj_tab_len workaround from PR openresty#274 (1b3e6c0).
The incorrect integer check kept keys with integer values in the hash part.
The workaround always used the slow path, which found those keys.
Thus, the workaround appeared to correct the problem.
The slow path assumes that the last array slot is not nil.
With correct conversions, keys go into the array part.
This assumption then causes incorrect lengths. For example, #{1,2,3}
returns 4.

Previous tests used QEMU user emulation on ppc64le with Debian bookworm
and GCC 12. The results were as follows:

* The corrected test harness passed all 125 assertions for 35 inputs.
  These assertions included the signed -2^63 boundary.
* All 1,530,534 extended conversion checks passed.
* During signal handling, 393 samples from the four functions showed no
  stack-pointer changes or invalid stack back chains.
* Failures in luajit2-test-suite decreased from 39 to 2 of 172 tests.
  The two failures did not change: ffi_lib_z.lua requires the missing
  libz library, and debug_gc.lua has an error that occurred before this
  change.
* The DynASM output was identical in all bytes for four non-P64
  configurations.

Tests on native POWER hardware are still necessary.

Fixes openresty/openresty#1152.

Signed-off-by: Evan Wies <evan@neomantra.net>
@neomantra

Copy link
Copy Markdown
Member Author

I worked with other LLMs on it, but I admit I still do not fully understand it. But won't be making further changes without any feedback, so undrafting this. Thanks!

@neomantra
neomantra marked this pull request as ready for review September 25, 2026 21:32
neomantra added a commit to neomantra/docker-openresty that referenced this pull request Oct 6, 2026
Build restyrepo for linux/ppc64le under QEMU, replacing the bundled LuaJIT
with neomantra/openresty-luajit2 at the head of openresty/luajit2#279, which
fixes 64-bit number conversions on PPC64 (openresty/openresty#1152). The
build fails if that ref lacks the fctidz fix in vm_ppc.dasc. LuaJIT runs
interpreter-only, as the PPC64 port has no JIT.

The image is published only under restyrepo-ppc64le tags and is not added
to the restyrepo manifest until the fix is merged upstream.

Add tests/smoke/smoke.sh, ported from neomantra/openresty-ppc64le, and run
it after each restyrepo build: 64-bit FFI casts, ipairs, ngx.re through
resty, and an HTTP request to the default config.

Refs openresty#311

Signed-off-by: Evan Wies <evan@neomantra.net>
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.

Latest LuaJIT which shipped with Openresty 1.31.1.1 causing regression on ppc64le

1 participant