[Tarantool-patches] [PATCH luajit] Fix IR_ABC hoisting.

Sergey Bronnikov sergeyb at tarantool.org
Thu Mar 20 15:22:46 MSK 2025


Hi, Sergey!

thanks for the patch! LGTM with minor comments that can be ignored.

Sergey

On 27.02.2025 19:00, Sergey Kaplun wrote:
> From: Mike Pall <mike>
>
> Reported by pwnhacker0x18. Fixed by Peter Cawley.
>
> (cherry picked from commit 7369eff67d46d7f5fac9ee064e3fbf97a15458de)
>
> The `IR_ABC` has 2 different types:
> * `IRT_INT` for generic condition check.
> * `IRT_P32` as a marker for the invariant checks for upper and lower
>    boundaries of the loop index. These checks may be dropped during the
>    folding optimization in recording the variant part of the loop.
> The checks may be dropped if the instruction is not PHI, the first
> operand is invariant, and the upper bound of the array part of the table
> isn't changed, or the table itself isn't changed on the trace.
>
> The checks in the `abc_invar` fold rule assume that the constant values
> of the first operand of ABC may be dropped. But when this constant value
> comes from the folding chain that does CSE, the check is still necessary
> if the original instruction (not resulting after loop optimization
> substitution and the folding chain) isn't a constant.
>
> Hence, this patch additionally singularizes the `IRT_U32` type for the
> `IR_ABC` in the case when the asize is a constant on the trace so it can
> be simply dropped from the invariant part of the loop. For `IRT_P32`
> type, this patch adds a check that the first operand isn't a constant to
> be sure that it isn't a result of the fold chain.
>
> Sergey Kaplun:
> * added the description and the test for the problem
>
> Part of tarantool/tarantool#11055
> ---
>
> Branch:https://github.com/tarantool/luajit/tree/skaplun/lj-1194-abc-hoisting
> Related issues:
> *https://github.com/LuaJIT/LuaJIT/issues/1194
> *https://github.com/tarantool/tarantool/issues/11055
>
>   src/lj_opt_fold.c                             |  5 +-
>   src/lj_record.c                               |  5 +-
>   .../lj-1194-abc-hoisting.test.lua             | 77 +++++++++++++++++++
>   3 files changed, 83 insertions(+), 4 deletions(-)
>   create mode 100644 test/tarantool-tests/lj-1194-abc-hoisting.test.lua
>
> diff --git a/src/lj_opt_fold.c b/src/lj_opt_fold.c
> index cd4395bb..83105816 100644
> --- a/src/lj_opt_fold.c
> +++ b/src/lj_opt_fold.c
> @@ -1906,9 +1906,10 @@ LJFOLDF(abc_k)
>   LJFOLD(ABC any any)
>   LJFOLDF(abc_invar)
>   {
> -  /* Invariant ABC marked as PTR. Drop if op1 is invariant, too. */
> +  /* Invariant ABC marked as P32 or U32. Drop if op1 is invariant too. */
>     if (!irt_isint(fins->t) && fins->op1 < J->chain[IR_LOOP] &&
> -      !irt_isphi(IR(fins->op1)->t))
> +      (irt_isu32(fins->t) ||
> +       (!irref_isk(fins->op1) && !irt_isphi(IR(fins->op1)->t))))
>       return DROPFOLD;
>     return NEXTFOLD;
>   }
> diff --git a/src/lj_record.c b/src/lj_record.c
> index 5345fa63..3dc6e840 100644
> --- a/src/lj_record.c
> +++ b/src/lj_record.c
> @@ -1315,12 +1315,13 @@ static void rec_idx_abc(jit_State *J, TRef asizeref, TRef ikey, uint32_t asize)
>         /* Runtime value for stop of loop is within bounds? */
>         if ((uint64_t)stop + ofs < (uint64_t)asize) {
>   	/* Emit invariant bounds check for stop. */
> -	emitir(IRTG(IR_ABC, IRT_P32), asizeref, ofs == 0 ? J->scev.stop :
> +	uint32_t abc = IRTG(IR_ABC, tref_isk(asizeref) ? IRT_U32 : IRT_P32);
> +	emitir(abc, asizeref, ofs == 0 ? J->scev.stop :
>   	       emitir(IRTI(IR_ADD), J->scev.stop, ofsref));
>   	/* Emit invariant bounds check for start, if not const or negative. */
>   	if (!(J->scev.dir && J->scev.start &&
>   	      (int64_t)IR(J->scev.start)->i + ofs >= 0))
> -	  emitir(IRTG(IR_ABC, IRT_P32), asizeref, ikey);
> +	  emitir(abc, asizeref, ikey);
>   	return;
>         }
>       }
> diff --git a/test/tarantool-tests/lj-1194-abc-hoisting.test.lua b/test/tarantool-tests/lj-1194-abc-hoisting.test.lua
> new file mode 100644
> index 00000000..3a78c34e
> --- /dev/null
> +++ b/test/tarantool-tests/lj-1194-abc-hoisting.test.lua
> @@ -0,0 +1,77 @@
> +local tap = require('tap')
> +
> +-- Test file to demonstrate LuaJIT's incorrect hoisting out of the
> +-- loop for Array Bound Check.
> +-- See alsohttps://github.com/LuaJIT/LuaJIT/issues/1194.
> +
> +local test = tap.test('lj-1194-abc-hoisting'):skipcond({
> +  ['Test requires JIT enabled'] = not jit.status(),
> +})
> +
> +test:plan(1)
> +
> +local table_new = require('table.new')
> +
> +-- Before the patch, the `for` cycle in the `test_func()` below
> +-- produces the following trace:
> +--
> +-- | 0006    int FLOAD  0005  tab.asize
> +-- | 0007 >  p32 ABC    0006  0001
> +-- | 0008    p32 FLOAD  0005  tab.array
> +-- | 0009    p32 AREF   0008  0003
> +-- | 0010    tab FLOAD  0005  tab.meta
> +-- | 0011 >  tab EQ     0010  NULL
> +-- | 0012    nil ASTORE 0009  nil
> +-- | 0013 >+ tab TNEW   #0    #0
> +-- | 0014  + int ADD    0003  +1
> +-- | 0015 >  int LE     0014  0001
> +-- | 0016 ------ LOOP ------------
> +-- | 0017 >  int NE     0014  +0
> +-- | 0018    p32 FLOAD  0013  tab.array
> +-- | 0019    p32 AREF   0018  0014
> +-- | 0020    nil ASTORE 0019  nil
> +--
> +-- As you can see, the `0007 ABC` instruction is dropped from the
> +-- variant part of the loop.
> +
> +-- Disable fusing to simplify the reproducer it now will be crash
> +-- on loading of an address from the `t->array`.
> +jit.opt.start('hotloop=1', '-fuse')
> +
> +local function test_func()
> +  -- 1 iteration for hotcount warm-up. 2 and 3 to record invariant

As usually we care about readers, I propose to make text more 
human-firendly:

s/1/The first/

s/2 and 3/second and third/

> +  -- and variant parts of the loop. The trace is run via an
> +  -- additional call to this function.
> +  local limit = 3
> +  -- Create a table with a fixed array size (`limit` + 1), so all
> +  -- access to it fits into the array part.
> +  local s = table_new(limit, 0)
> +  for i = 1, limit do
> +    -- Don't change the table on hotcount warm-up.
> +    if i ~= 1 then
> +      -- `0020 ASTORE` causes the SegFault on the trace on the
> +      -- last (3rd) iteration, since the table (set on the first
> +      -- iteration) has `asize == 0`, and t->array == NULL`.
> +      -- luacheck: no unused
> +      s[i] = nil
> +      s = {}
> +    end
> +  end
> +end
> +
> +-- Compile the `for` trace inside `test_func()`.
> +-- The invariant part of this trace contains the ABC check, while
> +-- the invariant does not. So, first compile the trace, then use
> +-- the second call to run it from the beginning with all guards
> +-- passing in the invariant part.
> +test_func()
> +
> +-- Avoid compilation of any other traces.
> +jit.off()
> +
> +-- Run that trace.
> +test_func()
> +
> +test:ok(true, 'no crash on the trace due to incorrect ABC hoisting')
> +
> +test:done(true)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.tarantool.org/pipermail/tarantool-patches/attachments/20250320/67c68b74/attachment.htm>


More information about the Tarantool-patches mailing list