Skip to content

Commit 4bb976f

Browse files
Mike PallBuristan
Mike Pall
authored andcommitted
Fix detection of inconsistent renames due to sunk values.
Thanks to Sergey Kaplun. (cherry picked from commit 811e448) This commit is a follow-up to the commit 3a2e484 ("Detect inconsistent renames even in the presence of sunk values."). Due to the reversed assembling order of a trace, all registers are allocated from the bottom of the trace to the top. Furthermore, if the snapshot contains sunk values, IRs for them will contain the `RID_SUNK` after the first processing. If there is another snapshot (with a smaller number) containing this sunk IR, this IR will not be processed during the bloom filter marking in the allocation of the slot that escapes this snapshot (since it is already sunk). Hence, such IRs still may lead to the rename invariant violation like in the aforementioned commit. This patch prevents scanning from stopping when already sunk IR is faced during snapshot processing so bloom filters contain actual information. The disadvantage of this approach is that it assumes that any sunk table-typed IR can't refer to the same table inside it (so there should not be any cycling references in the sunk table). Sergey Kaplun: * added the description and the test for the problem Resolves tarantool/tarantool#10746 Relates to tarantool/aeon#282 Part of tarantool/tarantool#11055 Reviewed-by: Sergey Bronnikov <[email protected]> Signed-off-by: Sergey Kaplun <[email protected]> (cherry picked from commit e0c8208)
1 parent 96783ce commit 4bb976f

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed

src/lj_asm.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -903,11 +903,11 @@ static int asm_sunk_store(ASMState *as, IRIns *ira, IRIns *irs)
903903
static void asm_snap_alloc1(ASMState *as, IRRef ref)
904904
{
905905
IRIns *ir = IR(ref);
906-
if (!irref_isk(ref) && ir->r != RID_SUNK) {
906+
if (!irref_isk(ref)) {
907907
bloomset(as->snapfilt1, ref);
908908
bloomset(as->snapfilt2, hashrot(ref, ref + HASH_BIAS));
909909
if (ra_used(ir)) return;
910-
if (ir->r == RID_SINK) {
910+
if (ir->r == RID_SINK || ir->r == RID_SUNK) {
911911
ir->r = RID_SUNK;
912912
#if LJ_HASFFI
913913
if (ir->o == IR_CNEWI) { /* Allocate CNEWI value. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
local tap = require('tap')
2+
3+
-- Test file to demonstrate LuaJIT assembler misbehaviour.
4+
-- For more info, proceed to the issues:
5+
-- * https://github.com/LuaJIT/LuaJIT/issues/584,
6+
-- * https://github.com/LuaJIT/LuaJIT/issues/1295,
7+
-- * https://github.com/tarantool/tarantool/issues/10746.
8+
9+
local test = tap.test('lj-1295-bad-renames-for-sunk-values'):skipcond({
10+
['Test requires JIT enabled'] = not jit.status(),
11+
})
12+
13+
test:plan(1)
14+
15+
-- XXX: The reproducer requires specific snapshotting and register
16+
-- allocations, so the reproducer mostly copies the relevant code
17+
-- from https://github.com/luafun/luafun.
18+
19+
----- Related part of luafun.lua. --------------------------------
20+
21+
local iterator_mt = {
22+
-- Usually called by the for-in loop.
23+
__call = function(self, param, state)
24+
return self.gen(param, state)
25+
end,
26+
}
27+
28+
local wrap = function(gen, param, state)
29+
return setmetatable({
30+
gen = gen,
31+
param = param,
32+
state = state
33+
}, iterator_mt), param, state
34+
end
35+
36+
-- These functions call each other to implement a flat iterator
37+
-- over the several iterable objects.
38+
local chain_gen_r1
39+
local chain_gen_r2 = function(param, state, state_x, ...)
40+
if state_x == nil then
41+
local i = state[1]
42+
i = i + 1
43+
if param[3 * i - 2] == nil then
44+
return nil
45+
end
46+
return chain_gen_r1(param, {i, param[3 * i]})
47+
end
48+
return {state[1], state_x}, ...
49+
end
50+
51+
chain_gen_r1 = function(param, state)
52+
local i, _ = state[1], state[2]
53+
local gen_x, param_x = param[3 * i - 2], param[3 * i - 1]
54+
return chain_gen_r2(param, state, gen_x(param_x, state[2]))
55+
end
56+
57+
local chain = function(...)
58+
local n = select('#', ...)
59+
local param = { [3 * n] = 0 }
60+
local gen_x, param_x, state_x
61+
for i = 1, n do
62+
local elem = select(i, ...)
63+
-- Put gen, param, state into param table.
64+
gen_x, param_x, state_x = wrap(ipairs(elem))
65+
param[3 * i - 2] = gen_x
66+
param[3 * i - 1] = param_x
67+
param[3 * i] = state_x
68+
end
69+
70+
return wrap(chain_gen_r1, param, {1, param[3]})
71+
end
72+
73+
----- Reproducer. ------------------------------------------------
74+
75+
-- XXX: Should be different tables.
76+
local a = {{'a'}, {'a'}}
77+
local b = {{'a'}, {'a'}}
78+
79+
-- XXX: Here one can find the rationale for the 'hotloop' value.
80+
-- 1. The innermost loop in a bunch of calls tries to compile the
81+
-- `__call` metamethod first, but this trace is aborted due to
82+
-- leaving the `for _ in` loop.
83+
-- 2. The trace we are interested in started to compile: this is
84+
-- the inner `for _ in` loop with the full inlined body.
85+
--
86+
-- The chain loop in this case iterates over 4 elements. Hence, 2
87+
-- iterations of the outer loop are not enough. The trace
88+
-- mentioned in 2 is recorded on the 8th inner cycle iteration but
89+
-- never started. Hence, let's run 3 iterations of the outer loop
90+
-- instead.
91+
92+
-- JIT fine-tuning via the 'hotloop' option allows us to catch
93+
-- this elusive case, which we achieved in the last bullet. The
94+
-- reason why this case leads to misbehaviour while restoring the
95+
-- guest stack at the trace exit is described in the following
96+
-- LuaJIT issue: https://github.com/LuaJIT/LuaJIT/issues/1295.
97+
98+
-- Specific `hotloop` to get only one trace we are interested in.
99+
jit.opt.start('hotloop=7')
100+
101+
xpcall(function()
102+
for _ = 1, 3 do
103+
for _ in chain(a, b) do
104+
end
105+
end
106+
test:ok('All emitted RENAMEs are fine')
107+
end, function()
108+
test:fail('Invalid Lua stack has been restored')
109+
end)
110+
111+
test:done(true)

0 commit comments

Comments
 (0)