|
| 1 | +local tap = require('tap') |
| 2 | + |
| 3 | +local test = tap.test('lj-584-bad-renames-for-sunk-values') |
| 4 | +test:plan(1) |
| 5 | + |
| 6 | +-- Test file to demonstrate LuaJIT assembler misbehaviour. |
| 7 | +-- For more info, proceed to the issues: |
| 8 | +-- * https://github.com/LuaJIT/LuaJIT/issues/584 |
| 9 | +-- * https://github.com/tarantool/tarantool/issues/4252 |
| 10 | + |
| 11 | +----- Related part of luafun.lua. -------------------------------- |
| 12 | + |
| 13 | +local iterator_mt = { |
| 14 | + __call = function(self, param, state) return self.gen(param, state) end, |
| 15 | +} |
| 16 | + |
| 17 | +local wrap = function(gen, param, state) |
| 18 | + return setmetatable({ |
| 19 | + gen = gen, |
| 20 | + param = param, |
| 21 | + state = state |
| 22 | + }, iterator_mt), param, state |
| 23 | +end |
| 24 | + |
| 25 | +-- These functions call each other to implement a flat iterator |
| 26 | +-- over the several iterable objects. |
| 27 | +local chain_gen_r1, chain_gen_r2 |
| 28 | + |
| 29 | +chain_gen_r2 = function(param, state, state_x, ...) |
| 30 | + if state_x ~= nil then return { state[1], state_x }, ... end |
| 31 | + local i = state[1] + 1 |
| 32 | + if param[3 * i - 1] == nil then return nil end |
| 33 | + return chain_gen_r1(param, { i, param[3 * i] }) |
| 34 | +end |
| 35 | + |
| 36 | +chain_gen_r1 = function(param, state) |
| 37 | + local i, state_x = state[1], state[2] |
| 38 | + local gen_x, param_x = param[3 * i - 2], param[3 * i - 1] |
| 39 | + return chain_gen_r2(param, state, gen_x(param_x, state_x)) |
| 40 | +end |
| 41 | + |
| 42 | +local chain = function(...) |
| 43 | + local param = { } |
| 44 | + for i = 1, select('#', ...) do |
| 45 | + -- Put gen, param, state into param table. |
| 46 | + param[3 * i - 2], param[3 * i - 1], param[3 * i] |
| 47 | + = wrap(ipairs(select(i, ...))) |
| 48 | + end |
| 49 | + return wrap(chain_gen_r1, param, { 1, param[3] }) |
| 50 | +end |
| 51 | + |
| 52 | +----- Reproducer. ------------------------------------------------ |
| 53 | + |
| 54 | +-- XXX: Here one can find the rationale for the 'hotloop' value. |
| 55 | +-- 1. The most inner while loop on the line 86 starts recording |
| 56 | +-- for the third element (i.e. 'c') and successfully compiles |
| 57 | +-- as TRACE 1. However, its execution stops, since type guard |
| 58 | +-- for <gen_x> result value on line 39 is violated (nil is |
| 59 | +-- returned from <ipairs_aux>) and trace execution is stopped. |
| 60 | +-- 2. Next time TRACE 1 enters the field is iterating through the |
| 61 | +-- second table given to <chain>. Its execution also stops at |
| 62 | +-- the similar assertion but in the variant part this time. |
| 63 | +-- 3. <wrap> function becomes hot enough while building new |
| 64 | +-- <chain> iterator, and it is compiled as TRACE 2. |
| 65 | +-- There are also other attempts, but all of them failed. |
| 66 | +-- 4. Again, TRACE 1 reigns while iterating through the first |
| 67 | +-- table given to <chain> and finishes at the same guard the |
| 68 | +-- previous run does. Anyway, everything above is just an |
| 69 | +-- auxiliary activity preparing the JIT environment for the |
| 70 | +-- following result. |
| 71 | +-- 5. Here we finally come: <chain_gen_r1> is finally ready to be |
| 72 | +-- recorded. It successfully compiles as TRACE 3. However, the |
| 73 | +-- boundary case is recorded, so the trace execution stops |
| 74 | +-- since nil *is not* returned from <ipairs_aux> on the next |
| 75 | +-- iteration. |
| 76 | +-- |
| 77 | +-- JIT fine tuning via 'hotloop' option allows to catch this |
| 78 | +-- elusive case, we achieved in a last bullet. The reason, why |
| 79 | +-- this case leads to a misbehaviour while restoring the guest |
| 80 | +-- stack at the trace exit, is described in the following LuaJIT |
| 81 | +-- issue: https://github.com/LuaJIT/LuaJIT/issues/584. |
| 82 | +jit.opt.start('hotloop=3') |
| 83 | + |
| 84 | +xpcall(function() |
| 85 | + for _ = 1, 3 do |
| 86 | + local gen_x, param_x, state_x = chain({ 'a', 'b', 'c' }, { 'q', 'w', 'e' }) |
| 87 | + while true do |
| 88 | + state_x = gen_x(param_x, state_x) |
| 89 | + if state_x == nil then break end |
| 90 | + end |
| 91 | + end |
| 92 | + test:ok('All emitted RENAMEs are fine') |
| 93 | +end, function() |
| 94 | + test:fail('Invalid Lua stack has been restored') |
| 95 | +end) |
| 96 | + |
| 97 | +os.exit(test:check() and 0 or 1) |
0 commit comments