|
| 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