Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions amd/comgr/hotswap/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
)

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()
19 changes: 19 additions & 0 deletions amd/comgr/hotswap/README.md
Original file line number Diff line number Diff line change
@@ -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
```
48 changes: 48 additions & 0 deletions amd/comgr/hotswap/code_object_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef HOTSWAP_TRANSPILER_CODE_OBJECT_UTILS_HPP
#define HOTSWAP_TRANSPILER_CODE_OBJECT_UTILS_HPP

#include <cstdint>
#include <string>
#include <vector>

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 (`<name>.kd`).
struct KernelMeta {
std::string name;
int kernargSegmentSize = 0;
int groupSegmentFixedSize = 0;
int privateSegmentFixedSize = 0;
int maxFlatWorkgroupSize = 256;
std::vector<KernelArgMeta> 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
13 changes: 13 additions & 0 deletions amd/comgr/hotswap/raise_failure.cpp
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions amd/comgr/hotswap/raise_failure.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef HOTSWAP_TRANSPILER_RAISE_FAILURE_HPP
#define HOTSWAP_TRANSPILER_RAISE_FAILURE_HPP

#include <cstdint>
#include <string>

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
49 changes: 49 additions & 0 deletions amd/comgr/hotswap/raiser.cpp
Original file line number Diff line number Diff line change
@@ -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<uint8_t> /*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>();
LLVMContext &C = *result.ctx;

result.module = std::make_unique<Module>("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
45 changes: 45 additions & 0 deletions amd/comgr/hotswap/raiser.hpp
Original file line number Diff line number Diff line change
@@ -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 <memory>
#include <string>
#include <vector>

namespace llvm {
class LLVMContext;
class Module;
} // namespace llvm

namespace transpiler {

struct RaiseResult {
std::unique_ptr<llvm::LLVMContext> ctx;
std::unique_ptr<llvm::Module> 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<uint8_t> textBytes,
llvm::StringRef sourceISA,
llvm::StringRef kernelName,
const KernelMeta &meta,
uint64_t kernelOffset = 0,
llvm::StringRef compilationTargetISA = "");

} // namespace transpiler

#endif
37 changes: 37 additions & 0 deletions amd/comgr/hotswap/tests/raiser_empty_module_test.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
6 changes: 6 additions & 0 deletions amd/comgr/hotswap/tests/test_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "gtest/gtest.h"

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Loading