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

>From 3d29af3b776c407de606cf507c9b03c49daa11b0 Mon Sep 17 00:00:00 2001
From: Chelsea Cassanova <chelsea_cassan...@apple.com>
Date: Wed, 30 Apr 2025 09:04:40 -0700
Subject: [PATCH] [lldb][RPC] Design doc for upstreaming PR

This mainly adds as design doc to help follow with the current PRs up
for upstreaming the `lldb-rpc-gen` tool and emitters.
---
 lldb/docs/rpc-design-doc.rst | 181 +++++++++++++++++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100644 lldb/docs/rpc-design-doc.rst

diff --git a/lldb/docs/rpc-design-doc.rst b/lldb/docs/rpc-design-doc.rst
new file mode 100644
index 0000000000000..8e816bab74c29
--- /dev/null
+++ b/lldb/docs/rpc-design-doc.rst
@@ -0,0 +1,181 @@
+LLDB RPC Upstreaming Design Doc
+===============================
+
+This document aims to explain the general structure of the upstreaming patches 
for adding LLDB RPC. The 2 primary concepts explained here will be:
+
+* How LLDB RPC is used
+* How the ``lldb-rpc-gen`` works and what it outputs
+
+LLDB RPC
+*********
+
+LLDB RPC is a framework by which processes can communicate with LLDB out of 
process while maintaining compatibility with the SB API. More details are 
explained in the 
`RFC<https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804>`_ for 
upstreaming LLDB RPC, but the main focus in this doc for this section will be 
how exactly the code is structured for the PRs that will upstream this code.
+
+The ``lldb-rpc-gen`` tool
+*************************
+
+``lldb-rpc-gen`` is the tool that generates the main client and server 
interfaces for LLDB RPC. It is a ``ClangTool`` that reads all SB API header 
files and their functions and outputs the client/server interfaces and certain 
other pieces of code, such as RPC-specfic versions of Python bindings used for 
the test suite. There's 3 main components behind ``lldb-rpc-gen``:
+
+1. The ``lldb-rpc-gen`` tool itself, which contains the main driver that uses 
the ``ClangTool``.
+2. The code that generates all interfaces, which we call "emitters". All 
generated code for the interfaces are in C++, so the server side has one 
emitter for its generated source code and another for its generated header 
code. The client side has the same.
+3. All common code shared between all emitters, such as helper methods and 
information about exceptions to take when emitting.
+
+There are currently 2 PRs up for upstreaming RPC:
+- One that adds the ``lldb-rpc-gen`` tool and its common code: 
https://github.com/llvm/llvm-project/pull/138031
+- One that adds the RPC client-side interface code emitters: 
https://github.com/llvm/llvm-project/pull/147655
+
+The `current PR<https://github.com/llvm/llvm-project/pull/136748>`_ up for 
upstreaming LLDB RPC upstreams a subset of the code used for the tool. It 
upstreams the ``lldb-rpc-gen`` tool and all code needed for the server side 
emitters. Here's an example of what ``lldb-rpc-gen`` will output for the server 
side interface:
+
+Input
+-----
+
+We'll use ``SBDebugger::CreateTarget(const char *filename)`` as an example. 
``lldb-rpc-gen`` will read this method from ``SBDebugger.h``. The output is as 
follows.
+
+Server-side Output
+******************
+
+Source Code Output
+------------------
+
+Server-side Source Code
+~~~~~~~~~~~~~~~~~~~~~~~
+::
+
+   bool 
rpc_server::_ZN4lldb10SBDebugger12CreateTargetEPKc::HandleRPCCall(rpc_common::Connection
 &connection, RPCStream &send, RPCStream &response) {
+   // 1) Make local storage for incoming function arguments
+   lldb::SBDebugger *this_ptr = nullptr;
+   rpc_common::ConstCharPointer filename;
+   // 2) Decode all function arguments
+   this_ptr = RPCServerObjectDecoder<lldb::SBDebugger>(send, 
rpc_common::RPCPacket::ValueType::Argument);
+   if (!this_ptr)
+   return false;
+   if (!RPCValueDecoder(send, rpc_common::RPCPacket::ValueType::Argument, 
filename))
+   return false;
+   // 3) Call the method and encode the return value
+   lldb::SBTarget && __result = this_ptr->CreateTarget(filename.c_str());
+   RPCServerObjectEncoder(response, 
rpc_common::RPCPacket::ValueType::ReturnValue, std::move(__result));
+   return true;
+   }
+
+Function signature
+~~~~~~~~~~~~~~~~~~
+
+All server-side source code functions have a function signature that take the 
format ``bool 
rpc_server::<mangled-function-name>::HandleRPCCall(rpc_common::Connection 
&connection, RPCStream &send, RPCStream &response)``. The mangled name is used 
in order to differentiate between overloaded methods. Here the ``connection`` 
is what's maintained between the client and server. The ``send`` variable is a 
byte stream that carries information sent from the client. ``response`` is also 
a byte stream that will be populated with the return value obtained from the 
call into the SB API function that will be sent back to the client.
+
+For the client-side sources, the function signature is identical to that of 
what the signature looks like in the main SB API.
+
+Local variable storage
+~~~~~~~~~~~~~~~~~~~~~~
+
+First, variables are created to hold all arguments coming in from the client 
side. These variables will be a pointer for the SB API class in question, and 
corresponding variables for all parameters that the function has. Since this 
signature for ``SBDebugger::CreateTarget()`` only has one parameter, a ``const 
char *``, 2 local variables get created. A pointer for an ``SBDebugger`` 
object, and an ``RPCCommon::ConstCharPointer`` for the ``const char * 
filename`` parameter. The ``ConstCharPointer`` is a class backed by 
``std::string`` in the main RPC core code.
+
+Incoming stream decoding
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Following this, ``RPCServerObjectDecoder`` is used to decode the ``send`` byte 
stream. In this case, we're decoding this stream into the ``SBDebugger`` 
pointer we created earlier. We then decode the ``send`` stream again to obtain 
the ``const char * filename`` sent by the client. Each decoded argument from 
the client is checked for validity and the function will exit early if any are 
invalid.
+
+SB API function call
+~~~~~~~~~~~~~~~~~~~~
+
+Once all arguments have been decoded, the underlying SB API function called 
with the decoded arguments. ``RPCServerObjectEncoder`` is then used to encode 
the return value from the SB API call into the ``response`` stream, and this is 
then sent back to the client.
+
+Header Code Output
+------------------
+::
+
+   class _ZN4lldb10SBDebugger12CreateTargetEPKc : public 
rpc_common::RPCFunctionInstance {
+   public:
+   _ZN4lldb10SBDebugger12CreateTargetEPKc() : 
RPCFunctionInstance("_ZN4lldb10SBDebugger12CreateTargetEPKc") {}
+   ~_ZN4lldb10SBDebugger12CreateTargetEPKc() override {}
+   bool HandleRPCCall(rpc_common::Connection &connection, 
rpc_common::RPCStream &send, rpc_common::RPCStream &response) override;
+   };
+
+Class definition and ``HandleRPCCall``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All RPC server side functions are subclasses of ``RPCFunctionInstance``. Each 
class will then define their ``HandleRPCCall`` function that is seen in the 
source code above. This subclassing and ``HandleRPCCall`` definition is what is 
emitted in the header code for server.
+
+Client-side Output
+******************
+
+Client-side Source Code
+~~~~~~~~~~~~~~~~~~~~~~~
+::
+
+   lldb_rpc::SBTarget lldb_rpc::SBDebugger::CreateTarget(const char * 
filename) {
+   // 1) Perform setup
+   // Storage for return value
+   lldb_rpc::SBTarget __result = {};
+   // Deriving connection from this.
+   rpc_common::ConnectionSP connection_sp = ObjectRefGetConnectionSP();
+   if (!connection_sp) return __result;
+   // RPC Communication setup
+   static RPCFunctionInfo g_func("_ZN4lldb10SBDebugger12CreateTargetEPKc");
+   RPCStream send;
+   RPCStream response;
+   g_func.Encode(send);
+   RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, *this);
+   RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, filename);
+   // 2) Send RPC call
+   if (!connection_sp->SendRPCCallAndWaitForResponse(send, response))
+   return __result;
+   // 3) Decode return values
+   RPCValueDecoder(response, rpc_common::RPCPacket::ValueType::ReturnValue, 
__result);
+   return __result;
+   }
+
+Function signature
+~~~~~~~~~~~~~~~~~~
+
+For the client-side sources, the function signature is almost always identical 
to that of what the
+signature looks like in the main SB API, with the namespace changing from 
``lldb`` to ``lldb_rpc``. For some methods, the function signature might need 
to change to prepend an RPC connection as the first argument. This happens in 
the event that the function is static. Since RPC functions usually derive their 
connection from their instance, static functions must be given a connection as 
they have no instance to derive one from.
+
+Return Value Storage
+~~~~~~~~~~~~~~~~~~~~
+
+We first need to create storage for the return value. For this method we 
return an ``SBTarget``, so we need to create an ``lldb_rpc::SBTarget`` for the 
return value and initialize it to an empty value. Since we are on the 
client-side, all instances where we use SB API classes will be from the 
``lldb_rpc`` namespace.
+
+Obtaining RPC Connection
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+We then need to obtain the RPC connection. In this case, we obtain the 
connection by deriving from the ``SBDebugger`` instance that would've been 
created prior to this function call by using ``ObjectRefGetConnectionSP()``. If 
this connection is invalid then we return an empty value.
+
+Encoding RPC Stream Information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Using the mangled function name for the function information, we encode the 
streams used to send the function's parameters and receive the return info from 
the server-side call. For this function, we need to encode the pointer to the 
``SBDebugger()`` instance itself, and the ``const char *filename*`` parameter 
as well.
+
+Sending Encoded Information to Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once the information has been encoded, ``SendRPCCallAndWaitForResponse(send, 
response)`` is used to send the information to the server-side, where the 
underlying call to ``lldb::SBDebugger::CreateTarget(const char **)`` will be 
made. If this call failed, then an empty value will be returned.
+
+Decoding Information Received from Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the call to send the encoded information was successful, then we need to 
decode what the server sent in response. This decoded value is then placed in 
the return value storage that we set up earlier and is then returned to the 
connected client itself.
+
+Header Code Output
+------------------
+
+::
+   lldb_rpc::SBTarget CreateTarget(const char * filename);
+
+The output for the header code on the client-side is significantly simpler. 
Similar to the function signature for the client-side sources, the header file 
output is just the function signature for each method. This means that it will 
match the original SB API function signature, with the only exceptions being 
for static methods as stated above for the source code function signature.
+
+``lldb-rpc-gen`` emitters
+*****************************
+
+The bulk of the code is generated using the emitters. For the server side, we 
have ``RPCServerSourceEmitter`` and ``RPCServerHeaderEmitter``. The former 
handles generation of the source code and the latter handles generation of the 
header code seen above. Similarly for the client side, we have 
``RPCLibrarySourceEmitter`` and ``RPCLibraryHeaderEmitter``. Similar the server 
emitters, these emitter handle generating the source and header code for the 
client side, respectively.
+
+Emitters largely have similar structure. Constituent sections of code, such as 
function headers, function bodies and others are typically given their own 
method. As an example, the function to emit a function header is 
``EmitFunctionHeader()`` and the function to emit a function body is 
``EmitFunctionBody()``. Information that will be written to the output file is 
written using ``EmitLine()``, which uses ``llvm::raw_string_ostreams`` and is 
defined in ``RPCCommon.h``.
+
+Since this is a ``ClangTool``, information about each method is obtained from 
Clang itself and stored in the ``Method`` struct located in ``RPCCommon.h`` in 
``lldb-rpc-gen``'s directory. ``Method`` is used for simplicity and 
abstraction, and other information that would be needed from the SB API is 
obtained from Clang directly.
+
+Testing
+*******
+
+The RPC interface and the code emitters are tested in 2 main ways:
+
+- The RPC client and server interfaces are tested by running the full LLDB SB 
API test suite against liblldbrpc. Using this, Python acts as the client binary 
connecting to RPC, and all SB API calls from API tests will go through the RPC 
client/server flow as described above. This has its own ninja target, 
``check-lldb-rpc``.
+- The RPC client and server emitters are tested using shell tests where 
FileCheck checks the output of the emitters against a set of heuristics that we 
have. Currently, these shell tests exist for the client side emitters as they 
have more heuristics than the server-side emitters.

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

Reply via email to