Skip to content

[lldb] Upstream lldb-rpc-gen and LLDB RPC server-side emitters #136748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

chelcassanova
Copy link
Contributor

This commit upstreams lldb-rpc-gen, a tool that emits the client and server interfaces used for LLDB RPC. This is the initial commit in the upstreaming process for LLDB RPC. lldb-rpc-gen is a ClangTool that reads the LLDB SB API headers and uses their information to generate the interfaces for RPC. This commit specifically adds the server-side emitters for easier review. The client-side interface will be added in a later commit.

RFC: https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804

@llvmbot
Copy link
Member

llvmbot commented Apr 22, 2025

@llvm/pr-subscribers-lldb

Author: Chelsea Cassanova (chelcassanova)

Changes

This commit upstreams lldb-rpc-gen, a tool that emits the client and server interfaces used for LLDB RPC. This is the initial commit in the upstreaming process for LLDB RPC. lldb-rpc-gen is a ClangTool that reads the LLDB SB API headers and uses their information to generate the interfaces for RPC. This commit specifically adds the server-side emitters for easier review. The client-side interface will be added in a later commit.

RFC: https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804


Patch is 92.84 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/136748.diff

15 Files Affected:

  • (added) lldb/scripts/convert-lldb-header-to-rpc-header.py (+50)
  • (added) lldb/scripts/framework-header-include-fix.py (+30)
  • (added) lldb/scripts/framework-header-version-fix.py (+38)
  • (modified) lldb/tools/CMakeLists.txt (+2)
  • (added) lldb/tools/lldb-rpc/CMakeLists.txt (+19)
  • (added) lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake (+95)
  • (added) lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake (+119)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt (+23)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp (+532)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h (+105)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.cpp (+73)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.h (+46)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.cpp (+589)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.h (+80)
  • (added) lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp (+540)
diff --git a/lldb/scripts/convert-lldb-header-to-rpc-header.py b/lldb/scripts/convert-lldb-header-to-rpc-header.py
new file mode 100755
index 0000000000000..bf1eb526e8b3a
--- /dev/null
+++ b/lldb/scripts/convert-lldb-header-to-rpc-header.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+# Usage: convert-lldb-header-to-rpc-header.py <path/to/input-header.h> <path/to/output-header.h>
+
+import argparse
+import os
+import re
+import subprocess
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("input")
+    parser.add_argument("output")
+    args = parser.parse_args()
+    input_path = str(args.input)
+    output_path = str(args.output)
+    with open(input_path, "r+") as input_file:
+        lines = input_file.readlines()
+
+    with open(output_path, "w+") as output_file:
+        for line in lines:
+            # NOTE: We do not use lldb-forward or lldb-versioning in RPC.
+            if re.match(r'#include "lldb/lldb-forward|#include "lldb/lldb-versioning', line):
+                continue
+            # For lldb-defines.h
+            elif re.match(r'.+LLDB_LLDB_', line):
+                output_file.write(re.sub(r'LLDB_LLDB_', r'LLDB_RPC_', line))
+            # For SBDefines.h
+            elif re.match(r'.+LLDB_API_', line):
+                output_file.write(re.sub(r'LLDB_API_', r'LLDB_RPC_API_', line))
+            # For lldb-rpc-version.h and lldb-rpc-defines.h
+            elif re.match(r'.+LLDB_VERSION', line):
+                output_file.write(re.sub(r'LLDB_VERSION', r'LLDB_RPC_VERSION', line))
+            elif re.match(r'.+LLDB_REVISION', line):
+                output_file.write(re.sub(r'LLDB_REVISION', r'LLDB_RPC_REVISION', line))
+            elif re.match(r'.+LLDB_VERSION_STRING', line):
+                output_file.write(re.sub(r'LLDB_VERSION_STRING', r'LLDB_RPC_VERSION_STRING', line))
+            # For local #includes
+            elif re.match(r'#include "lldb/lldb-', line):
+                output_file.write(re.sub(r'lldb/lldb-', r'lldb-rpc-', line))
+            # Rename the lldb namespace to lldb-rpc
+            elif re.match(r'namespace lldb', line):
+                output_file.write(re.sub(r'lldb', r'lldb_rpc', line))
+            # Rename namespace references
+            elif re.match(r'.+lldb::', line):
+                output_file.write(re.sub(r'lldb::', r'lldb_rpc::', line))
+            else:
+                # Write any line that doesn't need to be converted
+                output_file.write(line)
+if __name__ == "__main__":
+    main()
diff --git a/lldb/scripts/framework-header-include-fix.py b/lldb/scripts/framework-header-include-fix.py
new file mode 100755
index 0000000000000..fcca7f31b2954
--- /dev/null
+++ b/lldb/scripts/framework-header-include-fix.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+# Usage: framework-header-include-fix.py <path/to/input-header.h> <path/to/output-header.h>
+
+import argparse
+import os
+import re
+import subprocess
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("input")
+    parser.add_argument("output")
+    args = parser.parse_args()
+    input_path = str(args.input)
+    output_path = str(args.output)
+    with open(input_path, "r+") as input_file:
+        lines = input_file.readlines()
+
+    with open(output_path, "w+") as output_file:
+        for line in lines:
+            if re.match(r'.+<lldb-rpc/common', line):
+                output_file.write(re.sub(r'<lldb-rpc/common', r'<LLDBRPC', line))
+            elif re.match(r'#include "(.*)"', line):
+                include_filename = re.search(r'#include "(.*)"', line).groups()[0]
+                output_file.write(re.sub(r'#include "(.*)"', r'#include <LLDBRPC/' + include_filename + '>', line))
+            else:
+                # Write any line that doesn't need to be converted
+                output_file.write(line)
+if __name__ == "__main__":
+    main()
diff --git a/lldb/scripts/framework-header-version-fix.py b/lldb/scripts/framework-header-version-fix.py
new file mode 100755
index 0000000000000..d2417ea4f9414
--- /dev/null
+++ b/lldb/scripts/framework-header-version-fix.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# Usage: framework-header-version-fix.py <path/to/input-header.h> <path/to/output-header.h> MAJOR MINOR PATCH
+
+import argparse
+import os
+import re
+import subprocess
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("input")
+    parser.add_argument("output")
+    parser.add_argument("lldb_version_major")
+    parser.add_argument("lldb_version_minor")
+    parser.add_argument("lldb_version_patch")
+    args = parser.parse_args()
+    input_path = str(args.input)
+    output_path = str(args.output)
+    lldb_version_major = str(args.lldb_version_major)
+    lldb_version_minor = str(args.lldb_version_minor)
+    lldb_version_patch = str(args.lldb_version_patch)
+
+    with open(input_path, "r+") as input_file:
+        lines = input_file.readlines()
+
+    with open(output_path, "w+") as output_file:
+        for line in lines:
+            if re.match(r'//#define LLDB_RPC_VERSION$', line):
+                output_file.write(re.sub(r'//#define LLDB_RPC_VERSION', r'#define LLDB_RPC_VERSION ' + lldb_version_major, line))
+            elif re.match(r'//#define LLDB_RPC_REVISION$', line):
+                output_file.write(re.sub(r'//#define LLDB_RPC_REVISION', r'#define LLDB_RPC_REVISION ' + lldb_version_minor, line))
+            elif re.match(r'//#define LLDB_RPC_VERSION_STRING$', line):
+                output_file.write(re.sub(r'//#define LLDB_RPC_VERSION_STRING', r'#define LLDB_RPC_VERSION_STRING "{0}.{1}.{2}"'.format(lldb_version_major, lldb_version_minor, lldb_version_patch), line))
+            else:
+                # Write any line that doesn't need to be converted
+                output_file.write(line)
+if __name__ == "__main__":
+    main()
diff --git a/lldb/tools/CMakeLists.txt b/lldb/tools/CMakeLists.txt
index 6804dc234555b..6a2859d042148 100644
--- a/lldb/tools/CMakeLists.txt
+++ b/lldb/tools/CMakeLists.txt
@@ -7,6 +7,8 @@ add_subdirectory(intel-features)
 # example is `check-lldb`. So, we pass EXCLUDE_FROM_ALL here.
 add_subdirectory(lldb-test EXCLUDE_FROM_ALL)
 add_subdirectory(lldb-fuzzer EXCLUDE_FROM_ALL)
+add_subdirectory(lldb-rpc EXCLUDE_FROM_ALL)
+
 
 add_lldb_tool_subdirectory(lldb-instr)
 add_lldb_tool_subdirectory(lldb-dap)
diff --git a/lldb/tools/lldb-rpc/CMakeLists.txt b/lldb/tools/lldb-rpc/CMakeLists.txt
new file mode 100644
index 0000000000000..ab1e8517b5909
--- /dev/null
+++ b/lldb/tools/lldb-rpc/CMakeLists.txt
@@ -0,0 +1,19 @@
+if(LLDB_CODESIGN_IDENTITY)
+  # Use explicit LLDB identity
+  set(LLVM_CODESIGNING_IDENTITY ${LLDB_CODESIGN_IDENTITY})
+else()
+  # Use explicit LLVM identity or default to ad-hoc signing if empty
+  if(NOT LLVM_CODESIGNING_IDENTITY)
+    set(LLVM_CODESIGNING_IDENTITY -)
+  endif()
+endif()
+
+check_cxx_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments"
+  CXX_SUPPORTS_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS)
+
+# LLDBRPCGeneration.cmake needs the LLDB_RPC_GEN_EXE variable
+# which gets defined in the lldb-rpc-gen folder, so we're adding
+# this folder before we add that file.
+add_lldb_tool_subdirectory(lldb-rpc-gen)
+include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCGeneration.cmake)
+include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCHeaders.cmake)
diff --git a/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake
new file mode 100644
index 0000000000000..c0a041f3e96b7
--- /dev/null
+++ b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake
@@ -0,0 +1,95 @@
+if (NOT DEFINED LLDB_RPC_GEN_EXE)
+  message(FATAL_ERROR
+    "Unable to generate lldb-rpc sources because LLDB_RPC_GEN_EXE is not
+    defined. If you are cross-compiling, please build lldb-rpc-gen for your host
+    platform.")
+endif()
+set(lldb_rpc_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
+set(lldb_rpc_server_generated_source_dir "${lldb_rpc_generated_dir}/server")
+set(lldb_rpc_lib_generated_source_dir "${lldb_rpc_generated_dir}/lib")
+set(lldb_rpc_client_generated_source_dir "${lldb_rpc_generated_dir}/client")
+
+file(GLOB api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SB*.h)
+# We don't generate SBCommunication
+list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBCommunication.h)
+# SBDefines.h is mostly definitions and forward declarations, nothing to
+# generate.
+list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBDefines.h)
+
+# Generate the list of byproducts. Note that we cannot just glob the files in
+# the directory with the generated sources because BYPRODUCTS needs to be known
+# at configure time but the files are generated at build time.
+set(lldb_rpc_gen_byproducts
+  ${lldb_rpc_generated_dir}/SBClasses.def
+  ${lldb_rpc_generated_dir}/SBAPI.def
+  ${lldb_rpc_generated_dir}/lldb.py
+  ${lldb_rpc_server_generated_source_dir}/SBAPI.h
+  ${lldb_rpc_lib_generated_source_dir}/LLDBRPC.h
+)
+
+set(lldb_rpc_gen_server_impl_files)
+set(lldb_rpc_gen_lib_header_files)
+set(lldb_rpc_gen_lib_impl_files)
+foreach(path ${api_headers})
+  get_filename_component(filename_no_ext ${path} NAME_WLE)
+
+  set(server_header_file "Server_${filename_no_ext}.h")
+  list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_header_file}")
+
+  set(server_impl_file "Server_${filename_no_ext}.cpp")
+  list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_impl_file}")
+  list(APPEND lldb_rpc_gen_server_impl_files "${lldb_rpc_server_generated_source_dir}/${server_impl_file}")
+
+  set(lib_header_file "${filename_no_ext}.h")
+  list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_lib_generated_source_dir}/${lib_header_file}")
+  list(APPEND lldb_rpc_gen_lib_header_files "${lldb_rpc_lib_generated_source_dir}/${lib_header_file}")
+
+  set(lib_impl_file "${filename_no_ext}.cpp")
+  list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_lib_generated_source_dir}/${lib_impl_file}")
+  list(APPEND lldb_rpc_gen_lib_impl_files "${lldb_rpc_lib_generated_source_dir}/${lib_impl_file}")
+endforeach()
+list(APPEND lldb_rpc_gen_lib_impl_files "${CMAKE_CURRENT_BINARY_DIR}/generated/lib/RPCClientSideCallbacks.cpp")
+list(APPEND lldb_rpc_gen_lib_header_files "${lldb_rpc_lib_generated_source_dir}/LLDBRPC.h")
+
+# Make sure that the clang-resource-dir is set correctly or else the tool will
+# fail to run. This is only needed when we do a standalone build.
+set(clang_resource_dir_arg)
+if (LLDB_BUILT_STANDALONE)
+  if (TARGET clang-resource-headers)
+    set(clang_resource_headers_dir
+      $<TARGET_PROPERTY:clang-resource-headers,INTERFACE_INCLUDE_DIRECTORIES>)
+    set(clang_resource_dir_arg --extra-arg="-resource-dir=${clang_resource_headers_dir}/..")
+  else()
+    set(clang_resource_dir_arg --extra-arg="-resource-dir=${LLDB_EXTERNAL_CLANG_RESOURCE_DIR}")
+  endif()
+endif()
+
+add_custom_command(OUTPUT ${lldb_rpc_gen_byproducts}
+  COMMAND ${CMAKE_COMMAND} -E make_directory
+    ${lldb_rpc_generated_dir}
+
+  COMMAND ${CMAKE_COMMAND} -E make_directory
+    ${lldb_rpc_server_generated_source_dir}
+
+  COMMAND ${CMAKE_COMMAND} -E make_directory
+    ${lldb_rpc_lib_generated_source_dir}
+
+  COMMAND ${CMAKE_COMMAND} -E make_directory
+    ${lldb_rpc_client_generated_source_dir}
+
+  COMMAND ${LLDB_RPC_GEN_EXE}
+    -p ${CMAKE_BINARY_DIR}
+    --output-dir=${lldb_rpc_generated_dir}
+    ${clang_resource_dir_arg}
+    --extra-arg="-USWIG"
+    ${api_headers}
+
+  DEPENDS ${LLDB_RPC_GEN_EXE} ${api_headers}
+  COMMENT "Generating sources for lldb-rpc-server..."
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
+add_custom_target(lldb-rpc-generate-sources
+  DEPENDS
+    ${lldb_rpc_gen_byproducts}
+    lldb-sbapi-dwarf-enums)
diff --git a/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake
new file mode 100644
index 0000000000000..7a066caceb9cb
--- /dev/null
+++ b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake
@@ -0,0 +1,119 @@
+set(derived_headers_location "${CMAKE_CURRENT_BINARY_DIR}/DerivedHeaders")
+set(original_headers_location "${LLDB_SOURCE_DIR}/include/lldb")
+set(headers_to_process
+  API/SBDefines.h
+  lldb-defines.h
+  lldb-enumerations.h
+  lldb-types.h
+)
+
+file(MAKE_DIRECTORY ${derived_headers_location})
+
+set(original_headers)
+set(derived_headers)
+foreach(header ${headers_to_process})
+  set(original_header "${original_headers_location}/${header}")
+
+  get_filename_component(header_filename ${header} NAME)
+  string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}")
+  set(derived_header "${derived_headers_location}/${rpc_header_filename}")
+
+  list(APPEND original_headers "${original_header}")
+  list(APPEND derived_headers "${derived_header}")
+  add_custom_command(OUTPUT ${derived_header}
+    COMMAND ${CMAKE_COMMAND} -E copy ${original_header} ${derived_header}
+    COMMAND ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.sh
+            ${derived_header}
+    DEPENDS ${original_header}
+
+    COMMENT "Creating ${derived_header}"
+  )
+endforeach()
+
+set(generated_headers_to_process
+  API/SBLanguages.h
+)
+foreach(header ${generated_headers_to_process})
+  set(original_header "${LLDB_OBJ_DIR}/include/lldb/${header}")
+
+  get_filename_component(header_filename ${header} NAME)
+  string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}")
+  set(derived_header "${derived_headers_location}/${rpc_header_filename}")
+
+  list(APPEND original_headers "${original_header}")
+  list(APPEND derived_headers "${derived_header}")
+  add_custom_command(OUTPUT ${derived_header}
+    COMMAND ${CMAKE_COMMAND} -E copy ${original_header} ${derived_header}
+    COMMAND ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.sh
+            ${derived_header}
+    DEPENDS lldb-sbapi-dwarf-enums
+
+    COMMENT "Creating ${derived_header}"
+  )
+endforeach()
+
+
+add_custom_target(copy-aux-rpc-headers DEPENDS ${derived_headers})
+
+set(public_headers ${lldb_rpc_gen_lib_header_files})
+list(APPEND public_headers
+  ${derived_headers_location}/SBDefines.h
+  ${derived_headers_location}/SBLanguages.h
+  ${derived_headers_location}/lldb-rpc-enumerations.h
+  ${derived_headers_location}/lldb-rpc-types.h
+)
+
+# Collect and preprocess headers for the framework bundle
+set(version_header
+  ${derived_headers_location}/lldb-rpc-defines.h
+)
+
+function(FixIncludePaths in subfolder out)
+  get_filename_component(base_name ${in} NAME)
+  set(parked_header ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders/${subfolder}/${base_name})
+  set(${out} ${parked_header} PARENT_SCOPE)
+
+  add_custom_command(OUTPUT ${parked_header}
+    COMMAND ${CMAKE_COMMAND} -E copy
+            ${in} ${parked_header}
+    COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-include-fix.sh
+            ${parked_header}
+    DEPENDS ${in}
+    COMMENT "Fixing includes in ${in}"
+  )
+endfunction()
+
+function(FixVersions in subfolder out)
+  get_filename_component(base_name ${in} NAME)
+  set(parked_header ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders/${subfolder}/${base_name})
+  set(${out} ${parked_header} PARENT_SCOPE)
+
+  add_custom_command(OUTPUT ${parked_header}
+    COMMAND ${CMAKE_COMMAND} -E copy ${in} ${parked_header}
+    COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-version-fix.sh
+            ${parked_header} ${LLDB_VERSION_MAJOR} ${LLDB_VERSION_MINOR} ${LLDB_VERSION_PATCH}
+    DEPENDS ${in}
+    COMMENT "Fixing versions in ${liblldbrpc_version_header}"
+  )
+endfunction()
+
+set(preprocessed_headers)
+
+# Apply include-paths fix on all headers and park them.
+foreach(source_header ${public_headers})
+  FixIncludePaths(${source_header} Headers parked_header)
+  list(APPEND preprocessed_headers ${parked_header})
+endforeach()
+
+# Apply include-paths fix and stage in parent directory.
+# Then apply version fix and park together with all the others.
+FixIncludePaths(${version_header} ".." staged_header)
+FixVersions(${staged_header} Headers parked_header)
+list(APPEND preprocessed_headers ${parked_header})
+
+# Wrap header preprocessing in a target, so liblldbrpc can depend on.
+add_custom_target(liblldbrpc-headers DEPENDS ${preprocessed_headers})
+add_dependencies(liblldbrpc-headers copy-aux-rpc-headers)
+set_target_properties(liblldbrpc-headers PROPERTIES
+  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders
+)
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt
new file mode 100644
index 0000000000000..bc1be9865e159
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt
@@ -0,0 +1,23 @@
+add_lldb_tool(lldb-rpc-gen
+    RPCCommon.cpp
+    RPCServerHeaderEmitter.cpp
+    RPCServerSourceEmitter.cpp
+    lldb-rpc-gen.cpp
+
+    CLANG_LIBS
+      clangAST
+      clangBasic
+      clangCodeGen
+      clangFrontend
+      clangLex
+      clangRewrite
+      clangSerialization
+      clangTooling
+
+    LINK_COMPONENTS
+      Support
+  )
+
+if (NOT DEFINED LLDB_RPC_GEN_EXE)
+  set(LLDB_RPC_GEN_EXE $<TARGET_FILE:lldb-rpc-gen> CACHE STRING "Executable that generates lldb-rpc-server")
+endif()
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp
new file mode 100644
index 0000000000000..821386644b548
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp
@@ -0,0 +1,532 @@
+//===-- RPCCommon.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 "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "clang/AST/Mangle.h"
+#include "clang/Lex/Lexer.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+
+// We intentionally do not generate some classes because they are currently
+// inconvenient, they aren't really used by most consumers, or we're not sure
+// why they exist.
+static constexpr llvm::StringRef DisallowedClasses[] = {
+    "SBCommunication",          // What is this used for?
+    "SBInputReader",            // What is this used for?
+    "SBCommandPluginInterface", // This is hard to support, we can do it if
+                                // really needed though.
+    "SBCommand", // There's nothing too difficult about this one, but many of
+                 // its methods take a SBCommandPluginInterface pointer so
+                 // there's no reason to support this.
+};
+
+// We intentionally avoid generating certain methods either because they are
+// difficult to support correctly or they aren't really used much from C++.
+// FIXME: We should be able to annotate these methods instead of maintaining a
+// list in the generator itself.
+static constexpr llvm::StringRef DisallowedMethods[] = {
+    // The threading functionality in SBHostOS is deprecated and thus we do not
+    // generate them. It would be ideal to add the annotations to the methods
+    // and then support not generating deprecated methods. However, without
+    // annotations the generator generates most things correctly. This one is
+    // problematic because it returns a pointer to an "opaque" structure
+    // (thread_t) that is not `void *`, so special casing it is more effort than
+    // it's worth.
+    "_ZN4lldb8SBHostOS10ThreadJoinEP17_opaque_pthread_tPPvPNS_7SBErrorE",
+    "_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE",
+    "_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE",
+    "_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE",
+    "_ZN4lldb8SBHostOS13ThreadCreatedEPKc",
+};
+
+static constexpr llvm::StringRef ClassesWithoutDefaultCtor[] = {
+    "SBHostOS",
+    "SBReproducer",
+};
+
+static constexpr llvm::StringRef ClassesWithoutCopyOperations[] = {
+    "SBHostOS",
+    "SBReproducer",
+    "SBStream",
+    "SBProgress",
+};
+
+static constexpr llvm::StringRef MethodsWithPointerPlusLen[] = {
+    "_ZN4lldb6SBData11ReadRawDataERNS_7SBErrorEyPvm",
+    "_ZN4lldb6SBData7SetDataERNS_7SBErrorEPKvmNS_9ByteOrderEh",
+    "_ZN4lldb6SBData20SetDataWithOwnershipERNS_7SBErrorEPKvmNS_9ByteOrderEh",
+    "_ZN...
[truncated]

Copy link

github-actions bot commented Apr 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link

github-actions bot commented Apr 22, 2025

✅ With the latest revision this PR passed the Python code formatter.

@chelcassanova chelcassanova force-pushed the lldb-rpc/upstream-lldb-rpc-gen branch 2 times, most recently from ded625a to dd04b9d Compare April 24, 2025 16:16
@chelcassanova chelcassanova changed the title [DRAFT][lldb] Upstream lldb-rpc-gen and LLDB RPC server-side emitters [lldb] Upstream lldb-rpc-gen and LLDB RPC server-side emitters Apr 24, 2025
@chelcassanova chelcassanova force-pushed the lldb-rpc/upstream-lldb-rpc-gen branch from dd04b9d to 28266f9 Compare April 24, 2025 16:18
@DavidSpickett
Copy link
Collaborator

A few failures in Linux CI. All are like:

CheckArrayPointer.test.script: line 3: fg: no job control

@@ -0,0 +1,13 @@
// Skipping temporarily due to rdar://149500008
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a great sign that all the client tests are disabled on the system that they're originally intended for.

What exactly is the issue here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably just an artifact of the downstream fork, I believe @chelcassanova has a fix for this already

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that the tests need the clang resource dir in order to run properly (since lldb-rpc-gen is a ClangTool). They were originally skipped so that I could add that dir to LLDB lit here: #136761

Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot to deal with. So I have a bunch of nits on the first few files but my lack of overall understanding of the process meant I can't assess 1000s of lines of C++ implementing it.

I know you explained the mechanics of this in the RFC, but, I think we should have a page on the website about it eventually anyway, and a design doc would really help review this too.

So could you draft something like that? Separate PR from this.

For now I'd just be looking for:

  • The use case of LLDB-RPC
  • The steps taken to generate the components of it, and what they should produce.

That can remain in review while you upstream the rest of the code, and then we can think of any other sections to add before landing it. E.g. how to maintain LLDB-RPC and so on.

args = parser.parse_args()
input_path = str(args.input)
output_path = str(args.output)
with open(input_path, "r+") as input_file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could/should this be opened read only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be read only, the general pattern that I'm doing with these scripts is to readlines() the input file and make all changes in the output file so I shouldn't need to write to the input file here.

with open(input_path, "r+") as input_file:
lines = input_file.readlines()

with open(output_path, "w+") as output_file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be opened for writing only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll change this.


with open(output_path, "w+") as output_file:
for line in lines:
# NOTE: We do not use lldb-forward or lldb-versioning in RPC.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are headers, right? If so:

# NOTE: We do not use lldb-forward.h or lldb-versioning.h in RPC.

For clarity.

output_path = str(args.output)
with open(input_path, "r+") as input_file:
lines = input_file.readlines()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment stating the purpose of this whole loop. It's replacing things, a lot of things, but it's hard to see the overall picture.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a comment at the top of the script stating what it does would be good. The usage is nice, but not enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add this, ditto to adding comments for what each of these scripts do.

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
# Usage: framework-header-include-fix.py <path/to/input-header.h> <path/to/output-header.h>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a short description of what this script does. Mainly, what is it fixing specifically?

args = parser.parse_args()
input_path = str(args.input)
output_path = str(args.output)
with open(input_path, "r+") as input_file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See if you need r+ or would r do. Same for w+ below.

endif()
endif()

check_cxx_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this used for / supposed to be used for? Because you check the compiler flag works but then don't appear to do anything with it.

Is something later looking for CXX_SUPPORTS_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CXX_SUPPORTS_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS variable is used later on in files that haven't been upstreamed yet (If this variable is set then the -Wno-gnu-zero-variadic-macro-arguments gets added as a compile option). For simplicity's sake, this line could be removed until those files are upstreamed.

// why they exist.
static constexpr llvm::StringRef DisallowedClasses[] = {
"SBCommunication", // What is this used for?
"SBInputReader", // What is this used for?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now this is upstream, can we answer these questions? I have zero idea either :)

"_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE",
"_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE",
"_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE",
"_ZN4lldb8SBHostOS13ThreadCreatedEPKc",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these mangled names, is the mangling stable and how stable across compilers and platforms? Is GCC on Linux going to give different results for example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that the mangling should be stable on platforms using Clang. Since this is a ClangTool itself, I'm actually currently unsure as to how this would compile using GCC.

By the way, we can continue this line of discussion on one of the smaller patches I put up to upstream this tool and its shared common code: #138031

"SBValueList",
};

static llvm::StringMap<llvm::SmallVector<llvm::StringRef>>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const

@chelcassanova
Copy link
Contributor Author

So could you draft something like that? Separate PR from this.

Absolutely! I know this is a lot of code to review, so anything that can be done to break all this down is more than something I'm willing to do. I can write up a design doc in order to better explain exactly what you're looking at here.

@chelcassanova chelcassanova force-pushed the lldb-rpc/upstream-lldb-rpc-gen branch from 28266f9 to ec76377 Compare April 29, 2025 22:12
This commit upstreams lldb-rpc-gen, a tool that emits the client and
server interfaces used for LLDB RPC. This is the initial commit in the
upstreaming process for LLDB RPC. lldb-rpc-gen is a ClangTool that reads
the LLDB SB API headers and uses their information to generate the
interfaces for RPC. This commit specifically adds the server-side
emitters for easier review. The client-side interface will be added in a
later commit.

RFC: https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804
@chelcassanova chelcassanova force-pushed the lldb-rpc/upstream-lldb-rpc-gen branch from ec76377 to 9ab6fdf Compare April 29, 2025 22:35
@chelcassanova
Copy link
Contributor Author

Looking at this patch again, I think this can actually be split into 4 patches:

  • One that just has the Python scripts
  • One that has the shell tests
  • One with the emitters (RPCServerSourceEmitter and RPCServerHeaderEmitter)
  • One with the lldb-rpc-gen tool and the RPC common code for lldb-rpc-gen

That should make this easier to review. @DavidSpickett I can still draft up a design doc, but for the sake of readability I'm also going to split this patch up as well.

@chelcassanova
Copy link
Contributor Author

I split this large patch up into 3 smaller ones:
This patch upstreams the lldb-rpc-gen tool itself as well as its common code: #138031
This upstreams the server-side emitter: #138032
This upstreams the Python scripts used to modify headers: #138028.

This current patch itself is very very large, so I'll close it and let discussion continue on the above 3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants