[lldb] Add a compiler/interpreter of LLDB data formatter bytecode to lldb/examples (#113398)
This PR adds a proof-of-concept for a bytecode designed to ship and run LLDB data formatters. More motivation and context can be found in the `formatter-bytecode.md` file and on discourse. https://discourse.llvm.org/t/a-bytecode-for-lldb-data-formatters/82696
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// A bare-bones llvm::Optional reimplementation.
|
||||
|
||||
template <typename T> struct MyOptionalStorage {
|
||||
MyOptionalStorage(T val) : value(val), hasVal(true) {}
|
||||
MyOptionalStorage() {}
|
||||
T value;
|
||||
bool hasVal = false;
|
||||
};
|
||||
|
||||
template <typename T> struct MyOptional {
|
||||
MyOptionalStorage<T> Storage;
|
||||
MyOptional(T val) : Storage(val) {}
|
||||
MyOptional() {}
|
||||
T &operator*() { return Storage.value; }
|
||||
};
|
||||
|
||||
void stop() {}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
MyOptional<int> x, y = 42;
|
||||
stop(); // break here
|
||||
return *y;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
This is the llvm::Optional data formatter from llvm/utils/lldbDataFormatters.py
|
||||
with the implementation replaced by bytecode.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from formatter_bytecode import *
|
||||
import lldb
|
||||
|
||||
|
||||
def __lldb_init_module(debugger, internal_dict):
|
||||
debugger.HandleCommand(
|
||||
"type synthetic add -w llvm "
|
||||
f"-l {__name__}.MyOptionalSynthProvider "
|
||||
'-x "^MyOptional<.+>$"'
|
||||
)
|
||||
debugger.HandleCommand(
|
||||
"type summary add -w llvm "
|
||||
f"-e -F {__name__}.MyOptionalSummaryProvider "
|
||||
'-x "^MyOptional<.+>$"'
|
||||
)
|
||||
|
||||
|
||||
def stringify(bytecode: bytearray) -> str:
|
||||
s = ""
|
||||
in_hex = False
|
||||
for b in bytecode:
|
||||
if (b < 32 or b > 127 or chr(b) in ['"', "`", "'"]) or (
|
||||
in_hex
|
||||
and chr(b).lower()
|
||||
in [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
]
|
||||
):
|
||||
s += r"\x" + hex(b)[2:]
|
||||
in_hex = True
|
||||
else:
|
||||
s += chr(b)
|
||||
in_hex = False
|
||||
return s
|
||||
|
||||
|
||||
def evaluate(assembler: str, data: list):
|
||||
bytecode = compile(assembler)
|
||||
trace = True
|
||||
if trace:
|
||||
print(
|
||||
"Compiled to {0} bytes of bytecode:\n{1}".format(
|
||||
len(bytecode), stringify(bytecode)
|
||||
)
|
||||
)
|
||||
result = interpret(bytecode, [], data, False) # trace)
|
||||
if trace:
|
||||
print("--> {0}".format(result))
|
||||
return result
|
||||
|
||||
|
||||
# def GetOptionalValue(valobj):
|
||||
# storage = valobj.GetChildMemberWithName("Storage")
|
||||
# if not storage:
|
||||
# storage = valobj
|
||||
#
|
||||
# failure = 2
|
||||
# hasVal = storage.GetChildMemberWithName("hasVal").GetValueAsUnsigned(failure)
|
||||
# if hasVal == failure:
|
||||
# return "<could not read MyOptional>"
|
||||
#
|
||||
# if hasVal == 0:
|
||||
# return None
|
||||
#
|
||||
# underlying_type = storage.GetType().GetTemplateArgumentType(0)
|
||||
# storage = storage.GetChildMemberWithName("value")
|
||||
# return storage.Cast(underlying_type)
|
||||
|
||||
|
||||
def MyOptionalSummaryProvider(valobj, internal_dict):
|
||||
# val = GetOptionalValue(valobj)
|
||||
# if val is None:
|
||||
# return "None"
|
||||
# if val.summary:
|
||||
# return val.summary
|
||||
# return val.GetValue()
|
||||
summary = ""
|
||||
summary += ' dup "Storage" @get_child_with_name call' # valobj storage
|
||||
summary += " dup is_null ~ { swap } if drop" # storage
|
||||
summary += ' dup "hasVal" @get_child_with_name call' # storage obj(hasVal)
|
||||
summary += ' dup is_null { drop "<could not read MyOptional>" } {'
|
||||
summary += " @get_value_as_unsigned call" # storage int(hasVal)
|
||||
summary += ' 0u = { "None" } {'
|
||||
summary += " dup @get_type call"
|
||||
summary += " 0u @get_template_argument_type call" # storage type
|
||||
summary += " swap" # type storage
|
||||
summary += ' "value" @get_child_with_name call' # type value
|
||||
summary += " swap @cast call" # type(value)
|
||||
summary += ' dup is_null { "None" } {'
|
||||
summary += (
|
||||
" dup @summary call dup @strlen call { @get_value call } { drop } ifelse"
|
||||
)
|
||||
summary += " } ifelse"
|
||||
summary += " } ifelse"
|
||||
summary += " } ifelse"
|
||||
return evaluate(summary, [valobj])
|
||||
|
||||
|
||||
class MyOptionalSynthProvider:
|
||||
"""Provides deref support to llvm::Optional<T>"""
|
||||
|
||||
def __init__(self, valobj, internal_dict):
|
||||
self.valobj = valobj
|
||||
|
||||
def num_children(self):
|
||||
# return self.valobj.num_children
|
||||
num_children = " @get_num_children call"
|
||||
return evaluate(num_children, [self.valobj])
|
||||
|
||||
def get_child_index(self, name):
|
||||
# if name == "$$dereference$$":
|
||||
# return self.valobj.num_children
|
||||
# return self.valobj.GetIndexOfChildWithName(name)
|
||||
get_child_index = ' dup "$$dereference$$" ='
|
||||
get_child_index += " { drop @get_num_children call } {" # obj name
|
||||
get_child_index += " @get_child_index call" # index
|
||||
get_child_index += " } ifelse"
|
||||
return evaluate(get_child_index, [self.valobj, name])
|
||||
|
||||
def get_child_at_index(self, index):
|
||||
# if index < self.valobj.num_children:
|
||||
# return self.valobj.GetChildAtIndex(index)
|
||||
# return GetOptionalValue(self.valobj) or lldb.SBValue()
|
||||
get_child_at_index = " over over swap" # obj index index obj
|
||||
get_child_at_index += " @get_num_children call" # obj index index n
|
||||
get_child_at_index += " < { @get_child_at_index call } {" # obj index
|
||||
|
||||
get_opt_val = ' dup "Storage" @get_child_with_name call' # valobj storage
|
||||
get_opt_val += " dup { swap } if drop" # storage
|
||||
get_opt_val += ' dup "hasVal" @get_child_with_name call' # storage
|
||||
get_opt_val += " @get_value_as_unsigned call" # storage int(hasVal)
|
||||
get_opt_val += ' dup 2 = { drop "<could not read MyOptional>" } {'
|
||||
get_opt_val += ' 0 = { "None" } {'
|
||||
get_opt_val += (
|
||||
" dup @get_type call 0 @get_template_argument_type call" # storage type
|
||||
)
|
||||
get_opt_val += " swap" # type storage
|
||||
get_opt_val += ' "value" @get_child_with_name call' # type value
|
||||
get_opt_val += " swap @cast call" # type(value)
|
||||
get_opt_val += " } ifelse"
|
||||
get_opt_val += " } ifelse"
|
||||
|
||||
get_child_at_index += get_opt_val
|
||||
get_child_at_index += " } ifelse"
|
||||
|
||||
return evaluate(get_child_at_index, [self.valobj, index])
|
||||
16
lldb/test/Shell/ScriptInterpreter/Python/bytecode.test
Normal file
16
lldb/test/Shell/ScriptInterpreter/Python/bytecode.test
Normal file
@@ -0,0 +1,16 @@
|
||||
# RUN: %python %S/../../../../examples/python/formatter_bytecode.py --test
|
||||
# RUN: %python %S/../../../../examples/python/formatter_bytecode.py --compile "1u dup" | FileCheck %s --check-prefix=COMPILE
|
||||
# RUN: %python %S/../../../../examples/Python/formatter_bytecode.py --disassemble "200101" | FileCheck %s --check-prefix=DISASSEMBLE
|
||||
# COMPILE: 200101
|
||||
# DISASSEMBLE: 1u dup
|
||||
|
||||
# RUN: %clang_host -std=c++17 -g %S/Inputs/FormatterBytecode/MyOptional.cpp -o %t.exe
|
||||
# RUN: %lldb %t.exe -o "command script import %S/../../../../examples/Python/formatter_bytecode.py" -o "command script import %S/Inputs/FormatterBytecode/formatter.py" -o "b -p here" -o "r" -o "v x" -o "v y" -o q | FileCheck %s --check-prefix=OPTIONAL
|
||||
# OPTIONAL: (lldb) v x
|
||||
# OPTIONAL: (MyOptional<int>) x = {
|
||||
# OPTIONAL: hasVal = false
|
||||
# OPTIONAL: }
|
||||
# OPTIONAL: (lldb) v y
|
||||
# OPTIONAL: (MyOptional<int>) y = {
|
||||
# OPTIONAL: Storage = (value = 42, hasVal = true)
|
||||
# OPTIONAL: }
|
||||
Reference in New Issue
Block a user