From 2675e9251a675ff0e80b9834c2819aac7e18f9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20L=C3=BCcke?= Date: Tue, 5 May 2026 12:14:43 +0200 Subject: [PATCH] [Comgr][hotswap] Add bare-bones hotswap transpiler scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lands the minimal scaffolding needed to build a `hotswap-transpiler` static library: * `raiser.{hpp,cpp}` — `raiseToIR()` entry point. The implementation creates an `llvm::Module` with a single `ret void` AMDGPU kernel function for any input. ELF ingestion, MC disassembly and the per-format raise handlers land in subsequent patches. * `raise_failure.{hpp,cpp}` — structured failure values consumed by `RaiseResult::failure`. * `code_object_utils.hpp` — `KernelMeta` struct used in the `raiseToIR` signature. * Minimal `CMakeLists.txt` linking only `LLVMCore`, `LLVMSupport`, `LLVMTargetParser`. The full library/include surface grows incrementally as later patches need it. * `tests/raiser_empty_module_test.cpp` (gtest) verifies the scaffolding contract: an empty input produces a well-formed module with one `AMDGPU_KERNEL` function whose body is `ret void`. Co-Authored-By: Claude Opus 4.7 (1M context) --- amd/comgr/hotswap/CMakeLists.txt | 79 +++++++++++++++++++ amd/comgr/hotswap/README.md | 19 +++++ amd/comgr/hotswap/code_object_utils.hpp | 48 +++++++++++ amd/comgr/hotswap/raise_failure.cpp | 13 +++ amd/comgr/hotswap/raise_failure.hpp | 29 +++++++ amd/comgr/hotswap/raiser.cpp | 49 ++++++++++++ amd/comgr/hotswap/raiser.hpp | 45 +++++++++++ .../tests/raiser_empty_module_test.cpp | 37 +++++++++ amd/comgr/hotswap/tests/test_main.cpp | 6 ++ 9 files changed, 325 insertions(+) create mode 100644 amd/comgr/hotswap/CMakeLists.txt create mode 100644 amd/comgr/hotswap/README.md create mode 100644 amd/comgr/hotswap/code_object_utils.hpp create mode 100644 amd/comgr/hotswap/raise_failure.cpp create mode 100644 amd/comgr/hotswap/raise_failure.hpp create mode 100644 amd/comgr/hotswap/raiser.cpp create mode 100644 amd/comgr/hotswap/raiser.hpp create mode 100644 amd/comgr/hotswap/tests/raiser_empty_module_test.cpp create mode 100644 amd/comgr/hotswap/tests/test_main.cpp diff --git a/amd/comgr/hotswap/CMakeLists.txt b/amd/comgr/hotswap/CMakeLists.txt new file mode 100644 index 0000000000000..49ce24ed0a4b9 --- /dev/null +++ b/amd/comgr/hotswap/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.20) +project(hotswap-transpiler LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# When this CMakeLists is the top-level project we build the gtest-based +# transpiler_tests binary. When pulled in by a parent project (e.g. COMGR) +# via add_subdirectory we only build the library. +if(NOT DEFINED HOTSWAP_TRANSPILER_BUILD_TOOLS) + if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(HOTSWAP_TRANSPILER_BUILD_TOOLS ON) + else() + set(HOTSWAP_TRANSPILER_BUILD_TOOLS OFF) + endif() +endif() + +# --- LLVM --------------------------------------------------------------------- +if(NOT TARGET LLVMSupport) + find_package(LLVM REQUIRED CONFIG) + list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") + include(AddLLVM) +endif() + +include_directories(${LLVM_INCLUDE_DIRS}) +link_directories(${LLVM_LIBRARY_DIRS}) +add_definitions(${LLVM_DEFINITIONS}) + +# --- Library ------------------------------------------------------------------ +add_library(hotswap-transpiler STATIC + raiser.cpp + raise_failure.cpp +) + +if(NOT TARGET hotswap::transpiler) + add_library(hotswap::transpiler ALIAS hotswap-transpiler) +endif() + +# Match LLVM's compile flags (no-rtti, no-exceptions) — every LLVM type we +# consume relies on LLVM's hand-rolled RTTI rather than C++ typeid; mixing +# RTTI-on TUs with RTTI-off LLVM libs produces undefined-reference errors +# for `typeinfo` symbols on any class with a virtual method. +llvm_update_compile_flags(hotswap-transpiler) + +set_target_properties(hotswap-transpiler PROPERTIES POSITION_INDEPENDENT_CODE ON) + +# Public include root so consumers can `#include "hotswap/raiser.hpp"`. +target_include_directories(hotswap-transpiler PUBLIC + $ +) + +target_link_libraries(hotswap-transpiler + PUBLIC + LLVMCore + LLVMSupport + LLVMTargetParser +) + +# --- Tests -------------------------------------------------------------------- +if(HOTSWAP_TRANSPILER_BUILD_TOOLS) + find_package(GTest REQUIRED) + enable_testing() + include(GoogleTest) + + add_executable(transpiler_tests + tests/test_main.cpp + tests/raiser_empty_module_test.cpp + ) + llvm_update_compile_flags(transpiler_tests) + target_link_libraries(transpiler_tests + PRIVATE + hotswap-transpiler + GTest::GTest + ) + gtest_discover_tests(transpiler_tests + PROPERTIES TIMEOUT 60 LABELS "transpiler" + ) +endif() diff --git a/amd/comgr/hotswap/README.md b/amd/comgr/hotswap/README.md new file mode 100644 index 0000000000000..2b4d4d2bfa9f3 --- /dev/null +++ b/amd/comgr/hotswap/README.md @@ -0,0 +1,19 @@ +# Hotswap Transpiler + +The hotswap transpiler raises AMDGPU code objects into LLVM IR, re-lowers +them through the stock AMDGPU backend for a different target ISA, and +relinks the result into a single merged HSACO. It is a sibling to the +byte-level `amd_comgr_hotswap_rewrite` API: where rewrite applies a small +set of stepping-specific patches in place, transpilation hands the entire +code object to the IR pipeline. + +## Build + +The library can be configured standalone for development: + +``` +cmake -S amd/comgr/hotswap -B build-hotswap \ + -DLLVM_DIR=$PWD/build/lib/cmake/llvm +ninja -C build-hotswap +ctest --test-dir build-hotswap -L transpiler +``` diff --git a/amd/comgr/hotswap/code_object_utils.hpp b/amd/comgr/hotswap/code_object_utils.hpp new file mode 100644 index 0000000000000..5b6f2450a7fe8 --- /dev/null +++ b/amd/comgr/hotswap/code_object_utils.hpp @@ -0,0 +1,48 @@ +#ifndef HOTSWAP_TRANSPILER_CODE_OBJECT_UTILS_HPP +#define HOTSWAP_TRANSPILER_CODE_OBJECT_UTILS_HPP + +#include +#include +#include + +namespace transpiler { + +struct KernelArgMeta { + std::string name; + int offset = 0; + int size = 0; + std::string valueKind; + int addressSpace = -1; +}; + +// Per-kernel metadata extracted from the AMDGPU code object's MsgPack notes +// + kernel descriptor (`.kd`). +struct KernelMeta { + std::string name; + int kernargSegmentSize = 0; + int groupSegmentFixedSize = 0; + int privateSegmentFixedSize = 0; + int maxFlatWorkgroupSize = 256; + std::vector args; + + bool hasKernelDescriptor = false; + uint32_t computePgmRsrc1 = 0; + uint32_t computePgmRsrc2 = 0; + uint16_t kernelCodeProperties = 0; + uint16_t kernargPreload = 0; + + int implicitArgsBase() const { + int maxEnd = 0; + for (auto &a : args) { + if (a.valueKind.rfind("hidden_", 0) == 0) + continue; + int end = a.offset + a.size; + if (end > maxEnd) maxEnd = end; + } + return (maxEnd + 7) & ~7; + } +}; + +} // namespace transpiler + +#endif diff --git a/amd/comgr/hotswap/raise_failure.cpp b/amd/comgr/hotswap/raise_failure.cpp new file mode 100644 index 0000000000000..ef65fcd78f076 --- /dev/null +++ b/amd/comgr/hotswap/raise_failure.cpp @@ -0,0 +1,13 @@ +#include "raise_failure.hpp" + +namespace transpiler { + +const char *reasonString(RaiseFailureReason r) { + switch (r) { + case RaiseFailureReason::None: + return "None"; + } + return "UnknownRaiseFailureReason"; +} + +} // namespace transpiler diff --git a/amd/comgr/hotswap/raise_failure.hpp b/amd/comgr/hotswap/raise_failure.hpp new file mode 100644 index 0000000000000..2f2b2d4c93fb1 --- /dev/null +++ b/amd/comgr/hotswap/raise_failure.hpp @@ -0,0 +1,29 @@ +#ifndef HOTSWAP_TRANSPILER_RAISE_FAILURE_HPP +#define HOTSWAP_TRANSPILER_RAISE_FAILURE_HPP + +#include +#include + +namespace transpiler { + +// Structured reason for a raise failure. Lives in its own header so the +// handler layer (`raise_context.hpp`) can depend on failure values +// without pulling in `RaiseResult` and the rest of the top-level +// `raiser.hpp` interface. +enum class RaiseFailureReason : uint16_t { + None = 0, +}; + +const char *reasonString(RaiseFailureReason r); + +struct RaiseFailure { + RaiseFailureReason reason = RaiseFailureReason::None; + // Optional human-readable context. + std::string detail; + + bool hasFailed() const { return reason != RaiseFailureReason::None; } +}; + +} // namespace transpiler + +#endif diff --git a/amd/comgr/hotswap/raiser.cpp b/amd/comgr/hotswap/raiser.cpp new file mode 100644 index 0000000000000..b0f77c05781c0 --- /dev/null +++ b/amd/comgr/hotswap/raiser.cpp @@ -0,0 +1,49 @@ +//===- raiser.cpp - Hotswap MC -> LLVM IR raiser ---------------------------===// +// +// Produce an empty `llvm::Module` that contains a single kernel function with +// `ret void`. See `raiser.hpp` for the full raise pipeline (ELF ingestion -> +// decode -> per-format handlers -> post-raise analyses). +// +//===----------------------------------------------------------------------===// + +#include "raiser.hpp" + +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/TargetParser/Triple.h" + +namespace transpiler { + +RaiseResult raiseToIR(llvm::ArrayRef /*textBytes*/, + llvm::StringRef /*sourceISA*/, + llvm::StringRef kernelName, + const KernelMeta & /*meta*/, + uint64_t /*kernelOffset*/, + llvm::StringRef /*compilationTargetISA*/) { + using namespace llvm; + RaiseResult result; + result.ctx = std::make_unique(); + LLVMContext &C = *result.ctx; + + result.module = std::make_unique("transpiler_module", C); + Module &M = *result.module; + M.setTargetTriple(Triple("amdgcn-amd-amdhsa")); + + auto *funcTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg=*/false); + Function *F = + Function::Create(funcTy, GlobalValue::ExternalLinkage, kernelName, &M); + F->setCallingConv(CallingConv::AMDGPU_KERNEL); + + BasicBlock *entry = BasicBlock::Create(C, "entry", F); + IRBuilder<> B(entry); + B.CreateRetVoid(); + + result.success = true; + return result; +} + +} // namespace transpiler diff --git a/amd/comgr/hotswap/raiser.hpp b/amd/comgr/hotswap/raiser.hpp new file mode 100644 index 0000000000000..f39ece3fd39c8 --- /dev/null +++ b/amd/comgr/hotswap/raiser.hpp @@ -0,0 +1,45 @@ +#ifndef HOTSWAP_TRANSPILER_RAISER_HPP +#define HOTSWAP_TRANSPILER_RAISER_HPP + +#include "code_object_utils.hpp" +#include "raise_failure.hpp" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" + +#include +#include +#include + +namespace llvm { +class LLVMContext; +class Module; +} // namespace llvm + +namespace transpiler { + +struct RaiseResult { + std::unique_ptr ctx; + std::unique_ptr module; + int liftedCount = 0; + int totalCount = 0; + std::string irText; + std::string disasmText; + // Structured failure description. `failure.reason == None` iff `success`. + RaiseFailure failure; + bool usesScratchPrivateSegment = false; + uint32_t sourcePrivateSegmentFixedSize = 0; + bool success = false; + bool hasDivergentExec = false; +}; + +RaiseResult raiseToIR(llvm::ArrayRef textBytes, + llvm::StringRef sourceISA, + llvm::StringRef kernelName, + const KernelMeta &meta, + uint64_t kernelOffset = 0, + llvm::StringRef compilationTargetISA = ""); + +} // namespace transpiler + +#endif diff --git a/amd/comgr/hotswap/tests/raiser_empty_module_test.cpp b/amd/comgr/hotswap/tests/raiser_empty_module_test.cpp new file mode 100644 index 0000000000000..1ab9668bbb40e --- /dev/null +++ b/amd/comgr/hotswap/tests/raiser_empty_module_test.cpp @@ -0,0 +1,37 @@ +//===- raiser_empty_module_test.cpp - empty-module smoke test -------------===// +// +// Verifies that the bare-bones raiser produces a well-formed empty module: +// a single AMDGPU_KERNEL function whose body is `ret void`. +// +//===----------------------------------------------------------------------===// + +#include "hotswap/raiser.hpp" + +#include "llvm/IR/CallingConv.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Support/raw_ostream.h" + +#include "gtest/gtest.h" + +using namespace transpiler; + +TEST(Raiser, EmptyModuleIsValid) { + KernelMeta meta; + meta.name = "kernel"; + meta.hasKernelDescriptor = true; + RaiseResult result = raiseToIR({}, "gfx942", "kernel", meta); + + ASSERT_TRUE(result.success); + ASSERT_NE(result.module, nullptr); + + std::string err; + llvm::raw_string_ostream errStream(err); + EXPECT_FALSE(llvm::verifyModule(*result.module, &errStream)) << err; + + llvm::Function *fn = result.module->getFunction("kernel"); + ASSERT_NE(fn, nullptr); + EXPECT_EQ(fn->getCallingConv(), llvm::CallingConv::AMDGPU_KERNEL); + EXPECT_FALSE(fn->empty()); +} diff --git a/amd/comgr/hotswap/tests/test_main.cpp b/amd/comgr/hotswap/tests/test_main.cpp new file mode 100644 index 0000000000000..4483c91af4082 --- /dev/null +++ b/amd/comgr/hotswap/tests/test_main.cpp @@ -0,0 +1,6 @@ +#include "gtest/gtest.h" + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}