<HTML><BODY><div class="cl-c5g3e17fu8"><div>Hi, Sergey!</div><div> </div><div>Thanks for your updates and for your answers, LGTM.<br><br> </div><div data-signature-widget="container"><div data-signature-widget="content"><div>--<br>Best regards,</div><div>Evgeniy Temirgaleev</div></div></div><br><div class="mail-quote-collapse"><blockquote style="border-left:1px solid #0857A6;margin:10px;padding:0 0 0 10px"><span>From: Sergey Kaplun <<a href="mailto:skaplun@tarantool.org">skaplun@tarantool.org</a>><br>To: Evgeniy Temirgaleev <<a href="mailto:e.temirgaleev@tarantool.org">e.temirgaleev@tarantool.org</a>><br>Cc: tarantool-patches@dev.tarantool.org, Sergey Bronnikov <<a href="mailto:sergeyb@tarantool.org">sergeyb@tarantool.org</a>><br>Date: Sunday, June 28, 2026 7:33 PM +03:00</span><br> <div><div id=""><div class="cl-z0l8px9n4a"><div class="js-helper_mr_css_attr js-readmsg-msg_mr_css_attr"><div id="style_17826643970950772660_mr_css_attr"><div id="style_17826643970950772660_BODY_mr_css_attr">Hi, Evgeniy!<br>Thanks for the review!<br>See my answers below.<br><br>Branch is force-pushed with the fixes.<br><br>On 28.06.26, Evgeniy Temirgaleev wrote:<br>> Hi, Sergey!<br>><br>> Thanks for the patch. Please, see my comments.<br>><br>> --<br>> Best regards,<br>> Evgeniy Temirgaleev<br>><br>> ><br>> > От кого: Sergey Kaplun <<a href="mailto:skaplun@tarantool.org">skaplun@tarantool.org</a>><br>> > Кому: Sergey Bronnikov <<a href="mailto:sergeyb@tarantool.org">sergeyb@tarantool.org</a>>, Evgeniy Temirgaleev <<a href="mailto:e.temirgaleev@tarantool.org">e.temirgaleev@tarantool.org</a><br>> > ><br>> > Копия: tarantool-patches@dev.tarantool.org, Sergey Kaplun <<a href="mailto:skaplun@tarantool.org">skaplun@tarantool.org</a><br>> > ><br>> > Дата: Четверг, 25 июня 2026, 23:29 +03:00<br>> > This patch adds dumpers for a single IR instruction (`lj-ir`), as well<br>> > as for all bytecodes inside one trace (`lj-trace`). Its dump is quite<br>> > similar to the -jdump flag but also reports types of register operands<br>> > (`ref`, `lit`, `cst`) and operation mode (`N`, `A`, `W`, etc.).<br>> > The `lj-trace` command accepts optional /rs flags to dump registers<br>> > associated with IR and snapshots for the trace correspondingly.<br>> > The `lj-ir` command can be used for dumping IR constants as well.<br>> > The `lj-jslots` command dumps the content of `J->slot`. It is useful to<br>> > simplify debugging of `rec_check_slots()` assertion failures.<br>> ><br>> > For LLDB value, the `__getitem__` metamethod now accepts bool keys.<br>> > Also, `__index__` is set to allow lldb.value to be used as an index<br>> > without explicit conversion to int. Old GDB versions (below 7.12) are<br>> > not supported because of the gdb.Value lacks the `__index__` metamethod<br>> > and can't be monkey-patched. The support for these versions may be added<br>> > by demand.<br>> ><br>> > Part of tarantool/tarantool#4808<br>> > ---<br>> > src/luajit_dbg.py | 1216 ++++++++++++++++-<br>> > .../debug-extension-tests.py | 365 +++++<br>> > 2 files changed, 1570 insertions(+), 11 deletions(-)<br>> ><br>> > diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>> > index 2edb199a..fd6ca8a5 100644<br>> > --- a/src/luajit_dbg.py<br>> > +++ b/src/luajit_dbg.py<br><br><snipped><br><br>> > + if tp.GetTypeClass() == lldb.eTypeClassStruct:<br>> > + len_fields = tp.GetNumberOfFields()<br>> > + for n_field in range(len_fields):<br>> > + islast = n_field == (len_fields - 1)<br>> > + field = tp.GetFieldAtIndex(n_field)<br>> > + start_field = field.GetOffsetInBytes()<br>> > + if not islast:<br>> > + end_field = tp.GetFieldAtIndex(<br>> > + n_field + 1<br>> > + ).GetOffsetInBytes()<br>> > + else:<br>> > + end_field = tp.GetByteSize()<br>> > + if start_field <= offset and offset < end_field:<br>> > + next_name = self.member_by_offset(<br>> > + field.GetType(),<br>> > + offset - start_field,<br>> > + prev_name=field.GetName()<br>> > + )<br>> > + return '.{field}{suffix}'.format(<br>> > + field=field.GetName(),<br>> > + suffix=next_name if next_name else ''<br>> > + )<br>> > + if tp.GetTypeClass() == lldb.eTypeClassArray:<br>> ><br>><br>> Typo?: elif<br><br>Fixed, thanks!<br><br>===================================================================<br>diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>index 62cd65d5..3b7cf7a1 100644<br>--- a/src/luajit_dbg.py<br>+++ b/src/luajit_dbg.py<br>@@ -657,7 +657,7 @@ class _LLDBDebugger(Debugger):<br>field=field.GetName(),<br>suffix=next_name if next_name else ''<br>)<br>- if tp.GetTypeClass() == lldb.eTypeClassArray:<br>+ elif tp.GetTypeClass() == lldb.eTypeClassArray:<br># Get array field type.<br>target = tp.GetArrayElementType()<br>tsize = target.GetByteSize()<br>===================================================================<br><br><snipped><br><br>> > +# Mode bits: Commutative, {Normal/Ref, Alloc, Load, Store},<br>> > +# Non-weak guard. */<br>> ><br>><br>> Typo: C comment end */<br><br>Removed, thanks!<br><br>===================================================================<br>diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>index 62cd65d5..3b7cf7a1 100644<br>@@ -1612,12 +1612,16 @@ IRS = [<br><br><br># Mode bits: Commutative, {Normal/Ref, Alloc, Load, Store},<br>-# Non-weak guard. */<br>+# Non-weak guard.<br>IRM_C = 0x10<br>IRM_A = 0x20<br>IRM_L = 0x40<br>===================================================================<br><br>><br>> ><br>> > +IRM_C = 0x10<br>> > +IRM_A = 0x20<br>> > +IRM_L = 0x40<br>> > +IRM_S = 0x60<br>> > +IRM_W = 0x80<br>> > +<br>> > +<br>> > +# IR operand mode (2 bit).<br>> > +IRM = [<br>> > + 'ref',<br>> > + 'lit',<br>> > + 'cst',<br>> > + '', # none<br>> > +]<br>> > +<br>> > +<br>> > +lj_ir_mode_ = None<br>> > +<br>> > +<br>> > +def lj_ir_mode():<br>> > + global lj_ir_mode_<br>> > + if lj_ir_mode_:<br>> > + return lj_ir_mode_<br>> > + lj_ir_mode_ = dbg.lookup_global('lj_ir_mode')<br>> > + return lj_ir_mode_<br>> > +<br>> > +<br>> > +def ir_left(op):<br>> > + return IRM[int(lj_ir_mode()[op] & 3)]<br>> ><br>><br>> May be binary constant will be more clear? xxx & 0b0011<br><br>I prefer to leave it consistent with the original sources, see<br><src/lj_ir.h>. Also, the bc decoding has the same format.<br><br>><br>> ><br>> > +<br>> > +<br>> > +def ir_right(op):<br>> > + return IRM[int(lj_ir_mode()[op] >> 2 & 3)]<br>> ><br>><br>> May be binary constant will be more clear? (xxx & 0b1100) >> 2<br><br>Ditto.<br><br>><br>> ><br>> > +<br>> > +<br>> > +def ir_mode(op):<br>> > + mode = ''<br>> > + ir_mode = int(lj_ir_mode()[op] ^ IRM_W)<br>> ><br>><br>> ><br>> > + if ir_mode == IRM_C:<br>> > + mode = 'C'<br>> > + elif ir_mode == IRM_A:<br>> > + mode = 'A'<br>> > + elif ir_mode == IRM_L:<br>> > + mode = 'L'<br>> > + elif ir_mode == IRM_S:<br>> > + mode = 'S'<br>> > + else:<br>> > + mode = 'N'<br>> ><br>><br>> ><br>> > + mode += 'W' if ir_mode & IRM_W else ''<br>> ><br>><br>> May be table with 16 items and comments will be more clear? E. g. return XXX[(lj_ir_mode()[op] & 0b11110000) >> 4]<br>> And it will contain invalid values also.<br>> # <flag bits in a comment><br>> XXX[0b0000] = ‘NW’ # Normal/Ref | !Non-weak guard<br>> XXX[0b0001] = ‘CW’ # Commutative | !Non-weak guard<br>> XXX[0b0011] = ‘Invalid’<br>> ...<br>> XXX[0b1000] = ‘N’ # Normal/Ref | Non-weak guard<br>> XXX[0b1001] = ‘C’ # Commutative | Non-weak guard<br>> XXX[0b1011] = ‘Invalid’<br>> ...<br><br>Rewrote with table usage as the following:<br>Also, you help me to notice that the original implementation was<br>incorrect (due to bits of operand modes after xor). Tests was corrupted<br>as well, fixed. Thanks!<br><br>===================================================================<br>diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>index d4f89eb5..32b0cea7 100644<br>--- a/src/luajit_dbg.py<br>+++ b/src/luajit_dbg.py<br>@@ -1613,11 +1613,15 @@ IRS = [<br><br># Mode bits: Commutative, {Normal/Ref, Alloc, Load, Store},<br># Non-weak guard.<br>-IRM_C = 0x10<br>-IRM_A = 0x20<br>-IRM_L = 0x40<br>-IRM_S = 0x60<br>-IRM_W = 0x80<br>+IRM_BITS_W = 0x80<br>+IRM_BITS = {<br>+ 0x00: 'N',<br>+ 0x10: 'C',<br>+ 0x20: 'A',<br>+ 0x40: 'L',<br>+ 0x60: 'S',<br>+}<br>+IRM_BITS_MASK = 0x70<br><br><br># IR operand mode (2 bit).<br>@@ -1649,19 +1653,10 @@ def ir_right(op):<br><br><br>def ir_mode(op):<br>- mode = ''<br>- ir_mode = int(lj_ir_mode()[op] ^ IRM_W)<br>- if ir_mode == IRM_C:<br>- mode = 'C'<br>- elif ir_mode == IRM_A:<br>- mode = 'A'<br>- elif ir_mode == IRM_L:<br>- mode = 'L'<br>- elif ir_mode == IRM_S:<br>- mode = 'S'<br>- else:<br>- mode = 'N'<br>- mode += 'W' if ir_mode & IRM_W else ''<br>+ irmode = int((lj_ir_mode()[op]))<br>+ isweak = not bool(irmode & IRM_BITS_W)<br>+ mode = IRM_BITS[irmode & IRM_BITS_MASK]<br>+ mode += 'W' if isweak else ''<br>return mode<br><br><br>diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py<br>index 8e069fe0..f17de27e 100644<br>--- a/test/tarantool-debugger-tests/debug-extension-tests.py<br>+++ b/test/tarantool-debugger-tests/debug-extension-tests.py<br>@@ -530,12 +530,12 @@ class TestLJTraceBase(TestCaseBase):<br>r'\t*proto: ' + RX_ADDR + r'\n' +<br>r'\t*BC: ' + RX_ADDR + r'\n' +<br>r'---- TRACE IR\n' +<br>- RX_IRN + r'\s+ int SLOAD \[N \] lit: #[12] lit: C?I\n' +<br>+ RX_IRN + r'\s+ int SLOAD \[L \] lit: #[12] lit: C?I\n' +<br>RX_IRN + r'\s+ \+ int ADD \[C \] ref: ' + RX_IRN +<br>r' ref: integer 1\n' +<br>RX_IRN + r'\s+ > int LE \[N \] ref: ' + RX_IRN +<br>r' ref: integer 4\n' +<br>- RX_IRN + r'\s+ > --- LOOP \[N \]\s*\n' +<br>+ RX_IRN + r'\s+ > --- LOOP \[S \]\s*\n' +<br>RX_IRN + r'\s+ \+ int ADD \[C \] ref: ' + RX_IRN +<br>r' ref: integer 1\n' +<br>RX_IRN + r'\s+ > int LE \[N \] ref: ' + RX_IRN +<br>===================================================================<br><br>But intentionally didn't use bit notation to be consistent with original<br>declarations in <src/lj_ir.h>. I've used the mask to strip lower bits<br>related to "NonWeak" guard and operand modes.<br><br><snipped><br><br>> > +# Don't use *[ to be compatible with Python 2.<br>> > +REGISTERS = {'x64': [<br>> > + 'rax',<br>> > + 'rcx',<br>> > + 'rdx',<br>> > + 'rbx',<br>> > + 'rsp',<br>> > + 'rbp',<br>> > + 'rsi',<br>> > + 'rdi',<br>> > +] + [<br>> > + 'r{}'.format(i) for i in range(8, 16) # r8 .. r15<br>> > +] + [<br>> > + 'xmm{}'.format(i) for i in range(0, 16) # xmm0 .. xmm15<br>> > +], 'arm64': [<br>> > + 'x{}'.format(i) for i in range(0, 31) # x0 .. x30<br>> > +] + ['sp'] + [ # x31<br>> > + 'd{}'.format(i) for i in range(0, 32) # d0 .. d31<br>> > +]}<br>> ><br>><br>> It seems, the ‘arm64’ registers are missed.<br><br>Actiually no, but I understand your confusion.<br>Reformated as the following:<br>===================================================================<br>diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>index 32b0cea7..a79caad0 100644<br>--- a/src/luajit_dbg.py<br>+++ b/src/luajit_dbg.py<br>@@ -1728,24 +1728,29 @@ IRFPMS = [<br><br><br># Don't use *[ to be compatible with Python 2.<br>-REGISTERS = {'x64': [<br>- 'rax',<br>- 'rcx',<br>- 'rdx',<br>- 'rbx',<br>- 'rsp',<br>- 'rbp',<br>- 'rsi',<br>- 'rdi',<br>-] + [<br>- 'r{}'.format(i) for i in range(8, 16) # r8 .. r15<br>-] + [<br>- 'xmm{}'.format(i) for i in range(0, 16) # xmm0 .. xmm15<br>-], 'arm64': [<br>- 'x{}'.format(i) for i in range(0, 31) # x0 .. x30<br>-] + ['sp'] + [ # x31<br>- 'd{}'.format(i) for i in range(0, 32) # d0 .. d31<br>-]}<br>+REGISTERS = {<br>+ 'x64': [<br>+ 'rax',<br>+ 'rcx',<br>+ 'rdx',<br>+ 'rbx',<br>+ 'rsp',<br>+ 'rbp',<br>+ 'rsi',<br>+ 'rdi',<br>+ ] + [<br>+ 'r{}'.format(i) for i in range(8, 16) # r8 .. r15<br>+ ] + [<br>+ 'xmm{}'.format(i) for i in range(0, 16) # xmm0 .. xmm15<br>+ ],<br>+ 'arm64': [<br>+ 'x{}'.format(i) for i in range(0, 31) # x0 .. x30<br>+ ] + [<br>+ 'sp' # x31<br>+ ] + [<br>+ 'd{}'.format(i) for i in range(0, 32) # d0 .. d31<br>+ ]<br>+}<br><br><br>IR_CALLS = [<br>===================================================================<br><br>><br><br><snipped><br><br>> > +def litname_xload(mode):<br>> > + flags = ['-', 'R', 'V', 'RV', 'U', 'RU', 'VU', 'RVU']<br>> ><br>><br>> Does we need a range check as in litname_bufhdr()?<br><br>I prefer error raising if something goes wrong here (invalid IR or<br>incorrect extension implementation).<br><br>><br>> ><br>> > + return flags[mode]<br>> > +<br>> > +<br>> > +def litname_conv(mode):<br>> ><br>><br>> Does we need some range checking here?<br><br>Ditto.<br><br>><br>> ><br><br><snippped><br><br>> > +<br>> > +<br>> > +def irt_ismarked(t):<br>> > + return t['irt'] & IRT_MARK<br>> ><br>><br>> I propose explicit bool cast (!= 0) here and below.<br><br>Added:<br>===================================================================<br>diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py<br>index a79caad0..6b0827d9 100644<br>--- a/src/luajit_dbg.py<br>+++ b/src/luajit_dbg.py<br>@@ -1978,15 +1978,15 @@ def tref_ref(tr):<br><br><br>def irt_ismarked(t):<br>- return t['irt'] & IRT_MARK<br>+ return bool(t['irt'] & IRT_MARK)<br><br><br>def irt_isphi(t):<br>- return t['irt'] & IRT_ISPHI<br>+ return bool(t['irt'] & IRT_ISPHI)<br><br><br>def irt_isguard(t):<br>- return t['irt'] & IRT_GUARD<br>+ return bool(t['irt'] & IRT_GUARD)<br><br><br>def irt_toitype(irt):<br>===================================================================<br><br>><br>> ><br>> > +<br>> > +<br>> > +def irt_isphi(t):<br>> > + return t['irt'] & IRT_ISPHI<br>> > +<br>> > +<br>> > +def irt_isguard(t):<br>> > + return t['irt'] & IRT_GUARD<br>> > +<br>> > +<br><br><snipped><br><br>> ><br>> > --<br>> > 2.54.0<br>> ><br><br>--<br>Best regards,<br>Sergey Kaplun</div></div></div></div></div></div></blockquote></div></div></BODY></HTML>