From: Sergey Bronnikov via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: tarantool-patches@dev.tarantool.org,
Sergey Kaplun <skaplun@tarantool.org>,
e.temirgaleev@tarantool.org
Subject: [Tarantool-patches] [PATCH luajit] Optionally return PC position in jit.util.tracesnap().
Date: Thu, 8 Oct 2026 11:44:35 +0000 [thread overview]
Message-ID: <befa92e38394efa80b1b77d136c2c3d3ff39eb25.1791459869.git.estetus@gmail.com> (raw)
From: Mike Pall <mike>
Suggested by Sergey Bronnikov.
(cherry picked from commit 8e6520a7aecd0517e792b359afbbfd7274791f5f)
The optional `getpos` argument is needed for ljopt [1], a tool that
verifies the correctness of LuaJIT optimizations by translating the
recorded IR into SMT-LIB and checking the formulas with an SMT solver.
Traces often lack sufficient information for accurate modeling, so
ljopt extracts the address of the bytecode pointed to by the snapshot
directly from LuaJIT during execution.
The LuaJIT internal API includes the function `jit.util.tracesnap(tr,
sn)`. In principle, this function could be extended to return the
program counter (pc). This patch does exactly that: an optional third
argument is added, and when it is truthy the second value returned by
`jit.util.tracesnap()` is the PC position of the snapshot in the
bytecode of the function where the trace starts.
Also, the patch adds a test that records a root loop trace and
checks that the returned position is the `FORL` jump target (the
loop entry), that no position is returned without the optional
argument or with a false value, and that every snapshot of the
trace reports a valid bytecode offset.
1. https://github.com/ligurio/ljopt
Sergey Bronnikov:
* added the description and the test for the feature
---
Branch: https://github.com/tarantool/luajit/tree/ligurio/gh-xxxx-tracesnap-getpos
src/lib_jit.c | 9 +-
.../lj-1472-tracesnap-getpos.test.lua | 89 +++++++++++++++++++
2 files changed, 97 insertions(+), 1 deletion(-)
create mode 100644 test/tarantool-tests/lj-1472-tracesnap-getpos.test.lua
diff --git a/src/lib_jit.c b/src/lib_jit.c
index 9f870f68f..5a0d38611 100644
--- a/src/lib_jit.c
+++ b/src/lib_jit.c
@@ -351,11 +351,12 @@ LJLIB_CF(jit_util_tracek)
return 0;
}
-/* local snap = jit.util.tracesnap(tr, sn) */
+/* local snap = jit.util.tracesnap(tr, sn[, getpos]) */
LJLIB_CF(jit_util_tracesnap)
{
GCtrace *T = jit_checktrace(L);
SnapNo sn = (SnapNo)lj_lib_checkint(L, 2);
+ int getpos = (L->base+2 < L->top && tvistruecond(L->base+2));
if (T && sn < T->nsnap) {
SnapShot *snap = &T->snap[sn];
SnapEntry *map = &T->snapmap[snap->mapofs];
@@ -368,6 +369,12 @@ LJLIB_CF(jit_util_tracesnap)
for (n = 0; n < nent; n++)
setintV(lj_tab_setint(L, t, (int32_t)(n+2)), (int32_t)map[n]);
setintV(lj_tab_setint(L, t, (int32_t)(nent+2)), (int32_t)SNAP(255, 0, 0));
+ if (getpos) {
+ const BCIns *pc = snap_pc(&map[nent]), *startpc = pc;
+ while (bc_op(*startpc) < BC_FUNCF) startpc--;
+ setintV(L->top++, (int)(pc - startpc));
+ return 2;
+ }
return 1;
}
return 0;
diff --git a/test/tarantool-tests/lj-1472-tracesnap-getpos.test.lua b/test/tarantool-tests/lj-1472-tracesnap-getpos.test.lua
new file mode 100644
index 000000000..a56a7a679
--- /dev/null
+++ b/test/tarantool-tests/lj-1472-tracesnap-getpos.test.lua
@@ -0,0 +1,89 @@
+local tap = require('tap')
+local bit = require('bit')
+local jutil = require('jit.util')
+local vmdef = require('jit.vmdef')
+
+-- The test checks the optional `getpos` argument of
+-- `jit.util.tracesnap()` that makes the function return the PC
+-- position of the snapshot in the function bytecode.
+-- See also https://github.com/LuaJIT/LuaJIT/issues/1472.
+
+local test = tap.test('lj-1472-tracesnap-getpos'):skipcond({
+ ['Test requires JIT enabled'] = not jit.status(),
+})
+
+test:plan(8)
+
+-- Entries in `vmdef.bcnames` are 6 characters long.
+local BC_NAME_LENGTH = 6
+
+-- Return the name of the bytecode operation for the given
+-- instruction, that is encoded in the low byte.
+local function opname(ins)
+ local oidx = BC_NAME_LENGTH * bit.band(ins, 0xff)
+ local name = vmdef.bcnames:sub(oidx + 1, oidx + BC_NAME_LENGTH)
+ return (name:gsub('%s+$', ''))
+end
+
+local function payload()
+ local sum = 0
+ for i = 1, 100 do
+ sum = sum + i
+ end
+ return sum
+end
+
+-- Find the loop back-edge (FORL) in the function bytecode and
+-- compute its jump target, that is the first instruction of the
+-- loop body. `pc == 0` is the JCproto header, so real
+-- instructions start from `pc == 1`. JIT compilation is disabled
+-- while scanning, so this loop is not recorded as a trace.
+jit.off()
+local nbc = jutil.funcinfo(payload).bytecodes
+local loop_entry
+for pc = 1, nbc - 1 do
+ local ins = jutil.funcbc(payload, pc)
+ if opname(ins) == 'FORL' then
+ -- The jump offset is relative to the next instruction and is
+ -- biased by `BCBIAS_J`.
+ local jmp = bit.rshift(ins, 16) - 0x8000
+ loop_entry = pc + 1 + jmp
+ break
+ end
+end
+test:isnt(loop_entry, nil, 'FORL instruction is found')
+
+-- Record a root loop trace in `payload`. Snapshot #0 points to
+-- the loop entry (the FORL jump target).
+jit.on()
+jit.flush()
+jit.opt.start('hotloop=1')
+payload()
+
+local info = jutil.traceinfo(1)
+test:ok(info, 'the root loop trace is recorded')
+
+local snap, pos = jutil.tracesnap(1, 0, true)
+test:istable(snap, 'the snapshot table is returned')
+test:is(pos, loop_entry, 'the PC position points to the loop entry')
+
+local _, pos_without = jutil.tracesnap(1, 0)
+test:is(pos_without, nil, 'no PC position without getpos')
+
+local _, pos_false = jutil.tracesnap(1, 0, false)
+test:is(pos_false, nil, 'no PC position with getpos=false')
+
+local all_valid = true
+for sn = 0, info.nexit - 1 do
+ local _, p = jutil.tracesnap(1, sn, true)
+ if type(p) ~= 'number' or p < 0 or p >= nbc or
+ jutil.funcbc(payload, p) == nil then
+ all_valid = false
+ end
+end
+test:ok(all_valid, 'positions of all snapshots are valid offsets')
+
+test:is(select('#', jutil.tracesnap(1, info.nexit, true)), 0,
+ 'out-of-range snapshot returns nothing')
+
+test:done(true)
--
2.51.0
reply other threads:[~2026-10-08 11:45 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=befa92e38394efa80b1b77d136c2c3d3ff39eb25.1791459869.git.estetus@gmail.com \
--to=tarantool-patches@dev.tarantool.org \
--cc=e.temirgaleev@tarantool.org \
--cc=estetus@gmail.com \
--cc=skaplun@tarantool.org \
--subject='Re: [Tarantool-patches] [PATCH luajit] Optionally return PC position in jit.util.tracesnap().' \
/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