Tarantool development patches archive
 help / color / mirror / Atom feed
From: Sergey Kaplun via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Mikhail Elhimov <m.elhimov@vk.team>
Cc: tarantool-patches@dev.tarantool.org
Subject: Re: [Tarantool-patches] [PATCH luajit] dbg: avoid hardcoded enums (get them from target)
Date: Mon, 21 Sep 2026 21:47:34 +0300	[thread overview]
Message-ID: <arF7xltOWlZisx77@root> (raw)
In-Reply-To: <20260909084908.354159-1-m.elhimov@vk.team>

Hi, Mikhail!
Thanks for the patch!
I really like this! It helps to avoid copy-pasting and makes the code
much more robust. Brilliant idea to use type of enums!
I left some stillistic comments and asked for some patch clean-up
below.

On 09.09.26, Mikhail Elhimov wrote:
> Besides reducing lines of code this way the extension become compatible

Typo: s/become/becomes/

> with various version of luajit because different version might use

Typo: s/version/versions/g

> different set of enum members (newer version might introduce additional

Typo: s/set/sets/
Typo: s/newer/a newer/

> BC/IR/etc.)
> 
> Prior to this patch string was used as a debugger-agnostic way to

Typo: s/this/this, the/

> specify type, but this way might not work in case of enum because

Typo: s/case of enum/the case of an enum/

> debugging information might be optimized out if no variable of such enum

Typo: s/enum/an enum/

> type is declared and its members are used only as a predefined

Typo: s/a //

> constants. The type of enum is needed to be able to get human readable

Typo: s/human readable/human-readable/

> value (member name instead of number). The alternative way to get enum
> type is get it from the value, i.e. somehow obtain the value that is of

Typo: s/is get/is to get/

> enum type and then get its type object.
> 
> To do that, separate method to create enum value was introduced in

Typo: s/separate/a separate/

> Debugger API, LLDB value class was monkey-patched to get value type in
> the same way as GDB value class does and 'cast' method was adjusted to
> accept also type object, not only string.

I would prefer to keep the `cast()` API to be used only for the string
as the first parameter, since it is very helpful for reading of the
code.

May I suggest the little bit different approach here?

Introduce the `dbg.cast_typeof()` API to have the following semantics:

| dbg.cast_typeof(obj_with_type, value)

The idea is the same, but it doesn't allow getting and working with
internal types somehow in the debugger extension.

I'll mention this in the cast usages below.

> 
> Other adjustments:
> - [lldb] 'eval' adjusted to return object of the same type as 'cast'
>   method (this improves consistency)
> - [gdb] in 'eval' method dropped check of the value returned by
>   gdb.parse_and_eval() as it would fail also for expression like '0'
>   (looks like a kind of legacy code that is not needed now).
> - [gdb/lldb] renamed eval argument to reflect its meaning (it is
>   an expression rather than a command).
> 
> Closes tarantool/tarantool#13094

Nit: s/Closes/Resolves/
Since technically speaking it will be closed after LuaJIT's bump in
Tarantool.

> ---
> This patch is to be applied after the 'fix mapping of FPMATHOP' patch.
> 
> Branch: https://github.com/tarantool/luajit/tree/elhimov/gh-13094-dbg-use-enums-from-inferior
> Related issue: https://github.com/tarantool/tarantool/issues/13094
> 
>  src/luajit_dbg.py | 628 +++++++++++-----------------------------------
>  1 file changed, 141 insertions(+), 487 deletions(-)
> 
> diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
> index 76001b7d..1ac1d275 100644
> --- a/src/luajit_dbg.py
> +++ b/src/luajit_dbg.py
> @@ -110,9 +110,21 @@ class Debugger(object):
>                  self.write('{} command initialized\n'.format(name))
>              self.write('LuaJIT debug extension is successfully loaded\n')
>  
> +    def cast(self, tp, val):
> +        '''Cast the value to the given type (it is either C type string
> +        or the debugger-specific type object).'''
> +        if isinstance(tp, str):
> +            tp = self._dbgtype(tp)

I would prefer to avoid casting to the given debugger-specific type.
It is better to introduce `cast_typeof()` method to be used with another
object given as the first argument (it should be read approximately as
`cast(typeof(x), y))`.

| def cast_typeof(self, typed_object, val):
|     '''Cast the value to the type of the given object.
|     It is used mostly for casting when the string name of the type isn't
|     accessible (for example, anonymous enums).'''

> +        return self._cast(tp, val)
> +
> +    @abc.abstractmethod
> +    def _cast(self, tp, val):
> +        '''Cast the value to the debugger-specific type.'''
> +        pass
> +
>      @abc.abstractmethod
> -    def cast(self, typestr, val):
> -        '''Cast the value to the required C type.'''
> +    def _dbgtype(self, typestr):
> +        '''Convert C type string into debugger-specific type object.'''
>          pass

I suppose since it is internal for each debugger, this should not be
declared as the required type in the parent class.

>  
>      @abc.abstractmethod
> @@ -146,8 +158,9 @@ class Debugger(object):
>          pass
>  
>      @abc.abstractmethod
> -    def eval(self, command):
> -        '''Parse and evaluate the given debugger command.'''
> +    def eval(self, expr):
> +        '''Parse and evaluate the given debugger expression.
> +        Return debugger-specific value.'''

Nit: This clarification looks unrelated to the patch itself.
It is better to move it to the separate commit if you really want it.

>          pass
>  
>      @abc.abstractmethod
> @@ -187,6 +200,13 @@ class Debugger(object):
>          '''Register the command with the corresponding name.'''
>          pass
>  

I suggest adding some comments with motivations about such a specific
method (mostly described in the first paragraph in the commit message).

> +    @abc.abstractmethod
> +    def create_enum_value(self, enum_name, enum_member_name):
> +        '''Return debugger-specific value object that represents
> +        the given enum member.
> +        '''
> +        pass
> +
>      @abc.abstractproperty
>      def LJBase(self):
>          '''Base command class.
> @@ -211,8 +231,9 @@ class _GDBDebugger(Debugger):
>          super(_GDBDebugger, self).__init__()
>          self.CONNECTED = False
>  
> -    def cast(self, typestr, val):
> -        return gdb.Value(val).cast(self._dbgtype(typestr))
> +    def _cast(self, tp, val):
> +        assert isinstance(tp, gdb.Type)
> +        return gdb.Value(val).cast(tp)
>  
>      def sizeof(self, typestr):
>          return self._dbgtype(typestr).sizeof
> @@ -268,14 +289,11 @@ class _GDBDebugger(Debugger):
>          else:
>              return None
>  
> -    def eval(self, command):
> -        if not command:
> +    def eval(self, expr):
> +        if not expr:
>              return None
>  
> -        ret = gdb.parse_and_eval(command)
> -        if not ret:
> -            raise gdb.GdbError('table argument empty')

Hmm, indeed, I can't find an example when this returns something not
evaluated as `True`...
Probably it may be removed. But it should be done in the separate clean
up patch since it is unrelated to the refactoring of the enums.

> -        return ret
> +        return gdb.parse_and_eval(expr)
>  
>      def detect_arch(self):
>          if hasattr(self, 'arch'):
> @@ -340,6 +358,9 @@ class _GDBDebugger(Debugger):
>      def register_command(self, command, name):
>          command(name)
>  
> +    def create_enum_value(self, enum_name, enum_member_name):
> +        return self.eval(enum_member_name)
> +
>      class LJBase(gdb and gdb.Command or object):
>          def __init__(ljbase, name):
>              # XXX Fragile: Though the command initialization looks
> @@ -378,6 +399,10 @@ class _LLDBDebugger(Debugger):
>              lldb.eBasicTypeInt128
>          ]
>  
> +    def _lldb_tp_isenum(self, tp):
> +        return tp.GetCanonicalType().GetTypeClass() == \
> +            lldb.eTypeClassEnumeration
> +
>      def _lldb_value_from_raw(self, raw_value, size, tp):
>          isfp = self._lldb_tp_isfp(tp)
>          if isfp:
> @@ -491,8 +516,7 @@ class _LLDBDebugger(Debugger):
>              # Instead of default GetSummary.
>              if not lldbval.sbvalue.TypeIsPointerType():
>                  tp = lldbval.sbvalue.GetType()
> -                is_float = self._lldb_tp_isfp(tp)
> -                if is_float:
> +                if self._lldb_tp_isfp(tp) or self._lldb_tp_isenum(tp):
>                      return lldbval.sbvalue.GetValue()
>                  else:
>                      return str(int(lldbval))
> @@ -530,6 +554,9 @@ class _LLDBDebugger(Debugger):
>              else:
>                  return int(lldbval) - int(other)
>  
> +        def lldb_gettype(lldbval):
> +            return lldbval.sbvalue.type
> +

I prefer to drop this method in favor of `cast_typeof()`.

>          super(_LLDBDebugger, self).__init__()
>          self.target = lldb.debugger.GetSelectedTarget()
>          # Monkey-patch the lldb.value class.
> @@ -545,6 +572,7 @@ class _LLDBDebugger(Debugger):
>          lldb.value.__ror__ = lldb__or__  # Same semantics.
>          lldb.value.__str__ = lldb__str__
>          lldb.value.__sub__ = lldb__sub__
> +        lldb.value.type = property(lldb_gettype)

I prefer to drop this field in favor of `cast_typeof()`.

>  
>          def lldb_major_version():
>              version_string = lldb.SBDebugger.GetVersionString()
> @@ -568,11 +596,11 @@ class _LLDBDebugger(Debugger):
>          self.dbgtype_cache[typestr] = dbgtype
>          return dbgtype
>  
> -    def cast(self, typestr, val):
> +    def _cast(self, tp, val):
> +        assert isinstance(tp, lldb.SBType)
>          if isinstance(val, lldb.value):
>              val = val.sbvalue
>          elif type(val) is int:
> -            tp = self._dbgtype(typestr)
>              return self._lldb_value_from_raw(val, tp.GetByteSize(), tp)
>          elif not isinstance(val, lldb.SBValue):
>              raise Exception(
> @@ -582,7 +610,6 @@ class _LLDBDebugger(Debugger):
>          # XXX: Simply SBValue.Cast() works incorrectly since it
>          # may take the 8 bytes of memory instead of 4, before the
>          # cast. Construct the value on the fly.
> -        tp = self._dbgtype(typestr)
>          if self._lldb_tp_isfp(tp):
>              rawval = float(val.GetValue())
>          elif self._lldb_tp_issigned(tp):
> @@ -670,15 +697,15 @@ class _LLDBDebugger(Debugger):
>          else:
>              return None
>  
> -    def eval(self, command):
> -        if not command:
> +    def eval(self, expr):
> +        if not expr:
>              return None
>  
>          process = self.target.GetProcess()
>          thread = process.GetSelectedThread()
>          frame = thread.GetSelectedFrame()
> -        ret = frame.EvaluateExpression(command)
> -        return ret
> +        ret = frame.EvaluateExpression(expr)
> +        return lldb.value(ret)

Nit: Lets drop unneeded variable `ret`:
| return lldb.value(frame.EvaluateExpression(expr))

>  
>      def detect_arch(self):
>          if hasattr(self, 'arch'):
> @@ -724,6 +751,37 @@ class _LLDBDebugger(Debugger):
>              )
>          )
>  
> +    def create_enum_value(self, enum_name, enum_member_name):
> +        val = self.eval(enum_name + "::" + enum_member_name)

Please add the comment about this eval construction.

> +        if val.sbvalue.IsValid() and val.sbvalue.error.Success():

Why do we need to check `val.sbvalue.error.Success()` value?
Why is `val.sbvalue.IsValid()` not enough?

> +            return val
> +
> +        # LLDB uses enum name in expression above but debugging information

Typo: s/enum/the enum/
Typo: s/expression/the expression/

> +        # about enum name migth be optimized out if no variable of the given

Typo: s/enum/the enum/

> +        # enum type is declared and its members are only used as the predefined
> +        # constants (like IRFieldID).

Nit: Please, use 66 comment line width.

> +
> +        # In this case the above method doesn't work so trying to discover
> +        # enum type by the given enum member.

Typo: s/enum/the enum/

Nit: Please, use 66 comment line width.

> +
> +        def find_enum_type_member(enum_type, enum_member_name):
> +            # SBTypeEnumMemberList supports members iteration and [] access
> +            # (both by index and by member name) only starting from lldb-12
> +            # so this implementation is used to handle earlier versions.

Nit: Please, use 66 comment line width.

> +            members = enum_type.GetEnumMembers()
> +            for i in range(members.GetSize()):
> +                item = members.GetTypeEnumMemberAtIndex(i)
> +                if item.name == enum_member_name:
> +                    return item
> +            return None
> +
> +        for m in self.target.modules:
> +            for et in m.GetTypes(lldb.eTypeClassEnumeration):
> +                et_member = find_enum_type_member(et, enum_member_name)
> +                if et_member is not None:
> +                    return self.cast(et, et_member.unsigned)

`self.cast_typeof(et, et_member.unsigned)`

> +        return None
> +
>      class LJBase(object):
>          # Ignore given parameters by LLDB.
>          def __init__(ljbase, debugger, unused):
> @@ -800,6 +858,45 @@ def strx64(val):
>      return re.sub('L?$', '', hex(int(tou64(val))))
>  
>  
> +class EnumBasedList(object):
> +    def __init__(self, enum_name, max_enum_member, map_func=None,
> +                 *map_func_extra_args):
> +        self.__enum_name = enum_name
> +        self.__max_enum_member = max_enum_member
> +        self.__map_func = map_func
> +        self.__map_func_extra_args = map_func_extra_args
> +        # Lazy initialization (see __get_items method) as the required

Nit: Please, use 66 comment line width.

Side note: I like this approach, it helps to avoid performance issues.

> +        # information might be unavailable at this moment.
> +        self.__items = None
> +
> +    def __iter__(self):
> +        return iter(self.__get_items())
> +
> +    def __getitem__(self, key):
> +        return self.__get_items()[key]
> +
> +    def __len__(self):
> +        return len(self.__get_items())
> +
> +    def __get_items(self):
> +        if self.__items is None:
> +            max_enum_value = dbg.create_enum_value(
> +                self.__enum_name, self.__max_enum_member
> +            )
> +            items = []
> +            for i in range(dbg.cast('int', max_enum_value)):

Can it be just:
| for i in range(int(max_enum_value))
instead?

> +                item = str(dbg.cast(max_enum_value.type, dbg.eval(str(i))))

| item = str(cast_typeof(max_enum_value, dbg.eval(str(i))))

Be aware, that debugger-specific type usage should be part of the
`cast_typeof()`. Hence, it allows dropping `.type` setting for LLDB.

> +                if self.__map_func:
> +                    item = self.__map_func(item, *self.__map_func_extra_args)
> +                items.append(item)
> +            self.__items = items
> +        return self.__items

<snipped>

> +IRTYPES = EnumBasedList('IRType', 'IRT__MAX', lambda x: {
> +                            'IRT_LIGHTUD': 'lud',
> +                            'IRT_CDATA': 'cdt',
> +                            'IRT_UDATA': 'udt',
> +                            'IRT_FLOAT': 'flt',
> +                            'IRT_SOFTFP': 'sfp',

Minor: since this is not the full spectrum of values, lets sort them
alphabetically.

> +                        }.get(x, cut_prefix(x, 'IRT_')[:3].ljust(3).lower()))

<snipped>

> -- 
> 2.43.0
> 

-- 
Best regards,
Sergey Kaplun

      parent reply	other threads:[~2026-09-21 18:47 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09  8:49 Mikhail Elhimov via Tarantool-patches
2026-09-17  9:52 ` Sergey Bronnikov via Tarantool-patches
2026-09-17 22:42   ` Mikhail Elhimov via Tarantool-patches
2026-09-17 22:42   ` Mikhail Elhimov via Tarantool-patches
2026-09-21 18:47 ` Sergey Kaplun via Tarantool-patches [this message]

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=arF7xltOWlZisx77@root \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=m.elhimov@vk.team \
    --cc=skaplun@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH luajit] dbg: avoid hardcoded enums (get them from target)' \
    /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