[Lldb-commits] [lldb] [lldb][rpc] Upstream RPC Client Library Emitters (PR #147655)
David Spickett via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 17 03:32:30 PDT 2025
================
@@ -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.
----------------
DavidSpickett wrote:
Assuming this applies to the "else if" below it, indent it to match that.
https://github.com/llvm/llvm-project/pull/147655
More information about the lldb-commits
mailing list