================
@@ -0,0 +1,592 @@
+//===-- 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) {
+  // FIXME: The hand-written lldb-rpc-server currently doesn't emit destructors
+  // for LocalObjectRefs... even if the type is an ObjectRef. What should we do
+  // here?
+
+  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 &&`
----------------
bulbazord wrote:

This comment is a bit misleading (and I wrote it, so you can blame me :p).

The code we generated here would look like this:
```
SBFoo &&__result = this_ptr->GetTheFoo();
RPCServerObjectEncoder(response, rpc_common::RPCPacket::ValueType::ReturnValue, 
std::move(__result));
return true;
```

`this_ptr->GetTheFoo` would create a temporary `SBFoo` value and `__result` is 
a reference (rvalue ref) to that temporary value. This extends the lifetime of 
that temporary `SBFoo`.

On the next line, we'll "encode" that result value into the `response` stream 
(which is going to get turned into a packet). This line does a lot of work that 
you don't have access to yet, but effectively it will:
1. Put the SBFoo into an "ObjectInstance" map. The client and server speak in 
terms of "object IDs" instead of moving information about an object in their 
protocol packets. The server maintains a mapping of (Object ID -> Object 
Instance), and when we encode the SBFoo return value, the instance gets 
assigned an Object ID and stuck in the map.
2. Encode the return value's object ID into the "response" packet and indicate 
that it is a return value from this method.
3. Returning "true" indicates that the RPC call succeeded on the server-side.

https://github.com/llvm/llvm-project/pull/138032
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to