From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 00A93437874; Tue, 3 Mar 2026 13:44:44 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 00A93437874 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1772534685; bh=ZK0Ql4EgouMOtmEVJwSsJJQT47JBUSQaJL8XJ+T++LY=; h=Date:To:Cc:References:In-Reply-To:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=SVGgOxg+hUx+m10l5iOzzZhZo2Wj10nlIf5DAce3uSbyistH8gD9y2kAGQihYF039 L3JRATn8zxh/zId31xl+SrgezLh0zQStooRq37nhOIGyRqt8C3GkLSYo6wJd4cgZhh oG2t8i7t2rgk1XOQu1bCmeBCewNuAWptaHC1BtQo= Received: from send57.i.mail.ru (send57.i.mail.ru [89.221.237.152]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 432FA437874 for ; Tue, 3 Mar 2026 13:44:43 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 432FA437874 Received: by exim-smtp-558f87dcd7-trgb7 with esmtpa (envelope-from ) id 1vxNF4-00000000GWj-0Vyr; Tue, 03 Mar 2026 13:44:42 +0300 Content-Type: multipart/alternative; boundary="------------PHwJb8NTN0tCUueTAyurZeOT" Message-ID: <0aee43fd-f0a6-410b-b67b-10461724a66f@tarantool.org> Date: Tue, 3 Mar 2026 13:44:41 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US To: Sergey Kaplun Cc: tarantool-patches@dev.tarantool.org References: <20260225121140.18847-1-skaplun@tarantool.org> In-Reply-To: <20260225121140.18847-1-skaplun@tarantool.org> X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: EEAE043A70213CC8 X-77F55803: 4F1203BC0FB41BD9B83249A38DAD341BB117451084EABF5E434F3916F9322781182A05F5380850404C228DA9ACA6FE279A080278733C6B973DE06ABAFEAF6705700065D561ACD797157B2E6BC30E067258F334736D7A8F2F X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7F8E53417176C7207EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB55337566CF7C7957E472532883ACC3018400F369C2C867631CA97F1556B113DCFE304E06389733CBF5DBD5E913377AFFFEAFD269176DF2183F8FC7C0ECC8AC47CD0EDEFF8941B15DA834481FCF19DD082D7633A0EF3E4896CB9E6436389733CBF5DBD5E9D5E8D9A59859A8B636DA1BED736F9328CC7F00164DA146DA6F5DAA56C3B73B237318B6A418E8EAB8D32BA5DBAC0009BE9E8FC8737B5C22494705F91287EA2BDB76E601842F6C81A12EF20D2F80756B5FB606B96278B59C4276E601842F6C81A127C277FBC8AE2E8BA1EEF9386886340E3AA81AA40904B5D99C9F4D5AE37F343AD1F44FA8B9022EA23BBE47FD9DD3FB595F5C1EE8F4F765FC72CEEB2601E22B093A03B725D353964B0B7D0EA88DDEDAC722CA9DD8327EE4930A3850AC1BE2E735026D3A1080F4EF5CC4224003CC83647689D4C264860C145E X-C1DE0DAB: 0D63561A33F958A540B3A63E696BEA5C5002B1117B3ED696E63B9D6CC4D469FD6E5F408120975D33823CB91A9FED034534781492E4B8EEAD17AEC49845D0B908 X-C8649E89: 1C3962B70DF3F0AD73CAD6646DEDE191716CD42B3DD1D34CAB70F9BE574AE9C625B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D659A68AC863EFB210BC9CC540352071693FBAF4DC219D5E781ECC2EBDC5767FDBC5D32F620769C94E13B8341EE9D5BE9A0AD164467A102DA7ABD958E4500BBC05D2D487FD76F18E3E3A8CD93680B12512CF4C41F94D744909CE2512F26BEC029E55448553D2254B8D95CD72808BE417F3B9E0E7457915DAA85F X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVbwN8XFWZxQUkhS5FI5/BVM= X-Mailru-Sender: C4F68CFF4024C8867DFDF7C7F2588458F25B6DB28C2D86BB1356DD24693B53BC24334582E4452849E2B5EDAE2FAEEC47645D15D82EE4B272BD6E4642A116CA93524AA66B5ACBE6721EF430B9A63E2A504198E0F3ECE9B5443453F38A29522196 X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH luajit] Prevent snapshot purge while recording a function header. X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" This is a multi-part message in MIME format. --------------PHwJb8NTN0tCUueTAyurZeOT Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Hi, Sergey, thanks for the patch! LGTM with a minor comment. Sergey On 2/25/26 15:11, Sergey Kaplun wrote: > From: Mike Pall > > Thanks to Sergey Kaplun. > > (cherry picked from commit d459c6ce503e880dc30aefb6b61aa7f2124c7a6e) > > This patch is a follow-up to the commit > 7505e78bd6c24cac6e93f5163675021734801b65 "Handle on-trace OOM errors > from helper functions.". Since this commit, the `pcall()` (and > `xpcall()`) emits an additional snapshot, but the arguments to the > pcall-ed function are momentarily purged from the snapshot since they > are not used. Hence, in several cases `debug.getlocal()` can't find the > corresponding slot for the previous frame. > > This patch prevents purging right after `pcall()`, `xpcall()` recording. > > Sergey Kaplun: > * added the description and the test for the problem > > Part of tarantool/tarantool#12134 > --- > > Branch:https://github.com/tarantool/luajit/tree/skaplun/lj-1425-pcall-snap-purge > Related issues: > *https://github.com/LuaJIT/LuaJIT/issues/1425 > *https://github.com/tarantool/tarantool/issues/12134 > > src/lj_record.c | 2 +- > .../lj-1425-pcall-snap-purge.test.lua | 93 +++++++++++++++++++ > 2 files changed, 94 insertions(+), 1 deletion(-) > create mode 100644 test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua > > diff --git a/src/lj_record.c b/src/lj_record.c > index ba409a61..e2e86f79 100644 > --- a/src/lj_record.c > +++ b/src/lj_record.c > @@ -2120,7 +2120,7 @@ void lj_record_ins(jit_State *J) > /* Need snapshot before recording next bytecode (e.g. after a store). */ > if (J->needsnap) { > J->needsnap = 0; > - if (J->pt) lj_snap_purge(J); > + if (J->pt && bc_op(*J->pc) < BC_FUNCF) lj_snap_purge(J); > lj_snap_add(J); > J->mergesnap = 1; > } > diff --git a/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua b/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua > new file mode 100644 > index 00000000..353842f1 > --- /dev/null > +++ b/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua > @@ -0,0 +1,93 @@ > +local tap = require('tap') > + > +-- Test file to demonstrate incorrect snapshot purge for the > +-- `pcall()` and `xpcall()`. > +-- See also:https://github.com/LuaJIT/LuaJIT/issues/1425. > + > +local test = tap.test('lj-1425-pcall-snap-purge'):skipcond({ > + ['Test requires JIT enabled'] = not jit.status(), > +}) > + > +-- `pcall()` and `xpcall()`. > +test:plan(2) > + > +-- XXX: simplify `jit.dump()` output. > +local type = type > +local pcall = pcall > +local xpcall = xpcall > +local math_modf = math.modf > +local debug_getlocal = debug.getlocal > + > +local checkers = {} > + > +-- Called twice for the pseudo-type that aliases base Lua type via > +-- checkers map. > +local function checks(expected_type) > + -- Value expected to be `checks_tab()` or `checks_obj()` > + -- argument. It is always a table. > + local _, value = debug_getlocal(2, 1) > + -- Simple stitching function. Additional arguments are needed to > + -- occupy the corresponding slot. It is not clear for me why this corresponding slot is needed. The bug can be reproduced without double nil's passed to math_modf(). > + math_modf(0, nil, nil) > + -- Start trace now, one iteration only. > + -- luacheck: ignore 512 > + while true do > + -- Base type? > + if type(value) == expected_type then > + return true > + end > + -- Pseudo types fallbacks to the map. > + local checker = checkers[expected_type] > + -- For the xpcall. > + if checker(value) == true then > + return true > + end > + break > + end > + error('Unreachable path taken') > +end > + > +-- Need to be pcalled. > +local function checks_tab(_) > + checks('table') > +end > + > +local function checks_tab_p(map) > + return pcall(checks_tab, map) > +end > + > +local function nop() > +end > + > +local function checks_tab_xp(map) > + return xpcall(checks_tab, nop, map) > +end > + > +local function checks_obj(_) > + checks('obj') > +end > + > +local function check_ff(name, checks_func) > +test:test(name, function(subtest) > +subtest:plan(1) > + > + checkers['obj'] = checks_func > + > + jit.flush() > + jit.opt.start('hotloop=1') > + > + checks_obj({}) > + -- Forcify stack reallocation on trace in `checks()`. The > + -- source stack lacks the needed slot. > + coroutine.wrap(function() > + checks_obj({}) > + end)() > + > +subtest:ok(true, 'No error for ' .. name) > + end) > +end > + > +check_ff('pcall', checks_tab_p) > +check_ff('xpcall', checks_tab_xp) > + > +test:done(true) --------------PHwJb8NTN0tCUueTAyurZeOT Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit

Hi, Sergey,

thanks for the patch! LGTM with a minor comment.

Sergey

On 2/25/26 15:11, Sergey Kaplun wrote:
From: Mike Pall <mike>

Thanks to Sergey Kaplun.

(cherry picked from commit d459c6ce503e880dc30aefb6b61aa7f2124c7a6e)

This patch is a follow-up to the commit
7505e78bd6c24cac6e93f5163675021734801b65 "Handle on-trace OOM errors
from helper functions.". Since this commit, the `pcall()` (and
`xpcall()`) emits an additional snapshot, but the arguments to the
pcall-ed function are momentarily purged from the snapshot since they
are not used. Hence, in several cases `debug.getlocal()` can't find the
corresponding slot for the previous frame.

This patch prevents purging right after `pcall()`, `xpcall()` recording.

Sergey Kaplun:
* added the description and the test for the problem

Part of tarantool/tarantool#12134
---

Branch: https://github.com/tarantool/luajit/tree/skaplun/lj-1425-pcall-snap-purge
Related issues:
* https://github.com/LuaJIT/LuaJIT/issues/1425
* https://github.com/tarantool/tarantool/issues/12134

 src/lj_record.c                               |  2 +-
 .../lj-1425-pcall-snap-purge.test.lua         | 93 +++++++++++++++++++
 2 files changed, 94 insertions(+), 1 deletion(-)
 create mode 100644 test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua

diff --git a/src/lj_record.c b/src/lj_record.c
index ba409a61..e2e86f79 100644
--- a/src/lj_record.c
+++ b/src/lj_record.c
@@ -2120,7 +2120,7 @@ void lj_record_ins(jit_State *J)
   /* Need snapshot before recording next bytecode (e.g. after a store). */
   if (J->needsnap) {
     J->needsnap = 0;
-    if (J->pt) lj_snap_purge(J);
+    if (J->pt && bc_op(*J->pc) < BC_FUNCF) lj_snap_purge(J);
     lj_snap_add(J);
     J->mergesnap = 1;
   }
diff --git a/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua b/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua
new file mode 100644
index 00000000..353842f1
--- /dev/null
+++ b/test/tarantool-tests/lj-1425-pcall-snap-purge.test.lua
@@ -0,0 +1,93 @@
+local tap = require('tap')
+
+-- Test file to demonstrate incorrect snapshot purge for the
+-- `pcall()` and `xpcall()`.
+-- See also: https://github.com/LuaJIT/LuaJIT/issues/1425.
+
+local test = tap.test('lj-1425-pcall-snap-purge'):skipcond({
+  ['Test requires JIT enabled'] = not jit.status(),
+})
+
+-- `pcall()` and `xpcall()`.
+test:plan(2)
+
+-- XXX: simplify `jit.dump()` output.
+local type = type
+local pcall = pcall
+local xpcall = xpcall
+local math_modf = math.modf
+local debug_getlocal = debug.getlocal
+
+local checkers = {}
+
+-- Called twice for the pseudo-type that aliases base Lua type via
+-- checkers map.
+local function checks(expected_type)
+  -- Value expected to be `checks_tab()` or `checks_obj()`
+  -- argument. It is always a table.
+  local _, value = debug_getlocal(2, 1)
+  -- Simple stitching function. Additional arguments are needed to
+  -- occupy the corresponding slot.

It is not clear for me why this corresponding slot is needed.

The bug can be reproduced without double nil's passed to math_modf().

+  math_modf(0, nil, nil)
+  -- Start trace now, one iteration only.
+  -- luacheck: ignore 512
+  while true do
+    -- Base type?
+    if type(value) == expected_type then
+      return true
+    end
+    -- Pseudo types fallbacks to the map.
+    local checker = checkers[expected_type]
+    -- For the xpcall.
+    if checker(value) == true then
+      return true
+    end
+    break
+  end
+  error('Unreachable path taken')
+end
+
+-- Need to be pcalled.
+local function checks_tab(_)
+  checks('table')
+end
+
+local function checks_tab_p(map)
+  return pcall(checks_tab, map)
+end
+
+local function nop()
+end
+
+local function checks_tab_xp(map)
+  return xpcall(checks_tab, nop, map)
+end
+
+local function checks_obj(_)
+  checks('obj')
+end
+
+local function check_ff(name, checks_func)
+  test:test(name, function(subtest)
+    subtest:plan(1)
+
+    checkers['obj'] = checks_func
+
+    jit.flush()
+    jit.opt.start('hotloop=1')
+
+    checks_obj({})
+    -- Forcify stack reallocation on trace in `checks()`. The
+    -- source stack lacks the needed slot.
+    coroutine.wrap(function()
+      checks_obj({})
+    end)()
+
+    subtest:ok(true, 'No error for ' .. name)
+  end)
+end
+
+check_ff('pcall', checks_tab_p)
+check_ff('xpcall', checks_tab_xp)
+
+test:done(true)
--------------PHwJb8NTN0tCUueTAyurZeOT--