Skip to content

Commit dc10bef

Browse files
Mike Palligormunkin
Mike Pall
authored andcommitted
Emit sunk IR_NEWREF only once per key on snapshot replay.
Thanks to Sergey Kaplun and Peter Cawley. (cherry-picked from commit 1761fd2) Assume we have the parent trace with the following IRs: | 0001 } tab TNEW #0 #0 | 0002 } p32 NEWREF 0001 "key" | 0003 } fal HSTORE 0002 false | .... SNAP #1 [ ---- ---- 0001 ---- ] | 0004 > num SLOAD #1 T | .... SNAP #2 [ ---- ---- 0001 ] | 0005 > num EQ 0004 0004 | 0006 } tru HSTORE 0002 true | .... SNAP #3 [ ---- ---- 0001 true ] The side trace for the third snapshot emits the following IRs: | 0001 tab TNEW #0 #0 | 0002 p32 NEWREF 0001 "key" | 0003 fal HSTORE 0002 false | 0004 p32 NEWREF 0001 "key" | 0005 tru HSTORE 0004 true As we can see, `NEWREF` is emitted twice. This is a violation of its semantics, so the second store isn't noticeable. This patch prevents the second emitting of IR NEWREF by checking the last one emitted NEWREF IR. There is no need to check NEWREFs beyond since it guarantees the snapshot is taken after it, because it may cause table rehashing, so all prior results are invalidated. Sergey Kaplun: * added the description and the test for the problem Resolves tarantool/tarantool#7937 Part of tarantool/tarantool#9145 Reviewed-by: Maxim Kokryashkin <[email protected]> Reviewed-by: Sergey Bronnikov <[email protected]> Signed-off-by: Igor Munkin <[email protected]> (cherry picked from commit 005e8ce)
1 parent 3493258 commit dc10bef

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

src/lj_snap.c

+16
Original file line numberDiff line numberDiff line change
@@ -583,9 +583,25 @@ void lj_snap_replay(jit_State *J, GCtrace *T)
583583
if (irr->o == IR_HREFK || irr->o == IR_AREF) {
584584
IRIns *irf = &T->ir[irr->op1];
585585
tmp = emitir(irf->ot, tmp, irf->op2);
586+
} else if (irr->o == IR_NEWREF) {
587+
IRRef allocref = tref_ref(tr);
588+
IRRef keyref = tref_ref(key);
589+
IRRef newref_ref = J->chain[IR_NEWREF];
590+
IRIns *newref = &J->cur.ir[newref_ref];
591+
lj_assertJ(irref_isk(keyref),
592+
"sunk store for parent IR %04d with bad key %04d",
593+
refp - REF_BIAS, keyref - REF_BIAS);
594+
if (newref_ref > allocref && newref->op2 == keyref) {
595+
lj_assertJ(newref->op1 == allocref,
596+
"sunk store for parent IR %04d with bad tab %04d",
597+
refp - REF_BIAS, allocref - REF_BIAS);
598+
tmp = newref_ref;
599+
goto skip_newref;
600+
}
586601
}
587602
}
588603
tmp = emitir(irr->ot, tmp, key);
604+
skip_newref:
589605
val = snap_pref(J, T, map, nent, seen, irs->op2);
590606
if (val == 0) {
591607
IRIns *irc = &T->ir[irs->op2];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
local tap = require('tap')
2+
3+
-- Test file to demonstrate LuaJIT incorrect restoring of sunk
4+
-- tables with double usage of IR_NEWREF.
5+
-- See also: https://github.com/LuaJIT/LuaJIT/issues/1128.
6+
7+
local test = tap.test('lj-1128-double-ir-newref-on-restore-sunk'):skipcond({
8+
['Test requires JIT enabled'] = not jit.status(),
9+
})
10+
11+
test:plan(3)
12+
13+
local take_side
14+
15+
local function trace_base(num)
16+
local tab = {}
17+
tab.key = false
18+
-- This check can't be folded since `num` can be NaN.
19+
tab.key = num == num
20+
-- luacheck: ignore
21+
-- This side trace emits the following IRs:
22+
-- 0001 tab TNEW #0 #0
23+
-- 0002 p64 NEWREF 0001 "key"
24+
-- 0003 fal HSTORE 0002 false
25+
-- 0004 p64 NEWREF 0001 "key"
26+
-- 0005 tru HSTORE 0004 true
27+
-- As we can see, `NEWREF` is emitted twice. This is a violation
28+
-- of its semantics, so the second store isn't noticeable.
29+
if take_side then end
30+
return tab.key
31+
end
32+
33+
-- Uncompiled function to end up side trace here.
34+
local function trace_base_wp(num)
35+
return trace_base(num)
36+
end
37+
jit.off(trace_base_wp)
38+
39+
-- Same function as above, but with two IRs NEWREF emitted.
40+
-- The last NEWREF references another key.
41+
local function trace_2newref(num)
42+
local tab = {}
43+
tab.key1 = false
44+
-- This + op can't be folded since `num` can be -0.
45+
tab.key1 = num + 0
46+
tab.key2 = false
47+
-- This check can't be folded since `num` can be NaN.
48+
tab.key2 = num == num
49+
-- luacheck: ignore
50+
if take_side then end
51+
return tab.key1, tab.key2
52+
end
53+
54+
-- Uncompiled function to end up side trace here.
55+
local function trace_2newref_wp(num)
56+
return trace_2newref(num)
57+
end
58+
jit.off(trace_2newref_wp)
59+
60+
jit.opt.start('hotloop=1', 'hotexit=1', 'tryside=1')
61+
62+
-- Compile parent traces.
63+
trace_base_wp(0)
64+
trace_base_wp(0)
65+
trace_2newref_wp(0)
66+
trace_2newref_wp(0)
67+
68+
-- Compile side traces.
69+
take_side = true
70+
trace_base_wp(0)
71+
trace_base_wp(0)
72+
trace_2newref_wp(0)
73+
trace_2newref_wp(0)
74+
75+
test:is(trace_base(0), true, 'sunk value restored correctly')
76+
77+
local arg = 0
78+
local r1, r2 = trace_2newref(arg)
79+
-- These tests didn't fail before the patch.
80+
-- They check the patch's correctness.
81+
test:is(r1, arg, 'sunk value restored correctly with 2 keys, first key')
82+
test:is(r2, true, 'sunk value restored correctly with 2 keys, second key')
83+
84+
test:done(true)

0 commit comments

Comments
 (0)