mirror of
https://github.com/llvm/llvm-project.git
synced 2026-10-26 13:53:31 +08:00
Updates the `@fmt` selector, by adding support for Python f-strings to
the Python to bytecode compiler, and updating the evaluation of `@fmt`
by improved interfacing with `llvm::formatv`.
Changes to `@fmt`:
* Consume the arguments, leaving only the resulting string on the stack.
Previously, the arguments were left on the stack, unlike what the docs
describe and what the interpreter does.
* In version 2 formatters, replacements are automatically indexed.
Explicit indexes,such as `{0}`, are an error. This makes the stack
effect of `@fmt` clear from the format string: the number of arguments
is the number of replacements, and each argument is used once, in stack
order. Arguments can be reordered or reused with the stack operations.
* In version 2 formatters, layouts and options (`{,8}`, `{:x}`) are
accepted, but are currently ignored. They are treated as best effort
hints, so that support for them can be added later without changing the
ABI.
* In version 2 formatters, the format string is checked by LLDB before
it reaches `formatv`, which is given only literal text, escaped braces,
and `{}`. Format strings come from the binary being debugged, and
`formatv` asserts on malformed input. Layouts with a very large width,
such as `{,4000000000}`, would also build a very large string, even in
release builds.
Version 1 formatters are unchanged, except for consuming their
arguments.
Changes to formatter_bytecode.py:
* f-strings are compiled to `@fmt`. Conversions (`{x!r}`) and format
specs (`{x:>8}`) are not supported, and are a compile error.
* String literals, including f-strings, escape quotes and backslashes
when emitting assembly.
* The interpreter's `@fmt` matches the C++ interpreter. It now passes
arguments in stack order, supports `{}`, and follows the `formatv` rules
for braces (`{{` is a literal `{`, and a lone `}` is a literal).
Assisted-by: claude
935 lines
34 KiB
C++
935 lines
34 KiB
C++
//===-- FormatterBytecode.cpp ---------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/DataFormatters/FormatterBytecode.h"
|
|
#include "lldb/Symbol/CompilerType.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/ValueObject/ValueObject.h"
|
|
#include "lldb/ValueObject/ValueObjectConstResult.h"
|
|
#include "lldb/lldb-forward.h"
|
|
#include "llvm/ADT/APSInt.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/DataExtractor.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/ErrorExtras.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include "llvm/Support/FormatProviders.h"
|
|
#include "llvm/Support/FormatVariadicDetails.h"
|
|
#include <type_traits>
|
|
|
|
using namespace lldb;
|
|
namespace lldb_private {
|
|
|
|
std::string toString(FormatterBytecode::OpCodes op) {
|
|
switch (op) {
|
|
#define DEFINE_OPCODE(OP, MNEMONIC, NAME) \
|
|
case OP: { \
|
|
const char *s = MNEMONIC; \
|
|
return s ? s : #NAME; \
|
|
}
|
|
#include "lldb/DataFormatters/FormatterBytecode.def"
|
|
#undef DEFINE_OPCODE
|
|
}
|
|
return llvm::utostr(op);
|
|
}
|
|
|
|
std::string toString(FormatterBytecode::Selectors sel) {
|
|
switch (sel) {
|
|
#define DEFINE_SELECTOR(ID, NAME) \
|
|
case ID: \
|
|
return "@" #NAME;
|
|
#include "lldb/DataFormatters/FormatterBytecode.def"
|
|
#undef DEFINE_SELECTOR
|
|
}
|
|
return "@" + llvm::utostr(sel);
|
|
}
|
|
|
|
std::string toString(FormatterBytecode::Signatures sig) {
|
|
switch (sig) {
|
|
#define DEFINE_SIGNATURE(ID, NAME) \
|
|
case ID: \
|
|
return "@" #NAME;
|
|
#include "lldb/DataFormatters/FormatterBytecode.def"
|
|
#undef DEFINE_SIGNATURE
|
|
}
|
|
return llvm::utostr(sig);
|
|
}
|
|
|
|
std::string toString(const FormatterBytecode::DataStack &data) {
|
|
std::string s;
|
|
llvm::raw_string_ostream os(s);
|
|
os << "[ ";
|
|
for (auto &d : data) {
|
|
if (auto s = std::get_if<std::string>(&d))
|
|
os << '"' << *s << '"';
|
|
else if (auto u = std::get_if<uint64_t>(&d))
|
|
os << *u << 'u';
|
|
else if (auto i = std::get_if<int64_t>(&d))
|
|
os << *i;
|
|
else if (auto ap = std::get_if<llvm::APSInt>(&d))
|
|
os << *ap;
|
|
else if (auto valobj = std::get_if<ValueObjectSP>(&d)) {
|
|
if (!valobj->get())
|
|
os << "null";
|
|
else
|
|
os << "object(" << valobj->get()->GetValueAsCString() << ')';
|
|
} else if (auto type = std::get_if<CompilerType>(&d)) {
|
|
os << '(' << type->GetTypeName(true) << ')';
|
|
} else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&d)) {
|
|
os << toString(*sel);
|
|
} else if (auto *dict =
|
|
std::get_if<std::shared_ptr<FormatterBytecode::Dictionary>>(
|
|
&d)) {
|
|
os << "dict(" << (*dict ? (*dict)->size() : 0) << ')';
|
|
}
|
|
os << ' ';
|
|
}
|
|
os << ']';
|
|
return s;
|
|
}
|
|
|
|
namespace FormatterBytecode {
|
|
|
|
/// Check a format string, and remove the layout and options of each
|
|
/// replacement. The format string may contain literal text, escaped braces
|
|
/// ("{{"), and automatically indexed replacements:
|
|
///
|
|
/// "{" [ "," layout ] [ ":" options ] "}"
|
|
///
|
|
/// Explicit indexes are not supported. Layouts and options are accepted, so
|
|
/// that support for them can be added without changing the ABI, but they are
|
|
/// currently ignored. The result contains only literal text, escaped braces,
|
|
/// and "{}", so malformed format strings never reach formatv.
|
|
static llvm::Expected<std::string> SanitizeFormatString(llvm::StringRef fmt) {
|
|
const llvm::StringRef original = fmt;
|
|
std::string result;
|
|
while (!fmt.empty()) {
|
|
// Copy the literal text up to the next brace.
|
|
llvm::StringRef literal = fmt.take_front(fmt.find('{'));
|
|
result += literal;
|
|
fmt = fmt.drop_front(literal.size());
|
|
if (fmt.empty())
|
|
break;
|
|
if (fmt.consume_front("{{")) {
|
|
result += "{{";
|
|
continue;
|
|
}
|
|
llvm::StringRef replacement = fmt;
|
|
fmt = fmt.drop_front();
|
|
// Skip the layout, which ends at the options or the closing brace.
|
|
if (fmt.consume_front(","))
|
|
fmt = fmt.substr(fmt.find_first_of("{}:"));
|
|
// Skip the options, which end at the closing brace.
|
|
if (fmt.consume_front(":"))
|
|
fmt = fmt.substr(fmt.find_first_of("{}"));
|
|
if (!fmt.consume_front("}"))
|
|
return llvm::createStringErrorV(
|
|
"invalid replacement in format string at offset {0}",
|
|
original.size() - replacement.size());
|
|
replacement = replacement.drop_back(fmt.size());
|
|
if (replacement != "{}")
|
|
LLDB_LOG(GetLog(LLDBLog::DataFormatters),
|
|
"ignoring unsupported layout or options in format replacement "
|
|
"'{0}'",
|
|
replacement);
|
|
result += "{}";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Implement the @format function.
|
|
static llvm::Error FormatImpl(DataStack &data, uint32_t version) {
|
|
auto fmt = data.Pop<std::string>();
|
|
if (version >= 2) {
|
|
auto sanitized = SanitizeFormatString(fmt);
|
|
if (!sanitized)
|
|
return sanitized.takeError();
|
|
fmt = std::move(*sanitized);
|
|
}
|
|
auto replacements =
|
|
llvm::formatv_object_base::parseFormatString(fmt, 0, false);
|
|
std::string s;
|
|
llvm::raw_string_ostream os(s);
|
|
unsigned num_args = 0;
|
|
for (const auto &r : replacements)
|
|
if (r.Type == llvm::ReplacementType::Format)
|
|
num_args = std::max(num_args, r.Index + 1);
|
|
|
|
if (data.size() < num_args)
|
|
return llvm::createStringError("not enough arguments");
|
|
|
|
for (const auto &r : replacements) {
|
|
if (r.Type == llvm::ReplacementType::Literal) {
|
|
os << r.Spec;
|
|
continue;
|
|
}
|
|
using namespace llvm::support::detail;
|
|
auto arg = data[data.size() - num_args + r.Index];
|
|
auto format = [&](FormatFunctorRef &&adapter) {
|
|
llvm::FmtAlign Align(adapter, r.Where, r.Width, r.Pad);
|
|
Align.format(os, r.Options);
|
|
};
|
|
|
|
if (auto s = std::get_if<std::string>(&arg))
|
|
format(FormatFunctor(s->c_str()));
|
|
else if (auto u = std::get_if<uint64_t>(&arg))
|
|
format(FormatFunctor(u));
|
|
else if (auto i = std::get_if<int64_t>(&arg))
|
|
format(FormatFunctor(i));
|
|
else if (auto ap = std::get_if<llvm::APSInt>(&arg))
|
|
format(FormatFunctor(*ap));
|
|
else if (auto valobj = std::get_if<ValueObjectSP>(&arg)) {
|
|
if (!valobj->get())
|
|
format(FormatFunctor("null object"));
|
|
else
|
|
format(FormatFunctor(valobj->get()->GetValueAsCString()));
|
|
} else if (auto type = std::get_if<CompilerType>(&arg))
|
|
format(FormatFunctor(type->GetDisplayTypeName()));
|
|
else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
|
|
format(FormatFunctor(toString(*sel)));
|
|
else if (std::holds_alternative<DictionarySP>(arg))
|
|
format(FormatFunctor("dict"));
|
|
}
|
|
// Consume the arguments, leaving only the result.
|
|
data.erase(data.end() - num_args, data.end());
|
|
data.Push(s);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type) {
|
|
if (data.size() < 1)
|
|
return llvm::createStringError("not enough elements on data stack");
|
|
|
|
auto &elem = data.back();
|
|
switch (type) {
|
|
case Any:
|
|
break;
|
|
case String:
|
|
if (!std::holds_alternative<std::string>(elem))
|
|
return llvm::createStringError("expected String");
|
|
break;
|
|
case UInt:
|
|
if (!std::holds_alternative<uint64_t>(elem))
|
|
return llvm::createStringError("expected UInt");
|
|
break;
|
|
case Int:
|
|
if (!std::holds_alternative<int64_t>(elem))
|
|
return llvm::createStringError("expected Int");
|
|
break;
|
|
case Object:
|
|
if (!std::holds_alternative<ValueObjectSP>(elem))
|
|
return llvm::createStringError("expected Object");
|
|
break;
|
|
case Type:
|
|
if (!std::holds_alternative<CompilerType>(elem))
|
|
return llvm::createStringError("expected Type");
|
|
break;
|
|
case Selector:
|
|
if (!std::holds_alternative<Selectors>(elem))
|
|
return llvm::createStringError("expected Selector");
|
|
break;
|
|
case Integer:
|
|
if (!std::holds_alternative<llvm::APSInt>(elem))
|
|
return llvm::createStringError("expected Integer");
|
|
break;
|
|
case Dict:
|
|
if (!std::holds_alternative<std::shared_ptr<FormatterBytecode::Dictionary>>(
|
|
elem))
|
|
return llvm::createStringError("expected Dictionary");
|
|
break;
|
|
}
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type1, DataType type2) {
|
|
if (auto error = TypeCheck(data, type2))
|
|
return error;
|
|
return TypeCheck(data.drop_back(), type1);
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type1, DataType type2, DataType type3) {
|
|
if (auto error = TypeCheck(data, type3))
|
|
return error;
|
|
return TypeCheck(data.drop_back(), type1, type2);
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type1, DataType type2, DataType type3,
|
|
DataType type4) {
|
|
if (auto error = TypeCheck(data, type4))
|
|
return error;
|
|
return TypeCheck(data.drop_back(), type1, type2, type3);
|
|
}
|
|
|
|
/// Wrap the result of a binary operator applied to two APSInts back into a
|
|
/// DataStackElement. Comparison operators yield bool and need bit_width/
|
|
/// is_unsigned to construct the boolean's APSInt representation; arithmetic
|
|
/// operators already yield a correctly-tagged APSInt and ignore them.
|
|
template <typename T>
|
|
static DataStackElement WrapAPSIntResult(T result, unsigned bit_width,
|
|
bool is_unsigned) {
|
|
if constexpr (std::is_same_v<T, bool>)
|
|
return llvm::APSInt(llvm::APInt(bit_width, result), is_unsigned);
|
|
else
|
|
return DataStackElement(std::move(result));
|
|
}
|
|
|
|
/// Returns true if `target` is transitively reachable via `from`. Likewise,
|
|
/// returns true if they are the same dictionary. This is used to prevent memory
|
|
/// leaks caused by retain cycles. Dictionaries can be shared by multiple
|
|
/// parents, so each one is visited only once to prevent exponential running
|
|
/// time.
|
|
static bool Reaches(const Dictionary *from, const Dictionary *target) {
|
|
llvm::SmallPtrSet<const Dictionary *, 8> visited;
|
|
llvm::SmallVector<const Dictionary *, 8> worklist = {from};
|
|
while (!worklist.empty()) {
|
|
const Dictionary *dict = worklist.pop_back_val();
|
|
if (dict == target)
|
|
return true;
|
|
if (!visited.insert(dict).second)
|
|
continue;
|
|
for (const auto &entry : *dict)
|
|
if (auto *nested = std::get_if<DictionarySP>(&entry.second))
|
|
worklist.push_back(nested->get());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
using EnableIfSigned =
|
|
std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, int>;
|
|
template <typename T>
|
|
using EnableIfUnsigned =
|
|
std::enable_if_t<std::is_integral_v<T> && std::is_unsigned_v<T> &&
|
|
!std::is_same_v<T, bool>,
|
|
int>;
|
|
|
|
template <typename T, EnableIfSigned<T> = 0>
|
|
static DataStackElement MakeInt(T value, uint32_t version) {
|
|
if (version < 2)
|
|
return int64_t(value);
|
|
return llvm::APSInt::get(value);
|
|
}
|
|
|
|
template <typename T, EnableIfUnsigned<T> = 0>
|
|
static DataStackElement MakeInt(T value, uint32_t version) {
|
|
if (version < 2)
|
|
return uint64_t(value);
|
|
unsigned width = uint64_t(value) > uint64_t(INT64_MAX) ? 65 : 64;
|
|
return llvm::APSInt(llvm::APInt(width, value), /*isUnsigned=*/false);
|
|
}
|
|
|
|
llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig,
|
|
uint32_t version) {
|
|
if (control.empty())
|
|
return llvm::Error::success();
|
|
// Since the only data types are single endian and ULEBs, the
|
|
// endianness should not matter.
|
|
llvm::DataExtractor cur_block(control.back(), true);
|
|
llvm::DataExtractor::Cursor pc(0);
|
|
|
|
while (!control.empty()) {
|
|
/// Activate the top most block from the control stack.
|
|
auto activate_block = [&]() {
|
|
// Save the return address.
|
|
if (control.size() > 1)
|
|
control[control.size() - 2] = cur_block.getData().drop_front(pc.tell());
|
|
cur_block = llvm::DataExtractor(control.back(), true);
|
|
if (pc)
|
|
pc = llvm::DataExtractor::Cursor(0);
|
|
};
|
|
|
|
/// Fetch the next byte in the instruction stream.
|
|
auto next_byte = [&]() -> uint8_t {
|
|
// At the end of the current block?
|
|
while (pc.tell() >= cur_block.size() && !control.empty()) {
|
|
if (control.size() == 1) {
|
|
control.pop_back();
|
|
return 0;
|
|
}
|
|
control.pop_back();
|
|
activate_block();
|
|
}
|
|
|
|
// Fetch the next instruction.
|
|
return cur_block.getU8(pc);
|
|
};
|
|
|
|
// Fetch the next opcode.
|
|
OpCodes opcode = (OpCodes)next_byte();
|
|
if (control.empty() || !pc)
|
|
return pc.takeError();
|
|
|
|
LLDB_LOG_VERBOSE(GetLog(LLDBLog::DataFormatters),
|
|
"[eval {0}] opcode={1}, control={2}, data={3}",
|
|
toString(sig), toString(opcode), control.size(),
|
|
toString(data));
|
|
|
|
// Various shorthands to improve the readability of error handling.
|
|
#define TYPE_CHECK(...) \
|
|
if (auto error = TypeCheck(data, __VA_ARGS__)) \
|
|
return error;
|
|
|
|
auto error = [&](llvm::Twine msg) {
|
|
return llvm::createStringError(msg + "(opcode=" + toString(opcode) + ")");
|
|
};
|
|
|
|
switch (opcode) {
|
|
// Data stack manipulation.
|
|
case op_dup:
|
|
TYPE_CHECK(Any);
|
|
data.Push(data.back());
|
|
continue;
|
|
case op_drop:
|
|
TYPE_CHECK(Any);
|
|
data.pop_back();
|
|
continue;
|
|
case op_pick: {
|
|
TYPE_CHECK(UInt);
|
|
uint64_t idx = data.Pop<uint64_t>();
|
|
if (idx >= data.size())
|
|
return error("index out of bounds");
|
|
data.Push(data[idx]);
|
|
continue;
|
|
}
|
|
case op_over:
|
|
TYPE_CHECK(Any, Any);
|
|
data.Push(data[data.size() - 2]);
|
|
continue;
|
|
case op_swap: {
|
|
TYPE_CHECK(Any, Any);
|
|
auto x = data.PopAny();
|
|
auto y = data.PopAny();
|
|
data.Push(x);
|
|
data.Push(y);
|
|
continue;
|
|
}
|
|
case op_rot: {
|
|
TYPE_CHECK(Any, Any, Any);
|
|
auto z = data.PopAny();
|
|
auto y = data.PopAny();
|
|
auto x = data.PopAny();
|
|
data.Push(z);
|
|
data.Push(x);
|
|
data.Push(y);
|
|
continue;
|
|
}
|
|
|
|
// Control stack manipulation.
|
|
case op_begin: {
|
|
uint64_t length = cur_block.getULEB128(pc);
|
|
if (!pc)
|
|
return pc.takeError();
|
|
llvm::StringRef block = cur_block.getBytes(pc, length);
|
|
if (!pc)
|
|
return pc.takeError();
|
|
control.push_back(block);
|
|
continue;
|
|
}
|
|
case op_if: {
|
|
auto cond = data.PopAny();
|
|
bool truthy;
|
|
if (auto *ap = std::get_if<llvm::APSInt>(&cond))
|
|
truthy = !ap->isZero();
|
|
else if (auto *u = std::get_if<uint64_t>(&cond))
|
|
// Deprecated.
|
|
truthy = *u != 0;
|
|
else
|
|
return error("expected Integer or UInt");
|
|
if (truthy) {
|
|
if (!cur_block.size())
|
|
return error("empty control stack");
|
|
activate_block();
|
|
} else
|
|
control.pop_back();
|
|
continue;
|
|
}
|
|
case op_ifelse: {
|
|
if (cur_block.size() < 2)
|
|
return error("empty control stack");
|
|
auto cond = data.PopAny();
|
|
bool truthy;
|
|
if (auto *ap = std::get_if<llvm::APSInt>(&cond))
|
|
truthy = !ap->isZero();
|
|
else if (auto *u = std::get_if<uint64_t>(&cond))
|
|
// Deprecated.
|
|
truthy = *u != 0;
|
|
else
|
|
return error("expected Integer or UInt");
|
|
if (!truthy)
|
|
control[control.size() - 2] = control.back();
|
|
control.pop_back();
|
|
activate_block();
|
|
continue;
|
|
}
|
|
case op_return:
|
|
control.clear();
|
|
return pc.takeError();
|
|
|
|
// Literals.
|
|
case op_lit_uint:
|
|
data.Push(cur_block.getULEB128(pc));
|
|
continue;
|
|
case op_lit_int:
|
|
data.Push(cur_block.getSLEB128(pc));
|
|
continue;
|
|
case op_lit_integer:
|
|
data.Push(cur_block.getSLEB128APSInt(pc));
|
|
continue;
|
|
case op_lit_selector:
|
|
data.Push(Selectors(cur_block.getU8(pc)));
|
|
continue;
|
|
case op_lit_null:
|
|
data.Push(ValueObjectSP());
|
|
continue;
|
|
case op_lit_string: {
|
|
uint64_t length = cur_block.getULEB128(pc);
|
|
llvm::StringRef bytes = cur_block.getBytes(pc, length);
|
|
data.Push(bytes.str());
|
|
continue;
|
|
}
|
|
case op_as_uint: {
|
|
TYPE_CHECK(Int);
|
|
uint64_t casted;
|
|
int64_t val = data.Pop<int64_t>();
|
|
memcpy(&casted, &val, sizeof(val));
|
|
data.Push(casted);
|
|
continue;
|
|
}
|
|
case op_as_int: {
|
|
TYPE_CHECK(UInt);
|
|
int64_t casted;
|
|
uint64_t val = data.Pop<uint64_t>();
|
|
memcpy(&casted, &val, sizeof(val));
|
|
data.Push(casted);
|
|
continue;
|
|
}
|
|
case op_is_null: {
|
|
TYPE_CHECK(Object);
|
|
data.Push(data.Pop<ValueObjectSP>() ? (uint64_t)0 : (uint64_t)1);
|
|
continue;
|
|
}
|
|
|
|
// Arithmetic operations.
|
|
#define BINOP_IMPL(OP, CHECK_ZERO) \
|
|
{ \
|
|
TYPE_CHECK(Any, Any); \
|
|
auto y = data.PopAny(); \
|
|
if (std::holds_alternative<uint64_t>(y)) { \
|
|
if (CHECK_ZERO && !std::get<uint64_t>(y)) \
|
|
return error(#OP " by zero"); \
|
|
TYPE_CHECK(UInt); \
|
|
data.Push((uint64_t)(data.Pop<uint64_t>() OP std::get<uint64_t>(y))); \
|
|
} else if (std::holds_alternative<int64_t>(y)) { \
|
|
if (CHECK_ZERO && !std::get<int64_t>(y)) \
|
|
return error(#OP " by zero"); \
|
|
TYPE_CHECK(Int); \
|
|
data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \
|
|
} else if (std::holds_alternative<llvm::APSInt>(y)) { \
|
|
TYPE_CHECK(Integer); \
|
|
llvm::APSInt rhs = std::get<llvm::APSInt>(y); \
|
|
llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \
|
|
if (lhs.isUnsigned() || rhs.isUnsigned()) \
|
|
return error("unsupported unsigned value"); \
|
|
unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \
|
|
lhs = lhs.extend(width); \
|
|
rhs = rhs.extend(width); \
|
|
if (CHECK_ZERO && rhs.isZero()) \
|
|
return error(#OP " by zero"); \
|
|
data.Push(WrapAPSIntResult(lhs OP rhs, width, lhs.isUnsigned())); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
#define BINOP(OP) BINOP_IMPL(OP, false)
|
|
#define BINOP_CHECKZERO(OP) BINOP_IMPL(OP, true)
|
|
|
|
// Comparison operations.
|
|
#define CMPOP(OP) \
|
|
{ \
|
|
TYPE_CHECK(Any, Any); \
|
|
auto y = data.PopAny(); \
|
|
if (std::holds_alternative<uint64_t>(y)) { \
|
|
TYPE_CHECK(UInt); \
|
|
data.Push((uint64_t)(data.Pop<uint64_t>() OP std::get<uint64_t>(y))); \
|
|
} else if (std::holds_alternative<int64_t>(y)) { \
|
|
TYPE_CHECK(Int); \
|
|
data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \
|
|
} else if (std::holds_alternative<llvm::APSInt>(y)) { \
|
|
TYPE_CHECK(Integer); \
|
|
llvm::APSInt rhs = std::get<llvm::APSInt>(y); \
|
|
llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \
|
|
if (lhs.isUnsigned() || rhs.isUnsigned()) \
|
|
return error("unsupported unsigned value"); \
|
|
unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \
|
|
lhs = lhs.extend(width); \
|
|
rhs = rhs.extend(width); \
|
|
data.Push(WrapAPSIntResult(lhs OP rhs, width, lhs.isUnsigned())); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
|
|
// Bitwise operations use an Integer's underlying bit pattern, not its
|
|
// mathematical value (ie signed-ness is ignored). This means >> is always a
|
|
// logical (zero-filling) shift, never an arithmetic shift. Mismatched bit
|
|
// widths are implicitly zero-extended (not sign-extended).
|
|
#define BITOP(OP) \
|
|
{ \
|
|
TYPE_CHECK(Any, Any); \
|
|
auto y = data.PopAny(); \
|
|
if (std::holds_alternative<uint64_t>(y)) { \
|
|
TYPE_CHECK(UInt); \
|
|
data.Push((uint64_t)(data.Pop<uint64_t>() OP std::get<uint64_t>(y))); \
|
|
} else if (std::holds_alternative<int64_t>(y)) { \
|
|
TYPE_CHECK(Int); \
|
|
data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \
|
|
} else if (std::holds_alternative<llvm::APSInt>(y)) { \
|
|
TYPE_CHECK(Integer); \
|
|
llvm::APSInt rhs = std::get<llvm::APSInt>(y); \
|
|
llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \
|
|
unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \
|
|
llvm::APInt lhs_bits = \
|
|
static_cast<const llvm::APInt &>(lhs).zext(width); \
|
|
llvm::APInt rhs_bits = \
|
|
static_cast<const llvm::APInt &>(rhs).zext(width); \
|
|
llvm::APInt bits = lhs_bits OP rhs_bits; \
|
|
data.Push(llvm::APSInt(std::move(bits), /*isUnsigned=*/false)); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
|
|
case op_plus:
|
|
BINOP(+);
|
|
continue;
|
|
case op_minus:
|
|
BINOP(-);
|
|
continue;
|
|
case op_mul:
|
|
BINOP(*);
|
|
continue;
|
|
case op_div:
|
|
BINOP_CHECKZERO(/);
|
|
continue;
|
|
case op_mod:
|
|
BINOP_CHECKZERO(%);
|
|
continue;
|
|
case op_shl:
|
|
#define SHIFTOP(OP, LEFT) \
|
|
{ \
|
|
TYPE_CHECK(Any, UInt); \
|
|
uint64_t y = data.Pop<uint64_t>(); \
|
|
if (y > 64) \
|
|
return error("shift out of bounds"); \
|
|
if (std::holds_alternative<uint64_t>(data.back())) { \
|
|
uint64_t x = data.Pop<uint64_t>(); \
|
|
data.Push(x OP y); \
|
|
} else if (std::holds_alternative<int64_t>(data.back())) { \
|
|
int64_t x = data.Pop<int64_t>(); \
|
|
if (x < 0 && LEFT) \
|
|
return error("left shift of negative value"); \
|
|
if (y > 64) \
|
|
return error("shift out of bounds"); \
|
|
data.Push(x OP y); \
|
|
} else if (std::holds_alternative<llvm::APSInt>(data.back())) { \
|
|
llvm::APSInt x = data.Pop<llvm::APSInt>(); \
|
|
if (y > x.getBitWidth()) \
|
|
return error("shift out of bounds"); \
|
|
const llvm::APInt &bits = x; \
|
|
llvm::APInt shifted = \
|
|
LEFT ? bits.shl((unsigned)y) : bits.lshr((unsigned)y); \
|
|
data.Push(llvm::APSInt(std::move(shifted), /*isUnsigned=*/false)); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
SHIFTOP(<<, true);
|
|
continue;
|
|
case op_shr:
|
|
SHIFTOP(>>, false);
|
|
continue;
|
|
case op_and:
|
|
BITOP(&);
|
|
continue;
|
|
case op_or:
|
|
BITOP(|);
|
|
continue;
|
|
case op_xor:
|
|
BITOP(^);
|
|
continue;
|
|
case op_not: {
|
|
TYPE_CHECK(Any);
|
|
auto x = data.PopAny();
|
|
if (std::holds_alternative<uint64_t>(x))
|
|
data.Push(~std::get<uint64_t>(x));
|
|
else if (auto *ap = std::get_if<llvm::APSInt>(&x)) {
|
|
llvm::APInt bits = ~static_cast<const llvm::APInt &>(*ap);
|
|
data.Push(llvm::APSInt(std::move(bits), /*isUnsigned=*/false));
|
|
} else
|
|
return error("unsupported data types");
|
|
continue;
|
|
}
|
|
case op_eq:
|
|
CMPOP(==);
|
|
continue;
|
|
case op_neq:
|
|
CMPOP(!=);
|
|
continue;
|
|
case op_lt:
|
|
CMPOP(<);
|
|
continue;
|
|
case op_gt:
|
|
CMPOP(>);
|
|
continue;
|
|
case op_le:
|
|
CMPOP(<=);
|
|
continue;
|
|
case op_ge:
|
|
CMPOP(>=);
|
|
continue;
|
|
case op_call: {
|
|
TYPE_CHECK(Selector);
|
|
Selectors sel = data.Pop<Selectors>();
|
|
|
|
// Shorthand to improve readability.
|
|
#define POP_VALOBJ(VALOBJ) \
|
|
auto VALOBJ = data.Pop<ValueObjectSP>(); \
|
|
if (!VALOBJ) \
|
|
return error("null object");
|
|
|
|
auto sel_error = [&](const char *msg) {
|
|
return llvm::createStringErrorV("{0} (opcode={1}, selector={2})", msg,
|
|
toString(opcode).c_str(),
|
|
toString(sel).c_str());
|
|
};
|
|
|
|
switch (sel) {
|
|
case sel_summary: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
const char *summary = valobj->GetSummaryAsCString();
|
|
data.Push(summary ? std::string(valobj->GetSummaryAsCString())
|
|
: std::string());
|
|
break;
|
|
}
|
|
case sel_get_num_children: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
auto result = valobj->GetNumChildren();
|
|
if (!result)
|
|
return result.takeError();
|
|
data.Push(MakeInt(*result, version));
|
|
break;
|
|
}
|
|
case sel_get_child_at_index: {
|
|
uint64_t index;
|
|
if (version >= 2) {
|
|
TYPE_CHECK(Object, Integer);
|
|
index = data.Pop<llvm::APSInt>().getLimitedValue(UINT32_MAX);
|
|
} else {
|
|
TYPE_CHECK(Object, UInt);
|
|
index = data.Pop<uint64_t>();
|
|
}
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetChildAtIndex(index));
|
|
break;
|
|
}
|
|
case sel_get_child_with_name: {
|
|
TYPE_CHECK(Object, String);
|
|
auto name = data.Pop<std::string>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetChildMemberWithName(name));
|
|
break;
|
|
}
|
|
case sel_get_child_index: {
|
|
TYPE_CHECK(Object, String);
|
|
auto name = data.Pop<std::string>();
|
|
POP_VALOBJ(valobj);
|
|
if (auto index_or_err = valobj->GetIndexOfChildWithName(name))
|
|
data.Push(MakeInt(*index_or_err, version));
|
|
else
|
|
return index_or_err.takeError();
|
|
break;
|
|
}
|
|
case sel_get_parent: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
auto *parent = valobj->GetParent();
|
|
data.Push(parent ? parent->GetSP() : ValueObjectSP());
|
|
break;
|
|
}
|
|
case sel_get_type: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
// FIXME: do we need to control dynamic type resolution?
|
|
data.Push(valobj->GetTypeImpl().GetCompilerType(false));
|
|
break;
|
|
}
|
|
case sel_get_template_argument_type: {
|
|
uint64_t index;
|
|
if (version >= 2) {
|
|
TYPE_CHECK(Type, Integer);
|
|
index = data.Pop<llvm::APSInt>().getLimitedValue();
|
|
} else {
|
|
TYPE_CHECK(Type, UInt);
|
|
index = data.Pop<uint64_t>();
|
|
}
|
|
auto type = data.Pop<CompilerType>();
|
|
// FIXME: There is more code in SBType::GetTemplateArgumentType().
|
|
data.Push(type.GetTypeTemplateArgument(index, true));
|
|
break;
|
|
}
|
|
case sel_get_synthetic_value: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetSyntheticValue());
|
|
break;
|
|
}
|
|
case sel_get_non_synthetic_value: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetNonSyntheticValue());
|
|
break;
|
|
}
|
|
case sel_get_value: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
data.Push(std::string(valobj->GetValueAsCString()));
|
|
break;
|
|
}
|
|
case sel_get_value_as_unsigned: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
uint64_t val = valobj->GetValueAsUnsigned(0, &success);
|
|
data.Push(MakeInt(val, version));
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
break;
|
|
}
|
|
case sel_get_value_as_signed: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
int64_t val = valobj->GetValueAsSigned(0, &success);
|
|
data.Push(MakeInt(val, version));
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
break;
|
|
}
|
|
case sel_get_value_as_address: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
uint64_t addr = valobj->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
if (auto process_sp = valobj->GetProcessSP())
|
|
addr = process_sp->FixDataAddress(addr);
|
|
data.Push(MakeInt(addr, version));
|
|
break;
|
|
}
|
|
case sel_cast: {
|
|
TYPE_CHECK(Object, Type);
|
|
auto type = data.Pop<CompilerType>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->Cast(type));
|
|
break;
|
|
}
|
|
case sel_clone: {
|
|
TYPE_CHECK(Object, String);
|
|
auto new_name = data.Pop<std::string>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->Clone(new_name));
|
|
break;
|
|
}
|
|
case sel_get_pointee_type: {
|
|
TYPE_CHECK(Type);
|
|
auto type = data.Pop<CompilerType>();
|
|
data.Push(type.GetPointeeType());
|
|
break;
|
|
}
|
|
case sel_get_byte_size: {
|
|
TYPE_CHECK(Type);
|
|
auto type = data.Pop<CompilerType>();
|
|
uint64_t size =
|
|
llvm::expectedToOptional(type.GetByteSize(nullptr)).value_or(0);
|
|
data.Push(llvm::APSInt::get(size));
|
|
break;
|
|
}
|
|
case sel_create_child_at_offset: {
|
|
TYPE_CHECK(Object, String, Integer, Type);
|
|
auto type = data.Pop<CompilerType>();
|
|
auto offset = data.Pop<llvm::APSInt>().getLimitedValue(UINT32_MAX);
|
|
ConstString name(data.Pop<std::string>());
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetSyntheticChildAtOffset(offset, type, true, name));
|
|
break;
|
|
}
|
|
case sel_strlen: {
|
|
TYPE_CHECK(String);
|
|
auto size = data.Pop<std::string>().size();
|
|
data.Push(MakeInt(size, version));
|
|
break;
|
|
}
|
|
case sel_fmt: {
|
|
TYPE_CHECK(String);
|
|
if (auto error = FormatImpl(data, version))
|
|
return error;
|
|
break;
|
|
}
|
|
default:
|
|
return sel_error("selector not implemented");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Dictionary operations.
|
|
case op_dict:
|
|
data.Push(std::make_shared<Dictionary>());
|
|
continue;
|
|
case op_dict_set: {
|
|
TYPE_CHECK(Dict, String, Any);
|
|
auto value = data.PopAny();
|
|
auto key = data.Pop<std::string>();
|
|
auto dict_sp = data.Pop<DictionarySP>();
|
|
if (auto *nested_sp = std::get_if<DictionarySP>(&value))
|
|
if (Reaches(nested_sp->get(), dict_sp.get()))
|
|
return error("dict_set would create a reference cycle");
|
|
(*dict_sp)[key] = std::move(value);
|
|
continue;
|
|
}
|
|
case op_dict_get: {
|
|
TYPE_CHECK(Dict, String);
|
|
auto key = data.Pop<std::string>();
|
|
auto dict_sp = data.Pop<DictionarySP>();
|
|
auto it = dict_sp->find(key);
|
|
if (it == dict_sp->end())
|
|
return error("key not found in dictionary");
|
|
data.Push(it->second);
|
|
continue;
|
|
}
|
|
case op_dict_has: {
|
|
TYPE_CHECK(Dict, String);
|
|
auto key = data.Pop<std::string>();
|
|
auto dict_sp = data.Pop<DictionarySP>();
|
|
bool found = dict_sp->find(key) != dict_sp->end();
|
|
data.Push(llvm::APSInt::get(found));
|
|
continue;
|
|
}
|
|
}
|
|
return error("opcode not implemented");
|
|
}
|
|
return pc.takeError();
|
|
}
|
|
} // namespace FormatterBytecode
|
|
|
|
} // namespace lldb_private
|