|
| 1 | +local tap = require('tap') |
| 2 | + |
| 3 | +-- Test file to demonstrate incorrect FOLD optimization for IR |
| 4 | +-- with REF_BASE operand across IR RETF. |
| 5 | +-- See also, https://github.com/LuaJIT/LuaJIT/issues/784. |
| 6 | + |
| 7 | +local test = tap.test('lj-784-cse-ref-base-over-retf'):skipcond({ |
| 8 | + ['Test requires JIT enabled'] = not jit.status(), |
| 9 | +}) |
| 10 | + |
| 11 | +test:plan(1) |
| 12 | + |
| 13 | +-- The RETF IR has a side effect: it shifts base when returning to |
| 14 | +-- a lower frame, i.e., it affects `REF_BASE` IR (0000) (thus, we |
| 15 | +-- can say that this IR is violating SSA form). |
| 16 | +-- So any optimization of IRs with `REF_BASE` as an operand across |
| 17 | +-- RETF IR may lead to incorrect optimizations. |
| 18 | +-- In this test, SUB uref REF_BASE IR was eliminated, so instead |
| 19 | +-- the following trace: |
| 20 | +-- |
| 21 | +-- 0004 p32 SUB 0003 0000 |
| 22 | +-- 0005 > p32 UGT 0004 +32 |
| 23 | +-- ... |
| 24 | +-- 0009 > p32 RETF proto: 0x407dc118 [0x407dc194] |
| 25 | +-- ... |
| 26 | +-- 0012 p32 SUB 0003 0000 |
| 27 | +-- 0013 > p32 UGT 0012 +72 |
| 28 | +-- |
| 29 | +-- We got the following: |
| 30 | +-- |
| 31 | +-- 0004 p32 SUB 0003 0000 |
| 32 | +-- 0005 > p32 UGT 0004 +32 |
| 33 | +-- ... |
| 34 | +-- 0009 > p32 RETF proto: 0x41ffe0c0 [0x41ffe13c] |
| 35 | +-- ... |
| 36 | +-- 0012 > p32 UGT 0004 +72 |
| 37 | +-- |
| 38 | +-- As you can see, the 0012 SUB IR is eliminated because it is the |
| 39 | +-- same as the 0004 IR. This leads to incorrect assertion guards |
| 40 | +-- in the resulted IR 0012 below. |
| 41 | + |
| 42 | +local MAGIC = 42 |
| 43 | +-- XXX: simplify `jit.dump()` output. |
| 44 | +local fmod = math.fmod |
| 45 | + |
| 46 | +local function exit_with_retf(closure) |
| 47 | + -- Forcify stitch. Any NYI is OK here. |
| 48 | + fmod(1, 1) |
| 49 | + -- Call the closure so that we have emitted `uref - REF_BASE`. |
| 50 | + closure(0) |
| 51 | + -- Exit with `IR_RETF`. This will change `REF_BASE`. |
| 52 | +end |
| 53 | + |
| 54 | +local function sub_uref_base(closure) |
| 55 | + local open_upvalue |
| 56 | + if closure == nil then |
| 57 | + closure = function(val) |
| 58 | + local old = open_upvalue |
| 59 | + open_upvalue = val |
| 60 | + return old |
| 61 | + end |
| 62 | + -- First, create an additional frame, so we got the trace, |
| 63 | + -- where the open upvalue reference is always < `REF_BASE`. |
| 64 | + sub_uref_base(closure) |
| 65 | + end |
| 66 | + for _ = 1, 4 do |
| 67 | + -- `closure` function is inherited from the previous frame. |
| 68 | + exit_with_retf(closure) |
| 69 | + open_upvalue = MAGIC |
| 70 | + -- The open upvalue guard will use CSE over `IR_RETF` for |
| 71 | + -- `uref - REF_BASE`. `IR_RETF` changed the value of |
| 72 | + -- `REF_BASE`. |
| 73 | + -- Thus, the guards afterwards take the wrong IR as the first |
| 74 | + -- operand, so they are not failed, and the wrong value is |
| 75 | + -- returned from the trace. |
| 76 | + open_upvalue = closure(0) |
| 77 | + end |
| 78 | + return open_upvalue |
| 79 | +end |
| 80 | + |
| 81 | +jit.opt.start('hotloop=1') |
| 82 | + |
| 83 | +local res = sub_uref_base() |
| 84 | +test:is(res, MAGIC, 'no SUB uref REF_BASE CSE across RETF') |
| 85 | + |
| 86 | +test:done(true) |
0 commit comments