[Tarantool-patches] [PATCH luajit] Fix JIT slot overflow during up-recursion.
Sergey Kaplun
skaplun at tarantool.org
Thu Jun 5 12:41:05 MSK 2025
From: Mike Pall <mike>
Reported by Sergey Kaplun.
(cherry picked from commit 048972dbfdb6b441fe8a9bfe4d1f048966579ba8)
In the case when LuaJIT is recording the side trace after the
up-recursion call, there is no check that the updated `maxslot` value
doesn't overflow the `LJ_MAX_JSLOTS` limit. If it records several huge
returns in a row, the overflow of the aforementioned limit may occur.
This triggers an assertion failure in `rec_check_slots()`.
This patch fixes it by adding the corresponding check in the
`lj_record_ret()`.
Sergey Kaplun:
* added the description and the test for the problem
Part of tarantool/tarantool#11278
---
Branch: https://github.com/tarantool/luajit/tree/skaplun/lj-1358-jslot-overflow-uprecursion
Related issues:
* https://github.com/tarantool/tarantool/issues/11278
* https://github.com/LuaJIT/LuaJIT/issues/1358
src/lj_record.c | 3 +-
...j-1358-jslot-overflow-uprecursion.test.lua | 82 +++++++++++++++++++
2 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 test/tarantool-tests/lj-1358-jslot-overflow-uprecursion.test.lua
diff --git a/src/lj_record.c b/src/lj_record.c
index d83fa38f..1dd22dac 100644
--- a/src/lj_record.c
+++ b/src/lj_record.c
@@ -889,7 +889,8 @@ void lj_record_ret(jit_State *J, BCReg rbase, ptrdiff_t gotresults)
lj_trace_err(J, LJ_TRERR_LLEAVE);
} else if (J->needsnap) { /* Tailcalled to ff with side-effects. */
lj_trace_err(J, LJ_TRERR_NYIRETL); /* No way to insert snapshot here. */
- } else if (1 + pt->framesize >= LJ_MAX_JSLOTS) {
+ } else if (1 + pt->framesize >= LJ_MAX_JSLOTS ||
+ J->baseslot + J->maxslot >= LJ_MAX_JSLOTS) {
lj_trace_err(J, LJ_TRERR_STACKOV);
} else { /* Return to lower frame. Guard for the target we return to. */
TRef trpt = lj_ir_kgc(J, obj2gco(pt), IRT_PROTO);
diff --git a/test/tarantool-tests/lj-1358-jslot-overflow-uprecursion.test.lua b/test/tarantool-tests/lj-1358-jslot-overflow-uprecursion.test.lua
new file mode 100644
index 00000000..1b151b56
--- /dev/null
+++ b/test/tarantool-tests/lj-1358-jslot-overflow-uprecursion.test.lua
@@ -0,0 +1,82 @@
+local tap = require('tap')
+
+-- The test file to demonstrate JIT slots overflow when compiling
+-- the return from the trace with up-recursion.
+-- See also: https://github.com/LuaJIT/LuaJIT/issues/1358.
+
+local test = tap.test('lj-1358-jslot-overflow-uprecursion'):skipcond({
+ ['Test requires JIT enabled'] = not jit.status(),
+})
+
+test:plan(1)
+
+-- The test generates the functions with the following workload:
+--
+-- | local uprec_func()
+-- | if cond then return end
+-- | return 'x', --[[...]] 'x', uprec_func()
+-- | end
+-- |
+-- | local function empty() end
+-- | empty('x', --[[...]] 'x', uprec_func())
+--
+-- The recording of the return from `uprec_func()` before the call
+-- to `empty()` causes the assertion failure in the
+-- `rec_check_slots()`.
+
+-- Generate a function with many return values plus up-recursion.
+local function generate_uprec_payload(n_returns)
+ local str_func = [[
+ local counter = 0
+ local function payload_f()
+ counter = counter + 1
+ if counter > 5 then return end
+ return
+ ]]
+ for _ = 1, n_returns do
+ str_func = str_func .. '"x", '
+ end
+ str_func = str_func .. [[
+ payload_f()
+ end
+ return payload_f
+ ]]
+ local f = assert(loadstring(str_func))
+ return f()
+end
+
+-- Generate the necessary number of locals for a huge enough
+-- `cbase`.
+local function generate_nloc_payload(n_locals)
+ local str_func = [[
+ -- Function to be called after return with all stack slots used.
+ local function empty() end
+ empty(
+ ]]
+ for _ = 1, n_locals do
+ str_func = str_func .. '"x", '
+ end
+ str_func = str_func .. [[
+ _G.uprec_func()
+ )
+ ]]
+ local f = assert(loadstring(str_func))
+ return f
+end
+
+-- Avoid an unrelated JIT output.
+jit.off()
+-- 30 * 5 = 150 returned values for the first call.
+_G.uprec_func = generate_uprec_payload(30)
+-- Plus 100 slots for locals, plus a slot for the function to be
+-- called causes JIT stack slots overflow.
+local test_func = generate_nloc_payload(100)
+
+jit.on()
+jit.opt.start('hotloop=1', 'hotexit=1', 'recunroll=1')
+
+test_func()
+
+test:ok(true, 'no assertion on JIT slots overflow')
+
+test:done(true)
--
2.49.0
More information about the Tarantool-patches
mailing list