From: Sergey Kaplun via Tarantool-patches <tarantool-patches@dev.tarantool.org> To: Sergey Bronnikov <estetus@gmail.com> Cc: tarantool-patches@dev.tarantool.org Subject: Re: [Tarantool-patches] [PATCH luajit][v2] Prevent loop in snap_usedef(). Date: Tue, 21 Jan 2025 15:27:53 +0300 [thread overview] Message-ID: <Z4-SyXYqzKeh18IK@root> (raw) In-Reply-To: <ee7355b49d019019ea5b8a5fa2fcfc779e64aae9.1737044333.git.sergeyb@tarantool.org> Hi, Sergey! Thanks for the patch and the fixes! LGTM, after fixing my several suggestions about comments rephrasing and a bunch of nits below. On 16.01.25, Sergey Bronnikov wrote: > Reported by XmiliaH. > > (cherry picked from commit 0e66fc96377853d898390f1a02723c54ec3a42f7) > > It is possible to get an infinite loop in a function `snap_usedef` > when a `UCLO` makes a tight loop. This infinite loop could happen > when `snap_usedef()` is called on trace exit processes UCLO It is called during trace recording (more precisely on creation of the snapshot for the guarded trace check). > bytecode instruction and this instruction attempts a jump with > negative value. The patch fixes the problem by checking a number > of slots in a jump argument and replace this value my `maxslot` if > a value is negative. Please also mention that "This means that no values will be purged from the snapshot". > > Sergey Bronnikov: > * added the description and the test for the problem > > Part of tarantool/tarantool#10709 > --- > > Branch: https://github.com/tarantool/luajit/tree/ligurio/lj-736-prevent-loop-in-snap_usedef > > Related issues: > * https://github.com/tarantool/tarantool/issues/10709 > * https://github.com/LuaJIT/LuaJIT/issues/736 > > v2 changes: > > - Updated test, now it hangs without patch with fix. > - Added more comments to the test with explanations. > > src/lj_snap.c | 7 +- > .../lj-736-BC_UCLO-triggers-infinite-loop.lua | 82 +++++++++++++++++++ > 2 files changed, 88 insertions(+), 1 deletion(-) > create mode 100644 test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua > > diff --git a/src/lj_snap.c b/src/lj_snap.c > index 8a33dc22..8d7bd868 100644 > --- a/src/lj_snap.c > +++ b/src/lj_snap.c <snipped> > diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua > new file mode 100644 > index 00000000..fb053e9a > --- /dev/null > +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua Filename suffix should be ".test.lua". The ctest doesn't recognize this file. > @@ -0,0 +1,82 @@ > +local tap = require('tap') > +local test = tap.test('lj-736-BC_UCLO-triggers-infinite-loop'):skipcond({ > + ['Test requires JIT enabled'] = not jit.status(), > +}) > + > +test:plan(2) > + > +-- Test reproduces an issue when BC_UCLO triggers an infinite loop. > +-- See details in https://github.com/LuaJIT/LuaJIT/issues/736. Nit: Lets be consistent with most of our test files and put the short description before the `tap.test()`: =================================================================== diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua index fb053e9a..f38098a8 100644 --- a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua @@ -1,13 +1,13 @@ local tap = require('tap') +-- Test file to demonstrate the infinite loop in LuaJIT during the +-- use-def analysis for upvalues. +-- See also https://github.com/LuaJIT/LuaJIT/issues/736. local test = tap.test('lj-736-BC_UCLO-triggers-infinite-loop'):skipcond({ ['Test requires JIT enabled'] = not jit.status(), }) test:plan(2) --- Test reproduces an issue when BC_UCLO triggers an infinite loop. --- See details in https://github.com/LuaJIT/LuaJIT/issues/736. --- -- Listing below demonstrates a problem - -- the bytecode UCLO on the line 13 makes a loop at 0013-0014: -- =================================================================== > +-- > +-- Listing below demonstrates a problem - > +-- the bytecode UCLO on the line 13 makes a loop at 0013-0014: > +-- > +-- - BYTECODE -- bc_uclo.lua:0-20 > +-- 0001 KPRI 0 0 > +-- 0002 FNEW 1 0 ; bc_uclo.lua:5 > +-- 0003 KSHORT 2 1 > +-- 0004 KSHORT 3 4 > +-- 0005 KSHORT 4 1 > +-- 0006 FORI 2 => 0011 > +-- 0007 => ISNEN 5 0 ; 2 > +-- 0008 JMP 6 => 0010 > +-- 0009 UCLO 0 => 0012 > +-- 0010 => FORL 2 => 0007 > +-- 0011 => UCLO 0 => 0012 > +-- 0012 => KPRI 0 0 > +-- 0013 UCLO 0 => 0012 > +-- 0014 FNEW 1 1 ; bc_uclo.lua:18 > +-- 0015 UCLO 0 => 0016 > +-- 0016 => RET0 0 1 I suggest to list only the necessary part of the bytecodes, also lets take the bytecode dump for this particular function: =================================================================== diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua index f38098a8..8d0558e9 100644 --- a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua @@ -8,27 +8,18 @@ local test = tap.test('lj-736-BC_UCLO-triggers-infinite-loop'):skipcond({ test:plan(2) --- Listing below demonstrates a problem - --- the bytecode UCLO on the line 13 makes a loop at 0013-0014: --- --- - BYTECODE -- bc_uclo.lua:0-20 --- 0001 KPRI 0 0 --- 0002 FNEW 1 0 ; bc_uclo.lua:5 --- 0003 KSHORT 2 1 --- 0004 KSHORT 3 4 --- 0005 KSHORT 4 1 --- 0006 FORI 2 => 0011 --- 0007 => ISNEN 5 0 ; 2 --- 0008 JMP 6 => 0010 --- 0009 UCLO 0 => 0012 --- 0010 => FORL 2 => 0007 --- 0011 => UCLO 0 => 0012 --- 0012 => KPRI 0 0 --- 0013 UCLO 0 => 0012 --- 0014 FNEW 1 1 ; bc_uclo.lua:18 --- 0015 UCLO 0 => 0016 --- 0016 => RET0 0 1 - +-- Before the patch, the code flow like in the `testcase()` below +-- may cause the problem -- use-def analysis for the 0019 UCLO +-- creates an infinite loop in 0014 - 0019: +-- | 0008 FORI base: 4 jump: => 0013 +-- | 0009 ISNEN var: 7 num: 0 ; number 2 +-- | 0010 JMP rbase: 8 jump: => 0012 +-- | 0011 UCLO rbase: 2 jump: => 0014 +-- | 0012 FORL base: 4 jump: => 0009 +-- | 0013 UCLO rbase: 2 jump: => 0014 +-- | 0014 KPRI dst: 2 pri: 0 ; Start of `assert()` line. +-- | ... +-- | 0019 UCLO rbase: 2 jump: => 0014 jit.opt.start('hotloop=1') local assert_msg = 'Infinite loop is not reproduced.' =================================================================== > + > +jit.opt.start('hotloop=1') > + > +local assert_msg = 'Infinite loop is not reproduced.' > +local assert = assert > + > +local function testcase() > + -- The code in the first scope `do`/`end` is a prerequisite. > + -- It is needed so that we have a trace at the exit from which > + -- the creation of the snapshot will begin. The needed part is not the creation of the snapshot but the use-def analysis for the first (`uv1`) UCLO. I would rephrase it like the following: =================================================================== diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua index 8d0558e9..3e6508d1 100644 --- a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua @@ -27,11 +27,9 @@ local assert = assert local function testcase() -- The code in the first scope `do`/`end` is a prerequisite. - -- It is needed so that we have a trace at the exit from which - -- the creation of the snapshot will begin. + -- It contains the UCLO instruction for the `uv1`. The use-def + -- analysis for it escapes this `do`/`end` scope. do - -- Upvalue below is not used actually, but it is required - -- for calling `snap_usedef()` on trace exit. local uv1 -- luacheck: ignore local _ = function() return uv1 end =================================================================== > + do > + -- Upvalue below is not used actually, but it is required > + -- for calling `snap_usedef()` on trace exit. Side note: The use-def analysis is called on trace recording. I would rather merge this comment with the comment above, as you can see in the diff. > + local uv1 -- luacheck: ignore Nit: It is better to use "no unused" here to be more specific. > + local _ = function() return uv1 end > + > + -- The loop below is required for recording a trace. > + -- The condition inside a loop executes `goto` to a label > + -- outside of the loop when the code executed by JIT and > + -- this triggers snapshotting. > + for i = 1, 2 do > + -- Exit to interpreter once trace is compiled. I would rephrase this 2 comments, since they a little bit misleading: =================================================================== diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua index 3e6508d1..a95651fd 100644 --- a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua @@ -33,12 +33,11 @@ local function testcase() local uv1 -- luacheck: ignore local _ = function() return uv1 end - -- The loop below is required for recording a trace. - -- The condition inside a loop executes `goto` to a label - -- outside of the loop when the code executed by JIT and - -- this triggers snapshotting. + -- Records the trace for which use-def analysis is applied. for i = 1, 2 do - -- Exit to interpreter once trace is compiled. + -- This condition triggers snapshoting and use-def analysis. + -- Before the patch this triggers the infinite loop in the + -- `snap_usedef()`, so the `goto` is never taken. if i == 2 then goto x end =================================================================== > + if i == 2 then > + goto x > + end > + end > + end > + > +::x:: > + do > + local uv2 -- luacheck: no unused > + > + -- `goto` if not executed without a patch and generates an > + -- UCLO bytecode that makes an infinite loop in a function > + -- `snap_usedef` when patch is not applied. `goto` must point > + -- to the label on one of the previous lines. `assert()` is > + -- executed when patch is applied. > + assert(nil, assert_msg) > + goto x > + > + -- Line below is required, it makes `uv` upvalue, and must be > + -- placed after `goto`, otherwise reproducer become broken. > + local _ = function() return uv2 end -- luacheck: ignore I would also rephrase this 2 comments like the following since they are repeated what we say before: =================================================================== diff --git a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua index a95651fd..8111d752 100644 --- a/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua +++ b/test/tarantool-tests/lj-736-BC_UCLO-triggers-infinite-loop.lua @@ -47,17 +47,11 @@ local function testcase() ::x:: do local uv2 -- luacheck: no unused - - -- `goto` if not executed without a patch and generates an - -- UCLO bytecode that makes an infinite loop in a function - -- `snap_usedef` when patch is not applied. `goto` must point - -- to the label on one of the previous lines. `assert()` is - -- executed when patch is applied. assert(nil, assert_msg) + -- Create a tight loop for the one more upvalue (`uv2`). + -- Before the patch, use-def analysis gets stuck in this code + -- flow. goto x - - -- Line below is required, it makes `uv` upvalue, and must be - -- placed after `goto`, otherwise reproducer become broken. local _ = function() return uv2 end -- luacheck: ignore end end =================================================================== Also I would add the following comment: | -- This code is unreachable by design. Prevent luacheck warning. > + end > +end > + > +local ok, err = pcall(testcase) > + > +test:is(ok, false, 'assertion is triggered in a function with testcase') > +test:ok(err:match(assert_msg), 'BC_UCLO does not trigger an infinite loop') I would rather say here 'correct error message'. Or, as an alternative, we may just use 1 test check: | pcall(testcase) | | test:ok(true, 'no infinite loop in the use-def analysis') instead of these 2 checks. > + > +os.exit(test:check() and 0 or 1) Minor: Please use `test:done(true)` like in all other tests. > -- > 2.34.1 > -- Best regards, Sergey Kaplun
next prev parent reply other threads:[~2025-01-21 12:28 UTC|newest] Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top 2025-01-16 16:36 Sergey Bronnikov via Tarantool-patches 2025-01-21 12:27 ` Sergey Kaplun via Tarantool-patches [this message] 2025-01-21 14:43 ` Sergey Bronnikov via Tarantool-patches 2025-01-21 15:01 ` Sergey Kaplun via Tarantool-patches 2025-01-31 9:29 ` Sergey Kaplun via Tarantool-patches
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=Z4-SyXYqzKeh18IK@root \ --to=tarantool-patches@dev.tarantool.org \ --cc=estetus@gmail.com \ --cc=skaplun@tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH luajit][v2] Prevent loop in snap_usedef().' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox