https://github.com/chelcassanova updated https://github.com/llvm/llvm-project/pull/138032
>From b6edf90f106ee2b339a162e13058167899f2ee21 Mon Sep 17 00:00:00 2001 From: Chelsea Cassanova <chelsea_cassan...@apple.com> Date: Wed, 30 Apr 2025 14:24:03 -0700 Subject: [PATCH] [lldb[RPC] Upstream RPC server interface emitters This commit upstreams the LLDB RPC server interface emitters. These emitters generate the server-side API interfaces for RPC, which communicate directly with liblldb itself out of process using the SB API. https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804 --- .../Inputs/Server/CheckBasicIncludesEmit.h | 6 + .../Inputs/Server/CheckConstCharPointer.h | 14 + .../Tests/Server/CheckBasicIncludesEmit.test | 14 + .../Tests/Server/CheckConstCharPointer.test | 10 + .../server/RPCServerHeaderEmitter.cpp | 75 +++ .../server/RPCServerHeaderEmitter.h | 47 ++ .../server/RPCServerSourceEmitter.cpp | 584 ++++++++++++++++++ .../server/RPCServerSourceEmitter.h | 81 +++ 8 files changed, 831 insertions(+) create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Server/CheckBasicIncludesEmit.h create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Server/CheckConstCharPointer.h create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Server/CheckBasicIncludesEmit.test create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Server/CheckConstCharPointer.test create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.cpp create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.h create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.cpp create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.h diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckBasicIncludesEmit.h b/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckBasicIncludesEmit.h new file mode 100644 index 0000000000000..77394aba12f7a --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckBasicIncludesEmit.h @@ -0,0 +1,6 @@ +// This ia a basic header file used to check that the server-side emitter +// for rpc-gen emits an expected set of includes in a generated source file. +#ifndef LLDB_API_SBRPC_CHECKBASICINCLUDE_H +#define LLDB_API_SBRPC_CHECKBASICINCLUDE_H + +#endif // LLDB_API_SBRPC_CHECKBASICINCLUDE_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckConstCharPointer.h b/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckConstCharPointer.h new file mode 100644 index 0000000000000..37121cd445267 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Server/CheckConstCharPointer.h @@ -0,0 +1,14 @@ +#ifndef LLDB_API_SBRPC_CHECKCONSTCHARPOINTER_H +#define LLDB_API_SBRPC_CHECKCONSTCHARPOINTER_H + +namespace lldb { +class LLDB_API SBRPC_CHECKCONSTCHARPOINTER { +public: + // const char * parameters must decoded as rpc_common::ConstCharPointer in server side + // source files. + int CheckConstCharPointer(char *string); + +}; // class SBRPC_CHECKCONSTCHARPOINTER +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKCONSTCHARPOINTER_H diff --git a/lldb/test/Shell/RPC/Generator/Tests/Server/CheckBasicIncludesEmit.test b/lldb/test/Shell/RPC/Generator/Tests/Server/CheckBasicIncludesEmit.test new file mode 100644 index 0000000000000..535d31886df6e --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Server/CheckBasicIncludesEmit.test @@ -0,0 +1,14 @@ +UNSUPPORTED: target=* +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckBasicIncludesEmit.h + +RUN: cat %t/lib/CheckBasicIncludesEmit.cpp | FileCheck %s + +# All server-side source files must have these includes at the top of their files. +CHECK: #include "RPCUserServer.h" +CHECK: #include "SBAPI.h" +CHECK: #include <lldb-rpc/common/RPCArgument.h> +CHECK: #include <lldb-rpc/common/RPCCommon.h> +CHECK: #include <lldb-rpc/common/RPCFunction.h> +CHECK: #include <lldb/API/LLDB.h> diff --git a/lldb/test/Shell/RPC/Generator/Tests/Server/CheckConstCharPointer.test b/lldb/test/Shell/RPC/Generator/Tests/Server/CheckConstCharPointer.test new file mode 100644 index 0000000000000..e3d92fa583431 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Server/CheckConstCharPointer.test @@ -0,0 +1,10 @@ +UNSUPPORTED: target=* +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckConstCharPointer.h + +RUN: cat %t/lib/CheckConstCharPointer.cpp | FileCheck %s + +# const char * pointers must be decoded as rpc_common::ConstCharPointer objects +# in server side source files. +CHECK: rpc_common::ConstCharPointer string diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.cpp new file mode 100644 index 0000000000000..30a18e3c7044b --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.cpp @@ -0,0 +1,75 @@ +//===-- RPCServerHeaderEmitter.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 "RPCServerHeaderEmitter.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 RPCServerHeaderEmitter::EmitMethod(const Method &method) { + // We'll be using the mangled name in order to disambiguate + // overloaded methods. + const std::string &MangledName = method.MangledName; + + EmitLine("class " + MangledName + + " : public rpc_common::RPCFunctionInstance {"); + EmitLine("public:"); + IndentLevel++; + EmitConstructor(MangledName); + EmitDestructor(MangledName); + EmitHandleRPCCall(); + IndentLevel--; + EmitLine("};"); +} + +void RPCServerHeaderEmitter::EmitHandleRPCCall() { + EmitLine("bool HandleRPCCall(rpc_common::Connection &connection, " + "rpc_common::RPCStream &send, rpc_common::RPCStream &response) " + "override;"); +} + +void RPCServerHeaderEmitter::EmitConstructor(const std::string &MangledName) { + EmitLine(MangledName + "() : RPCFunctionInstance(\"" + MangledName + + "\") {}"); +} + +void RPCServerHeaderEmitter::EmitDestructor(const std::string &MangledName) { + EmitLine("~" + MangledName + "() override {}"); +} + +std::string RPCServerHeaderEmitter::GetHeaderGuard() { + const std::string UpperFilenameNoExt = + llvm::sys::path::stem( + llvm::sys::path::filename(OutputFile->getFilename())) + .upper(); + return "GENERATED_LLDB_RPC_SERVER_" + UpperFilenameNoExt + "_H"; +} + +void RPCServerHeaderEmitter::Begin() { + const std::string HeaderGuard = GetHeaderGuard(); + EmitLine("#ifndef " + HeaderGuard); + EmitLine("#define " + HeaderGuard); + EmitLine(""); + EmitLine("#include <lldb-rpc/common/RPCFunction.h>"); + EmitLine(""); + EmitLine("namespace rpc_server {"); +} + +void RPCServerHeaderEmitter::End() { + EmitLine("} // namespace rpc_server"); + EmitLine("#endif // " + GetHeaderGuard()); +} diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.h new file mode 100644 index 0000000000000..a0e4f47d4f570 --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerHeaderEmitter.h @@ -0,0 +1,47 @@ +//===-- RPCServerHeaderEmitter.h ----------------------------------------===// +// +// 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_RPC_GEN_RPCSERVERHEADEREMITTER_H +#define LLDB_RPC_GEN_RPCSERVERHEADEREMITTER_H + +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "llvm/Support/ToolOutputFile.h" + +using namespace clang; + +namespace lldb_rpc_gen { +/// Emit the source code for server-side *.h files. +class RPCServerHeaderEmitter : public FileEmitter { +public: + RPCServerHeaderEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile) + : FileEmitter(std::move(OutputFile)) { + Begin(); + } + + ~RPCServerHeaderEmitter() { End(); } + + void EmitMethod(const Method &method); + +private: + void EmitHandleRPCCall(); + + void EmitConstructor(const std::string &MangledName); + + void EmitDestructor(const std::string &MangledName); + + std::string GetHeaderGuard(); + + void Begin(); + + void End(); +}; +} // namespace lldb_rpc_gen + +#endif // LLDB_RPC_GEN_RPCSERVERHEADEREMITTER_H diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.cpp new file mode 100644 index 0000000000000..a6d2528fe2c3c --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.cpp @@ -0,0 +1,584 @@ +//===-- RPCServerSourceEmitter.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 "RPCServerSourceEmitter.h" +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "clang/Frontend/CompilerInstance.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +#include <map> + +using namespace clang; +using namespace lldb_rpc_gen; + +// For methods with pointer return types, it's important that we know how big +// the type of the pointee is. We must correctly size a buffer (in the form of a +// Bytes object) before we can actually use it. +static const std::map<llvm::StringRef, size_t> MethodsWithPointerReturnTypes = { + {"_ZN4lldb12SBModuleSpec12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 16 + {"_ZNK4lldb8SBModule12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 16 +}; + +void RPCServerSourceEmitter::EmitMethod(const Method &method) { + if (method.ContainsFunctionPointerParameter) + EmitCallbackFunction(method); + + EmitCommentHeader(method); + EmitFunctionHeader(method); + EmitFunctionBody(method); + EmitFunctionFooter(); +} + +void RPCServerSourceEmitter::EmitCommentHeader(const Method &method) { + std::string CommentLine; + llvm::raw_string_ostream CommentStream(CommentLine); + + CommentStream << "// " << method.QualifiedName << "(" + << method.CreateParamListAsString(eServer) << ")"; + if (method.IsConst) + CommentStream << " const"; + + EmitLine("//------------------------------------------------------------"); + EmitLine(CommentLine); + EmitLine("//------------------------------------------------------------"); +} + +void RPCServerSourceEmitter::EmitFunctionHeader(const Method &method) { + std::string FunctionHeader; + llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader); + FunctionHeaderStream + << "bool rpc_server::" << method.MangledName + << "::HandleRPCCall(rpc_common::Connection &connection, RPCStream " + "&send, RPCStream &response) {"; + EmitLine(FunctionHeader); + IndentLevel++; +} + +void RPCServerSourceEmitter::EmitFunctionBody(const Method &method) { + EmitLine("// 1) Make local storage for incoming function arguments"); + EmitStorageForParameters(method); + EmitLine("// 2) Decode all function arguments"); + EmitDecodeForParameters(method); + EmitLine("// 3) Call the method and encode the return value"); + EmitMethodCallAndEncode(method); +} + +void RPCServerSourceEmitter::EmitFunctionFooter() { + EmitLine("return true;"); + IndentLevel--; + EmitLine("}"); +} + +void RPCServerSourceEmitter::EmitStorageForParameters(const Method &method) { + // If we have an instance method and it isn't a constructor, we'll need to + // emit a "this" pointer. + if (method.IsInstance && !method.IsCtor) + EmitStorageForOneParameter(method.ThisType, "this_ptr", method.Policy, + /* IsFollowedByLen = */ false); + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + EmitStorageForOneParameter(Iter->Type, Iter->Name, method.Policy, + Iter->IsFollowedByLen); + // Skip over the length parameter, we don't emit it. + if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + Iter->IsFollowedByLen) + Iter++; + } +} + +void RPCServerSourceEmitter::EmitStorageForOneParameter( + QualType ParamType, const std::string &ParamName, + const PrintingPolicy &Policy, bool IsFollowedByLen) { + // First, we consider `const char *`, `const char **`. They have special + // server-side types. + if (TypeIsConstCharPtr(ParamType)) { + EmitLine("rpc_common::ConstCharPointer " + ParamName + ";"); + return; + } else if (TypeIsConstCharPtrPtr(ParamType)) { + EmitLine("rpc_common::StringList " + ParamName + ";"); + return; + } + + QualType UnderlyingType = + lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType); + const bool IsSBClass = lldb_rpc_gen::TypeIsSBClass(UnderlyingType); + + if (ParamType->isPointerType() && !IsSBClass) { + // Void pointer with no length is usually a baton for a callback. We're + // going to hold onto the pointer value so we can send it back to the + // client-side when we implement callbacks. + if (ParamType->isVoidPointerType() && !IsFollowedByLen) { + EmitLine("void * " + ParamName + " = nullptr;"); + return; + } + + if (!ParamType->isFunctionPointerType()) { + EmitLine("Bytes " + ParamName + ";"); + return; + } + + assert(ParamType->isFunctionPointerType() && "Unhandled pointer type"); + EmitLine("rpc_common::function_ptr_t " + ParamName + " = nullptr;"); + return; + } + + std::string StorageDeclaration; + llvm::raw_string_ostream StorageDeclarationStream(StorageDeclaration); + + UnderlyingType.print(StorageDeclarationStream, Policy); + StorageDeclarationStream << " "; + if (IsSBClass) + StorageDeclarationStream << "*"; + StorageDeclarationStream << ParamName; + if (IsSBClass) + StorageDeclarationStream << " = nullptr"; + else + StorageDeclarationStream << " = {}"; + StorageDeclarationStream << ";"; + EmitLine(StorageDeclaration); +} + +void RPCServerSourceEmitter::EmitDecodeForParameters(const Method &method) { + if (method.IsInstance && !method.IsCtor) + EmitDecodeForOneParameter(method.ThisType, "this_ptr", method.Policy); + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + EmitDecodeForOneParameter(Iter->Type, Iter->Name, method.Policy); + if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + Iter->IsFollowedByLen) + Iter++; + } +} + +void RPCServerSourceEmitter::EmitDecodeForOneParameter( + QualType ParamType, const std::string &ParamName, + const PrintingPolicy &Policy) { + QualType UnderlyingType = + lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType); + + if (TypeIsSBClass(UnderlyingType)) { + std::string DecodeLine; + llvm::raw_string_ostream DecodeLineStream(DecodeLine); + DecodeLineStream << ParamName << " = " + << "RPCServerObjectDecoder<"; + UnderlyingType.print(DecodeLineStream, Policy); + DecodeLineStream << ">(send, rpc_common::RPCPacket::ValueType::Argument);"; + EmitLine(DecodeLine); + EmitLine("if (!" + ParamName + ")"); + IndentLevel++; + EmitLine("return false;"); + IndentLevel--; + } else { + EmitLine("if (!RPCValueDecoder(send, " + "rpc_common::RPCPacket::ValueType::Argument, " + + ParamName + "))"); + IndentLevel++; + EmitLine("return false;"); + IndentLevel--; + } +} + +std::string RPCServerSourceEmitter::CreateMethodCall(const Method &method) { + std::string MethodCall; + llvm::raw_string_ostream MethodCallStream(MethodCall); + if (method.IsInstance) { + if (!method.IsCtor) + MethodCallStream << "this_ptr->"; + MethodCallStream << method.BaseName; + } else + MethodCallStream << method.QualifiedName; + + std::vector<std::string> Args; + std::string FunctionPointerName; + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + std::string Arg; + // We must check for `const char *` and `const char **` first. + if (TypeIsConstCharPtr(Iter->Type)) { + // `const char *` is stored server-side as rpc_common::ConstCharPointer + Arg = Iter->Name + ".c_str()"; + } else if (TypeIsConstCharPtrPtr(Iter->Type)) { + // `const char **` is stored server-side as rpc_common::StringList + Arg = Iter->Name + ".argv()"; + } else if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) { + Arg = Iter->Name; + if (!Iter->Type->isPointerType()) + Arg = "*" + Iter->Name; + } else if (Iter->Type->isPointerType() && + !Iter->Type->isFunctionPointerType() && + (!Iter->Type->isVoidPointerType() || Iter->IsFollowedByLen)) { + // We move pointers between the server and client as 'Bytes' objects. + // Pointers with length arguments will have their length filled in below. + // Pointers with no length arguments are assumed to behave like an array + // with length of 1, except for void pointers which are handled + // differently. + Arg = "(" + Iter->Type.getAsString(method.Policy) + ")" + Iter->Name + + ".GetData()"; + } else if (Iter->Type->isFunctionPointerType()) { + // If we have a function pointer, we only want to pass something along if + // we got a real pointer. + Arg = Iter->Name + " ? " + method.MangledName + "_callback : nullptr"; + FunctionPointerName = Iter->Name; + } else if (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen && + method.ContainsFunctionPointerParameter) { + // Assumptions: + // - This is assumed to be the baton for the function pointer. + // - This is assumed to come after the function pointer parameter. + // We always produce this regardless of the value of the baton argument. + Arg = "new CallbackInfo(" + FunctionPointerName + ", " + Iter->Name + + ", connection.GetConnectionID())"; + } else + Arg = Iter->Name; + + if (Iter->Type->isRValueReferenceType()) + Arg = "std::move(" + Arg + ")"; + Args.push_back(Arg); + + if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + Iter->IsFollowedByLen) { + std::string LengthArg = Iter->Name + ".GetSize()"; + if (!Iter->Type->isVoidPointerType()) { + QualType UUT = lldb_rpc_gen::GetUnqualifiedUnderlyingType(Iter->Type); + LengthArg += " / sizeof(" + UUT.getAsString(method.Policy) + ")"; + } + Args.push_back(LengthArg); + Iter++; + } + } + MethodCallStream << "(" << llvm::join(Args, ", ") << ")"; + + return MethodCall; +} + +std::string RPCServerSourceEmitter::CreateEncodeLine(const std::string &Value, + bool IsEncodingSBClass) { + std::string EncodeLine; + llvm::raw_string_ostream EncodeLineStream(EncodeLine); + + if (IsEncodingSBClass) + EncodeLineStream << "RPCServerObjectEncoder("; + else + EncodeLineStream << "RPCValueEncoder("; + + EncodeLineStream + << "response, rpc_common::RPCPacket::ValueType::ReturnValue, "; + EncodeLineStream << Value; + EncodeLineStream << ");"; + return EncodeLine; +} + +// There are 4 cases to consider: +// - const SBClass &: No need to do anything. +// - const foo &: No need to do anything. +// - SBClass &: The server and the client hold on to IDs to refer to specific +// instances, so there's no need to send any information back to the client. +// - foo &: The client is sending us a value over the wire, but because the type +// is mutable, we must send the changed value back in case the method call +// mutated it. +// +// Updating a mutable reference is done as a return value from the RPC +// perspective. These return values need to be emitted after the method's return +// value, and they are emitted in the order in which they occur in the +// declaration. +void RPCServerSourceEmitter::EmitEncodesForMutableParameters( + const std::vector<Param> &Params) { + for (auto Iter = Params.begin(); Iter != Params.end(); Iter++) { + // No need to manually update an SBClass + if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) + continue; + + if (!Iter->Type->isReferenceType() && !Iter->Type->isPointerType()) + continue; + + // If we have a void pointer with no length, there's nothing to update. This + // is likely a baton for a callback. The same goes for function pointers. + if (Iter->Type->isFunctionPointerType() || + (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen)) + continue; + + // No need to update pointers and references to const-qualified data. + QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type); + if (UnderlyingType.isConstQualified()) + continue; + + const std::string EncodeLine = + CreateEncodeLine(Iter->Name, /* IsEncodingSBClass = */ false); + EmitLine(EncodeLine); + } +} + +// There are 3 possible scenarios that this method can encounter: +// 1. The method has no return value and is not a constructor. +// Only the method call itself is emitted. +// 2. The method is a constructor. +// The call to the constructor is emitted in the encode line. +// 3. The method has a return value. +// The method call is emitted and the return value is captured in a variable. +// After that, an encode call is emitted with the variable that captured the +// return value. +void RPCServerSourceEmitter::EmitMethodCallAndEncode(const Method &method) { + const std::string MethodCall = CreateMethodCall(method); + + // If this function returns nothing, we just emit the call and update any + // mutable references. Note that constructors have return type `void` so we + // must explicitly check for that here. + if (!method.IsCtor && method.ReturnType->isVoidType()) { + EmitLine(MethodCall + ";"); + EmitEncodesForMutableParameters(method.Params); + return; + } + + static constexpr llvm::StringLiteral ReturnVariableName("__result"); + + // If this isn't a constructor, we'll need to store the result of the method + // call in a result variable. + if (!method.IsCtor) { + // We need to determine what the appropriate return type is. Here is the + // strategy: + // 1.) `SBFoo` -> `SBFoo &&` + // 2.) If the type is a pointer other than `const char *` or `const char **` + // or `void *`, the return type will be `Bytes` (e.g. `const uint8_t *` + // -> `Bytes`). + // 3.) Otherwise, emit the exact same return type. + std::string ReturnTypeName; + std::string AssignLine; + llvm::raw_string_ostream AssignLineStream(AssignLine); + if (method.ReturnType->isPointerType() && + !lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType) && + !lldb_rpc_gen::TypeIsConstCharPtrPtr(method.ReturnType) && + !method.ReturnType->isVoidPointerType()) { + llvm::StringRef MangledNameRef(method.MangledName); + auto Pos = MethodsWithPointerReturnTypes.find(MangledNameRef); + assert(Pos != MethodsWithPointerReturnTypes.end() && + "Unable to determine the size of the return buffer"); + if (Pos == MethodsWithPointerReturnTypes.end()) { + EmitLine( + "// Intentionally inserting a compiler error. lldb-rpc-gen " + "was unable to determine how large the return buffer should be."); + EmitLine("#error: \"unable to determine size of return buffer\""); + return; + } + AssignLineStream << "Bytes " << ReturnVariableName << "(" << MethodCall + << ", " << Pos->second << ");"; + } else { + if (lldb_rpc_gen::TypeIsSBClass(method.ReturnType)) { + // We want to preserve constness, so we don't strip qualifications from + // the underlying type + QualType UnderlyingReturnType = + lldb_rpc_gen::GetUnderlyingType(method.ReturnType); + ReturnTypeName = + UnderlyingReturnType.getAsString(method.Policy) + " &&"; + } else + ReturnTypeName = method.ReturnType.getAsString(method.Policy); + + AssignLineStream << ReturnTypeName << " " << ReturnVariableName << " = " + << MethodCall << ";"; + } + EmitLine(AssignLine); + } + + const bool IsEncodingSBClass = + lldb_rpc_gen::TypeIsSBClass(method.ReturnType) || method.IsCtor; + + std::string ValueToEncode; + if (IsEncodingSBClass) { + if (method.IsCtor) + ValueToEncode = MethodCall; + else + ValueToEncode = "std::move(" + ReturnVariableName.str() + ")"; + } else + ValueToEncode = ReturnVariableName.str(); + + const std::string ReturnValueEncodeLine = + CreateEncodeLine(ValueToEncode, IsEncodingSBClass); + EmitLine(ReturnValueEncodeLine); + EmitEncodesForMutableParameters(method.Params); +} + +// NOTE: This contains most of the same knowledge as RPCLibrarySourceEmitter. I +// have chosen not to re-use code here because the needs are different enough +// that it would be more work to re-use than just reimplement portions of it. +// Specifically: +// - Callbacks do not neatly fit into a `Method` object, which currently +// assumes that you have a CXXMethodDecl (We have a FunctionDecl at most). +// - We only generate callbacks that have a `void *` baton parameter. We hijack +// those baton parameters and treat them differently. +// - Callbacks need to do something special for moving SB class references back +// to the client-side. +void RPCServerSourceEmitter::EmitCallbackFunction(const Method &method) { + // Check invariants and locate necessary resources + Param FuncPointerParam; + Param BatonParam; + for (const auto &Param : method.Params) + if (Param.Type->isFunctionPointerType()) + FuncPointerParam = Param; + else if (Param.Type->isVoidPointerType()) + BatonParam = Param; + + assert(FuncPointerParam.Type->isFunctionPointerType() && + "Emitting callback function with no function pointer"); + assert(BatonParam.Type->isVoidPointerType() && + "Emitting callback function with no baton"); + + QualType FuncType = FuncPointerParam.Type->getPointeeType(); + const auto *FuncProtoType = FuncType->getAs<FunctionProtoType>(); + assert(FuncProtoType && "Emitting callback with no parameter information"); + if (!FuncProtoType) + return; // If asserts are off, we'll just fail to compile. + + std::vector<Param> CallbackParams; + std::vector<std::string> CallbackParamsAsStrings; + uint8_t ArgIdx = 0; + for (QualType ParamType : FuncProtoType->param_types()) { + Param CallbackParam; + CallbackParam.IsFollowedByLen = false; + CallbackParam.Type = ParamType; + if (ParamType->isVoidPointerType()) + CallbackParam.Name = "baton"; + else + CallbackParam.Name = "arg" + std::to_string(ArgIdx++); + + CallbackParams.push_back(CallbackParam); + CallbackParamsAsStrings.push_back(ParamType.getAsString(method.Policy) + + " " + CallbackParam.Name); + } + const std::string CallbackReturnTypeName = + FuncProtoType->getReturnType().getAsString(method.Policy); + const std::string CallbackName = method.MangledName + "_callback"; + + // Emit Function Header + std::string Header; + llvm::raw_string_ostream HeaderStream(Header); + HeaderStream << "static " << CallbackReturnTypeName << " " << CallbackName + << "(" << llvm::join(CallbackParamsAsStrings, ", ") << ") {"; + EmitLine(Header); + IndentLevel++; + + // Emit Function Body + EmitLine("// RPC connection setup and sanity checking"); + EmitLine("CallbackInfo *callback_info = (CallbackInfo *)baton;"); + EmitLine("rpc_common::ConnectionSP connection_sp = " + "rpc_common::Connection::GetConnectionFromID(callback_info->" + "connection_id);"); + EmitLine("if (!connection_sp)"); + IndentLevel++; + if (FuncProtoType->getReturnType()->isVoidType()) + EmitLine("return;"); + else + EmitLine("return {};"); + IndentLevel--; + + EmitLine("// Preparing to make the call"); + EmitLine("static RPCFunctionInfo g_func(\"" + CallbackName + "\");"); + EmitLine("RPCStream send;"); + EmitLine("RPCStream response;"); + EmitLine("g_func.Encode(send);"); + + EmitLine("// The first thing we encode is the callback address so that the " + "client-side can know where the callback is"); + EmitLine("RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + "callback_info->callback);"); + EmitLine("// Encode all the arguments"); + for (const Param &CallbackParam : CallbackParams) { + if (lldb_rpc_gen::TypeIsSBClass(CallbackParam.Type)) { + + // FIXME: SB class server references are stored as non-const references so + // that we can actually change them as needed. If a parameter is marked + // const, we will fail to compile because we cannot make an + // SBFooServerReference from a `const SBFoo &`. + // To work around this issue, we'll apply a `const_cast` if needed so we + // can continue to generate callbacks for now, but we really should + // rethink the way we store object IDs server-side to support + // const-qualified parameters. + QualType UnderlyingSBClass = + lldb_rpc_gen::GetUnderlyingType(CallbackParam.Type); + QualType UnqualifiedUnderlyingSBClass = + UnderlyingSBClass.getUnqualifiedType(); + + std::string SBClassName = GetSBClassNameFromType(UnderlyingSBClass); + llvm::StringRef SBClassNameRef(SBClassName); + SBClassNameRef.consume_front("lldb::"); + + std::string ServerReferenceLine; + llvm::raw_string_ostream ServerReferenceLineStream(ServerReferenceLine); + ServerReferenceLineStream << "rpc_server::" << SBClassNameRef + << "ServerReference " << CallbackParam.Name + << "_ref("; + + if (UnderlyingSBClass.isConstQualified()) { + QualType NonConstSBType = + method.Context.getLValueReferenceType(UnqualifiedUnderlyingSBClass); + ServerReferenceLineStream << "const_cast<" << NonConstSBType << ">("; + } + ServerReferenceLineStream << CallbackParam.Name; + if (UnderlyingSBClass.isConstQualified()) + ServerReferenceLineStream << ")"; + + ServerReferenceLineStream << ");"; + EmitLine(ServerReferenceLine); + EmitLine( + CallbackParam.Name + + "_ref.Encode(send, rpc_common::RPCPacket::ValueType::Argument);"); + } else { + std::string ParamName; + if (CallbackParam.Type->isVoidPointerType()) + ParamName = "callback_info->baton"; + else + ParamName = CallbackParam.Name; + EmitLine( + "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + + ParamName + ");"); + } + } + + if (!FuncProtoType->getReturnType()->isVoidType()) { + EmitLine("// Storage for return value"); + const bool ReturnsSBClass = + lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType()); + std::string ReturnValueLine = CallbackReturnTypeName; + llvm::raw_string_ostream ReturnValueLineStream(ReturnValueLine); + + if (ReturnsSBClass) + ReturnValueLineStream << " *"; + ReturnValueLineStream << " __result = "; + if (ReturnsSBClass) + ReturnValueLineStream << "nullptr"; + else + ReturnValueLineStream << "{}"; + ReturnValueLineStream << ";"; + EmitLine(ReturnValueLine); + } + + EmitLine( + "if (connection_sp->SendRPCCallAndWaitForResponse(send, response)) {"); + IndentLevel++; + if (!FuncProtoType->getReturnType()->isVoidType()) { + if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType())) { + EmitLine("__result = rpc_server::RPCServerObjectDecoder<" + + CallbackReturnTypeName + + ">(response, rpc_common::RPCPacket::ValueType::ReturnValue);"); + } else + EmitLine("RPCValueDecoder(response, " + "rpc_common::RPCPacket::ValueType::ReturnValue, __result);"); + } + IndentLevel--; + EmitLine("}"); + if (!FuncProtoType->getReturnType()->isVoidType()) { + if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType())) + EmitLine("return *__result;"); + else + EmitLine("return __result;"); + } + + // Emit Function Footer; + IndentLevel--; + EmitLine("};"); +} diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.h new file mode 100644 index 0000000000000..a371dc100ff7a --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/server/RPCServerSourceEmitter.h @@ -0,0 +1,81 @@ +//===-- RPCServerSourceEmitter.h ------------------------------------------===// +// +// 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_RPC_GEN_RPCSERVERMETHODEMITTER_H +#define LLDB_RPC_GEN_RPCSERVERMETHODEMITTER_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 { +/// Emit the source code for server-side *.cpp files. +class RPCServerSourceEmitter : public FileEmitter { +public: + RPCServerSourceEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile) + : FileEmitter(std::move(OutputFile)) { + Begin(); + } + + /// Given a Method, emits a server-side implementation of the method + /// for lldb-rpc-server + void EmitMethod(const Method &method); + +private: + void EmitCommentHeader(const Method &method); + + void EmitFunctionHeader(const Method &method); + + void EmitFunctionBody(const Method &method); + + void EmitFunctionFooter(); + + void EmitStorageForParameters(const Method &method); + + void EmitStorageForOneParameter(QualType ParamType, + const std::string &ParamName, + const PrintingPolicy &Policy, + bool IsFollowedByLen); + + void EmitDecodeForParameters(const Method &method); + + void EmitDecodeForOneParameter(QualType ParamType, + const std::string &ParamName, + const PrintingPolicy &Policy); + + std::string CreateMethodCall(const Method &method); + + std::string CreateEncodeLine(const std::string &value, + bool IsEncodingSBClass); + + void EmitEncodesForMutableParameters(const std::vector<Param> &Params); + + void EmitMethodCallAndEncode(const Method &method); + + void EmitCallbackFunction(const Method &method); + + void Begin() { + EmitLine("#include \"RPCUserServer.h\""); + EmitLine("#include \"SBAPI.h\""); + EmitLine("#include <lldb-rpc/common/RPCArgument.h>"); + EmitLine("#include <lldb-rpc/common/RPCCommon.h>"); + EmitLine("#include <lldb-rpc/common/RPCFunction.h>"); + EmitLine("#include <lldb/API/LLDB.h>"); + EmitLine(""); + EmitLine("using namespace rpc_common;"); + EmitLine("using namespace lldb;"); + } +}; +} // namespace lldb_rpc_gen + +#endif // LLDB_RPC_GEN_RPCSERVERMETHODEMITTER_H _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits