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 514816ECF9; Thu, 25 Jun 2026 23:30:15 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 514816ECF9 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1782419415; bh=s6QEPwxM60Ndj9M/UlLmCF3x0biCvPeC7dZ/JLASb3Y=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=OTrSdzuDLB3H9Xa8Bfof6ZjUXE2J5gtwPtmcFgpJDUjD/T6aGr8Qz4i9MIbOibzRE B/GOM/ap7eMzoVB94WkOQ/hBLFjrHwQmbL+lyc+DEAHYxxEwV6sszsO3+322s2cIsh QGV4AiJe/5E1qNYbBOmby+GAVtvT9WbjMo5bMG6k= Received: from send150.i.mail.ru (send150.i.mail.ru [89.221.237.245]) (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 369D16ECF9 for ; Thu, 25 Jun 2026 23:29:44 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 369D16ECF9 Received: by exim-smtp-78b8b8c574-5xgpx with esmtpa (envelope-from ) id 1wcqhi-00000000ErT-37Jh; Thu, 25 Jun 2026 23:29:43 +0300 To: Sergey Bronnikov , Evgeniy Temirgaleev Date: Thu, 25 Jun 2026 23:29:01 +0300 Message-ID: <20260625202903.3157425-2-skaplun@tarantool.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260625202903.3157425-1-skaplun@tarantool.org> References: <20260625202903.3157425-1-skaplun@tarantool.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD918D6BB028DF8CB61AC5A1B0F33293F2579E1CDB0117D0826182A05F53808504036377C7D1EF367713DE06ABAFEAF67054A47EB9D61C1E9FF068316178BC98E6656AF9A33750679FD X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7F2EC3597058CFA6DEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB5533756637495F66AF669A39E21F7E4C795D5B9047292893F3A5C3AEA82CF7539F0C883E389733CBF5DBD5E913377AFFFEAFD269176DF2183F8FC7C0ECC8AC47CD0EDEFF8941B15DA834481FCF19DD082D7633A0EF3E4896CB9E6436389733CBF5DBD5E9D5E8D9A59859A8B636DA1BED736F9328CC7F00164DA146DA6F5DAA56C3B73B237318B6A418E8EAB86D1867E19FE14079C09775C1D3CA48CF17B107DEF921CE791DD303D21008E298D5E8D9A59859A8B6B372FE9A2E580EFC725E5C173C3A84C3C9BE88FFEDFA497A35872C767BF85DA2F004C90652538430E4A6367B16DE6309 X-C1DE0DAB: 0D63561A33F958A593C50874F4AF43605002B1117B3ED696514D31AFCE06FB878B25839F35DFE037823CB91A9FED034534781492E4B8EEAD1D9D41DD8D6ECF7DF36E2E0160E5C55395B8A2A0B6518DF68C46860778A80D548E8926FB43031F38 X-C8649E89: 1C3962B70DF3F0AD73CAD6646DEDE191716CD42B3DD1D34CAB70F9BE574AE9C625B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D6596971168C2E3EA05E126016036737B54BD78CCDE5A3A71543698B95DF91843BEEA80D1D56D83CF0AAB8341EE9D5BE9A0A6C320EE6597E72CFC5CAD4167DE3ECCC43EE36B2B747BDFFC7CEAA0681F5848F4C41F94D744909CECFA6C6B0C050A61A8CAF69B82BA93681CD72808BE417F3B9E0E7457915DAA85F X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVRSZSJkMhZtMuk2fqkyGcww= X-DA7885C5: FD7864483FBCEEFCF255D290C0D534F97ED3A73A7D26B2DEA5F963E7CAE401F82976ABFF975FDE6C5B1A4C17EAA7BC4BEF2421ABFA55128DAF83EF9164C44C7E X-Mailru-Sender: 689FA8AB762F7393520AF17B8A65FDE2BA659AA2B86C50712317B2BD31D3A7861D2DFCC016AFD9DCE49D44BB4BD9522A059A1ED8796F048DB274557F927329BE89D5A3BC2B10C37545BD1C3CC395C826B4A721A3011E896F X-Mras: Ok Subject: [Tarantool-patches] [PATCH luajit 1/3] dbg: introduce lj-ir, lj-jslots, lj-trace dumpers 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 Kaplun via Tarantool-patches Reply-To: Sergey Kaplun Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" This patch adds dumpers for a single IR instruction (`lj-ir`), as well as for all bytecodes inside one trace (`lj-trace`). Its dump is quite similar to the -jdump flag but also reports types of register operands (`ref`, `lit`, `cst`) and operation mode (`N`, `A`, `W`, etc.). The `lj-trace` command accepts optional /rs flags to dump registers associated with IR and snapshots for the trace correspondingly. The `lj-ir` command can be used for dumping IR constants as well. The `lj-jslots` command dumps the content of `J->slot`. It is useful to simplify debugging of `rec_check_slots()` assertion failures. For LLDB value, the `__getitem__` metamethod now accepts bool keys. Also, `__index__` is set to allow lldb.value to be used as an index without explicit conversion to int. Old GDB versions (below 7.12) are not supported because of the gdb.Value lacks the `__index__` metamethod and can't be monkey-patched. The support for these versions may be added by demand. Part of tarantool/tarantool#4808 --- src/luajit_dbg.py | 1216 ++++++++++++++++- .../debug-extension-tests.py | 365 +++++ 2 files changed, 1570 insertions(+), 11 deletions(-) diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py index 2edb199a..fd6ca8a5 100644 --- a/src/luajit_dbg.py +++ b/src/luajit_dbg.py @@ -58,6 +58,26 @@ class Debugger(object): self.LLDB = True return super(Debugger, self).__new__(_LLDBDebugger) + def parse_flags(self, raw_flags, permitted_flags): + flags = {} + for flag in raw_flags: + if flag not in permitted_flags: + raise self.error('Unrecongnized option: "{}"'.format(flag)) + flags[flag] = True + return flags + + def extract_flags(self, arg, permitted_flags): + if not arg: + return None, None + flags = {} + if arg.startswith('/'): + match = re.match(r'/(\S*)\s+(.*)$', arg) + if not match: + return arg, flags + raw_flags, arg = match.group(1, 2) + flags = self.parse_flags(raw_flags, permitted_flags) + return arg, flags + def configure(self): global PADDING, LJ_TISNUM if not self.check_libluajit(): @@ -70,6 +90,17 @@ class Debugger(object): self.write('luajit_dbg.py failed to load: ' 'no debugging symbols found for libluajit\n') return False + + # Setup arch. + try: + self.arch = str(self.eval('LJ_ARCH_NAME')).split('"')[1] + except Exception: + try: + self.arch = self.detect_arch() + except Exception: + # Setup on demand if necessary. + pass + return True def initialize_extension(self, commands): @@ -99,21 +130,42 @@ class Debugger(object): '''Return the content of the string by the given pointer.''' pass + @abc.abstractmethod + def address(self, obj): + '''Return the address in memory of the given object.''' + pass + @abc.abstractmethod def lookup_global(self, symbol): '''Look up the global C symbol by the given name.''' pass + @abc.abstractmethod + def member_by_offset(self, typename, offset, prev_name=None): + '''Look up the global C symbol by the given name.''' + pass + @abc.abstractmethod def eval(self, command): '''Parse and evaluate the given debugger command.''' pass + @abc.abstractmethod + def detect_arch(self): + '''Detect the CPU architecture and canonicalize it to the LuaJIT + notation.''' + pass + @abc.abstractmethod def write(self, msg): '''Print the message.''' pass + @abc.abstractmethod + def error(self, msg): + '''Create the error object with message.''' + pass + @abc.abstractmethod def check_libluajit(self): '''Check that libluajit is loaded. @@ -172,10 +224,50 @@ class _GDBDebugger(Debugger): # A string is printed with a pointer to it. Just strip it. return re.sub(r'^0x[a-f0-9]+\s+(?=")', '', str(strptr)) + def address(self, obj): + return obj.address + def lookup_global(self, symbol): variable, _ = gdb.lookup_symbol(symbol) return variable.value() if variable else None + def member_by_offset(self, tp, offset, prev_name=None): + if isinstance(tp, str): + tp = self._dbgtype(tp) + assert offset < tp.sizeof, 'offset is bigger than object size' + if tp.code == gdb.TYPE_CODE_TYPEDEF: + tp = tp.strip_typedefs() + if tp.code == gdb.TYPE_CODE_STRUCT: + fields = tp.fields() + for n_field in range(len(fields)): + islast = n_field == (len(fields) - 1) + field = fields[n_field] + start_field = field.bitpos / 8 + end_field = fields[n_field + 1].bitpos / 8 if not islast \ + else tp.sizeof + if start_field <= offset and offset < end_field: + next_name = self.member_by_offset( + field.type, + offset - start_field, + prev_name=field.name + ) + return '.{field}{suffix}'.format( + field=field.name, + suffix=next_name if next_name else '' + ) + elif tp.code == gdb.TYPE_CODE_ARRAY: + # Get array field type. + target = tp.target() + tsize = target.sizeof + idx = int(offset // tsize) + next_name = self.member_by_offset(target, offset - idx * tsize) + idxname = idx_name(prev_name) + if idxname and idx in idxname: + idx = idxname[idx] + return '[{}]{}'.format(idx, next_name if next_name else '') + else: + return None + def eval(self, command): if not command: return None @@ -185,9 +277,23 @@ class _GDBDebugger(Debugger): raise gdb.GdbError('table argument empty') return ret + def detect_arch(self): + if hasattr(self, 'arch'): + return self.arch + target = str(gdb.execute('info target', False, True)) + if re.match('.*x86-64.*', target, flags=re.DOTALL): + return 'x64' + elif re.match('.*aarch64.*', target, flags=re.DOTALL): + return 'arm64' + else: + return '' + def write(self, msg): gdb.write(msg) + def error(self, errmsg): + return gdb.GdbError(errmsg) + def check_libluajit(self): # XXX Fragile: Though connecting the callback looks bad, # it respects both Python 2 and Python 3 (see #4828). @@ -322,8 +428,26 @@ class _LLDBDebugger(Debugger): def lldb__getitem__(lldbval, key): if type(key) is lldb.value: key = int(key) + if type(key) is bool: + key = int(key) if type(key) is int: # Allow array access. + ltp = lldbval.sbvalue.GetType() + # XXX: LLDB in versions 17 - 19 can't use an array + # object as the initializer for `lldb.value` since + # `GetValue()` for it returns `None` leading to + # the invalid result. See + # https://github.com/llvm/llvm-project/pull/90144. + if (self.version < 17 or self.version > 19) or \ + ltp.GetTypeClass() != lldb.eTypeClassArray: + pass + else: + ptr_tp = ltp.GetArrayElementType().GetPointerType() + lldbval = self._lldb_value_from_raw( + lldbval.sbvalue.GetLoadAddress(), + ptr_tp.GetByteSize(), + ptr_tp + ) if key >= 0 and not lldbval.sbvalue.TypeIsPointerType(): return lldb.value( lldbval.sbvalue.GetValueForExpressionPath('[%i]' % key) @@ -349,6 +473,9 @@ class _LLDBDebugger(Debugger): def lldb__gt__(lldbval, other): return int(lldbval) > int(other) + def lldb__index__(lldbval): + return int(lldbval) + def lldb__le__(lldbval, other): return int(lldbval) <= int(other) @@ -406,6 +533,7 @@ class _LLDBDebugger(Debugger): lldb.value.__ge__ = lldb__ge__ lldb.value.__getitem__ = lldb__getitem__ lldb.value.__gt__ = lldb__gt__ + lldb.value.__index__ = lldb__index__ lldb.value.__le__ = lldb__le__ lldb.value.__lt__ = lldb__lt__ lldb.value.__str__ = lldb__str__ @@ -474,6 +602,9 @@ class _LLDBDebugger(Debugger): def cstr(self, strptr): return strptr.sbvalue.summary + def address(self, obj): + return lldb.value(obj.sbvalue.address_of) + def lookup_global(self, symbol): sbvalue = self.target.FindFirstGlobalVariable(symbol) tp = sbvalue.GetType() @@ -492,6 +623,46 @@ class _LLDBDebugger(Debugger): ptr_tp ) + def member_by_offset(self, tp, offset, prev_name=None): + if isinstance(tp, str): + tp = self._dbgtype(tp) + assert offset < tp.GetByteSize(), 'offset is bigger than object size' + tp = tp.GetCanonicalType() + if tp.GetTypeClass() == lldb.eTypeClassStruct: + len_fields = tp.GetNumberOfFields() + for n_field in range(len_fields): + islast = n_field == (len_fields - 1) + field = tp.GetFieldAtIndex(n_field) + start_field = field.GetOffsetInBytes() + if not islast: + end_field = tp.GetFieldAtIndex( + n_field + 1 + ).GetOffsetInBytes() + else: + end_field = tp.GetByteSize() + if start_field <= offset and offset < end_field: + next_name = self.member_by_offset( + field.GetType(), + offset - start_field, + prev_name=field.GetName() + ) + return '.{field}{suffix}'.format( + field=field.GetName(), + suffix=next_name if next_name else '' + ) + if tp.GetTypeClass() == lldb.eTypeClassArray: + # Get array field type. + target = tp.GetArrayElementType() + tsize = target.GetByteSize() + idx = int(offset // tsize) + next_name = self.member_by_offset(target, offset - idx * tsize) + idxname = idx_name(prev_name) + if idxname and idx in idxname: + idx = idxname[idx] + return '[{}]{}'.format(idx, next_name if next_name else '') + else: + return None + def eval(self, command): if not command: return None @@ -502,9 +673,23 @@ class _LLDBDebugger(Debugger): ret = frame.EvaluateExpression(command) return ret + def detect_arch(self): + if hasattr(self, 'arch'): + return self.arch + target = self.target.GetTriple().split('-')[0] + if target == 'x86_64': + return 'x64' + elif target == 'arm64' or target == 'aarch64': + return 'arm64' + else: + return '' + def write(self, msg): sys.stdout.write(msg) + def error(self, errmsg): + return Exception(errmsg) + def check_libluajit(self): # TODO: Implement postpone loading for LLDB too. return True @@ -997,6 +1182,86 @@ def J(g): return dbg.cast('jit_State *', dbg.cast('char *', g) - g_offset + J_offset) +# Matched `MMDEF(_)`. +MM_NAMES = [ + 'index', + 'newindex', + 'gc', + 'mode', + 'eq', + 'len', + 'lt', + 'le', + 'concat', + 'call', + 'add', + 'sub', + 'mul', + 'div', + 'mod', + 'pow', + 'unm', + 'metatable', + 'tostring', + # TODO: depends on LJ_HASFFI, see `MMDEF_FFI(_)`. + 'new', + # TODO: depends on LJ_52 || LJ_HASFFI, see `MMDEF_PAIRS(_)`. + 'pairs', + 'ipairs', +] + + +GCROOT_MMNAME = 0 +GCROOT_BASEMT = GCROOT_MMNAME + len(MM_NAMES) +GCROOT_IO_INPUT = GCROOT_BASEMT + i2notu32(LJ_T['NUMX']) + 1 +GCROOT_IO_OUTPUT = GCROOT_IO_INPUT + 1 + + +# Get the name of the index in the predefined arrays. +def idx_name(field_name): + # Don't use **{ to be compatible with Python 2. + gcroot = {} + gcroot.update({ + i: 'GCROOT_MMNAME_' + MM_NAMES[i] for i in range(len(MM_NAMES)) + }) + gcroot.update({ + i2notu32(LJ_T[k]) + GCROOT_BASEMT: 'GCROOT_BASEMT_' + k + for k in LJ_T.keys() + }) + gcroot.update({ + GCROOT_IO_INPUT: 'GCROOT_IO_INPUT', + GCROOT_IO_OUTPUT: 'GCROOT_IO_OUTPUT', + }) + return { + # May be one of 2 slots depending on the result address. + 'ksimd': { + 0 * 2 + 0: 'LJ_KSIMD_ABS', + 0 * 2 + 1: 'LJ_KSIMD_ABS', + 1 * 2 + 0: 'LJ_KSIMD_NEG', + 1 * 2 + 1: 'LJ_KSIMD_NEG', + }, + 'gcroot': gcroot, + }.get(field_name, None) + + +ggfname_cache = {} + + +# Get GG field name by given offset. Use in JIT dump. +def ggfname_by_offset(offset): + if offset in ggfname_cache: + return ggfname_cache[offset] + + field_path = dbg.member_by_offset('GG_State', offset) + if not field_path: + return None + + # Remove first '.'. + ggfname = 'offsetof(GG, {})'.format(field_path[1:]) + ggfname_cache[offset] = ggfname + return ggfname + + def vm_state(g): return { i2notu32(0): 'INTERP', @@ -1087,6 +1352,555 @@ def lightudV(tv): return gcval(tv['gcr']) +# JIT engine. + + +IRS = [ + # Guarded assertions. + 'LT', + 'GE', + 'LE', + 'GT', + + 'ULT', + 'UGE', + 'ULE', + 'UGT', + + 'EQ', + 'NE', + + 'ABC', + 'RETF', + + # Miscellaneous ops. + 'NOP', + 'BASE', + 'PVAL', + 'GCSTEP', + 'HIOP', + 'LOOP', + 'USE', + 'PHI', + 'RENAME', + 'PROF', + + # Constants. + 'KPRI', + 'KINT', + 'KGC', + 'KPTR', + 'KKPTR', + 'KNULL', + 'KNUM', + 'KINT64', + 'KSLOT', + + # Bit ops. + 'BNOT', + 'BSWAP', + 'BAND', + 'BOR', + 'BXOR', + 'BSHL', + 'BSHR', + 'BSAR', + 'BROL', + 'BROR', + + # Arithmetic ops. ORDER ARITH + 'ADD', + 'SUB', + 'MUL', + 'DIV', + 'MOD', + 'POW', + 'NEG', + + 'ABS', + 'LDEXP', + 'MIN', + 'MAX', + 'FPMATH', + + # Overflow-checking arithmetic ops. + 'ADDOV', + 'SUBOV', + 'MULOV', + + # Memory ops. A = array, H = hash, U = upvalue, F = field, + # S = stack. + + # Memory references. + 'AREF', + 'HREFK', + 'HREF', + 'NEWREF', + 'UREFO', + 'UREFC', + 'FREF', + 'STRREF', + 'LREF', + + # Loads and Stores. These must be in the same order. + 'ALOAD', + 'HLOAD', + 'ULOAD', + 'FLOAD', + 'XLOAD', + 'SLOAD', + 'VLOAD', + + 'ASTORE', + 'HSTORE', + 'USTORE', + 'FSTORE', + 'XSTORE', + + # Allocations. + 'SNEW', + 'XSNEW', + 'TNEW', + 'TDUP', + 'CNEW', + 'CNEWI', + + # Buffer operations. + 'BUFHDR', + 'BUFPUT', + 'BUFSTR', + + # Barriers. + 'TBAR', + 'OBAR', + 'XBAR', + + # Type conversions. + 'CONV', + 'TOBIT', + 'TOSTR', + 'STRTO', + + # Calls. + 'CALLN', + 'CALLA', + 'CALLL', + 'CALLS', + 'CALLXS', + 'CARG', +] + + +# Mode bits: Commutative, {Normal/Ref, Alloc, Load, Store}, +# Non-weak guard. */ +IRM_C = 0x10 +IRM_A = 0x20 +IRM_L = 0x40 +IRM_S = 0x60 +IRM_W = 0x80 + + +# IR operand mode (2 bit). +IRM = [ + 'ref', + 'lit', + 'cst', + '', # none +] + + +lj_ir_mode_ = None + + +def lj_ir_mode(): + global lj_ir_mode_ + if lj_ir_mode_: + return lj_ir_mode_ + lj_ir_mode_ = dbg.lookup_global('lj_ir_mode') + return lj_ir_mode_ + + +def ir_left(op): + return IRM[int(lj_ir_mode()[op] & 3)] + + +def ir_right(op): + return IRM[int(lj_ir_mode()[op] >> 2 & 3)] + + +def ir_mode(op): + mode = '' + ir_mode = int(lj_ir_mode()[op] ^ IRM_W) + if ir_mode == IRM_C: + mode = 'C' + elif ir_mode == IRM_A: + mode = 'A' + elif ir_mode == IRM_L: + mode = 'L' + elif ir_mode == IRM_S: + mode = 'S' + else: + mode = 'N' + mode += 'W' if ir_mode & IRM_W else '' + return mode + + +IRTYPES = [ + 'nil', + 'fal', + 'tru', + 'lud', + 'str', + 'p32', + 'thr', + 'pro', + 'fun', + 'p64', + 'cdt', + 'tab', + 'udt', + 'flt', + 'num', + 'i8 ', + 'u8 ', + 'i16', + 'u16', + 'int', + 'u32', + 'i64', + 'u64', + 'sfp', +] + + +IRT_NUM = 14 +assert IRTYPES[IRT_NUM] == 'num', 'incorrect IRT_NUM definition' + + +IRFIELDS = [ + 'str.len', + 'func.env', + 'func.pc', + 'func.ffid', + 'thread.env', + 'tab.meta', + 'tab.array', + 'tab.node', + 'tab.asize', + 'tab.hmask', + 'tab.nomm', + 'udata.meta', + 'udata.udtype', + 'udata.file', + 'cdata.ctypeid', + 'cdata.ptr', + 'cdata.int', + 'cdata.int64', + 'cdata.int64_4', +] + + +IRFPMS = [ + 'floor', + 'ceil', + 'trunc', + 'sqrt', + 'exp2', + 'log', + 'log2', + 'other' +] + + +# Don't use *[ to be compatible with Python 2. +REGISTERS = {'x64': [ + 'rax', + 'rcx', + 'rdx', + 'rbx', + 'rsp', + 'rbp', + 'rsi', + 'rdi', +] + [ + 'r{}'.format(i) for i in range(8, 16) # r8 .. r15 +] + [ + 'xmm{}'.format(i) for i in range(0, 16) # xmm0 .. xmm15 +], 'arm64': [ + 'x{}'.format(i) for i in range(0, 31) # x0 .. x30 +] + ['sp'] + [ # x31 + 'd{}'.format(i) for i in range(0, 32) # d0 .. d31 +]} + + +IR_CALLS = [ + 'lj_str_cmp', + 'lj_str_find', + 'lj_str_new', + 'lj_strscan_num', + 'lj_strfmt_int', + 'lj_strfmt_num', + 'lj_strfmt_char', + 'lj_strfmt_putint', + 'lj_strfmt_putnum', + 'lj_strfmt_putquoted', + 'lj_strfmt_putfxint', + 'lj_strfmt_putfnum_int', + 'lj_strfmt_putfnum_uint', + 'lj_strfmt_putfnum', + 'lj_strfmt_putfstr', + 'lj_strfmt_putfchar', + 'lj_buf_putmem', + 'lj_buf_putstr', + 'lj_buf_putchar', + 'lj_buf_putstr_reverse', + 'lj_buf_putstr_lower', + 'lj_buf_putstr_upper', + 'lj_buf_putstr_rep', + 'lj_buf_puttab', + 'lj_buf_tostr', + 'lj_tab_new_ah', + 'lj_tab_new1', + 'lj_tab_dup', + 'lj_tab_clear', + 'lj_tab_newkey', + 'lj_tab_len', + 'lj_gc_step_jit', + 'lj_gc_barrieruv', + 'lj_mem_newgco', + 'lj_math_random_step', + 'lj_vm_modi', + 'log10', + 'exp', + 'sin', + 'cos', + 'tan', + 'asin', + 'acos', + 'atan', + 'sinh', + 'cosh', + 'tanh', + 'fputc', + 'fwrite', + 'fflush', + 'lj_vm_floor', + 'lj_vm_ceil', + 'lj_vm_trunc', + 'sqrt', + 'log', + 'lj_vm_log2', + 'pow', + 'atan2', + 'ldexp', + 'lj_vm_tobit', + 'softfp_add', + 'softfp_sub', + 'softfp_mul', + 'softfp_div', + 'softfp_cmp', + 'softfp_i2d', + 'softfp_d2i', + 'lj_vm_sfmin', + 'lj_vm_sfmax', + 'lj_vm_tointg', + 'softfp_ui2d', + 'softfp_f2d', + 'softfp_d2ui', + 'softfp_d2f', + 'softfp_i2f', + 'softfp_ui2f', + 'softfp_f2i', + 'softfp_f2ui', + 'fp64_l2d', + 'fp64_ul2d', + 'fp64_l2f', + 'fp64_ul2f', + 'fp64_d2l', + 'fp64_d2ul', + 'fp64_f2l', + 'fp64_f2ul', + 'lj_carith_divi64', + 'lj_carith_divu64', + 'lj_carith_modi64', + 'lj_carith_modu64', + 'lj_carith_powi64', + 'lj_carith_powu64', + 'lj_cdata_newv', + 'lj_cdata_setfin', + 'strlen', + 'memcpy', + 'memset', + 'lj_vm_errno', + 'lj_carith_mul64', + 'lj_carith_shl64', + 'lj_carith_shr64', + 'lj_carith_sar64', + 'lj_carith_rol64', + 'lj_carith_ror64', +] + + +def regname(reg_number): + if not hasattr(dbg, 'arch'): + dbg.arch = dbg.detect_arch() + return REGISTERS[dbg.arch][reg_number] + + +def litname_sload(mode): + modes_str = '' + modes_str += 'P' if mode & 0x1 else '' + modes_str += 'F' if mode & 0x2 else '' + modes_str += 'T' if mode & 0x4 else '' + modes_str += 'C' if mode & 0x8 else '' + modes_str += 'R' if mode & 0x10 else '' + modes_str += 'I' if mode & 0x20 else '' + return modes_str + + +def litname_xload(mode): + flags = ['-', 'R', 'V', 'RV', 'U', 'RU', 'VU', 'RVU'] + return flags[mode] + + +def litname_conv(mode): + IRCONV_DSH = 5 + IRCONV_CSH = 12 + IRCONV_SEXT = 0x800 + IRCONV_SRCMASK = 0x1f + conv_str = '{to}.{frm}'.format( + to=IRTYPES[(mode >> IRCONV_DSH) & IRCONV_SRCMASK], + frm=IRTYPES[mode & IRCONV_SRCMASK] + ) + conv_str += ' sext' if mode & IRCONV_SEXT else '' + num2int_mode = mode >> IRCONV_CSH + if num2int_mode == 2: + conv_str += ' index' + elif num2int_mode == 3: + conv_str += ' check' + return conv_str + + +def litname_irfield(mode): + if mode >= len(IRFIELDS): + return 'unknown irfield' + return IRFIELDS[mode] + + +def litname_fpm(mode): + if mode >= len(IRFPMS): + return 'unknown irfpm' + return IRFPMS[mode] + + +def litname_bufhdr(mode): + modes = ['RESET', 'APPEND'] + if mode >= len(modes): + return 'unknown bufhdr mode' + return modes[mode] + + +def litname_tostr(mode): + modes = ['INT', 'NUM', 'CHAR'] + if mode >= len(modes): + return 'unknown tostr mode' + return modes[mode] + + +IR_LITNAMES = { + 'SLOAD': litname_sload, + 'XLOAD': litname_xload, + 'CONV': litname_conv, + 'FLOAD': litname_irfield, + 'FREF': litname_irfield, + 'FPMATH': litname_fpm, + 'BUFHDR': litname_bufhdr, + 'TOSTR': litname_tostr +} + +# Additional flags. +IRT_MARK = 0x20 # Marker for misc. purposes. +IRT_ISPHI = 0x40 # Instruction is left or right PHI operand. +IRT_GUARD = 0x80 # Instruction is a guard. +# Masks. +IRT_TYPE = 0x1f + +RID_NONE = 0x80 +RID_MASK = 0x7f +RID_INIT = (RID_NONE | RID_MASK) +RID_SINK = (RID_INIT - 1) +RID_SUNK = (RID_INIT - 2) +# Spill slot 0 means no spill slot has been allocated. +SPS_NONE = 0 + +REF_BIAS = 0x8000 + +TREF_SHIFT = 24 + +TREF_REFMASK = 0x0000ffff +TREF_FRAME = 0x00010000 +TREF_CONT = 0x00020000 +# Snapshot flags and masks. +SNAP_FRAME = 0x010000 +SNAP_SOFTFPNUM = 0x080000 + + +def irt_type(t): + return dbg.cast('IRType', t['irt'] & IRT_TYPE) + + +def tref_type(tr): + return dbg.cast('IRType', (tr >> TREF_SHIFT) & IRT_TYPE) + + +def tref_ref(tr): + return int(tr & TREF_REFMASK) + + +def irt_ismarked(t): + return t['irt'] & IRT_MARK + + +def irt_isphi(t): + return t['irt'] & IRT_ISPHI + + +def irt_isguard(t): + return t['irt'] & IRT_GUARD + + +def irt_toitype(irt): + t = irt_type(irt) + if LJ_DUALNUM and t > IRT_NUM: + return LJ_T['NUMX'] + else: + return i2notu32(t) + + +def ir_kptr(ir): + irname = IRS[ir['o']] + assert irname == 'KPTR' or irname == 'KKPTR', 'wrong IR for ir_iptr()' + return mref('void *', dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['ptr']) + + +def ir_kgc(ir): + irname = IRS[ir['o']] + assert irname == 'KGC', 'wrong IR for ir_kgc()' + return gcref(dbg.cast('IRIns *', dbg.address(ir))[LJ_GC64]['gcr']) + + +def ir_knum(ir): + irname = IRS[ir['o']] + assert irname == 'KNUM', 'wrong IR for ir_knum()' + return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv']) + + +def ir_kint64(ir): + irname = IRS[ir['o']] + assert irname == 'KINT64', 'wrong IR for ir_knum()' + return dbg.address(dbg.cast('IRIns *', dbg.address(ir))[1]['tv']) + + # Dumpers. # GCobj dumpers. @@ -1467,6 +2281,325 @@ def dump_func(func): return 'fast function #{}\n'.format(int(ffid)) +# JIT dumpers. + + +def dump_call_func(trace, callop): + ctype = '' + if callop > 0: + ir = trace['ir'][REF_BIAS + callop] + if IRTYPES[irt_type(ir['t'])] == 'nil': # nil == CARG(func, ctype) + callop = int(ir['op1']) - REF_BIAS + cdt_idx_irk = trace['ir'][ir['op2']] + assert IRS[cdt_idx_irk['o']] == 'KINT', \ + 'unexpected IR for ctype storage' + ctype_idx = cdt_idx_irk['i'] + ctype = 'ctype: {}'.format(ctype_idx) + + func_str = '' + if callop < 0: + irk = trace['ir'][REF_BIAS + callop] + assert IRS[irk['o']] == 'KINT64', \ + 'unexpected IR for FFI function storage' + func_addr = int(ir_kint64(irk)['u64']) + # TODO: Symbol demangling. + func_str = '[{:#x}]'.format(func_addr) + else: + func_str = '[{:04d}]'.format(callop) + + return func_str, ctype + + +def dump_call_args(trace, ins): + if ins < 0: + return '{{{}}}'.format(dump_irk(trace, ins)) + else: + ir = trace['ir'][REF_BIAS + ins] + irname = IRS[ir['o']] + if irname == 'CARG': + last_arg = '' + args = dump_call_args(trace, int(ir['op1']) - REF_BIAS) + op2 = int(ir['op2']) - REF_BIAS + if op2 < 0: + last_arg = '{{{}}}'.format(dump_irk(trace, op2)) + else: + last_arg = '{{{:04d}}}'.format(op2) + return args + ', ' + last_arg + else: + return '{{{:04d}}}'.format(ins) + + +# Special FP constant. +CONST_BIAS = 2 ** 52 + 2 ** 51 + + +def dump_irk(trace, idx): + ref = idx + REF_BIAS + assert ref >= trace['nk'] and ref < REF_BIAS, 'bad constant in IR dump' + irins = trace['ir'][ref] + irname = IRS[irins['o']] + slot = '' + if irname == 'KSLOT': + slot = ' KSLOT: @{}'.format(int(irins['op2'])) + irins = trace['ir'][irins['op1']] + irname = IRS[irins['o']] + + irtype = irins['t'] + if irname == 'KPRI': + typename = typenames(irt_toitype(irtype)) + # Trivial dump for primitives. + irk = tv_dumpers.get( + typename, dump_lj_tv_invalid # noqa: F821 # Generated. + )(0) + elif irname == 'KINT': + irk = 'integer {}'.format(dbg.cast('int32_t', irins['i'])) + elif irname == 'KGC': + typename = typenames(irt_toitype(irtype)) + irk = gco_dumpers.get(typename, dump_lj_gco_invalid)(ir_kgc(irins)) + elif irname == 'KKPTR': + addr = ir_kptr(irins) + if addr == dbg.address(G(L())['nilnode']): + return '[g->nilnode]' + slot + irk = '[{}]'.format(strx64(addr)) + elif irname == 'KPTR': + irk = '[{}]'.format(strx64(ir_kptr(irins))) + elif irname == 'KNULL': + irk = 'NULL' + elif irname == 'KNUM': + tv_num = ir_knum(irins) + if float(tv_num['n']) == CONST_BIAS: + return 'bias' + irk = dump_lj_tv_numx(tv_num) + elif irname == 'KINT64': + irk = 'int64_t {}'.format(dbg.cast( + 'int64_t', int(ir_kint64(irins)['u64']) + )) + else: + return 'Unknown IRK: ' + irname + return irk + slot + + +def dump_irins(irins, trace=None): + irop = int(irins['o']) + if irop >= len(IRS): + return 'INVALID' + + irname = IRS[irop] + leftop = ir_left(irop) + rightop = ir_right(irop) + irt = irins['t'] + is_sinksunk = irins['r'] == RID_SINK or irins['r'] == RID_SUNK + flags = '{is_sinksunk}{is_marked}{is_guard}{is_phi}'.format( + # Sink flag should be the first to match sink slots during + # the dump of registers. + is_sinksunk='}' if is_sinksunk else ' ', + is_marked='!' if irt_ismarked(irt) else ' ', + is_guard='>' if irt_isguard(irt) else ' ', + is_phi='+' if irt_isphi(irt) else ' ' + ) + + if not trace: + g = G(L(None)) + compiling = jit_state(g) != 'IDLE' + assert compiling, 'attempt to dump IR for J.cur trace in bad VM state' + trace = J(g)['cur'] + + left = '' + right = '' + lisref = leftop == 'ref' + risref = rightop == 'ref' + op1 = int((irins['op1'] - REF_BIAS) if lisref else irins['op1']) + op2 = int((irins['op2'] - REF_BIAS) if risref else irins['op2']) + + skip_right = False + if re.match('CALL', irname): + ctype = '' + args = '' + if rightop == 'lit': + func = IR_CALLS[op2] + else: + func, ctype = dump_call_func(trace, op2) + + if op1 != -1: + args = dump_call_args(trace, int(op1)) + + return '{flags} {type} {name:6} [{mode:2}] {f}({args}) {ct}\n'.format( + flags=flags, + name=irname, + mode=ir_mode(irop), + type=IRTYPES[irt_type(irt)], + ct=ctype, + args=args, + f=func, + ) + elif irname == 'CNEW' and op2 == -1: + left = dump_irk(trace, op1) + skip_right = True + elif leftop: + if op1 < 0: + left = dump_irk(trace, op1) + elif leftop == 'cst': + idx = irins - dbg.address(trace['ir'][REF_BIAS]) + left = dump_irk(trace, idx) + else: + left = ('{:04d}' if lisref else '#{:<3d}').format(op1) + + if rightop: + if rightop == 'lit': + litname = IR_LITNAMES.get(irname, None) + if litname: + # Try to handle `lj_ir_ggfload()`. + ggfname = None + if irname == 'FLOAD' and left == 'nil' \ + and op2 >= len(IRFIELDS): + ggfname = ggfname_by_offset(op2 << 2) + + if ggfname: + right = ggfname + else: + right = litname(op2) + elif irname == 'UREFO' or irname == 'UREFC': + right = '#{:<3d}'.format(op2 >> 8) + else: + right = '#{:<3d}'.format(op2) + elif op2 < 0: + right = dump_irk(trace, op2) + else: + right = ('{:04d}').format(op2) + + typename = '' + if irname == 'LOOP': + typename = '---' + elif irname == 'NOP': + typename = ' ' + else: + typename = IRTYPES[irt_type(irt)] + + return '{flags} {type} {name:6} [{mode:2}] {left:<9s} {right}\n'.format( + flags=flags, + name=irname, + mode=ir_mode(irop), + type=typename, + left=(leftop + ': ' + left) if leftop else '', + right=(rightop + ': ' + right) if rightop and not skip_right else '', + ) + + +def dump_snap(trace, snapno, snap): + dump = 'SNAP #{:<3d} ['.format(snapno) + snap_map = dbg.address(trace['snapmap'][snap['mapofs']]) + snap_entry_num = 0 + for slot in range(0, snap['nslots']): + dump += ' ' + snap_entry = int(snap_map[snap_entry_num]) + if snap_entry_num < snap['nent'] and snap_entry >> TREF_SHIFT == slot: + snap_entry_num += 1 + ref = int((snap_entry & TREF_REFMASK) - REF_BIAS) + if ref < 0: + if int(snap_entry) == 0x1057fff: + dump += '----' + continue + elif (snap_entry & TREF_CONT): + dump += 'contpc' + elif (snap_entry & TREF_FRAME): + dump += 'ftsz ' + else: + dump += '{{{const}}}'.format(const=dump_irk(trace, ref)) + elif snap_entry & SNAP_SOFTFPNUM: + dump += '{:04d}/{:04d}'.format(ref, ref + 1) + else: + dump += '{:04d}'.format(ref) + + if snap_entry & SNAP_FRAME: + dump += '|' + else: + dump += '----' + + dump += ' ]\n' + return dump + + +def dump_sink_slot(rid, spill, ins_number): + assert rid == RID_SINK or rid == RID_SUNK, 'incorrect rid in sink dump' + tp = 'sink' if rid == RID_SINK else 'sunk' + return '{{{}'.format(tp) if spill == RID_INIT or spill == SPS_NONE \ + else '{{{:04d}'.format(int(ins_number - spill)) + + +def dump_regsp(irins, ins_number): + rid = irins['r'] + spill = irins['s'] + if rid == RID_SINK or rid == RID_SUNK: + return dump_sink_slot(rid, spill, ins_number) + elif irins['prev'] > 255: + return '[{:#05x}]'.format(int(spill * 4)) + elif rid < 128: + return regname(rid) + else: + return '' + + +def dump_trace(trace, flags): + dump = 'Trace {num} start\n\tproto: {start_pt}\n\tBC: {start_bc}\n'.format( + num=trace['traceno'], + start_pt=gcref(trace['startpt']), + start_bc=mref('BCIns *', trace['startpc']), + ) + + nins = trace['nins'] - REF_BIAS + dump += '---- TRACE IR\n' + nsnap = 0 + snap = trace['snap'][nsnap] + snapref = snap['ref'] + for irnum in range(1, nins): + irref = REF_BIAS + irnum + if 's' in flags and irref >= snapref and nsnap < trace['nsnap']: + dump += '.... ' + if 'r' in flags: + dump += ' ' * 7 + dump += dump_snap(trace, nsnap, snap) + nsnap += 1 + snap = trace['snap'][nsnap] + snapref = snap['ref'] + dump += '{:04d} '.format(irnum) + if 'r' in flags: + dump += '{:>7}'.format(dump_regsp(trace['ir'][irref], irnum)) + dump += dump_irins(trace['ir'][irref], trace) + return dump + + +def dump_tref(tref): + return '[{F}{C}] {tp} {ref:#x}'.format( + F='F' if tref & TREF_FRAME else ' ', + C='C' if tref & TREF_CONT else ' ', + tp=IRTYPES[tref_type(tref)], + ref=tref_ref(tref) + ) + + +def dump_jslots(coroutine): + lstate = L(None) + g = G(lstate or coroutine) + j = J(g) + + dump = '' + maxslot = j['baseslot'] + j['maxslot'] + first_base_slot = 1 + LJ_FR2 + for n in reversed(range(first_base_slot, maxslot)): + tref = j['slot'][n] + ref = tref_ref(tref) + address = dbg.address(tref) + dump += '{addr} {nslot:04d} {base:1s} {tref}{const}\n'.format( + addr=address, + base='B' if address == j['base'] else ' ', + nslot=n, + tref=dump_tref(tref), + const=' ' + dump_irk(j['cur'], ref - REF_BIAS) + if ref != 0 and ref < REF_BIAS else '' + ) + return dump + + # Extension commands. ############################################ @@ -1600,6 +2733,42 @@ error message occurs. dbg.write('{}\n'.format(dump_gcobj(gcobj))) +class LJDumpIR(dbg.LJBase): + ''' +lj-ir + +The command receives a pointer to (IRIns address) and dumps +the IR type and some info related to it. The format is similar to +the `jit.dump` tool but also provides information about IR mode and +operands modes. + +For the list of IR names and modes (operand types), see: +https://github.com/tarantool/tarantool/wiki/LuaJIT-SSA-IR. + ''' + + def execute(self, arg): + dbg.write('{}'.format(dump_irins(dbg.cast('IRIns *', dbg.eval(arg))))) + + +class LJDumpJSlots(dbg.LJBase): + ''' +lj-jslots [] + +The command receives an optional lua_State address and dumps the +slots of JIT stack map: + + [] + +The lua_State pointer is optional to help in finding the VM's JIT state +when there is no coroutine to be inspected in the debugged frame. + ''' + + def execute(self, arg): + dbg.write('{}'.format( + dump_jslots(dbg.cast('lua_State *', dbg.eval(arg))) + )) + + class LJDumpProto(dbg.LJBase): ''' lj-proto @@ -1784,19 +2953,44 @@ error message occurs. dbg.write('{}\n'.format(dump_tvalue(tv))) +class LJDumpTrace(dbg.LJBase): + ''' +lj-trace [/FLAGS] + +The command receives a pointer to (IRIns address) and dumps +its number, IRs, and information about start location. The format is +similar to the `jit.dump` tool but also provides information about +IR mode and operands modes. + +Trace may be preceded with /FLAGS: +* r: Dump registers associated with IR, if any. +* s: Dump snapshots for the trace. + ''' + + def execute(self, arg): + arg, flags = dbg.extract_flags(arg, 'rs') + dbg.write('{}'.format(dump_trace( + dbg.cast('GCtrace *', dbg.eval(arg)), + flags + ))) + + def load(event=None): dbg.initialize_extension({ - 'lj-arch': LJDumpArch, - 'lj-bc': LJDumpBC, - 'lj-func': LJDumpFunc, - 'lj-gc': LJGC, - 'lj-gco': LJDumpGCobj, - 'lj-proto': LJDumpProto, - 'lj-stack': LJDumpStack, - 'lj-state': LJState, - 'lj-str': LJDumpString, - 'lj-tab': LJDumpTable, - 'lj-tv': LJDumpTValue, + 'lj-arch': LJDumpArch, + 'lj-bc': LJDumpBC, + 'lj-func': LJDumpFunc, + 'lj-gc': LJGC, + 'lj-gco': LJDumpGCobj, + 'lj-ir': LJDumpIR, + 'lj-jslots': LJDumpJSlots, + 'lj-proto': LJDumpProto, + 'lj-stack': LJDumpStack, + 'lj-state': LJState, + 'lj-str': LJDumpString, + 'lj-tab': LJDumpTable, + 'lj-trace': LJDumpTrace, + 'lj-tv': LJDumpTValue, }) diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py index 7e8ea5a2..76543daa 100644 --- a/test/tarantool-debugger-tests/debug-extension-tests.py +++ b/test/tarantool-debugger-tests/debug-extension-tests.py @@ -46,7 +46,9 @@ else: RX_ADDR = r'0x[a-f0-9]+' RX_HASH = RX_ADDR # The same pattern for hexademic values. RX_BCN = r'00\d\d' +RX_IRN = RX_BCN # The same as for the bytecodes. RX_FRAME = r'\[(S|\s)(B|\s)(T|\s)(M|\s)\]' +RX_IRREF = r'0x\d\d\d\d' def persist(data): @@ -101,6 +103,9 @@ IS_GC64 = execute_process([ LUAJIT_BINARY, '-e', "print(require('ffi').abi('gc64'))" ]).strip() == 'true' +# Regexp for pointer type in IR. +RX_P = 'p64' if IS_GC64 else 'p32' + # If it is the guaranteed DUALNUM build (for example, on aarch64), # we use this regexp for the guaranteed 'integer' check and # 'number' for single-number build. @@ -108,6 +113,18 @@ RX_INT = r'integer' if IS_DUALNUM else r'number' RX_ISDUALNUM = r'True' if IS_DUALNUM else r'False' +# Assume not cross-platform debugging. +machine = os.uname().machine +if machine == 'x86_64': + RX_GPR = r'r\w\w' + RX_FPR = r'xmm\d+' +elif machine == 'arm64' or machine == 'aarch64': + RX_GPR = r'x\d+' + RX_FPR = r'd\d+' +else: + raise Exception('Unknown archeticture in testing') + + class TestCaseBase(unittest.TestCase): @classmethod def construct_cmds(cls): @@ -193,6 +210,16 @@ def mref(arg, tp): return '((' + tp + '*)(' + arg + ').ptr32)' +def gcref(arg): + if SUPPORT_MACRO_EXPAND: + return 'gcref(' + arg + ')' + else: + if IS_GC64: + return '(' + arg + ').gcptr64' + else: + return '(' + arg + ').gcptr32' + + class TestLoad(TestCaseBase): extension_cmds = '' location = 'lj_cf_print' @@ -203,11 +230,14 @@ class TestLoad(TestCaseBase): r'lj-func command initialized\n' r'lj-gc command initialized\n' r'lj-gco command initialized\n' + r'lj-ir command initialized\n' + r'lj-jslots command initialized\n' r'lj-proto command initialized\n' r'lj-stack command initialized\n' r'lj-state command initialized\n' r'lj-str command initialized\n' r'lj-tab command initialized\n' + r'lj-trace command initialized\n' r'lj-tv command initialized\n' r'LuaJIT debug extension is successfully loaded' ) @@ -473,6 +503,341 @@ class TestLJBC(TestCaseBase): ) +# JIT engine. + + +class TestLJTraceBase(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'for _ = 1, 4 do end\n' + 'print()\n' + ) + pattern = ( + r'Trace 1 start\n' + r'\t*proto: ' + RX_ADDR + r'\n' + + r'\t*BC: ' + RX_ADDR + r'\n' + + r'---- TRACE IR\n' + + RX_IRN + r'\s+ int SLOAD \[N \] lit: #[12] lit: C?I\n' + + RX_IRN + r'\s+ \+ int ADD \[C \] ref: ' + RX_IRN + + r' ref: integer 1\n' + + RX_IRN + r'\s+ > int LE \[N \] ref: ' + RX_IRN + + r' ref: integer 4\n' + + RX_IRN + r'\s+ > --- LOOP \[N \]\s*\n' + + RX_IRN + r'\s+ \+ int ADD \[C \] ref: ' + RX_IRN + + r' ref: integer 1\n' + + RX_IRN + r'\s+ > int LE \[N \] ref: ' + RX_IRN + + r' ref: integer 4\n' + + RX_IRN + r'\s+ int PHI \[S \] ref: ' + RX_IRN + r' ref: ' + + RX_IRN + r'\n' + + RX_IRN + r'\s+ NOP \[N \]\s*\n' + ) + + +# Check the IR enumeration correcness by test the lowest (LT) and +# the highest (CARG) IRs. Also, checks CALL* occasionally. +class TestLJTraceIRRange(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'local ffi = require("ffi")\n' + 'ffi.cdef[[int getpid(int, int);]]\n' # Use argument for testing. + 'jit.opt.start("hotloop=1")\n' + 'for i = 1, 4 do\n' + ' if i < 100 then\n' # LT. + ' ffi.C.getpid(i, 1LL)\n' # CARG and CALLXS. + ' end\n' + 'end\n' + 'print()\n' + ) + # IRs from variant part of the trace. + pattern = ( + RX_IRN + r'\s+ > int LT \[N \] ref: ' + + RX_IRN + r' ref: integer 100\n' + + RX_IRN + r'\s+ nil CARG \[N \] ref: ' + + RX_IRN + r' ref: integer 1\n' + + RX_IRN + r'\s+ int CALLXS \[S \] \[' + RX_ADDR + + r'\]\(\{' + RX_IRN + r'\}, \{integer 1\}\)' + ) + + +# Test /rs flags. +class TestLJTraceFlags(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace /rs ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local r = 0.1\n' + 'for i = 1, 4 do\n' + ' r = i + r\n' + 'end\n' + 'print()\n' + ) + # IRs and snapshot from variant part of the trace. + pattern = ( + RX_IRN + r'\s+' + RX_FPR + r'\s* \+ num ADD.*\n' + + RX_IRN + r'\s+' + RX_GPR + r'\s* \+ int ADD.*\n' + + r'\.\.\.\.\s* SNAP #\d \[ (---- )*' + RX_IRN + r' \]' + ) + + +class TestLJIRConst(TestCaseBase): + location = 'trace_stop' + + # No narrowing of 42. + if IS_DUALNUM: + # KNUM occupies 2 slots. + _knum_irnum = '6' + _kgc_irnum = '8' if IS_GC64 else '7' + _kptr_irnum = '10' if IS_GC64 else '8' + else: + # KNUM occupies 2 slots. + _knum_irnum = '8' + _kgc_irnum = '10' if IS_GC64 else '9' + _kptr_irnum = '12' if IS_GC64 else '10' + extension_cmds = ( + 'n\n' # Load J. + 'lj-ir &J->cur.ir[0x8000 - 0]\n' + 'lj-ir &J->cur.ir[0x8000 - 1]\n' + 'lj-ir &J->cur.ir[0x8000 - 2]\n' + 'lj-ir &J->cur.ir[0x8000 - 3]\n' + 'lj-ir &J->cur.ir[0x8000 - 4]\n' + # Skip non-DUALNUM narrowed value. + 'lj-ir &J->cur.ir[0x8000 - ' + _knum_irnum + ']\n' + 'lj-ir &J->cur.ir[0x8000 - ' + _kgc_irnum + ']\n' + 'lj-ir &J->cur.ir[0x8000 - ' + _kptr_irnum + ']\n' + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local function trace(x)\n' + ' return x + 42, x + 0.5, x .. "1"\n' + 'end\n' + 'trace(1)\n' + 'trace(1)\n' + ) + pattern = ( + RX_P + r' BASE.*\n' + + r'\s* nil KPRI.*\n' + r'\s* fal KPRI.*\n' + r'\s* tru KPRI.*\n' + r'\s* int KINT.*cst: integer 42\s*\n' + r'\s* num KNUM.*cst: number 0.5\s*\n' + r'\s* str KGC.*cst: string "1".*\n' + + r'\s*' + RX_P + r' KPTR.*cst: \[' + RX_ADDR + r'\]' + ) + + +class TestLJIRFloadNeg(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local function trace(a)\n' + ' local x = -a\n' + ' return x\n' + 'end\n' + 'trace(1.1)\n' + 'trace(1.1)\n' + 'print()\n' + ) + pattern = ( + r'num FLOAD .* ref: nil lit: offsetof\(GG, J\.ksimd\[LJ_KSIMD_NEG\]\)' + ) + + +class TestLJIRFloadAbs(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local math_abs = math.abs\n' + 'local function trace(a)\n' + ' local x = math_abs(a)\n' + ' return x\n' + 'end\n' + 'trace(1)\n' + 'trace(1)\n' + 'print()\n' + ) + pattern = ( + r'num FLOAD .* ref: nil lit: offsetof\(GG, J\.ksimd\[LJ_KSIMD_ABS\]\)' + ) + + +# XXX: Implemented only for GC64 in LuaJIT until backporting the +# corresponding commit. +if IS_GC64: + class TestLJIRFloadGCRootBaseMT(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local function trace(a)\n' + 'local x = a.sub(1, 2)\n' + ' return x\n' + 'end\n' + 'trace("12")\n' + 'trace("12")\n' + 'print()\n' + ) + pattern = ( + r'tab FLOAD .* ref: nil lit: ' + r'offsetof\(GG, g\.gcroot\[GCROOT_BASEMT_STR\]\.gcptr64\)' + ) + + class TestLJIRFloadGCRootIO(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local io_flush = io.flush\n' + 'local function trace()\n' + ' io_flush()\n' + 'end\n' + 'trace()\n' + 'trace()\n' + 'print()\n' + ) + pattern = ( + r'udt FLOAD .* ref: nil lit: ' + r'offsetof\(GG, g\.gcroot\[GCROOT_IO_OUTPUT\]\.gcptr64\)' + ) + + +# Some IRs related to tables. +class TestLJIRTable(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local function trace(t)\n' + ' t.a = nil\n' + ' t.b = 1\n' + ' return t\n' + 'end\n' + 'trace({a = 1})\n' + 'trace({a = 1})\n' + 'print()\n' + ) + pattern = ( + r'(?s)int FLOAD .* tab\.hmask\n' + r'.*' + RX_P + r' FLOAD .* tab\.node\n' + r'.*' + RX_P + r' HREFK .* string "a" @ ' + RX_ADDR + + r' KSLOT: @\d\n' + r'.*' + RX_P + r' HREF .* string "b" @ ' + RX_ADDR + r'\s*\n' + r'.*' + RX_P + r' EQ .* \[g->nilnode\]' + ) + + +class TestLJIRUref(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'local uv = 0\n' + 'local function trace(a)\n' + ' uv = a\n' + ' return uv\n' + 'end\n' + 'trace(1)\n' + 'trace(1)\n' + 'print()\n' + ) + pattern = r'UREFO .* lit: #0' + + +# Check border values (that always avalable) of CALL IRs. +class TestLJIRCall(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'local ffi = require("ffi")\n' + 'jit.opt.start("hotloop=1")\n' + 'local function trace(a, b)\n' + ' return a < b, ffi.errno()\n' + 'end\n' + 'trace("abc", "abd")\n' + 'trace("abc", "abd")\n' + 'print(1)\n' + ) + pattern = ( + r'(?s)int CALLN .* ' + r'lj_str_cmp\(\{' + RX_IRN + r'\}, \{' + RX_IRN + r'\}\)' + r'.*int CALLS .* lj_vm_errno\(\)' + ) + + +# Test ffi call with ctype stored in CARG. +class TestLJIRCallXSCType(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'n\n' # Load L. + 'lj-trace ' + gcref('((GG_State *)L)->J->trace[1]') + ) + lua_script = ( + 'local ffi = require("ffi")\n' + 'ffi.cdef[[int printf(const char *fmt, ...);]]\n' + 'jit.opt.start("hotloop=1")\n' + 'local function trace()\n' + ' local t = ffi.C.printf("")\n' + ' return t\n' + 'end\n' + 'trace()\n' + 'trace()\n' + 'print()\n' + ) + pattern = r'int CALLXS .* [' + RX_ADDR + r'\]\(.*\) ctype: \d+' + + +class TestLJJSlotsBase(TestCaseBase): + location = 'trace_stop' + extension_cmds = ( + 'n\n' # Load J. + 'lj-jslots J->L\n' + ) + lua_script = ( + 'jit.opt.start("hotloop=1")\n' + 'for _ = 1, 4 do end\n' + ) + pattern = ( + r'(?s)(.*' + + RX_ADDR + ' ' + RX_IRN + r' (B|\s) \[(F|\s)(C|\s)\] \w\w\w ' + + RX_IRREF + + r'.*)+' + ) + + for test_cls in TestCaseBase.__subclasses__(): test_cls.test = lambda self: self.check() -- 2.54.0