[Lldb-commits] [lldb] 87659a1 - Reland: [lldb] Implement a formatter bytecode interpreter in C++

Adrian Prantl via lldb-commits lldb-commits at lists.llvm.org
Tue Dec 10 16:45:05 PST 2024


Author: Adrian Prantl
Date: 2024-12-10T16:37:53-08:00
New Revision: 87659a17d0703c1244211d9f8d1f0c21e816f0e1

URL: https://github.com/llvm/llvm-project/commit/87659a17d0703c1244211d9f8d1f0c21e816f0e1
DIFF: https://github.com/llvm/llvm-project/commit/87659a17d0703c1244211d9f8d1f0c21e816f0e1.diff

LOG: Reland: [lldb] Implement a formatter bytecode interpreter in C++

Compared to the python version, this also does type checking and error
handling, so it's slightly longer, however, it's still comfortably
under 500 lines.

Relanding with more explicit type conversions.

Added: 
    lldb/include/lldb/DataFormatters/FormatterSection.h
    lldb/source/DataFormatters/FormatterBytecode.cpp
    lldb/source/DataFormatters/FormatterBytecode.def
    lldb/source/DataFormatters/FormatterBytecode.h
    lldb/source/DataFormatters/FormatterSection.cpp
    lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
    lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
    lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
    lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp

Modified: 
    lldb/include/lldb/DataFormatters/FormattersHelpers.h
    lldb/include/lldb/DataFormatters/TypeSummary.h
    lldb/include/lldb/lldb-enumerations.h
    lldb/source/API/SBTypeSummary.cpp
    lldb/source/Core/Section.cpp
    lldb/source/DataFormatters/CMakeLists.txt
    lldb/source/DataFormatters/TypeSummary.cpp
    lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
    lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
    lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
    lldb/source/Symbol/ObjectFile.cpp
    lldb/source/Target/Target.cpp
    lldb/unittests/DataFormatter/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/DataFormatters/FormatterSection.h b/lldb/include/lldb/DataFormatters/FormatterSection.h
new file mode 100644
index 00000000000000..0613fba6c3a602
--- /dev/null
+++ b/lldb/include/lldb/DataFormatters/FormatterSection.h
@@ -0,0 +1,26 @@
+//===-- FormattersSection.h -------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_DATAFORMATTERS_FORMATTERSECTION_H
+#define LLDB_DATAFORMATTERS_FORMATTERSECTION_H
+
+#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
+
+namespace lldb_private {
+/// Load type summaries embedded in the binary. These are type summaries
+/// provided by the authors of the code.
+void LoadTypeSummariesForModule(lldb::ModuleSP module_sp);
+
+/// Load data formatters embedded in the binary. These are formatters provided
+/// by the authors of the code using LLDB formatter bytecode.
+void LoadFormattersForModule(lldb::ModuleSP module_sp);
+
+} // namespace lldb_private
+
+#endif // LLDB_DATAFORMATTERS_FORMATTERSECTION_H

diff  --git a/lldb/include/lldb/DataFormatters/FormattersHelpers.h b/lldb/include/lldb/DataFormatters/FormattersHelpers.h
index a2e8521d96651b..a98042fd40f93a 100644
--- a/lldb/include/lldb/DataFormatters/FormattersHelpers.h
+++ b/lldb/include/lldb/DataFormatters/FormattersHelpers.h
@@ -1,5 +1,4 @@
-//===-- FormattersHelpers.h --------------------------------------*- C++
-//-*-===//
+//===-- FormattersHelpers.h -------------------------------------*- C++ -*-===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.

diff  --git a/lldb/include/lldb/DataFormatters/TypeSummary.h b/lldb/include/lldb/DataFormatters/TypeSummary.h
index 382824aa2813da..f4d5563516ecb8 100644
--- a/lldb/include/lldb/DataFormatters/TypeSummary.h
+++ b/lldb/include/lldb/DataFormatters/TypeSummary.h
@@ -22,6 +22,10 @@
 #include "lldb/Utility/Status.h"
 #include "lldb/Utility/StructuredData.h"
 
+namespace llvm {
+class MemoryBuffer;
+}
+
 namespace lldb_private {
 class TypeSummaryOptions {
 public:
@@ -44,7 +48,7 @@ class TypeSummaryOptions {
 
 class TypeSummaryImpl {
 public:
-  enum class Kind { eSummaryString, eScript, eCallback, eInternal };
+  enum class Kind { eSummaryString, eScript, eBytecode, eCallback, eInternal };
 
   virtual ~TypeSummaryImpl() = default;
 
@@ -409,6 +413,23 @@ struct ScriptSummaryFormat : public TypeSummaryImpl {
   ScriptSummaryFormat(const ScriptSummaryFormat &) = delete;
   const ScriptSummaryFormat &operator=(const ScriptSummaryFormat &) = delete;
 };
+
+/// A summary formatter that is defined in LLDB formmater bytecode.
+class BytecodeSummaryFormat : public TypeSummaryImpl {
+  std::unique_ptr<llvm::MemoryBuffer> m_bytecode;
+
+public:
+  BytecodeSummaryFormat(const TypeSummaryImpl::Flags &flags,
+                        std::unique_ptr<llvm::MemoryBuffer> bytecode);
+  bool FormatObject(ValueObject *valobj, std::string &dest,
+                    const TypeSummaryOptions &options) override;
+  std::string GetDescription() override;
+  std::string GetName() override;
+  static bool classof(const TypeSummaryImpl *S) {
+    return S->GetKind() == Kind::eBytecode;
+  }
+};
+
 } // namespace lldb_private
 
 #endif // LLDB_DATAFORMATTERS_TYPESUMMARY_H

diff  --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index e00ec7f17c5cb8..0094fcd596fdf7 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -764,6 +764,7 @@ enum SectionType {
   eSectionTypeDWARFDebugTuIndex,
   eSectionTypeCTF,
   eSectionTypeLLDBTypeSummaries,
+  eSectionTypeLLDBFormatters,
   eSectionTypeSwiftModules,
 };
 

diff  --git a/lldb/source/API/SBTypeSummary.cpp b/lldb/source/API/SBTypeSummary.cpp
index a5e87188c9f630..856ee0ed3175b3 100644
--- a/lldb/source/API/SBTypeSummary.cpp
+++ b/lldb/source/API/SBTypeSummary.cpp
@@ -343,6 +343,7 @@ bool SBTypeSummary::IsEqualTo(lldb::SBTypeSummary &rhs) {
   case TypeSummaryImpl::Kind::eCallback:
     return llvm::dyn_cast<CXXFunctionSummaryFormat>(m_opaque_sp.get()) ==
            llvm::dyn_cast<CXXFunctionSummaryFormat>(rhs.m_opaque_sp.get());
+  case TypeSummaryImpl::Kind::eBytecode:
   case TypeSummaryImpl::Kind::eScript:
     if (IsFunctionCode() != rhs.IsFunctionCode())
       return false;

diff  --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp
index ee01b4ce06ca1e..31273ede618f21 100644
--- a/lldb/source/Core/Section.cpp
+++ b/lldb/source/Core/Section.cpp
@@ -149,10 +149,12 @@ const char *Section::GetTypeAsCString() const {
     return "ctf";
   case eSectionTypeLLDBTypeSummaries:
     return "lldb-type-summaries";
-  case eSectionTypeOther:
-    return "regular";
+  case eSectionTypeLLDBFormatters:
+    return "lldb-formatters";
   case eSectionTypeSwiftModules:
     return "swift-modules";
+  case eSectionTypeOther:
+    return "regular";
   }
   return "unknown";
 }
@@ -460,6 +462,7 @@ bool Section::ContainsOnlyDebugInfo() const {
   case eSectionTypeDWARFGNUDebugAltLink:
   case eSectionTypeCTF:
   case eSectionTypeLLDBTypeSummaries:
+  case eSectionTypeLLDBFormatters:
   case eSectionTypeSwiftModules:
     return true;
   }

diff  --git a/lldb/source/DataFormatters/CMakeLists.txt b/lldb/source/DataFormatters/CMakeLists.txt
index 13faf65227d2c9..91b10ba9e0ac8b 100644
--- a/lldb/source/DataFormatters/CMakeLists.txt
+++ b/lldb/source/DataFormatters/CMakeLists.txt
@@ -5,7 +5,9 @@ add_lldb_library(lldbDataFormatters NO_PLUGIN_DEPENDENCIES
   FormatCache.cpp
   FormatClasses.cpp
   FormatManager.cpp
+  FormatterBytecode.cpp
   FormattersHelpers.cpp
+  FormatterSection.cpp
   LanguageCategory.cpp
   StringPrinter.cpp
   TypeCategory.cpp

diff  --git a/lldb/source/DataFormatters/FormatterBytecode.cpp b/lldb/source/DataFormatters/FormatterBytecode.cpp
new file mode 100644
index 00000000000000..21e90e75202f68
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.cpp
@@ -0,0 +1,575 @@
+//===-- 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 "FormatterBytecode.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/DataExtractor.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/FormatProviders.h"
+#include "llvm/Support/FormatVariadicDetails.h"
+
+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 "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+  }
+  return llvm::utostr(op);
+}
+
+std::string toString(FormatterBytecode::Selectors sel) {
+  switch (sel) {
+#define DEFINE_SELECTOR(ID, NAME)                                              \
+  case ID:                                                                     \
+    return "@" #NAME;
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+  }
+  return "@" + llvm::utostr(sel);
+}
+
+std::string toString(FormatterBytecode::Signatures sig) {
+  switch (sig) {
+#define DEFINE_SIGNATURE(ID, NAME)                                             \
+  case ID:                                                                     \
+    return "@" #NAME;
+#include "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 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);
+    }
+    os << ' ';
+  }
+  os << ']';
+  return s;
+}
+
+namespace FormatterBytecode {
+
+/// Implement the @format function.
+static llvm::Error FormatImpl(DataStack &data) {
+  auto fmt = data.Pop<std::string>();
+  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 = [&](format_adapter &&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(build_format_adapter(s->c_str()));
+    else if (auto u = std::get_if<uint64_t>(&arg))
+      format(build_format_adapter(u));
+    else if (auto i = std::get_if<int64_t>(&arg))
+      format(build_format_adapter(i));
+    else if (auto valobj = std::get_if<ValueObjectSP>(&arg)) {
+      if (!valobj->get())
+        format(build_format_adapter("null object"));
+      else
+        format(build_format_adapter(valobj->get()->GetValueAsCString()));
+    } else if (auto type = std::get_if<CompilerType>(&arg))
+      format(build_format_adapter(type->GetDisplayTypeName()));
+    else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
+      format(build_format_adapter(toString(*sel)));
+  }
+  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;
+  }
+  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(1), type2, type1);
+}
+
+llvm::Error Interpret(std::vector<ControlStackElement> &control,
+                      DataStack &data, Selectors sel) {
+  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, 64);
+  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, 64);
+      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_LOGV(GetLog(LLDBLog::DataFormatters),
+              "[eval {0}] opcode={1}, control={2}, data={3}", toString(sel),
+              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:
+      TYPE_CHECK(UInt);
+      if (data.Pop<uint64_t>() != 0) {
+        if (!cur_block.size())
+          return error("empty control stack");
+        activate_block();
+      } else
+        control.pop_back();
+      continue;
+    case op_ifelse:
+      TYPE_CHECK(UInt);
+      if (cur_block.size() < 2)
+        return error("empty control stack");
+      if (data.Pop<uint64_t>() == 0)
+        control[control.size() - 2] = control.back();
+      control.pop_back();
+      activate_block();
+      continue;
+
+    // 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_selector:
+      data.Push(Selectors(cur_block.getU8(pc)));
+      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>() ? 0ULL : 1ULL);
+      continue;
+    }
+
+    // Arithmetic, logic, etc.
+#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                                                                     \
+      return error("unsupported data types");                                  \
+  }
+#define BINOP(OP) BINOP_IMPL(OP, false)
+#define BINOP_CHECKZERO(OP) BINOP_IMPL(OP, true)
+    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)                                                            \
+  {                                                                            \
+    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 (y > 64)                                                              \
+        return error("shift out of bounds");                                   \
+      if (y < 0)                                                               \
+        return error("shift out of bounds");                                   \
+      data.Push(x OP y);                                                       \
+    } else                                                                     \
+      return error("unsupported data types");                                  \
+  }
+      SHIFTOP(<<);
+      continue;
+    case op_shr:
+      SHIFTOP(<<);
+      continue;
+    case op_and:
+      BINOP(&);
+      continue;
+    case op_or:
+      BINOP(|);
+      continue;
+    case op_xor:
+      BINOP(^);
+      continue;
+    case op_not:
+      TYPE_CHECK(UInt);
+      data.Push(~data.Pop<uint64_t>());
+      continue;
+    case op_eq:
+      BINOP(==);
+      continue;
+    case op_neq:
+      BINOP(!=);
+      continue;
+    case op_lt:
+      BINOP(<);
+      continue;
+    case op_gt:
+      BINOP(>);
+      continue;
+    case op_le:
+      BINOP(<=);
+      continue;
+    case op_ge:
+      BINOP(>=);
+      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::createStringError("{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((uint64_t)*result);
+        break;
+      }
+      case sel_get_child_at_index: {
+        TYPE_CHECK(Object, UInt);
+        auto 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);
+        data.Push((uint64_t)valobj->GetIndexOfChildWithName(name));
+        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: {
+        TYPE_CHECK(Type, UInt);
+        auto 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_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(val);
+        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(val);
+        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(addr);
+        break;
+      }
+      case sel_cast: {
+        TYPE_CHECK(Object, Type);
+        auto type = data.Pop<CompilerType>();
+        POP_VALOBJ(valobj);
+        data.Push(valobj->Cast(type));
+        break;
+      }
+      case sel_strlen: {
+        TYPE_CHECK(String);
+        data.Push((uint64_t)data.Pop<std::string>().size());
+        break;
+      }
+      case sel_fmt: {
+        TYPE_CHECK(String);
+        if (auto error = FormatImpl(data))
+          return error;
+        break;
+      }
+      default:
+        return sel_error("selector not implemented");
+      }
+      continue;
+    }
+    }
+    return error("opcode not implemented");
+  }
+  return pc.takeError();
+}
+} // namespace FormatterBytecode
+
+} // namespace lldb_private

diff  --git a/lldb/source/DataFormatters/FormatterBytecode.def b/lldb/source/DataFormatters/FormatterBytecode.def
new file mode 100644
index 00000000000000..c6645631fa0065
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.def
@@ -0,0 +1,101 @@
+//===-- FormatterBytecode.def -----------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DEFINE_OPCODE
+#define DEFINE_OPCODE(OP, MNEMONIC, NAME)
+#endif
+#ifndef DEFINE_SELECTOR
+#define DEFINE_SELECTOR(ID, NAME)
+#endif
+#ifndef DEFINE_SIGNATURE
+#define DEFINE_SIGNATURE(ID, NAME)
+#endif
+
+// Opcodes.
+DEFINE_OPCODE(0x01, "dup",  dup)
+DEFINE_OPCODE(0x02, "drop", drop)
+DEFINE_OPCODE(0x03, "pick", pick)
+DEFINE_OPCODE(0x04, "over", over)
+DEFINE_OPCODE(0x05, "swap", swap)
+DEFINE_OPCODE(0x06, "rot",  rot)
+
+DEFINE_OPCODE(0x10, "{", begin)
+DEFINE_OPCODE(0x11, "if", if)
+DEFINE_OPCODE(0x12, "ifelse", ifelse)
+
+DEFINE_OPCODE(0x20, nullptr, lit_uint)
+DEFINE_OPCODE(0x21, nullptr, lit_int)
+DEFINE_OPCODE(0x22, nullptr, lit_string)
+DEFINE_OPCODE(0x23, nullptr, lit_selector)
+
+DEFINE_OPCODE(0x2a, "as_int", as_int)
+DEFINE_OPCODE(0x2b, "as_uint", as_uint)
+DEFINE_OPCODE(0x2c, "is_null", is_null)
+
+DEFINE_OPCODE(0x30, "+", plus)
+DEFINE_OPCODE(0x31, "-", minus)
+DEFINE_OPCODE(0x32, "*", mul)
+DEFINE_OPCODE(0x33, "/", div)
+DEFINE_OPCODE(0x34, "%", mod)
+DEFINE_OPCODE(0x35, "<<", shl)
+DEFINE_OPCODE(0x36, ">>", shr)
+
+DEFINE_OPCODE(0x40, "&", and)
+DEFINE_OPCODE(0x41, "|", or)
+DEFINE_OPCODE(0x42, "^", xor)
+DEFINE_OPCODE(0x43, "~", not)
+
+DEFINE_OPCODE(0x50, "=", eq)
+DEFINE_OPCODE(0x51, "!=", neq)
+DEFINE_OPCODE(0x52, "<", lt)
+DEFINE_OPCODE(0x53, ">", gt)
+DEFINE_OPCODE(0x54, "=<", le)
+DEFINE_OPCODE(0x55, ">=", ge)
+
+DEFINE_OPCODE(0x60, "call", call)
+
+// Selectors.
+DEFINE_SELECTOR(0x00, summary)
+DEFINE_SELECTOR(0x01, type_summary)
+
+DEFINE_SELECTOR(0x10, get_num_children)
+DEFINE_SELECTOR(0x11, get_child_at_index)
+DEFINE_SELECTOR(0x12, get_child_with_name)
+DEFINE_SELECTOR(0x13, get_child_index)
+DEFINE_SELECTOR(0x15, get_type)
+DEFINE_SELECTOR(0x16, get_template_argument_type)
+DEFINE_SELECTOR(0x17, cast)
+
+DEFINE_SELECTOR(0x20, get_value)
+DEFINE_SELECTOR(0x21, get_value_as_unsigned)
+DEFINE_SELECTOR(0x22, get_value_as_signed)
+DEFINE_SELECTOR(0x23, get_value_as_address)
+
+DEFINE_SELECTOR(0x40, read_memory_byte)
+DEFINE_SELECTOR(0x41, read_memory_uint32)
+DEFINE_SELECTOR(0x42, read_memory_int32)
+DEFINE_SELECTOR(0x43, read_memory_unsigned)
+DEFINE_SELECTOR(0x44, read_memory_signed)
+DEFINE_SELECTOR(0x45, read_memory_address)
+DEFINE_SELECTOR(0x46, read_memory)
+
+DEFINE_SELECTOR(0x50, fmt)
+DEFINE_SELECTOR(0x51, sprintf)
+DEFINE_SELECTOR(0x52, strlen)
+
+// Formatter signatures.
+DEFINE_SIGNATURE(0, summary)
+DEFINE_SIGNATURE(1, init)
+DEFINE_SIGNATURE(2, get_num_children)
+DEFINE_SIGNATURE(3, get_child_index)
+DEFINE_SIGNATURE(4, get_child_at_index)
+DEFINE_SIGNATURE(5, get_value)
+
+#undef DEFINE_OPCODE
+#undef DEFINE_SELECTOR
+#undef DEFINE_SIGNATURE

diff  --git a/lldb/source/DataFormatters/FormatterBytecode.h b/lldb/source/DataFormatters/FormatterBytecode.h
new file mode 100644
index 00000000000000..21454d9c7e231f
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.h
@@ -0,0 +1,64 @@
+//===-- FormatterBytecode.h -------------------------------------*- C++ -*-===//
+//
+// 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/TypeSummary.h"
+#include "lldb/Symbol/CompilerType.h"
+
+namespace lldb_private {
+
+namespace FormatterBytecode {
+
+enum DataType : uint8_t { Any, String, Int, UInt, Object, Type, Selector };
+
+enum OpCodes : uint8_t {
+#define DEFINE_OPCODE(OP, MNEMONIC, NAME) op_##NAME = OP,
+#include "FormatterBytecode.def"
+#undef DEFINE_OPCODE
+};
+
+enum Selectors : uint8_t {
+#define DEFINE_SELECTOR(ID, NAME) sel_##NAME = ID,
+#include "FormatterBytecode.def"
+#undef DEFINE_SELECTOR
+};
+
+enum Signatures : uint8_t {
+#define DEFINE_SIGNATURE(ID, NAME) sig_##NAME = ID,
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+};
+
+using ControlStackElement = llvm::StringRef;
+using DataStackElement =
+    std::variant<std::string, uint64_t, int64_t, lldb::ValueObjectSP,
+                 CompilerType, Selectors>;
+struct DataStack : public std::vector<DataStackElement> {
+  DataStack() = default;
+  DataStack(lldb::ValueObjectSP initial_value)
+      : std::vector<DataStackElement>({initial_value}) {}
+  void Push(DataStackElement el) { push_back(el); }
+  template <typename T> T Pop() {
+    T el = std::get<T>(back());
+    pop_back();
+    return el;
+  }
+  DataStackElement PopAny() {
+    DataStackElement el = back();
+    pop_back();
+    return el;
+  }
+};
+llvm::Error Interpret(std::vector<ControlStackElement> &control,
+                      DataStack &data, Selectors sel);
+} // namespace FormatterBytecode
+
+std::string toString(FormatterBytecode::OpCodes op);
+std::string toString(FormatterBytecode::Selectors sel);
+std::string toString(FormatterBytecode::Signatures sig);
+
+} // namespace lldb_private

diff  --git a/lldb/source/DataFormatters/FormatterSection.cpp b/lldb/source/DataFormatters/FormatterSection.cpp
new file mode 100644
index 00000000000000..f70f41fdeb736f
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterSection.cpp
@@ -0,0 +1,163 @@
+//===-- 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 "FormatterBytecode.h"
+#include "lldb/Core/Module.h"
+#include "lldb/DataFormatters/DataVisualization.h"
+#include "lldb/Utility/LLDBLog.h"
+
+using namespace lldb;
+
+namespace lldb_private {
+static void ForEachFormatterInModule(
+    Module &module, SectionType section_type,
+    std::function<void(llvm::DataExtractor, llvm::StringRef)> fn) {
+  auto *sections = module.GetSectionList();
+  if (!sections)
+    return;
+
+  auto section_sp = sections->FindSectionByType(section_type, true);
+  if (!section_sp)
+    return;
+
+  TypeCategoryImplSP category;
+  DataVisualization::Categories::GetCategory(ConstString("default"), category);
+
+  // The type summary record is serialized as follows.
+  //
+  // Each record contains, in order:
+  //   * Version number of the record format
+  //   * The remaining size of the record
+  //   * The size of the type identifier
+  //   * The type identifier, either a type name, or a regex
+  //   * The size of the entry
+  //   * The entry
+  //
+  // Integers are encoded using ULEB.
+  //
+  // Strings are encoded with first a length (ULEB), then the string contents,
+  // and lastly a null terminator. The length includes the null.
+
+  DataExtractor lldb_extractor;
+  auto section_size = section_sp->GetSectionData(lldb_extractor);
+  llvm::DataExtractor section = lldb_extractor.GetAsLLVM();
+  bool le = section.isLittleEndian();
+  uint8_t addr_size = section.getAddressSize();
+  llvm::DataExtractor::Cursor cursor(0);
+  while (cursor && cursor.tell() < section_size) {
+    uint64_t version = section.getULEB128(cursor);
+    uint64_t record_size = section.getULEB128(cursor);
+    if (version == 1) {
+      llvm::DataExtractor record(section.getData().drop_front(cursor.tell()),
+                                 le, addr_size);
+      llvm::DataExtractor::Cursor cursor(0);
+      uint64_t type_size = record.getULEB128(cursor);
+      llvm::StringRef type_name = record.getBytes(cursor, type_size);
+      llvm::Error error = cursor.takeError();
+      if (!error)
+        fn(llvm::DataExtractor(record.getData().drop_front(cursor.tell()), le,
+                               addr_size),
+           type_name);
+      else
+        LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), std::move(error),
+                       "{0}");
+    } else {
+      // Skip unsupported record.
+      LLDB_LOG(
+          GetLog(LLDBLog::DataFormatters),
+          "Skipping unsupported embedded type summary of version {0} in {1}.",
+          version, module.GetFileSpec());
+    }
+    section.skip(cursor, record_size);
+  }
+  if (!cursor)
+    LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(), "{0}");
+}
+
+void LoadTypeSummariesForModule(ModuleSP module_sp) {
+  ForEachFormatterInModule(
+      *module_sp, eSectionTypeLLDBTypeSummaries,
+      [&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
+        TypeCategoryImplSP category;
+        DataVisualization::Categories::GetCategory(ConstString("default"),
+                                                   category);
+        // The type summary record is serialized as follows.
+        //
+        //   * The size of the summary string
+        //   * The summary string
+        //
+        // Integers are encoded using ULEB.
+        llvm::DataExtractor::Cursor cursor(0);
+        uint64_t summary_size = extractor.getULEB128(cursor);
+        llvm::StringRef summary_string =
+            extractor.getBytes(cursor, summary_size);
+        if (!cursor) {
+          LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
+                         "{0}");
+          return;
+        }
+        if (type_name.empty() || summary_string.empty()) {
+          LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+                   "Missing string(s) in embedded type summary in {0}, "
+                   "type_name={1}, summary={2}",
+                   module_sp->GetFileSpec(), type_name, summary_string);
+          return;
+        }
+        TypeSummaryImpl::Flags flags;
+        auto summary_sp = std::make_shared<StringSummaryFormat>(
+            flags, summary_string.str().c_str());
+        FormatterMatchType match_type = eFormatterMatchExact;
+        if (type_name.front() == '^')
+          match_type = eFormatterMatchRegex;
+        category->AddTypeSummary(type_name, match_type, summary_sp);
+        LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+                 "Loaded embedded type summary for '{0}' from {1}.", type_name,
+                 module_sp->GetFileSpec());
+      });
+}
+
+void LoadFormattersForModule(ModuleSP module_sp) {
+  ForEachFormatterInModule(
+      *module_sp, eSectionTypeLLDBFormatters,
+      [&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
+        // * Function signature (1 byte)
+        // * Length of the program (ULEB128)
+        // * The program bytecode
+        TypeCategoryImplSP category;
+        DataVisualization::Categories::GetCategory(ConstString("default"),
+                                                   category);
+        llvm::DataExtractor::Cursor cursor(0);
+        uint64_t flags = extractor.getULEB128(cursor);
+        while (cursor && cursor.tell() < extractor.size()) {
+          uint8_t signature = extractor.getU8(cursor);
+          uint64_t size = extractor.getULEB128(cursor);
+          llvm::StringRef bytecode = extractor.getBytes(cursor, size);
+          if (!cursor) {
+            LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
+                           "{0}");
+            return;
+          }
+          if (signature == 0) {
+            auto summary_sp = std::make_shared<BytecodeSummaryFormat>(
+                TypeSummaryImpl::Flags(flags),
+                llvm::MemoryBuffer::getMemBufferCopy(bytecode));
+            FormatterMatchType match_type = eFormatterMatchExact;
+            if (type_name.front() == '^')
+              match_type = eFormatterMatchRegex;
+            category->AddTypeSummary(type_name, match_type, summary_sp);
+            LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+                     "Loaded embedded type summary for '{0}' from {1}.",
+                     type_name, module_sp->GetFileSpec());
+          } else
+            LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+                     "Unsupported formatter signature {0} for '{1}' in {2}",
+                     signature, type_name, module_sp->GetFileSpec());
+        }
+      });
+}
+} // namespace lldb_private

diff  --git a/lldb/source/DataFormatters/TypeSummary.cpp b/lldb/source/DataFormatters/TypeSummary.cpp
index 3538c1b60c8e9e..2c863b364538f3 100644
--- a/lldb/source/DataFormatters/TypeSummary.cpp
+++ b/lldb/source/DataFormatters/TypeSummary.cpp
@@ -8,9 +8,7 @@
 
 #include "lldb/DataFormatters/TypeSummary.h"
 
-
-
-
+#include "FormatterBytecode.h"
 #include "lldb/lldb-enumerations.h"
 #include "lldb/lldb-public.h"
 
@@ -58,6 +56,8 @@ std::string TypeSummaryImpl::GetSummaryKindName() {
     return "python";
   case Kind::eInternal:
     return "c++";
+  case Kind::eBytecode:
+    return "bytecode";
   }
 }
 
@@ -230,3 +230,74 @@ std::string ScriptSummaryFormat::GetDescription() {
 }
 
 std::string ScriptSummaryFormat::GetName() { return m_script_formatter_name; }
+
+BytecodeSummaryFormat::BytecodeSummaryFormat(
+    const TypeSummaryImpl::Flags &flags,
+    std::unique_ptr<llvm::MemoryBuffer> bytecode)
+    : TypeSummaryImpl(Kind::eBytecode, flags), m_bytecode(std::move(bytecode)) {
+}
+
+bool BytecodeSummaryFormat::FormatObject(ValueObject *valobj,
+                                         std::string &retval,
+                                         const TypeSummaryOptions &options) {
+  if (!valobj)
+    return false;
+
+  TargetSP target_sp(valobj->GetTargetSP());
+
+  if (!target_sp) {
+    retval.assign("error: no target");
+    return false;
+  }
+
+  std::vector<FormatterBytecode::ControlStackElement> control(
+      {m_bytecode->getBuffer()});
+  FormatterBytecode::DataStack data({valobj->GetSP()});
+  llvm::Error error = FormatterBytecode::Interpret(
+      control, data, FormatterBytecode::sel_summary);
+  if (error) {
+    retval = llvm::toString(std::move(error));
+    return false;
+  }
+  if (!data.size()) {
+    retval = "empty stack";
+    return false;
+  }
+  auto &top = data.back();
+  retval = "";
+  llvm::raw_string_ostream os(retval);
+  if (auto s = std::get_if<std::string>(&top))
+    os << *s;
+  else if (auto u = std::get_if<uint64_t>(&top))
+    os << *u;
+  else if (auto i = std::get_if<int64_t>(&top))
+    os << *i;
+  else if (auto valobj = std::get_if<ValueObjectSP>(&top)) {
+    if (!valobj->get())
+      os << "empty object";
+    else
+      os << valobj->get()->GetValueAsCString();
+  } else if (auto type = std::get_if<CompilerType>(&top)) {
+    os << type->TypeDescription();
+  } else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&top)) {
+    os << toString(*sel);
+  }
+  return true;
+}
+
+std::string BytecodeSummaryFormat::GetDescription() {
+  StreamString sstr;
+  sstr.Printf("%s%s%s%s%s%s%s\n  ", Cascades() ? "" : " (not cascading)",
+              !DoesPrintChildren(nullptr) ? "" : " (show children)",
+              !DoesPrintValue(nullptr) ? " (hide value)" : "",
+              IsOneLiner() ? " (one-line printout)" : "",
+              SkipsPointers() ? " (skip pointers)" : "",
+              SkipsReferences() ? " (skip references)" : "",
+              HideNames(nullptr) ? " (hide member names)" : "");
+  // FIXME: sstr.PutCString(disassembly);
+  return std::string(sstr.GetString());
+}
+
+std::string BytecodeSummaryFormat::GetName() {
+  return "LLDB bytecode formatter";
+}

diff  --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
index a8249cf103833e..b16cb114bec88e 100644
--- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -1697,6 +1697,7 @@ static SectionType GetSectionTypeFromName(llvm::StringRef Name) {
       .Case(".gosymtab", eSectionTypeGoSymtab)
       .Case(".text", eSectionTypeCode)
       .Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
+      .Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
       .Case(".swift_ast", eSectionTypeSwiftModules)
       .Default(eSectionTypeOther);
 }

diff  --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index 7e17fd8c53cd01..488c9bd1e54af1 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -1210,6 +1210,7 @@ AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) {
         case eSectionTypeDWARFGNUDebugAltLink:
         case eSectionTypeCTF:
         case eSectionTypeLLDBTypeSummaries:
+        case eSectionTypeLLDBFormatters:
         case eSectionTypeSwiftModules:
           return AddressClass::eDebug;
 
@@ -1486,6 +1487,7 @@ static lldb::SectionType GetSectionType(uint32_t flags,
   static ConstString g_sect_name_go_symtab("__gosymtab");
   static ConstString g_sect_name_ctf("__ctf");
   static ConstString g_sect_name_lldb_summaries("__lldbsummaries");
+  static ConstString g_sect_name_lldb_formatters("__lldbformatters");
   static ConstString g_sect_name_swift_ast("__swift_ast");
 
   if (section_name == g_sect_name_dwarf_debug_abbrev)
@@ -1568,6 +1570,8 @@ static lldb::SectionType GetSectionType(uint32_t flags,
     return eSectionTypeCTF;
   if (section_name == g_sect_name_lldb_summaries)
     return lldb::eSectionTypeLLDBTypeSummaries;
+  if (section_name == g_sect_name_lldb_formatters)
+    return lldb::eSectionTypeLLDBFormatters;
   if (section_name == g_sect_name_swift_ast)
     return eSectionTypeSwiftModules;
   if (section_name == g_sect_name_objc_data ||

diff  --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index bb712da7f6d67d..bfdb8140e40aff 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -1011,6 +1011,7 @@ SectionType ObjectFilePECOFF::GetSectionType(llvm::StringRef sect_name,
           .Cases(".eh_frame", ".eh_fram", eSectionTypeEHFrame)
           .Case(".gosymtab", eSectionTypeGoSymtab)
           .Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
+          .Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
           .Case("swiftast", eSectionTypeSwiftModules)
           .Default(eSectionTypeInvalid);
   if (section_type != eSectionTypeInvalid)

diff  --git a/lldb/source/Symbol/ObjectFile.cpp b/lldb/source/Symbol/ObjectFile.cpp
index 3100e6b813b631..d3881f8ccf7fe8 100644
--- a/lldb/source/Symbol/ObjectFile.cpp
+++ b/lldb/source/Symbol/ObjectFile.cpp
@@ -366,6 +366,7 @@ AddressClass ObjectFile::GetAddressClass(addr_t file_addr) {
           case eSectionTypeDWARFAppleObjC:
           case eSectionTypeDWARFGNUDebugAltLink:
           case eSectionTypeCTF:
+          case eSectionTypeLLDBFormatters:
           case eSectionTypeLLDBTypeSummaries:
           case eSectionTypeSwiftModules:
             return AddressClass::eDebug;

diff  --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 9ac85ad3ed5ee6..46216ba2d566d7 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -24,9 +24,7 @@
 #include "lldb/Core/Section.h"
 #include "lldb/Core/SourceManager.h"
 #include "lldb/Core/StructuredDataImpl.h"
-#include "lldb/Core/ValueObject.h"
-#include "lldb/Core/ValueObjectConstResult.h"
-#include "lldb/DataFormatters/DataVisualization.h"
+#include "lldb/DataFormatters/FormatterSection.h"
 #include "lldb/Expression/DiagnosticManager.h"
 #include "lldb/Expression/ExpressionVariable.h"
 #include "lldb/Expression/REPL.h"
@@ -66,8 +64,6 @@
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/StreamString.h"
 #include "lldb/Utility/Timer.h"
-#include "lldb/ValueObject/ValueObject.h"
-#include "lldb/ValueObject/ValueObjectConstResult.h"
 
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SetVector.h"
@@ -1541,76 +1537,6 @@ static void LoadScriptingResourceForModule(const ModuleSP &module_sp,
                                                   feedback_stream.GetData());
 }
 
-// Load type summaries embedded in the binary. These are type summaries provided
-// by the authors of the code.
-static void LoadTypeSummariesForModule(ModuleSP module_sp) {
-  auto *sections = module_sp->GetSectionList();
-  if (!sections)
-    return;
-
-  auto summaries_sp =
-      sections->FindSectionByType(eSectionTypeLLDBTypeSummaries, true);
-  if (!summaries_sp)
-    return;
-
-  Log *log = GetLog(LLDBLog::DataFormatters);
-  const char *module_name = module_sp->GetObjectName().GetCString();
-
-  TypeCategoryImplSP category;
-  DataVisualization::Categories::GetCategory(ConstString("default"), category);
-
-  // The type summary record is serialized as follows.
-  //
-  // Each record contains, in order:
-  //   * Version number of the record format
-  //   * The remaining size of the record
-  //   * The size of the type identifier
-  //   * The type identifier, either a type name, or a regex
-  //   * The size of the summary string
-  //   * The summary string
-  //
-  // Integers are encoded using ULEB.
-  //
-  // Strings are encoded with first a length (ULEB), then the string contents,
-  // and lastly a null terminator. The length includes the null.
-
-  DataExtractor extractor;
-  auto section_size = summaries_sp->GetSectionData(extractor);
-  lldb::offset_t offset = 0;
-  while (offset < section_size) {
-    uint64_t version = extractor.GetULEB128(&offset);
-    uint64_t record_size = extractor.GetULEB128(&offset);
-    if (version == 1) {
-      uint64_t type_size = extractor.GetULEB128(&offset);
-      llvm::StringRef type_name = extractor.GetCStr(&offset, type_size);
-      uint64_t summary_size = extractor.GetULEB128(&offset);
-      llvm::StringRef summary_string = extractor.GetCStr(&offset, summary_size);
-      if (!type_name.empty() && !summary_string.empty()) {
-        TypeSummaryImpl::Flags flags;
-        auto summary_sp =
-            std::make_shared<StringSummaryFormat>(flags, summary_string.data());
-        FormatterMatchType match_type = eFormatterMatchExact;
-        if (summary_string.front() == '^' && summary_string.back() == '$')
-          match_type = eFormatterMatchRegex;
-        category->AddTypeSummary(type_name, match_type, summary_sp);
-        LLDB_LOGF(log, "Loaded embedded type summary for '%s' from %s.",
-                  type_name.data(), module_name);
-      } else {
-        if (type_name.empty())
-          LLDB_LOGF(log, "Missing string(s) in embedded type summary in %s.",
-                    module_name);
-      }
-    } else {
-      // Skip unsupported record.
-      offset += record_size;
-      LLDB_LOGF(
-          log,
-          "Skipping unsupported embedded type summary of version %llu in %s.",
-          version, module_name);
-    }
-  }
-}
-
 void Target::ClearModules(bool delete_locations) {
   ModulesDidUnload(m_images, delete_locations);
   m_section_load_history.Clear();
@@ -1892,6 +1818,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
       ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
       LoadScriptingResourceForModule(module_sp, this);
       LoadTypeSummariesForModule(module_sp);
+      LoadFormattersForModule(module_sp);
     }
     m_breakpoint_list.UpdateBreakpoints(module_list, true, false);
     m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false);

diff  --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile b/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
new file mode 100644
index 00000000000000..c9319d6e6888a4
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules

diff  --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py b/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
new file mode 100644
index 00000000000000..2b27bd3cdcda71
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
@@ -0,0 +1,17 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+    @skipUnlessDarwin
+    def test(self):
+        self.build()
+        if self.TraceOn():
+            self.expect("log enable -v lldb formatters")
+        lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp")
+        )
+        self.expect("v x", substrs=["(MyOptional<int>) x = None"])
+        self.expect("v y", substrs=["(MyOptional<int>) y = 42"])

diff  --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp b/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
new file mode 100644
index 00000000000000..35406acd6d0c4e
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
@@ -0,0 +1,43 @@
+// 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;
+}
+
+// Produced from the assembler in
+// Shell/ScriptInterpreter/Python/Inputs/FormatterBytecode/formatter.py
+__attribute__((used, section("__DATA_CONST,__lldbformatters"))) unsigned char
+    _MyOptional_type_summary[] =
+        "\x01"             // version
+        "\xa4"             // record size
+        "\x01"             // record size
+        "\x10"             // type name size
+        "^MyOptional<.+>$" // type name
+        "\x00"             // flags
+        "\x00"             // sig_summary
+        "\x8d"             // program size
+        "\x01"             // program size
+        "\x1\x22\x7Storage#\x12\x60\x1,C\x10\x1\x5\x11\x2\x1\x22\x6hasVal#"
+        "\x12\x60\x1,\x10\x1e\x2\x22\x1b<could not read MyOptional>\x10G#!\x60 "
+        "\x0P\x10\x6\x22\x4None\x10\x36\x1#\x15\x60 "
+        "\x0#\x16\x60\x5\x22\x5value#\x12\x60\x5#\x17\x60\x1,"
+        "\x10\x6\x22\x4None\x10\x11\x1#\x0\x60\x1#R\x60\x10\x3# "
+        "\x60\x10\x1\x2\x12\x12\x12\x12"; // summary function

diff  --git a/lldb/unittests/DataFormatter/CMakeLists.txt b/lldb/unittests/DataFormatter/CMakeLists.txt
index 9d967a72bfd1fa..b858db1c716347 100644
--- a/lldb/unittests/DataFormatter/CMakeLists.txt
+++ b/lldb/unittests/DataFormatter/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_lldb_unittest(LLDBFormatterTests
   FormatManagerTests.cpp
   FormattersContainerTest.cpp
+  FormatterBytecodeTest.cpp
   StringPrinterTests.cpp
 
   LINK_LIBS

diff  --git a/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
new file mode 100644
index 00000000000000..15d9229de00332
--- /dev/null
+++ b/lldb/unittests/DataFormatter/FormatterBytecodeTest.cpp
@@ -0,0 +1,261 @@
+#include "DataFormatters/FormatterBytecode.h"
+#include "lldb/Utility/StreamString.h"
+
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+using namespace lldb;
+using namespace FormatterBytecode;
+using llvm::StringRef;
+
+namespace {
+class FormatterBytecodeTest : public ::testing::Test {};
+
+bool Interpret(std::vector<uint8_t> code, DataStack &data) {
+  auto buf =
+      StringRef(reinterpret_cast<const char *>(code.data()), code.size());
+  std::vector<ControlStackElement> control({buf});
+  if (auto error = Interpret(control, data, sel_summary)) {
+#ifndef NDEBUG
+    llvm::errs() << llvm::toString(std::move(error)) << '\n';
+#else
+    llvm::consumeError(std::move(error));
+#endif
+    return false;
+  }
+  return true;
+}
+
+} // namespace
+
+TEST_F(FormatterBytecodeTest, StackOps) {
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 23, op_dup, op_plus}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 46u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_drop}, data));
+    ASSERT_EQ(data.size(), 0u);
+  }
+  {
+    for (unsigned char i = 0; i < 3; ++i) {
+      DataStack data;
+
+      ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_lit_uint, 2,
+                             op_lit_uint, i, op_pick},
+                            data));
+      ASSERT_EQ(data.Pop<uint64_t>(), i);
+    }
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_over}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_swap}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret(
+        {op_lit_uint, 0, op_lit_uint, 1, op_lit_uint, 2, op_rot}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_EQ(data.Pop<uint64_t>(), 2u);
+  }
+}
+
+TEST_F(FormatterBytecodeTest, ControlOps) {
+  {
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_lit_uint, 0, op_begin, 2, op_lit_uint, 42, op_if}, data));
+    ASSERT_EQ(data.size(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(
+        Interpret({op_lit_uint, 1, op_begin, 2, op_lit_uint, 42, op_if}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 42u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_begin, 2, op_lit_uint, 42,
+                           op_begin, 2, op_lit_uint, 23, op_ifelse},
+                          data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 23u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_begin, 2, op_lit_uint, 42,
+                           op_begin, 2, op_lit_uint, 23, op_ifelse},
+                          data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 42u);
+  }
+  {
+    DataStack data(lldb::ValueObjectSP{});
+    ASSERT_TRUE(Interpret({op_is_null}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1u, op_as_int}, data));
+    ASSERT_EQ(data.Pop<int64_t>(), 1);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_int, 126, op_as_uint}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), ~1ULL);
+  }
+}
+
+TEST_F(FormatterBytecodeTest, ArithOps) {
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 2, op_lit_uint, 3, op_plus}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 5u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 3, op_lit_uint, 2, op_minus}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 3, op_lit_uint, 2, op_mul}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 6u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 6, op_lit_uint, 2, op_div}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 3u);
+  }
+  {
+    DataStack data;
+    ASSERT_FALSE(Interpret({op_lit_uint, 23, op_lit_uint, 0, op_div}, data));
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 2, op_shl}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 4u);
+  }
+  {
+    DataStack data;
+    unsigned char minus_one = 127;
+    ASSERT_TRUE(
+        Interpret({op_lit_int, minus_one, op_lit_uint, 2, op_shl}, data));
+    ASSERT_EQ(data.Pop<int64_t>(), -4);
+  }
+  {
+    DataStack data;
+    ASSERT_FALSE(Interpret({op_lit_uint, 1, op_lit_uint, 65, op_shl}, data));
+    ASSERT_FALSE(Interpret({op_lit_uint, 1, op_lit_uint, 65, op_shr}, data));
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_and}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_and}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_or}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_or}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_or}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_xor}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_xor}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_xor}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_not}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0xffffffffffffffff);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_eq}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_eq}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_neq}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_neq}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_lt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_lt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_lt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_gt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_gt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_gt}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_le}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_le}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_le}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+  {
+    DataStack data;
+    ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_ge}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 0u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_ge}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+    ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_ge}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 1u);
+  }
+}
+
+TEST_F(FormatterBytecodeTest, CallOps) {
+  {
+    DataStack data;
+    data.Push(std::string{"hello"});
+    ASSERT_TRUE(Interpret({op_lit_selector, sel_strlen, op_call}, data));
+    ASSERT_EQ(data.Pop<uint64_t>(), 5u);
+  }
+  {
+    DataStack data;
+    data.Push(std::string{"A"});
+    data.Push(std::string{"B"});
+    data.Push(std::string{"{1}{0}"});
+    ASSERT_TRUE(Interpret({op_lit_selector, sel_fmt, op_call}, data));
+    ASSERT_EQ(data.Pop<std::string>(), "BA");
+  }
+  {
+    DataStack data;
+    data.Push(std::string{"{0}"});
+    ASSERT_FALSE(Interpret({op_lit_selector, sel_fmt, op_call}, data));
+  }
+}


        


More information about the lldb-commits mailing list