|
| 1 | +local tap = require('tap') |
| 2 | +-- Test contains a reproducer for a problem when LuaJIT generates a wrong |
| 3 | +-- bytecode with a missed BC_UCLO instruction. |
| 4 | +local test = tap.test('lj-819-fix-missing-uclo'):skipcond({ |
| 5 | + ['Test requires JIT enabled'] = not jit.status(), |
| 6 | +}) |
| 7 | + |
| 8 | +test:plan(2) |
| 9 | + |
| 10 | +-- Let's take a look at listings Listing 1 and Listing 2 below with bytecode |
| 11 | +-- generated for a function missing_uclo() with and without a patch. |
| 12 | +-- Both listings contains two BC_UCLO instructions: |
| 13 | +-- - first one with id 0004 is generated for a statement 'break' inside |
| 14 | +-- condition, see label BC_UCLO1; |
| 15 | +-- - second one with id 0009 is generated for a statement 'return' inside |
| 16 | +-- a nested loop, see label BC_UCLO2; |
| 17 | +-- Both BC_UCLO's closes upvalues after leaving a function's scope. |
| 18 | +-- |
| 19 | +-- The problem is happen when fs_fixup_ret() traverses bytecode instructions in |
| 20 | +-- a function prototype, meets first BC_UCLO instruction (break) and forgives a |
| 21 | +-- second one (return). This leads to a wrong result produced by a function |
| 22 | +-- returned by missing_uclo() function. This also explains why do we need a |
| 23 | +-- dead code in reproducer - without first BC_UCLO fs_fixup_ret() successfully |
| 24 | +-- fixup BC_UCLO and problem does not appear. |
| 25 | +-- |
| 26 | +-- Listing 1. Bytecode with a fix. |
| 27 | +-- |
| 28 | +-- -- BYTECODE -- uclo.lua:1-59 |
| 29 | +-- 0001 => LOOP 0 => 0013 |
| 30 | +-- 0002 JMP 0 => 0003 |
| 31 | +-- 0003 => JMP 0 => 0005 |
| 32 | +-- 0004 UCLO 0 => 0013 |
| 33 | +-- 0005 => KPRI 0 0 |
| 34 | +-- 0006 => LOOP 1 => 0012 |
| 35 | +-- 0007 ISF 0 |
| 36 | +-- 0008 JMP 1 => 0010 |
| 37 | +-- 0009 UCLO 0 => 0014 |
| 38 | +-- 0010 => FNEW 0 0 ; uclo.lua:54 |
| 39 | +-- 0011 JMP 1 => 0006 |
| 40 | +-- 0012 => UCLO 0 => 0001 |
| 41 | +-- 0013 => RET0 0 1 |
| 42 | +-- 0014 => RET1 0 2 |
| 43 | +-- |
| 44 | +-- Listing 2. Bytecode without a fix. |
| 45 | +-- |
| 46 | +-- BYTECODE -- uclo.lua:1-59 |
| 47 | +-- 0001 => LOOP 0 => 0013 |
| 48 | +-- 0002 JMP 0 => 0003 |
| 49 | +-- 0003 => JMP 0 => 0005 |
| 50 | +-- 0004 UCLO 0 => 0013 |
| 51 | +-- 0005 => KPRI 0 0 |
| 52 | +-- 0006 => LOOP 1 => 0012 |
| 53 | +-- 0007 ISF 0 |
| 54 | +-- 0008 JMP 1 => 0010 |
| 55 | +-- 0009 UCLO 0 => 0014 |
| 56 | +-- 0010 => FNEW 0 0 ; uclo.lua:54 |
| 57 | +-- 0011 JMP 1 => 0006 |
| 58 | +-- 0012 => UCLO 0 => 0001 |
| 59 | +-- 0013 => RET0 0 1 |
| 60 | +-- 0014 => RET1 0 2 |
| 61 | +-- |
| 62 | +-- Listing 3. Changes in bytecode before and after a fix. |
| 63 | +-- |
| 64 | +-- @@ -11,11 +11,12 @@ |
| 65 | +-- 0006 => LOOP 1 => 0012 |
| 66 | +-- 0007 ISF 0 |
| 67 | +-- 0008 JMP 1 => 0010 |
| 68 | +-- -0009 RET1 0 2 |
| 69 | +-- +0009 UCLO 0 => 0014 |
| 70 | +-- 0010 => FNEW 0 0 ; uclo.lua:56 |
| 71 | +-- 0011 JMP 1 => 0006 |
| 72 | +-- 0012 => UCLO 0 => 0001 |
| 73 | +-- 0013 => RET0 0 1 |
| 74 | +-- +0014 => RET1 0 2 |
| 75 | +-- |
| 76 | +-- First testcase checks a correct bytecode generation by frontend |
| 77 | +-- and the second testcase checks consistency on a JIT compilation. |
| 78 | + |
| 79 | +local function missing_uclo() |
| 80 | + while true do -- luacheck: ignore |
| 81 | + -- Attention: it is not a dead code, it is a part of reproducer. |
| 82 | + -- label: BC_UCLO1 |
| 83 | + if false then |
| 84 | + break |
| 85 | + end |
| 86 | + local f |
| 87 | + while true do |
| 88 | + if f then |
| 89 | + -- label: BC_UCLO2 |
| 90 | + return f |
| 91 | + end |
| 92 | + f = function() |
| 93 | + return f |
| 94 | + end |
| 95 | + end |
| 96 | + end |
| 97 | +end |
| 98 | + |
| 99 | +local f = missing_uclo() |
| 100 | +local res = f() |
| 101 | +-- Without a patch we don't get here a function, because upvalue isn't closed |
| 102 | +-- as desirable. |
| 103 | +test:ok(type(res) == 'function', 'virtual machine consistency: type of returned value is correct') |
| 104 | + |
| 105 | +-- Make JIT compiler aggressive. |
| 106 | +jit.opt.start('hotloop=1') |
| 107 | + |
| 108 | +f = missing_uclo() |
| 109 | +f() |
| 110 | +f = missing_uclo() |
| 111 | +local _ |
| 112 | +_, res = pcall(f) |
| 113 | +test:ok(type(res) == 'function', 'consistency on compilation: type of returned value is correct') |
| 114 | + |
| 115 | +os.exit(test:check() and 0 or 1) |
0 commit comments