https://github.com/chelcassanova updated 
https://github.com/llvm/llvm-project/pull/147655

>From 899439d378923f22af7b11b99cafa1789ed436a7 Mon Sep 17 00:00:00 2001
From: Chelsea Cassanova <chelsea_cassan...@apple.com>
Date: Wed, 2 Jul 2025 00:17:31 -0700
Subject: [PATCH] [lldb][rpc] Upstream RPC Client Library Emitters

This commit upstreams the client side emitters for the lldb-rpc-gen tool
alongside tests for its functionality and heuristics.

https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804
---
 .../Inputs/Client/CheckArrayPointer.h         |  20 +
 .../Client/CheckConstCharPtrPtrWithLen.h      |  19 +
 .../Generator/Inputs/Client/CheckConstSBRef.h |  19 +
 .../Inputs/Client/CheckNonConstSBRef.h        |  20 +
 .../Generator/Inputs/Client/CheckSBPointer.h  |  21 +
 .../Generator/Inputs/Client/CheckVoidPtr.h    |  19 +
 .../Tests/Client/CheckArrayPointer.test       |  11 +
 .../Client/CheckConstCharPtrPtrWithLen.test   |  10 +
 .../Tests/Client/CheckConstSBRef.test         |  14 +
 .../Tests/Client/CheckNonConstSBRef.test      |  13 +
 .../Tests/Client/CheckSBPointer.test          |  15 +
 .../Generator/Tests/Client/CheckVoidPtr.test  |  10 +
 .../client/RPCLibraryHeaderEmitter.cpp        | 124 ++++
 .../client/RPCLibraryHeaderEmitter.h          |  44 ++
 .../client/RPCLibrarySourceEmitter.cpp        | 542 ++++++++++++++++++
 .../client/RPCLibrarySourceEmitter.h          |  92 +++
 .../lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp    | 414 +++++++++++++
 17 files changed, 1407 insertions(+)
 create mode 100644 
lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h
 create mode 100644 
lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h
 create mode 100644 
lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h
 create mode 100644 
lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h
 create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h
 create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h
 create mode 100644 
lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
 create mode 100644 
lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
 create mode 100644 
lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
 create mode 100644 
lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
 create mode 100644 
lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
 create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
 create mode 100644 
lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp
 create mode 100644 
lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h
 create mode 100644 
lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp
 create mode 100644 
lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h
 create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp

diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h
new file mode 100644
index 0000000000000..18ec92a976b8a
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h
@@ -0,0 +1,20 @@
+#ifndef LLDB_API_SBRPC_CHECKARRAYPTR_H
+#define LLDB_API_SBRPC_CHECKARRAYPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKARRAYPTR {
+public:
+  // Pointers to arrays followed by length must use a
+  // Bytes object constructed using that pointer and the sizeof()
+  // the array object.
+  int CheckArrayPtr(uint64_t *array, size_t array_len);
+
+}; // class SBRPC_CHECKARRAYPTR
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKARRAYPTR_H
diff --git 
a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h
new file mode 100644
index 0000000000000..fa64492cf5aa7
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h
@@ -0,0 +1,19 @@
+#ifndef LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H
+#define LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKCONSTCHARPTRPTRWITHLEN {
+public:
+  // const char ** followed by len must use a StringList
+  // when being encoded.
+  int CheckConstCharPtrPtrWithLen(const char **arg1, size_t len);
+
+}; // class SBRPC_CHECKCONSTCHARPTRPTRWITHLEN
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H
diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h
new file mode 100644
index 0000000000000..153fdad60a494
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h
@@ -0,0 +1,19 @@
+#ifndef LLDB_API_SBRPC_CHECKCONSTSBREF_H
+#define LLDB_API_SBRPC_CHECKCONSTSBREF_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKCONSTSBREF {
+public:
+  // Const references to SB classes should be encoded as usual without
+  // needing to create a new object with its own connection.
+  int CheckConstSBRef(const SBDebugger &debugger_ref);
+
+}; // class SBRPC_CHECKCONSTSBREF
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKCONSTSBREF_H
diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h
new file mode 100644
index 0000000000000..90ee3d3a7fbd6
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h
@@ -0,0 +1,20 @@
+#ifndef LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+#define LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKNONCONSTSBREF {
+public:
+  // Non-const references to SB classes will have new objects
+  // of that class constructed with the connection as the first parameter
+  // before being encoded if their existing connection is invalid.
+  int CheckNonConstSBRef(SBDebugger &debugger_ref);
+
+}; // class SBRPC_CHECKNONCONSTSBREF
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h
new file mode 100644
index 0000000000000..d9b7efa820f53
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h
@@ -0,0 +1,21 @@
+#ifndef LLDB_API_SBRPC_CHECKSBPTR_H
+#define LLDB_API_SBRPC_CHECKSBPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKSBPTR {
+public:
+  // Pointers to SB objects must be checked to
+  // see if they're null. If so, then a new object of the given
+  // class must be created and encoded. Otherwise, the original
+  // parameter will be encoded.
+  int CheckSBPtr(SBDebugger *debugger_ptr);
+
+}; // class SBRPC_CHECKSBPTR
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKSBPTR_H
diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h 
b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h
new file mode 100644
index 0000000000000..fa6484fc8d7cd
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h
@@ -0,0 +1,19 @@
+#ifndef LLDB_API_SBRPC_CHECKVOIDPTR_H
+#define LLDB_API_SBRPC_CHECKVOIDPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+class LLDB_API SBRPC_CHECKVOIDPTR {
+public:
+  // void * followed by length must use a Bytes object
+  // when being encoded.
+  int CheckVoidPtr(void *buf, size_t len);
+
+}; // class SBRPC_CHECKVOIDPTR
+} // namespace lldb
+
+#endif // LLDB_API_SBRPC_CHECKVOIDPTR_H
diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
new file mode 100644
index 0000000000000..1b58b389d612e
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
@@ -0,0 +1,11 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckArrayPointer.h
+
+RUN: cat %t/lib/CheckArrayPointer.cpp | FileCheck %s
+
+# Pointers to arrays followed by length must use a
+# Bytes object constructed using that pointer and the sizeof()
+# the array object.
+CHECK: lldb_rpc::SBRPC_CHECKARRAYPTR::CheckArrayPtr
+CHECK: Bytes array_buffer(array, sizeof(uint64_t));
diff --git 
a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
new file mode 100644
index 0000000000000..84d18c95fa7d2
--- /dev/null
+++ 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
@@ -0,0 +1,10 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t 
%S/../../Inputs/CheckConstCharPtrPtrWithLen.h
+
+RUN: cat %t/lib/CheckConstCharPtrPtrWithLen.cpp | FileCheck %s
+
+# const char ** followed by len must use a StringList
+# when being encoded.
+CHECK: lldb_rpc::SBRPC_CHECKCONSTCHARPTRPTRWITHLEN::CheckConstCharPtrPtrWithLen
+CHECK: StringList arg1_list
diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
new file mode 100644
index 0000000000000..9b10d1711a9bc
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
@@ -0,0 +1,14 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckConstSBRef.h
+
+RUN: cat %t/lib/CheckConstSBRef.cpp | FileCheck %s
+
+# Const references to SB classes should be encoded as usual without
+# needing to create a new object with its own connection. Here
+# we're checking to make sure that the given SB object ref will get
+# encoded immediately after the previous argument gets encoded without
+# anything happening in between.
+CHECK: lldb_rpc::SBRPC_CHECKCONSTSBREF::CheckConstSBRef
+CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, 
*this);
+CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, 
debugger_ref)
diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
new file mode 100644
index 0000000000000..d396e927b46d2
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
@@ -0,0 +1,13 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckNonConstSBRef.h
+
+RUN: cat %t/lib/CheckNonConstSBRef.cpp | FileCheck %s
+
+# Non-const references to SB classes will have new objects
+# of that class constructed with the connection as the first parameter
+# before being encoded if their existing connection is invalid.
+CHECK: lldb_rpc::SBRPC_CHECKNONCONSTSBREF::CheckNonConstSBRef
+CHECK: if (connection_sp && !debugger_ref.ObjectRefIsValid())
+CHECK:     debugger_ref = lldb_rpc::SBDebugger(connection_sp);
+CHECK:   RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, 
debugger_ref);
diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
new file mode 100644
index 0000000000000..5f355138182a8
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
@@ -0,0 +1,15 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckSBPointer.h
+
+RUN: cat %t/lib/CheckSBPointer.cpp | FileCheck %s
+
+# Pointers to SB objects must be checked to
+# see if they're null. If so, then a new object of the given
+# class must be created and encoded. Otherwise, the original
+# parameter will be encoded.
+CHECK: lldb_rpc::SBRPC_CHECKSBPTR::CheckSBPtr
+CHECK: if (debugger_ptr)
+CHECK:   RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, 
*debugger_ptr);
+CHECK: else
+CHECK:    RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, 
rpc::ObjectRef(ObjectRefGetConnectionID(), eClass_lldb_SBDebugger, 
LLDB_RPC_INVALID_OBJECT_ID));
diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test 
b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
new file mode 100644
index 0000000000000..9907126a28fa6
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
@@ -0,0 +1,10 @@
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckVoidPtr.h
+
+RUN: cat %t/lib/CheckVoidPtr.cpp | FileCheck %s
+
+# void * followed by length must use a Bytes object
+# when being encoded.
+CHECK: lldb_rpc::SBRPC_CHECKVOIDPTR::CheckVoidPtr
+CHECK: Bytes buf_buffer(buf, len);
diff --git 
a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp 
b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp
new file mode 100644
index 0000000000000..9537b490856f1
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp
@@ -0,0 +1,124 @@
+#include "RPCLibraryHeaderEmitter.h"
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "clang/AST/Mangle.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace lldb_rpc_gen;
+
+void RPCLibraryHeaderEmitter::StartClass(std::string ClassName) {
+  CurrentClass = std::move(ClassName);
+  std::string BaseClass =
+      lldb_rpc_gen::SBClassInheritsFromObjectRef(CurrentClass)
+          ? "ObjectRef"
+          : "LocalObjectRef";
+  EmitLine("class " + CurrentClass + " : public rpc::" + BaseClass + " {");
+  EmitLine("public:");
+  IndentLevel++;
+  if (lldb_rpc_gen::SBClassRequiresDefaultCtor(CurrentClass))
+    EmitLine(CurrentClass + "();");
+
+  // NOTE: There's currently only one RPC-specific extension that is actually
+  // used AFAICT. We can generalize this if we need more.
+  if (CurrentClass == "SBDebugger")
+    EmitLine("int SetIOFile(const char *path);");
+}
+
+void RPCLibraryHeaderEmitter::EndClass() {
+  if (lldb_rpc_gen::SBClassRequiresCopyCtorAssign(CurrentClass)) {
+    if (!CopyCtorEmitted)
+      EmitLine(CurrentClass + "(const lldb_rpc::" + CurrentClass + " &rhs);");
+    if (!CopyAssignEmitted)
+      EmitLine(CurrentClass + " &operator=(const lldb_rpc::" + CurrentClass +
+               " &rhs);");
+  }
+  if (!MoveCtorEmitted)
+    EmitLine(CurrentClass + "(lldb_rpc::" + CurrentClass + " &&rhs);");
+  if (!MoveAssignEmitted)
+    EmitLine(CurrentClass + " &operator=(" + CurrentClass + " &&rhs);");
+
+  IndentLevel--;
+  EmitLine("}; // class " + CurrentClass);
+  CurrentClass.clear();
+}
+
+void RPCLibraryHeaderEmitter::EmitMethod(const Method &method) {
+  std::string DeclarationLine;
+  llvm::raw_string_ostream DeclarationLineStream(DeclarationLine);
+
+  if (method.IsCopyCtor)
+    CopyCtorEmitted = true;
+  else if (method.IsCopyAssign)
+    CopyAssignEmitted = true;
+  else if (method.IsMoveCtor)
+    MoveCtorEmitted = true;
+  else if (method.IsMoveAssign)
+    MoveAssignEmitted = true;
+
+  if (method.IsExplicitCtorOrConversionMethod)
+    DeclarationLineStream << "explicit ";
+  else if (!method.IsInstance)
+    DeclarationLineStream << "static ";
+
+  if (!method.IsDtor && !method.IsConversionMethod && !method.IsCtor)
+    DeclarationLineStream << 
lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+                                 method.ReturnType.getAsString(method.Policy))
+                          << " ";
+  DeclarationLineStream << method.BaseName << "("
+                        << method.CreateParamListAsString(
+                               eLibrary, /*IncludeDefaultValue = */ true)
+                        << ")";
+  if (method.IsConst)
+    DeclarationLineStream << " const";
+  DeclarationLineStream << ";";
+
+  EmitLine(DeclarationLine);
+}
+
+void RPCLibraryHeaderEmitter::EmitEnum(EnumDecl *E) {
+  // NOTE: All of the enumerations embedded in SB classes are currently
+  // anonymous and backed by an unsigned int.
+  EmitLine("enum : unsigned {");
+  IndentLevel++;
+  for (const EnumConstantDecl *EC : E->enumerators()) {
+    std::string EnumValue = EC->getNameAsString();
+    SmallString<16> ValueStr;
+    EC->getInitVal().toString(ValueStr);
+    EnumValue += " = " + ValueStr.str().str() + ", ";
+    EmitLine(EnumValue);
+  }
+
+  IndentLevel--;
+  EmitLine("};");
+}
+
+std::string RPCLibraryHeaderEmitter::GetHeaderGuard() {
+  const std::string UpperFilenameNoExt =
+      llvm::sys::path::stem(
+          llvm::sys::path::filename(OutputFile->getFilename()))
+          .upper();
+  return "GENERATED_LLDB_RPC_LIBRARY_" + UpperFilenameNoExt + "_H";
+}
+
+void RPCLibraryHeaderEmitter::Begin() {
+  const std::string HeaderGuard = GetHeaderGuard();
+  EmitLine("#ifndef " + HeaderGuard);
+  EmitLine("#define " + HeaderGuard);
+  EmitLine("");
+  EmitLine("#include <lldb-rpc/common/RPCPublic.h>");
+  EmitLine("#include \"SBDefines.h\"");
+  EmitLine("#include \"LLDBRPC.h\"");
+  EmitLine("");
+  EmitLine("namespace lldb_rpc {");
+}
+
+void RPCLibraryHeaderEmitter::End() {
+  EmitLine("} // namespace lldb_rpc");
+  EmitLine("#endif // " + GetHeaderGuard());
+}
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h 
b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h
new file mode 100644
index 0000000000000..b9f22585c7955
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h
@@ -0,0 +1,44 @@
+#ifndef LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H
+#define LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H
+
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+using namespace clang;
+
+namespace lldb_rpc_gen {
+
+class RPCLibraryHeaderEmitter : public FileEmitter {
+public:
+  RPCLibraryHeaderEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile)
+      : FileEmitter(std::move(OutputFile)), CurrentClass() {
+    Begin();
+  }
+
+  ~RPCLibraryHeaderEmitter() { End(); }
+
+  void StartClass(std::string ClassName);
+
+  void EndClass();
+
+  void EmitMethod(const Method &method);
+
+  void EmitEnum(EnumDecl *E);
+
+private:
+  std::string GetHeaderGuard();
+
+  void Begin();
+
+  void End();
+
+  std::string CurrentClass;
+  bool CopyCtorEmitted = false;
+  bool CopyAssignEmitted = false;
+  bool MoveCtorEmitted = false;
+  bool MoveAssignEmitted = false;
+};
+} // namespace lldb_rpc_gen
+#endif // LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H
diff --git 
a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp 
b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp
new file mode 100644
index 0000000000000..571fedc6edcce
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp
@@ -0,0 +1,542 @@
+#include "RPCLibrarySourceEmitter.h"
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace clang;
+using namespace lldb_rpc_gen;
+
+static constexpr llvm::StringRef ReturnVariableName("__result");
+
+// This map stores any method that needs custom logic with a struct that
+// tells us where the logic needs to be inserted and what code needs to be
+// inserted. The code here is stored as a raw string literal.
+const llvm::StringMap<RPCLibrarySourceEmitter::CustomLogic>
+    CustomLogicForMethods = {
+        {"_ZN4lldb10SBDebugger6CreateEbPFvPKcPvES3_",
+         {RPCLibrarySourceEmitter::CustomLogicLocation::eAfterDecode, R"code(
+    // Now source the .lldbinit files manually since we can't rely on the
+    // LLDB.framework on the other side to have special support for sourcing 
the right file
+    // since it would try to source "~/.lldbinit-lldb-rpc-server" followed by
+    // "~/.lldbinit". We want it to try "~.lldbinit-%s" where %s is the
+    // current program basename followed by "~/.lldbinit".
+
+    if (source_init_files && __result.ObjectRefIsValid()) {
+      const char *program_basename = rpc::GetProgramBasename();
+      if (program_basename) {
+        char init_path[PATH_MAX];
+        snprintf(init_path, sizeof(init_path), "~/.lldbinit-%s",
+                 program_basename);
+        lldb_rpc::SBFileSpec program_init_file(connection, init_path, true);
+        if (program_init_file.Exists()) {
+          char command_str[PATH_MAX];
+          snprintf(command_str, sizeof(command_str),
+                   "command source -s 1 -c 1 -e 0 '%s'", init_path);
+          __result.HandleCommand(command_str);
+        } else {
+          __result.HandleCommand("command source -s 1 -c 1 -e 0 
'~/.lldbinit'");
+        }
+      }
+    })code"}},
+};
+
+static std::string GetLocalObjectRefCtor(const std::string &ClassName) {
+  return "rpc::LocalObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" +
+         ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)";
+}
+
+static std::string GetObjectRefCtor(const std::string &ClassName) {
+  return "rpc::ObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" +
+         ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)";
+}
+
+void RPCLibrarySourceEmitter::EmitCopyCtor() {
+  EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass +
+           "(const lldb_rpc::" + CurrentClass + " &rhs) = default;");
+}
+
+void RPCLibrarySourceEmitter::EmitCopyAssign() {
+  EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass +
+           "::operator=(const lldb_rpc::" + CurrentClass + " &rhs) = 
default;");
+}
+
+void RPCLibrarySourceEmitter::EmitMoveCtor() {
+  EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass +
+           "(lldb_rpc::" + CurrentClass + " &&rhs) = default;");
+}
+
+void RPCLibrarySourceEmitter::EmitMoveAssign() {
+  EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass +
+           "::operator=(lldb_rpc::" + CurrentClass + " &&rhs) = default;");
+}
+
+void RPCLibrarySourceEmitter::EmitMethod(const Method &method) {
+  if (method.IsCopyCtor) {
+    CopyCtorEmitted = true;
+    EmitCopyCtor();
+    return;
+  } else if (method.IsCopyAssign) {
+    CopyAssignEmitted = true;
+    EmitCopyAssign();
+    return;
+  } else if (method.IsMoveCtor) {
+    MoveCtorEmitted = true;
+    EmitMoveCtor();
+    return;
+  } else if (method.IsMoveAssign) {
+    MoveAssignEmitted = true;
+    EmitMoveAssign();
+    return;
+  }
+
+  EmitCommentHeader(method);
+  EmitFunctionHeader(method);
+  EmitFunctionBody(method);
+  EmitFunctionFooter();
+}
+
+void RPCLibrarySourceEmitter::StartClass(std::string ClassName) {
+  CurrentClass = std::move(ClassName);
+  if (lldb_rpc_gen::SBClassRequiresDefaultCtor(CurrentClass)) {
+    std::string BaseClassCtor =
+        lldb_rpc_gen::SBClassInheritsFromObjectRef(CurrentClass)
+            ? GetObjectRefCtor(CurrentClass)
+            : GetLocalObjectRefCtor(CurrentClass);
+    EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass +
+             "() : " + BaseClassCtor + " {}");
+  }
+}
+
+void RPCLibrarySourceEmitter::EndClass() {
+  if (lldb_rpc_gen::SBClassRequiresCopyCtorAssign(CurrentClass)) {
+    if (!CopyCtorEmitted)
+      EmitCopyCtor();
+
+    if (!CopyAssignEmitted)
+      EmitCopyAssign();
+  }
+
+  if (!MoveCtorEmitted)
+    EmitMoveCtor();
+
+  if (!MoveAssignEmitted)
+    EmitMoveAssign();
+
+  CopyCtorEmitted = false;
+  CopyAssignEmitted = false;
+  MoveCtorEmitted = false;
+  MoveAssignEmitted = false;
+}
+
+void RPCLibrarySourceEmitter::EmitCommentHeader(const Method &method) {
+  std::string CommentLine;
+  llvm::raw_string_ostream CommentStream(CommentLine);
+
+  CommentStream << "// "
+                << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+                       method.QualifiedName)
+                << "(" << method.CreateParamListAsString(eLibrary) << ")";
+  if (method.IsConst)
+    CommentStream << " const";
+
+  EmitLine("//-----------------------------------------------------------");
+  EmitLine(CommentLine);
+  EmitLine("//-----------------------------------------------------------");
+}
+
+void RPCLibrarySourceEmitter::EmitFunctionHeader(const Method &method) {
+  std::string FunctionHeader;
+  llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader);
+
+  if (!method.IsDtor && !method.IsConversionMethod && !method.IsCtor)
+    FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+                                method.ReturnType.getAsString(method.Policy))
+                         << " ";
+
+  FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+                              method.QualifiedName)
+                       << "(" << method.CreateParamListAsString(eLibrary)
+                       << ")";
+  if (method.IsConst)
+    FunctionHeaderStream << " const";
+  if (method.IsCtor) {
+    if (lldb_rpc_gen::SBClassInheritsFromObjectRef(method.BaseName))
+      FunctionHeaderStream << " : " << GetObjectRefCtor(method.BaseName);
+    else
+      FunctionHeaderStream << " : " << GetLocalObjectRefCtor(method.BaseName);
+  }
+  FunctionHeaderStream << " {";
+
+  EmitLine(FunctionHeader);
+  IndentLevel++;
+}
+
+void RPCLibrarySourceEmitter::EmitFunctionBody(const Method &method) {
+  // There's nothing to do for destructors. The LocalObjectRef destructor 
should
+  // handle everything for us.
+  if (method.IsDtor)
+    return;
+
+  EmitLine("// 1) Perform setup");
+  EmitFunctionSetup(method);
+  EmitLine("// 2) Send RPC call");
+  EmitSendRPCCall(method);
+  EmitLine("// 3) Decode return values");
+  EmitDecodeReturnValues(method);
+}
+
+void RPCLibrarySourceEmitter::EmitFunctionFooter() {
+  IndentLevel--;
+  EmitLine("}");
+}
+
+void RPCLibrarySourceEmitter::EmitFunctionSetup(const Method &method) {
+  if (!method.ReturnType->isVoidType())
+    EmitReturnValueStorage(method);
+
+  EmitConnectionSetup(method);
+
+  EmitLine("// RPC Communication setup");
+  EmitLine("static RPCFunctionInfo g_func(\"" + method.MangledName + "\");");
+  EmitLine("RPCStream send;");
+  EmitLine("RPCStream response;");
+  EmitLine("g_func.Encode(send);");
+
+  EmitEncodeParameters(method);
+
+  if (CustomLogicForMethods.lookup(method.MangledName).Location ==
+      CustomLogicLocation::eAfterSetup)
+    EmitCustomLogic(method);
+}
+
+void RPCLibrarySourceEmitter::EmitReturnValueStorage(const Method &method) {
+  assert(!method.ReturnType->isVoidType() &&
+         "Cannot emit return value storage when return type is 'void'");
+
+  EmitLine("// Storage for return value");
+  std::string ReturnValueStorage;
+  llvm::raw_string_ostream ReturnValueStorageStream(ReturnValueStorage);
+
+  std::string ReturnValueType;
+  if (lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType))
+    ReturnValueStorageStream << "rpc_common::ConstCharPointer "
+                             << ReturnVariableName << ";";
+  else if (method.ReturnType->isPointerType())
+    ReturnValueStorageStream << "Bytes " << ReturnVariableName << ";";
+  else {
+    // We need to get the unqualified type because we don't want the return
+    // variable to be marked `const`. That would prevent us from changing it
+    // during the decoding step.
+    QualType UnqualifiedReturnType = method.ReturnType.getUnqualifiedType();
+    ReturnValueStorageStream
+        << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+               UnqualifiedReturnType.getAsString(method.Policy))
+        << " " << ReturnVariableName << " = {};";
+  }
+  EmitLine(ReturnValueStorage);
+}
+
+void RPCLibrarySourceEmitter::EmitConnectionSetup(const Method &method) {
+  // Methods know if they require a connection parameter. We need to figure out
+  // which scenario we're in.
+  bool ConnectionDerived = false;
+  if (!method.RequiresConnectionParameter()) {
+    // If we have an instance method that is not a constructor, we have a valid
+    // connection from `this` via `ObjectRefGetConnectionSP()`.
+    if (!method.IsCtor && method.IsInstance) {
+      EmitLine("// Deriving connection from this.");
+      EmitLine("rpc_common::ConnectionSP connection_sp = "
+               "ObjectRefGetConnectionSP();");
+      ConnectionDerived = true;
+    }
+
+    // Otherewise, we try to derive it from an existing parameter.
+    if (!ConnectionDerived)
+      for (const auto &Param : method.Params) {
+        if (lldb_rpc_gen::TypeIsSBClass(Param.Type)) {
+          EmitLine("// Deriving connection from SB class parameter.");
+          std::string ConnectionLine =
+              "rpc_common::ConnectionSP connection_sp = " + Param.Name;
+          if (Param.Type->isPointerType())
+            ConnectionLine += "->";
+          else
+            ConnectionLine += ".";
+          ConnectionLine += "ObjectRefGetConnectionSP();";
+          EmitLine(ConnectionLine);
+          ConnectionDerived = true;
+          break;
+        }
+      }
+  } else {
+    // This method requires a connection parameter. It will always be named
+    // "connection" and it will always come first in the parameter list.
+    EmitLine("// Using connection parameter.");
+    EmitLine(
+        "rpc_common::ConnectionSP connection_sp = 
connection.GetConnection();");
+    ConnectionDerived = true;
+  }
+
+  assert(ConnectionDerived &&
+         "Unable to determine where method should derive connection from");
+
+  // NOTE: By this point, we should have already emitted the storage for the
+  // return value.
+  std::string FailureReturnExpression;
+  if (method.ReturnType->isPointerType())
+    FailureReturnExpression = "nullptr";
+  else if (!method.ReturnType->isVoidType())
+    FailureReturnExpression = "__result";
+  EmitLine("if (!connection_sp) return " + FailureReturnExpression + ";");
+}
+
+void RPCLibrarySourceEmitter::EmitEncodeParameters(const Method &method) {
+  // Encode parameters.
+  if (method.IsInstance && !method.IsCtor)
+    EmitLine("RPCValueEncoder(send, "
+             "rpc_common::RPCPacket::ValueType::Argument, *this);");
+
+  for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) 
{
+    // SBTarget::BreakpointCreateByNames specifically uses an
+    // rpc_common::StringList when encoding the list of symbol names in the
+    // handwritten version. This allows it to account for null terminators and
+    // without it, Xcode crashes when calling this function. The else-if block
+    // below replaces params that have a pointer and length with a Bytes 
object.
+    // We can use the same logic in order to replace const char **s with
+    // StringLists
+    //
+    // rdar://146976130
+    if (lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&
+        Iter->IsFollowedByLen) {
+      std::string StringListLine;
+      const std::string StringListName = Iter->Name + "_list";
+      StringListLine = "StringList " + StringListName + "(" + Iter->Name + ", 
";
+      Iter++;
+      StringListLine += Iter->Name + ");";
+      Iter--;
+      EmitLine(StringListLine);
+      EmitLine(
+          "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " 
+
+          StringListName + ");");
+      // When we have pointer parameters, in general the strategy is
+      // to create `Bytes` objects from them (basically a buffer with a size)
+      // and then move those over the wire. We're not moving the pointer 
itself,
+      // but the contents of memory being pointed to. There are a few 
exceptions
+      // to this:
+      //   - If the type is `const char *` or `const char **`, those are 
handled
+      //     specially and can be encoded directly.
+      //   - If we have a function pointer, we move the pointer value directly.
+      //     To do the callback from the server-side, we will need this pointer
+      //     value to correctly invoke the function client-side.
+      //   - If we have a baton (to support a callback), we need to move the
+      //     pointer value directly. This is for the same reason as callbacks
+      //     above.
+      //   - If we have a pointer to an SB class, we just dereference it and
+      //     encode it like a normal SB class object.
+    } else if (Iter->Type->isPointerType() &&
+               !Iter->Type->isFunctionPointerType() &&
+               (!Iter->Type->isVoidPointerType() || Iter->IsFollowedByLen) &&
+               !lldb_rpc_gen::TypeIsConstCharPtr(Iter->Type) &&
+               !lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&
+               !lldb_rpc_gen::TypeIsSBClass(Iter->Type)) {
+      // NOTE: We're aiming for this transformation:
+      //   (TYPE *buf, SIZE len) -> Bytes(buf, sizeof(TYPE) * len)
+      // The `sizeof` portion is dropped when TYPE is `void`. When there is no
+      // length argument, we implicitly assume that `len` is 1.
+      const std::string BufferName = Iter->Name + "_buffer";
+      QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type);
+      std::string BufferLine = "Bytes " + BufferName + "(" + Iter->Name + ", ";
+      if (!Iter->Type->isVoidPointerType())
+        BufferLine += "sizeof(" +
+                      ReplaceLLDBNamespaceWithRPCNamespace(
+                          UnderlyingType.getAsString(method.Policy)) +
+                      ")";
+
+      if (Iter->IsFollowedByLen && !Iter->Type->isVoidPointerType())
+        BufferLine += " * ";
+
+      if (Iter->IsFollowedByLen) {
+        Iter++;
+        BufferLine += Iter->Name;
+      }
+
+      BufferLine += ");";
+      EmitLine(BufferLine);
+      EmitLine("RPCValueEncoder(send, "
+               "rpc_common::RPCPacket::ValueType::Argument, " +
+               BufferName + ");");
+    } else if (lldb_rpc_gen::TypeIsSBClass(Iter->Type) &&
+               Iter->Type->isPointerType()) {
+      // If we have a pointer to an SB class, the strategy is to check for
+      // nullptr. If we have a valid pointer, we just encode the actual SB 
class
+      // itself. Otherwise, we'll need to create a blank one and send that
+      // along.
+      // Note: Currently all methods that take SB class pointers are instance
+      // methods. This assertion will fail if that changes.
+      assert(method.IsInstance &&
+             "Assumption that only instance methods have pointers to SB class "
+             "as parameters is no longer true. Please update this logic.");
+      EmitLine("if (" + Iter->Name + ")");
+      IndentLevel++;
+      EmitLine("RPCValueEncoder(send, "
+               "rpc_common::RPCPacket::ValueType::Argument, *" +
+               Iter->Name + ");");
+      IndentLevel--;
+      EmitLine("else");
+      IndentLevel++;
+      // FIXME: change this logic, not everything can be an rpc::ObjectRef!!!
+      const std::string ClassIdentifier =
+          "eClass_lldb_" + lldb_rpc_gen::GetSBClassNameFromType(Iter->Type);
+      EmitLine(
+          "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, "
+          "rpc::ObjectRef(ObjectRefGetConnectionID(), " +
+          ClassIdentifier + ", LLDB_RPC_INVALID_OBJECT_ID));");
+      IndentLevel--;
+    } else {
+      const std::string CallbackCast = Iter->Type->isFunctionPointerType()
+                                           ? "(rpc_common::function_ptr_t)"
+                                           : "";
+
+      // NOTE: We're going to assume that SB objects are coming in with a valid
+      // connection and we'll assert if they don't have one.
+      if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) {
+        std::string TypeName = 
lldb_rpc_gen::GetSBClassNameFromType(Iter->Type);
+        EmitLine("assert(" + Iter->Name +
+                 ".ObjectRefIsValid() && \"SB object refs must be valid before 
"
+                 "encoding\");");
+      }
+
+      EmitLine(
+          "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " 
+
+          CallbackCast + Iter->Name + ");");
+    }
+  }
+}
+
+void RPCLibrarySourceEmitter::EmitSendRPCCall(const Method &method) {
+  EmitLine(
+      "if (!connection_sp->SendRPCCallAndWaitForResponse(send, response))");
+  IndentLevel++;
+  if (method.ReturnType->isVoidType() || method.IsCtor)
+    EmitLine("return;");
+  else if (method.ReturnType->isPointerType())
+    EmitLine("return nullptr;");
+  else
+    EmitLine("return __result;");
+  IndentLevel--;
+  if (CustomLogicForMethods.lookup(method.MangledName).Location ==
+      CustomLogicLocation::eAfterRPCCall)
+    EmitCustomLogic(method);
+}
+
+void RPCLibrarySourceEmitter::EmitDecodeReturnValues(const Method &method) {
+  if (!method.ReturnType->isVoidType()) {
+    std::string DecodeReturnValueLine =
+        "RPCValueDecoder(response, "
+        "rpc_common::RPCPacket::ValueType::ReturnValue, " +
+        ReturnVariableName.str() + ");";
+    EmitLine(DecodeReturnValueLine);
+  } else if (method.IsCtor)
+    EmitLine("RPCValueDecoder(response, "
+             "rpc_common::RPCPacket::ValueType::ReturnValue, *this);");
+
+  // Update mutable parameters (references and pointers)
+  for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) 
{
+    // If what we have is not a reference type or a pointer type, we can safely
+    // skip this.
+    if (!Iter->Type->isReferenceType() && !Iter->Type->isPointerType())
+      continue;
+
+    // No need to update SB class instances on the client-side.
+    if (lldb_rpc_gen::TypeIsSBClass(Iter->Type))
+      continue;
+
+    // We skip over function pointers and their accompanying baton parameters.
+    if (Iter->Type->isFunctionPointerType() ||
+        (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen))
+      continue;
+
+    // We cannot update const-qualified parameters.
+    QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type);
+    // This is specific to pointers, but we need to get to the innermost type 
to
+    // get the qualification. For references, this loop will never execute.
+    while (UnderlyingType->isPointerType())
+      UnderlyingType = lldb_rpc_gen::GetUnderlyingType(UnderlyingType);
+
+    if (UnderlyingType.isConstQualified())
+      continue;
+
+    if (Iter->Type->isReferenceType())
+      EmitLine("RPCValueDecoder(response, "
+               "rpc_common::RPCPacket::ValueType::ReturnValue, " +
+               Iter->Name + ");");
+    else {
+      assert(Iter->Type->isPointerType() &&
+             "Mutable parameter is not reference or pointer!");
+      const std::string &PointerParameterName = Iter->Name;
+      const std::string BufferName = PointerParameterName + "_buffer";
+      std::string SizeExpression;
+
+      // If we have a `void *` with a length parameter, we are counting bytes.
+      // No `sizeof` will be needed.
+      if (!Iter->Type->isVoidPointerType()) {
+        QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type);
+        SizeExpression = "sizeof(" +
+                         ReplaceLLDBNamespaceWithRPCNamespace(
+                             UnderlyingType.getAsString(method.Policy)) +
+                         ")";
+      }
+
+      if (Iter->IsFollowedByLen) {
+        // If we have a sizeof, we must multiply by the length argument.
+        if (!SizeExpression.empty())
+          SizeExpression += " * ";
+        Iter++;
+        SizeExpression += Iter->Name;
+      }
+
+      EmitLine("RPCValueDecoder(response, "
+               "rpc_common::RPCPacket::ValueType::ReturnValue, " +
+               BufferName + ");");
+      EmitLine("assert(" + BufferName + ".GetSize() == " + SizeExpression +
+               " && \"Buffer was resized during RPC call\");");
+      // NOTE: We can just treat the pointers as `void *` and copy all the 
bytes
+      // needed.
+      EmitLine("memcpy(" + PointerParameterName + ", " + BufferName +
+               ".GetData(), " + BufferName + ".GetSize());");
+    }
+  }
+
+  if (CustomLogicForMethods.lookup(method.MangledName).Location ==
+      CustomLogicLocation::eAfterDecode)
+    EmitCustomLogic(method);
+  if (!method.ReturnType->isVoidType()) {
+    // FIXME: Find a solution that does not involve leaking memory.
+    // We have to persist the buffer we returned somewhere. We stick it in the
+    // RPCStringPool for now
+    if (method.ReturnType->isPointerType()) {
+      std::string ReturnExpression =
+          "return (" +
+          lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+              method.ReturnType.getAsString(method.Policy)) +
+          ")RPCStringPool::Add(" + ReturnVariableName.str() + ");";
+      EmitLine(ReturnExpression);
+    } else
+      EmitLine("return __result;");
+  }
+}
+
+void RPCLibrarySourceEmitter::EmitCustomLogic(const Method &method) {
+  assert(CustomLogicForMethods.contains(method.MangledName) &&
+         "Cannot emit custom logic for method that is not present in custom "
+         "logic map.");
+  EmitLine("// Custom logic:");
+  EmitLine(CustomLogicForMethods.lookup(method.MangledName).Code);
+}
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h 
b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h
new file mode 100644
index 0000000000000..be37c82f5f46e
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h
@@ -0,0 +1,92 @@
+#ifndef LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H
+#define LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H
+
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+
+namespace lldb_rpc_gen {
+class RPCLibrarySourceEmitter : public FileEmitter {
+public:
+  RPCLibrarySourceEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile)
+      : FileEmitter(std::move(OutputFile)) {
+    Begin();
+  }
+
+  enum class CustomLogicLocation {
+    eNone,
+    eAfterSetup,
+    eAfterRPCCall,
+    eAfterDecode
+  };
+
+  struct CustomLogic {
+    CustomLogicLocation Location;
+    std::string Code;
+  };
+
+  void StartClass(std::string ClassName);
+
+  void EndClass();
+
+  void EmitMethod(const Method &method);
+
+  void EmitEmptyConstructor(const std::string &ClassName);
+
+private:
+  void EmitCopyCtor();
+
+  void EmitCopyAssign();
+
+  void EmitMoveCtor();
+
+  void EmitMoveAssign();
+
+  void EmitCommentHeader(const Method &method);
+
+  void EmitFunctionHeader(const Method &method);
+
+  void EmitFunctionBody(const Method &method);
+
+  void EmitFunctionFooter();
+
+  void EmitFunctionSetup(const Method &method);
+
+  void EmitReturnValueStorage(const Method &method);
+
+  void EmitConnectionSetup(const Method &method);
+
+  void EmitEncodeParameters(const Method &method);
+
+  void EmitSendRPCCall(const Method &method);
+
+  void EmitDecodeReturnValues(const Method &method);
+
+  void EmitCustomLogic(const Method &method);
+
+  void Begin() {
+    EmitLine("#include <lldb-rpc/common/RPCArgument.h>");
+    EmitLine("#include <lldb-rpc/common/RPCCommon.h>");
+    EmitLine("#include <lldb-rpc/common/RPCFunction.h>");
+    EmitLine("#include <lldb-rpc/common/RPCStringPool.h>");
+    EmitLine("#include <lldb-rpc/liblldbrpc/RPCUserClient.h>");
+    EmitLine("#include \"LLDBRPC.h\"");
+    EmitLine("#include <cassert>");
+    EmitLine("using namespace rpc_common;");
+    EmitLine("using namespace lldb_rpc;");
+  }
+
+  std::string CurrentClass;
+  bool CopyCtorEmitted = false;
+  bool CopyAssignEmitted = false;
+  bool MoveCtorEmitted = false;
+  bool MoveAssignEmitted = false;
+};
+} // namespace lldb_rpc_gen
+
+#endif // LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp 
b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
new file mode 100644
index 0000000000000..92d0182c79beb
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
@@ -0,0 +1,414 @@
+//===-- lldb-rpc-gen.cpp ----------------------------------------*- 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 "RPCCommon.h"
+#include "RPCLibraryHeaderEmitter.h"
+#include "RPCLibrarySourceEmitter.h"
+
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/CodeGen/ObjectFilePCHContainerWriter.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Serialization/ObjectFilePCHContainerReader.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Tooling.h"
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace clang::driver;
+using namespace clang::tooling;
+
+static llvm::cl::OptionCategory RPCGenCategory("Tool for generating LLDBRPC");
+
+static llvm::cl::opt<std::string>
+    OutputDir("output-dir",
+              llvm::cl::desc("Directory to output generated files to"),
+              llvm::cl::init(""), llvm::cl::cat(RPCGenCategory));
+
+static std::string GetLibraryOutputDirectory() {
+  llvm::SmallString<128> Path(OutputDir.getValue());
+  llvm::sys::path::append(Path, "lib");
+  return std::string(Path);
+}
+
+static std::unique_ptr<llvm::ToolOutputFile>
+CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) {
+  llvm::SmallString<128> Path(OutputDir);
+  llvm::sys::path::append(Path, Filename);
+
+  std::error_code EC;
+  auto OutputFile =
+      std::make_unique<llvm::ToolOutputFile>(Path, EC, llvm::sys::fs::OF_None);
+  if (EC) {
+    llvm::errs() << "Failed to create output file: " << Path << "!\n";
+    return nullptr;
+  }
+  return OutputFile;
+}
+
+struct GeneratedByproducts {
+  std::set<std::string> ClassNames;
+  std::set<std::string> MangledMethodNames;
+  std::set<std::string> SkippedMethodNames;
+  std::set<lldb_rpc_gen::Method> CallbackMethods;
+};
+
+enum SupportLevel {
+  eUnsupported,
+  eUnimplemented,
+  eImplemented,
+};
+
+class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
+public:
+  SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager,
+            ASTContext &Context,
+            std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+            std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile)
+      : Byproducts(Byproducts), Manager(Manager), Context(Context),
+        LibrarySourceEmitter(std::move(LibrarySourceOutputFile)),
+        LibraryHeaderEmitter(std::move(LibraryHeaderOutputFile)) {}
+
+  ~SBVisitor() {}
+
+  bool VisitCXXRecordDecl(CXXRecordDecl *RDecl) {
+    if (ShouldSkipRecord(RDecl))
+      return true;
+
+    const std::string ClassName = RDecl->getNameAsString();
+    Byproducts.ClassNames.insert(ClassName);
+
+    // Print 'bool' instead of '_Bool'.
+    PrintingPolicy Policy(Context.getLangOpts());
+    Policy.Bool = true;
+
+    LibraryHeaderEmitter.StartClass(ClassName);
+    LibrarySourceEmitter.StartClass(ClassName);
+    for (Decl *D : RDecl->decls())
+      if (auto *E = dyn_cast_or_null<EnumDecl>(D))
+        LibraryHeaderEmitter.EmitEnum(E);
+
+    for (CXXMethodDecl *MDecl : RDecl->methods()) {
+      const std::string MangledName =
+          lldb_rpc_gen::GetMangledName(Context, MDecl);
+      const bool IsDisallowed = lldb_rpc_gen::MethodIsDisallowed(MangledName);
+      const bool HasCallbackParameter =
+          lldb_rpc_gen::HasCallbackParameter(MDecl);
+      SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl);
+      if (MethodSupportLevel == eImplemented && !IsDisallowed) {
+        const lldb_rpc_gen::Method Method(MDecl, Policy, Context);
+        LibrarySourceEmitter.EmitMethod(Method);
+        LibraryHeaderEmitter.EmitMethod(Method);
+        Byproducts.MangledMethodNames.insert(MangledName);
+      } else if (MethodSupportLevel == eUnimplemented)
+        Byproducts.SkippedMethodNames.insert(MangledName);
+    }
+    LibraryHeaderEmitter.EndClass();
+    LibrarySourceEmitter.EndClass();
+    return true;
+  }
+
+private:
+  /// Determines whether we should skip a RecordDecl.
+  /// Conditions for skipping:
+  ///   - Anything not in the header itself
+  ///   - Certain inconvenient classes
+  ///   - Records without definitions (forward declarations)
+  bool ShouldSkipRecord(CXXRecordDecl *Decl) {
+    const Type *DeclType = Decl->getTypeForDecl();
+    QualType CanonicalType = DeclType->getCanonicalTypeInternal();
+    return !Manager.isInMainFile(Decl->getBeginLoc()) ||
+           !Decl->hasDefinition() || Decl->getDefinition() != Decl ||
+           lldb_rpc_gen::TypeIsDisallowedClass(CanonicalType);
+  }
+
+  /// Check the support level for a type
+  /// Known unsupported types:
+  ///  - FILE * (We do not want to expose this primitive)
+  ///  - Types that are internal to LLDB
+  SupportLevel GetTypeSupportLevel(QualType Type) {
+    const std::string TypeName = Type.getAsString();
+    if (TypeName == "FILE *" || lldb_rpc_gen::TypeIsFromLLDBPrivate(Type))
+      return eUnsupported;
+
+    if (lldb_rpc_gen::TypeIsDisallowedClass(Type))
+      return eUnsupported;
+
+    return eImplemented;
+  }
+
+  /// Determine the support level of a given method.
+  /// Known unsupported methods:
+  ///   - Non-public methods (lldb-rpc is a client and can only see public
+  ///     things)
+  ///   - Copy assignment operators (the client side will handle this)
+  ///   - Move assignment operators (the client side will handle this)
+  ///   - Methods involving unsupported types.
+  /// Known unimplemented methods:
+  ///   - No variadic functions, e.g. Printf
+  SupportLevel GetMethodSupportLevel(CXXMethodDecl *MDecl) {
+    AccessSpecifier AS = MDecl->getAccess();
+    if (AS != AccessSpecifier::AS_public)
+      return eUnsupported;
+    if (MDecl->isCopyAssignmentOperator())
+      return eUnsupported;
+    if (MDecl->isMoveAssignmentOperator())
+      return eUnsupported;
+
+    if (MDecl->isVariadic())
+      return eUnimplemented;
+
+    SupportLevel ReturnTypeLevel = GetTypeSupportLevel(MDecl->getReturnType());
+    if (ReturnTypeLevel != eImplemented)
+      return ReturnTypeLevel;
+
+    for (auto *ParamDecl : MDecl->parameters()) {
+      SupportLevel ParamTypeLevel = GetTypeSupportLevel(ParamDecl->getType());
+      if (ParamTypeLevel != eImplemented)
+        return ParamTypeLevel;
+    }
+
+    // FIXME: If a callback does not take a `void *baton` parameter, it is
+    // considered unsupported at this time. On the server-side, we hijack the
+    // baton argument in order to pass additional information to the 
server-side
+    // callback so we can correctly perform a reverse RPC call back to the
+    // client. Without this baton, we would need the server-side callback to
+    // have some side channel by which it obtained that information, and
+    // spending time designing that doesn't outweight the cost of doing it at
+    // the moment.
+    bool HasCallbackParameter = false;
+    bool HasBatonParameter = false;
+    auto End = MDecl->parameters().end();
+    for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) {
+      if ((*Iter)->getType()->isFunctionPointerType()) {
+        HasCallbackParameter = true;
+        continue;
+      }
+
+      // FIXME: We assume that if we have a function pointer and a void pointer
+      // together in the same parameter list, that it is not followed by a
+      // length argument. If that changes, we will need to revisit this
+      // implementation.
+      if ((*Iter)->getType()->isVoidPointerType())
+        HasBatonParameter = true;
+    }
+
+    if (HasCallbackParameter && !HasBatonParameter)
+      return eUnimplemented;
+
+    return eImplemented;
+  }
+
+  GeneratedByproducts &Byproducts;
+  SourceManager &Manager;
+  ASTContext &Context;
+  lldb_rpc_gen::RPCLibrarySourceEmitter LibrarySourceEmitter;
+  lldb_rpc_gen::RPCLibraryHeaderEmitter LibraryHeaderEmitter;
+};
+
+class SBConsumer : public ASTConsumer {
+public:
+  SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager,
+             ASTContext &Context,
+             std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+             std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile)
+      : Visitor(Byproducts, Manager, Context,
+                std::move(LibrarySourceOutputFile),
+                std::move(LibraryHeaderOutputFile), ) {}
+  bool HandleTopLevelDecl(DeclGroupRef DR) override {
+    for (Decl *D : DR)
+      Visitor.TraverseDecl(D);
+
+    return true;
+  }
+
+private:
+  SBVisitor Visitor;
+};
+
+class SBAction : public ASTFrontendAction {
+public:
+  SBAction(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}
+
+  std::unique_ptr<ASTConsumer>
+  CreateASTConsumer(CompilerInstance &CI, llvm::StringRef File) override {
+    llvm::StringRef FilenameNoExt =
+        llvm::sys::path::stem(llvm::sys::path::filename(File));
+
+    const std::string LibrarySourceFilename = FilenameNoExt.str() + ".cpp";
+    std::unique_ptr<llvm::ToolOutputFile> LibrarySourceOutputFile =
+        CreateOutputFile(GetLibraryOutputDirectory(), LibrarySourceFilename);
+    if (!LibrarySourceOutputFile)
+      return nullptr;
+
+    const std::string LibraryHeaderFilename = FilenameNoExt.str() + ".h";
+    std::unique_ptr<llvm::ToolOutputFile> LibraryHeaderOutputFile =
+        CreateOutputFile(GetLibraryOutputDirectory(), LibraryHeaderFilename);
+    if (!LibraryHeaderOutputFile)
+      return nullptr;
+
+    LibrarySourceOutputFile->keep();
+    LibraryHeaderOutputFile->keep();
+    return std::make_unique<SBConsumer>(
+        Byproducts, CI.getSourceManager(), CI.getASTContext(),
+        std::move(LibrarySourceOutputFile), 
std::move(LibraryHeaderOutputFile));
+  }
+
+private:
+  GeneratedByproducts &Byproducts;
+};
+
+class SBActionFactory : public FrontendActionFactory {
+public:
+  SBActionFactory(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}
+
+  std::unique_ptr<FrontendAction> create() override {
+    return std::make_unique<SBAction>(Byproducts);
+  }
+
+private:
+  GeneratedByproducts &Byproducts;
+};
+
+bool EmitAmalgamatedLibraryHeader(const std::vector<std::string> &Files) {
+  static constexpr llvm::StringLiteral AmalgamatedLibraryHeaderName =
+      "LLDBRPC.h";
+  std::unique_ptr<llvm::ToolOutputFile> AmalgamatedLibraryHeader =
+      CreateOutputFile(GetLibraryOutputDirectory(),
+                       AmalgamatedLibraryHeaderName);
+  if (!AmalgamatedLibraryHeader)
+    return false;
+
+  AmalgamatedLibraryHeader->os() << "#ifndef LLDBRPC_H\n";
+  AmalgamatedLibraryHeader->os() << "#define LLDBRPC_H\n";
+  AmalgamatedLibraryHeader->os() << "#include \"SBLanguages.h\"\n";
+  for (const auto &File : Files) {
+    llvm::StringRef Filename = llvm::sys::path::filename(File);
+    AmalgamatedLibraryHeader->os() << "#include \"" << Filename << "\"\n";
+  }
+
+  AmalgamatedLibraryHeader->os() << "#endif // LLDBRPC_H\n";
+  AmalgamatedLibraryHeader->keep();
+  return true;
+}
+
+bool EmitClassNamesFile(std::set<std::string> &ClassNames) {
+  static constexpr llvm::StringLiteral ClassNamesFileName = "SBClasses.def";
+  std::unique_ptr<llvm::ToolOutputFile> ClassNamesFile =
+      CreateOutputFile(OutputDir.getValue(), ClassNamesFileName);
+  if (!ClassNamesFile)
+    return false;
+
+  ClassNamesFile->os() << "#ifndef SBCLASS\n";
+  ClassNamesFile->os() << "#error \"SBClass must be defined\"\n";
+  ClassNamesFile->os() << "#endif\n";
+
+  for (const auto &ClassName : ClassNames) {
+    if (ClassName == "SBStream" || ClassName == "SBProgress")
+      ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_NONCOPYABLE)\n";
+    else if (ClassName == "SBReproducer")
+      ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_STATICONLY)\n";
+
+    ClassNamesFile->os() << "SBCLASS(" << ClassName << ")\n";
+    if (ClassName == "SBStream" || ClassName == "SBReproducer" ||
+        ClassName == "SBProgress")
+      ClassNamesFile->os() << "#endif\n";
+  }
+  ClassNamesFile->keep();
+  return true;
+}
+
+bool EmitMethodNamesFile(std::set<std::string> &MangledMethodNames) {
+  static constexpr llvm::StringLiteral MethodNamesFileName = "SBAPI.def";
+  std::unique_ptr<llvm::ToolOutputFile> MethodNamesFile =
+      CreateOutputFile(OutputDir.getValue(), MethodNamesFileName);
+  if (!MethodNamesFile)
+    return false;
+
+  MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n";
+  MethodNamesFile->os() << "#error \"GENERATE_SBAPI must be defined\"\n";
+  MethodNamesFile->os() << "#endif\n";
+
+  for (const auto &MangledName : MangledMethodNames) {
+    MethodNamesFile->os() << "GENERATE_SBAPI(" << MangledName << ")\n";
+  }
+  MethodNamesFile->keep();
+  return true;
+}
+
+bool EmitSkippedMethodsFile(std::set<std::string> &SkippedMethodNames) {
+  static constexpr llvm::StringLiteral FileName = "SkippedMethods.txt";
+  std::unique_ptr<llvm::ToolOutputFile> File =
+      CreateOutputFile(OutputDir.getValue(), FileName);
+  if (!File)
+    return false;
+
+  for (const auto &Skipped : SkippedMethodNames) {
+    File->os() << Skipped << "\n";
+  }
+  File->keep();
+  return true;
+}
+
+int main(int argc, const char *argv[]) {
+  auto ExpectedParser = CommonOptionsParser::create(
+      argc, argv, RPCGenCategory, llvm::cl::OneOrMore,
+      "Tool for generating LLDBRPC interfaces and implementations");
+
+  if (!ExpectedParser) {
+    llvm::errs() << ExpectedParser.takeError();
+    return 1;
+  }
+
+  if (OutputDir.empty()) {
+    llvm::errs() << "Please specify an output directory for the generated "
+                    "files with --output-dir!\n";
+    return 1;
+  }
+
+  CommonOptionsParser &OP = ExpectedParser.get();
+  auto PCHOpts = std::make_shared<PCHContainerOperations>();
+  PCHOpts->registerWriter(std::make_unique<ObjectFilePCHContainerWriter>());
+  PCHOpts->registerReader(std::make_unique<ObjectFilePCHContainerReader>());
+
+  ClangTool T(OP.getCompilations(), OP.getSourcePathList(), PCHOpts);
+
+  if (!EmitAmalgamatedLibraryHeader(OP.getSourcePathList())) {
+    llvm::errs() << "Failed to create amalgamated library header\n";
+    return 1;
+  }
+
+  GeneratedByproducts Byproducts;
+
+  SBActionFactory Factory(Byproducts);
+  auto Result = T.run(&Factory);
+  if (!EmitClassNamesFile(Byproducts.ClassNames)) {
+    llvm::errs() << "Failed to create SB Class file\n";
+    return 1;
+  }
+  if (!EmitMethodNamesFile(Byproducts.MangledMethodNames)) {
+    llvm::errs() << "Failed to create Method Names file\n";
+    return 1;
+  }
+  if (!EmitSkippedMethodsFile(Byproducts.SkippedMethodNames)) {
+    llvm::errs() << "Failed to create Skipped Methods file\n";
+    return 1;
+  }
+
+  return Result;
+}

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to