Skip to content

Commit

Permalink
Fix snapshot PC when linking to BC_JLOOP that was a BC_RET*.
Browse files Browse the repository at this point in the history
Reported by Arseny Vakhrushev.
Fix contributed by Peter Cawley.

(cherry-picked from commit 5c46f47)

As specified in the comment in `lj_record_stop`, all loops must
set `J->pc` to the next instruction. However, the chunk of logic
in `lj_trace_exit` expects it to be set to `BC_JLOOP` itself if
it used to be a `BC_RET`. This wrong pc results in the execution
of random data that goes after `BC_JLOOP` in the case of
restoration from the snapshot.

This patch fixes that behavior by adapting the loop recording
logic to this specific case.

NOTICE: This patch is only a part of the original commit,
and the other part is backported in the previous commit. The
patch was split into two, so the test case becomes easier to
implement since it can now depend on this assertion instead
of memory layout.

Maxim Kokryashkin:
* added the description and the test for the problem

Part of tarantool/tarantool#9145

Reviewed-by: Sergey Kaplun <[email protected]>
Reviewed-by: Sergey Bronnikov <[email protected]>
Signed-off-by: Igor Munkin <[email protected]>
(cherry picked from commit 2ab0419)
  • Loading branch information
Mike Pall authored and igormunkin committed Dec 6, 2023
1 parent 8c47c8a commit daa31b0
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 4 deletions.
9 changes: 5 additions & 4 deletions src/lj_record.c
Original file line number Diff line number Diff line change
Expand Up @@ -570,10 +570,10 @@ static LoopEvent rec_iterl(jit_State *J, const BCIns iterins)
}

/* Record LOOP/JLOOP. Now, that was easy. */
static LoopEvent rec_loop(jit_State *J, BCReg ra)
static LoopEvent rec_loop(jit_State *J, BCReg ra, int skip)
{
if (ra < J->maxslot) J->maxslot = ra;
J->pc++;
J->pc += skip;
return LOOPEV_ENTER;
}

Expand Down Expand Up @@ -2441,7 +2441,7 @@ void lj_record_ins(jit_State *J)
rec_loop_interp(J, pc, rec_iterl(J, *pc));
break;
case BC_LOOP:
rec_loop_interp(J, pc, rec_loop(J, ra));
rec_loop_interp(J, pc, rec_loop(J, ra, 1));
break;

case BC_JFORL:
Expand All @@ -2451,7 +2451,8 @@ void lj_record_ins(jit_State *J)
rec_loop_jit(J, rc, rec_iterl(J, traceref(J, rc)->startins));
break;
case BC_JLOOP:
rec_loop_jit(J, rc, rec_loop(J, ra));
rec_loop_jit(J, rc, rec_loop(J, ra,
!bc_isret(bc_op(traceref(J, rc)->startins))));
break;

case BC_IFORL:
Expand Down
83 changes: 83 additions & 0 deletions test/tarantool-tests/lj-624-jloop-snapshot-pc.test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
local tap = require('tap')
local test = tap.test('lj-624-jloop-snapshot-pc'):skipcond({
['Test requires JIT enabled'] = not jit.status(),
})

test:plan(1)
-- XXX: The test case below triggers the assertion that was
-- added in the patch if tested without the fix itself. It
-- is hard to create a stable reproducer without turning off
-- ASLR and VM randomizations, which is not suitable for testing.

-- Reproducer below produces the following traces:
-- ---- TRACE 1 start test.lua:2
-- 0001 KSHORT 1 2
-- 0002 ISGE 0 1
-- 0003 JMP 1 => 0006
-- 0006 UGET 1 0 ; fib
-- 0007 SUBVN 2 0 0 ; 1
-- 0008 CALL 1 2 2
-- 0000 . FUNCF 4 ; test.lua:2
-- 0001 . KSHORT 1 2
-- 0002 . ISGE 0 1
-- 0003 . JMP 1 => 0006
-- 0006 . UGET 1 0 ; fib
-- 0007 . SUBVN 2 0 0 ; 1
-- 0008 . CALL 1 2 2
-- 0000 . . FUNCF 4 ; test.lua:2
-- 0001 . . KSHORT 1 2
-- 0002 . . ISGE 0 1
-- 0003 . . JMP 1 => 0006
-- 0006 . . UGET 1 0 ; fib
-- 0007 . . SUBVN 2 0 0 ; 1
-- 0008 . . CALL 1 2 2
-- 0000 . . . FUNCF 4 ; test.lua:2
-- ---- TRACE 1 stop -> up-recursion
--
-- ---- TRACE 1 exit 1
-- ---- TRACE 2 start 1/1 test.lua:3
-- 0004 ISTC 1 0
-- 0005 JMP 1 => 0013
-- 0013 RET1 1 2
-- 0009 UGET 2 0 ; fib
-- 0010 SUBVN 3 0 1 ; 2
-- 0011 CALL 2 2 2
-- 0000 . JFUNCF 4 1 ; test.lua:2
-- ---- TRACE 2 stop -> 1
--
-- ---- TRACE 2 exit 1
-- ---- TRACE 3 start 2/1 test.lua:3
-- 0013 RET1 1 2
-- 0012 ADDVV 1 1 2
-- 0013 RET1 1 2
-- ---- TRACE 3 abort test.lua:3 -- down-recursion, restarting
--
-- ---- TRACE 3 start test.lua:3
-- 0013 RET1 1 2
-- 0009 UGET 2 0 ; fib
-- 0010 SUBVN 3 0 1 ; 2
-- 0011 CALL 2 2 2
-- 0000 . JFUNCF 4 1 ; test.lua:2
-- ---- TRACE 3 stop -> 1
--
-- ---- TRACE 2 exit 1
-- ---- TRACE 4 start 2/1 test.lua:3
-- 0013 RET1 1 2
-- 0012 ADDVV 1 1 2
-- 0013 JLOOP 3 3
--
-- The assertion introduced in the previous patch is triggered during
-- recording of the last 0013 JLOOP.
--
-- See also:
-- https://github.com/luaJIT/LuaJIT/issues/624

jit.opt.start('hotloop=1', 'hotexit=1')
local function fib(n)
return n < 2 and n or fib(n - 1) + fib(n - 2)
end

fib(5)

test:ok(true, 'snapshot pc is correct')
test:done(true)

0 comments on commit daa31b0

Please sign in to comment.