Skip to content

Commit 47f5383

Browse files
Mike Palligormunkin
authored andcommitted
Fix BC_UCLO insertion for returns.
Contributed by XmiliaH. (cherry-picked from commit 93a65d3) Patch fixes a problem when LuaJIT generates a wrong bytecode with a missed BC_UCLO instruction. When some of BC_RET bytecode instructions are not fixup-ed, due to an early return, if UCLO is obtained before, those leads to VM inconsistency after return from the function. Patch makes the following changes in bytecode (thats it, emits extra BC_UCLO instruction that closes upvalues): @@ -11,11 +11,12 @@ 0006 => LOOP 1 => 0012 0007 ISF 0 0008 JMP 1 => 0010 -0009 RET1 0 2 +0009 UCLO 0 => 0014 0010 => FNEW 0 0 ; uclo.lua:56 0011 JMP 1 => 0006 0012 => UCLO 0 => 0001 0013 => RET0 0 1 +0014 => RET1 0 2 NOTE: After emitting the bytecode instruction BC_FNEW fixup is not required, because FuncState will set a flag PROTO_CHILD that will trigger emitting a pair of instructions BC_UCLO and BC_RET (see <src/lj_parse.c:2355>) and BC_RET will close all upvalues from a base equal to 0. JIT compilation of missing_uclo() function without a patch with fix is failed: src/lj_record.c:135: rec_check_slots: Assertion `((((((tr))>>24) & IRT_TYPE) - (TRef)(IRT_NUM) <= (TRef)(IRT_INT-IRT_NUM)))' failed. (Thanks to Sergey Kaplun for discovering this!) Thus second testcase in a test covers a case with compilation as well. Sergey Bronnikov: * added the description and the test for the problem Part of tarantool/tarantool#8825 Helped-by: Sergey Kaplun <skaplun@tarantool.org> Signed-off-by: Sergey Bronnikov <sergeyb@tarantool.org> Reviewed-by: Maxim Kokryashkin <m.kokryashkin@tarantool.org> Reviewed-by: Sergey Kaplun <skaplun@tarantool.org> Signed-off-by: Igor Munkin <imun@tarantool.org>
1 parent fd97529 commit 47f5383

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

src/lj_parse.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1546,7 +1546,7 @@ static void fs_fixup_ret(FuncState *fs)
15461546
/* Replace with UCLO plus branch. */
15471547
fs->bcbase[pc].ins = BCINS_AD(BC_UCLO, 0, offset);
15481548
break;
1549-
case BC_UCLO:
1549+
case BC_FNEW:
15501550
return; /* We're done. */
15511551
default:
15521552
break;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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

Comments
 (0)