Files
TheRock/rocm-systems/emulation/rocjitsu/tools/dbt_translate_cli.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

660 lines
23 KiB
C++

// Copyright (c) 2026 Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
#include "dbt_translate_cli.h"
#include "rocjitsu/code/amdgpu_code_object.h"
#include "rocjitsu/code/amdgpu_elf.h"
#include "rocjitsu/code/executable.h"
#include <array>
#include <charconv>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
using namespace rocjitsu;
using namespace rocjitsu::tools;
namespace {
constexpr int kUsageError = 1;
// Matches the translation-failure exit code used by translate_code_object so a
// non-dispatchable (skipped-kernel) artifact reports the same class of failure.
constexpr int kTranslationError = 3;
constexpr int kOutputError = 4;
enum class OutputMode {
Disasm,
CodeObject,
Diff,
};
struct TargetInfo {
std::string_view name;
rj_code_arch_t arch;
uint32_t mach;
rj_code_target_id_t code_object_target;
};
// \NPI new GPU: add a {"gfxNNNN", ARCH, EF_MACH, TARGET} row here
// (and bump the std::array size) so the DBT tool can translate to/from it.
constexpr std::array<TargetInfo, 5> kTargetInfos = {{
{"gfx942", ROCJITSU_CODE_ARCH_CDNA3, EF_AMDGPU_MACH_AMDGCN_GFX942, ROCJITSU_CODE_TARGET_GFX942},
{"gfx950", ROCJITSU_CODE_ARCH_CDNA4, EF_AMDGPU_MACH_AMDGCN_GFX950, ROCJITSU_CODE_TARGET_GFX950},
{"gfx1200", ROCJITSU_CODE_ARCH_RDNA4, EF_AMDGPU_MACH_AMDGCN_GFX1200,
ROCJITSU_CODE_TARGET_GFX1200},
{"gfx1201", ROCJITSU_CODE_ARCH_RDNA4, EF_AMDGPU_MACH_AMDGCN_GFX1201,
ROCJITSU_CODE_TARGET_GFX1201},
// gfx1250 A0 and B0 share the same ELF machine and structural ISA. Separate
// revision options select the translation direction.
{"gfx1250", ROCJITSU_CODE_ARCH_GFX1250, EF_AMDGPU_MACH_AMDGCN_GFX1250,
ROCJITSU_CODE_TARGET_GFX1250},
}};
struct CliOptions {
TranslateOptions translate;
std::string input_target_name;
std::string output_target_name;
OutputMode output_mode = OutputMode::Disasm;
bool list_code_objects = false;
bool show_help = false;
bool saw_input = false;
bool saw_input_target = false;
bool saw_output_target = false;
bool saw_input_revision = false;
bool saw_output_revision = false;
bool show_all_translations = false;
};
void print_supported_targets(std::ostream &os) {
for (size_t i = 0; i < kTargetInfos.size(); ++i) {
if (i != 0)
os << ", ";
os << kTargetInfos[i].name;
}
}
void print_help() {
std::cout
<< "Usage: rj_dbt_translate INPUT --input-target TARGET --output-target TARGET "
"[options]\n\n"
<< "Options:\n"
<< " --input-target TARGET Input LLVM machine, e.g. gfx950\n"
<< " --output-target TARGET Output LLVM machine, e.g. gfx1200\n"
<< " --input-revision REVISION Input revision: a0 or b0 (gfx1250 only)\n"
<< " --output-revision REVISION Output revision: a0 or b0 (gfx1250 only)\n"
<< " --code-object-index N Code-object index for executable input (default: 0)\n"
<< " --output-mode MODE disasm, code-object, or diff (default: disasm)\n"
<< " --debug-conservative-liveness N Only allocate free VGPR scratch at or above N\n"
<< " --debug-continue-after-failure Continue collecting diagnostics after failures\n"
<< " --skip-failed-kernels Preserve failed kernels and continue other kernels\n"
<< " --verify-idempotence Require a same-architecture second pass to be "
"unchanged\n"
<< " --show-all-translations Include unchanged identity mappings in diff output\n"
<< " --list-code-objects List extractable code objects and exit\n"
<< " --help Show this help\n\n"
<< "Note: gfx1250 b0-to-a0 selects its translation profile; equal revisions are identity.\n\n"
<< "Supported target names: ";
print_supported_targets(std::cout);
std::cout << ".\n";
}
[[nodiscard]] bool parse_u32(std::string_view text, uint32_t &value) {
int base = 10;
if (text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
text.remove_prefix(2);
base = 16;
}
auto *begin = text.data();
auto *end = text.data() + text.size();
auto [ptr, ec] = std::from_chars(begin, end, value, base);
return ec == std::errc{} && ptr == end;
}
[[nodiscard]] std::optional<TargetInfo> parse_target_info(std::string_view value) {
for (const TargetInfo &target : kTargetInfos) {
if (value == target.name)
return target;
}
return std::nullopt;
}
[[nodiscard]] std::optional<OutputMode> parse_output_mode(std::string_view value) {
if (value == "disasm")
return OutputMode::Disasm;
if (value == "code-object")
return OutputMode::CodeObject;
if (value == "diff")
return OutputMode::Diff;
return std::nullopt;
}
[[nodiscard]] std::optional<ProcessorRevision> parse_revision(std::string_view value) {
if (value == "a0")
return ProcessorRevision::Gfx1250A0;
if (value == "b0")
return ProcessorRevision::Gfx1250B0;
return std::nullopt;
}
[[nodiscard]] std::string_view revision_name(ProcessorRevision revision) {
switch (revision) {
case ProcessorRevision::Gfx1250A0:
return "a0";
case ProcessorRevision::Gfx1250B0:
return "b0";
case ProcessorRevision::Unspecified:
return "unspecified";
}
return "unspecified";
}
[[nodiscard]] bool require_value(int argc, char **argv, int &index, std::string_view flag,
std::string_view &value) {
if (index + 1 >= argc) {
std::cerr << "missing value for " << flag << "\n";
return false;
}
++index;
value = argv[index];
return true;
}
[[nodiscard]] bool parse_args(int argc, char **argv, CliOptions &options) {
for (int i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
std::string_view value;
if (arg == "--help" || arg == "-h") {
options.show_help = true;
return true;
}
if (arg == "--list-code-objects") {
options.list_code_objects = true;
continue;
}
if (arg == "--debug-conservative-liveness") {
if (!require_value(argc, argv, i, arg, value))
return false;
uint32_t min_free_vgpr = 0;
if (!parse_u32(value, min_free_vgpr) || min_free_vgpr > UINT16_MAX) {
std::cerr << "invalid minimum free VGPR for " << arg << ": " << value << "\n";
return false;
}
options.translate.debug_min_free_vgpr = static_cast<uint16_t>(min_free_vgpr);
continue;
}
if (arg == "--debug-continue-after-failure") {
options.translate.debug_continue_after_failure = true;
continue;
}
if (arg == "--skip-failed-kernels") {
options.translate.skip_failed_kernels = true;
continue;
}
if (arg == "--verify-idempotence") {
options.translate.verify_idempotence = true;
continue;
}
if (arg == "--input-target") {
if (!require_value(argc, argv, i, arg, value))
return false;
auto target = parse_target_info(value);
if (!target) {
std::cerr << "invalid input target: " << value << "\n";
return false;
}
options.translate.guest_arch = target->arch;
options.translate.input_target = target->code_object_target;
options.input_target_name = std::string(target->name);
options.saw_input_target = true;
} else if (arg == "--output-target") {
if (!require_value(argc, argv, i, arg, value))
return false;
auto target = parse_target_info(value);
if (!target) {
std::cerr << "invalid output target: " << value << "\n";
return false;
}
options.translate.host_arch = target->arch;
options.translate.target_mach = target->mach;
options.output_target_name = std::string(target->name);
options.saw_output_target = true;
} else if (arg == "--input-revision") {
if (!require_value(argc, argv, i, arg, value))
return false;
const auto revision = parse_revision(value);
if (!revision) {
std::cerr << "invalid input revision: " << value << " (expected a0 or b0)\n";
return false;
}
options.translate.input_revision = *revision;
options.saw_input_revision = true;
} else if (arg == "--output-revision") {
if (!require_value(argc, argv, i, arg, value))
return false;
const auto revision = parse_revision(value);
if (!revision) {
std::cerr << "invalid output revision: " << value << " (expected a0 or b0)\n";
return false;
}
options.translate.output_revision = *revision;
options.saw_output_revision = true;
} else if (arg == "--code-object-index") {
if (!require_value(argc, argv, i, arg, value))
return false;
if (!parse_u32(value, options.translate.code_object_index)) {
std::cerr << "invalid code-object index: " << value << "\n";
return false;
}
} else if (arg == "--output-mode") {
if (!require_value(argc, argv, i, arg, value))
return false;
auto mode = parse_output_mode(value);
if (!mode) {
std::cerr << "invalid output mode: " << value << "\n";
return false;
}
options.output_mode = *mode;
} else if (arg == "--show-all-translations") {
options.show_all_translations = true;
} else {
if (!arg.empty() && arg.front() != '-') {
if (options.saw_input) {
std::cerr << "unexpected positional argument: " << arg << "\n";
return false;
}
options.translate.input_path = std::string(arg);
options.saw_input = true;
continue;
}
std::cerr << "unknown option: " << arg << "\n";
return false;
}
}
return true;
}
struct ReportTotals {
size_t size_bytes = 0;
size_t instruction_count = 0;
size_t decode_failure_count = 0;
};
[[nodiscard]] ReportTotals report_totals(const CodeObjectReport &report) {
ReportTotals totals;
for (const auto &section : report.sections) {
totals.size_bytes += section.size_bytes;
totals.instruction_count += section.instruction_count;
totals.decode_failure_count += section.decode_failure_count;
}
return totals;
}
[[nodiscard]] std::string hex_offset(uint64_t value) {
std::ostringstream os;
os << "0x" << std::hex << std::setw(4) << std::setfill('0') << value;
return os.str();
}
[[nodiscard]] std::string words_text(const std::vector<uint32_t> &words) {
std::ostringstream os;
for (size_t i = 0; i < words.size(); ++i) {
if (i != 0)
os << ' ';
os << std::hex << std::setw(8) << std::setfill('0') << words[i];
}
return os.str();
}
[[nodiscard]] const char *diagnostic_severity_name(DiagnosticSeverity severity) {
switch (severity) {
case DiagnosticSeverity::Warning:
return "warning";
case DiagnosticSeverity::Error:
return "error";
}
return "diagnostic";
}
[[nodiscard]] const char *diagnostic_kind_name(DiagnosticKind kind) {
switch (kind) {
case DiagnosticKind::UnsupportedGuestArch:
return "unsupported-guest-arch";
case DiagnosticKind::KernelDescriptor:
return "kernel-descriptor";
case DiagnosticKind::Legalization:
return "legalization";
case DiagnosticKind::ExpandMissing:
return "expand-missing";
case DiagnosticKind::ExpandFailed:
return "expand-failed";
case DiagnosticKind::DataOnly:
return "data-only";
case DiagnosticKind::NothingToTranslate:
return "nothing-to-translate";
case DiagnosticKind::ResourceLimit:
return "resource-limit";
case DiagnosticKind::KernelSkipped:
return "kernel-skipped";
}
return "unknown";
}
void print_diagnostic(std::ostream &os, const TranslationDiagnostic &diagnostic,
std::string_view prefix = {}) {
if (!prefix.empty())
os << prefix << ' ';
os << diagnostic_severity_name(diagnostic.severity) << ": "
<< diagnostic_kind_name(diagnostic.kind);
if (diagnostic.guest_offset)
os << " .text+" << hex_offset(*diagnostic.guest_offset);
if (!diagnostic.mnemonic.empty())
os << " " << diagnostic.mnemonic;
os << ": " << diagnostic.message << "\n";
for (const auto &item : diagnostic.required_work) {
os << " ";
if (!prefix.empty())
os << prefix << ' ';
os << "required: " << item << "\n";
}
}
[[nodiscard]] const char *legalization_action_name(Action action) {
switch (action) {
case Action::Identity:
return "identity";
case Action::Substitute:
return "substitute";
case Action::Lower:
return "lower";
case Action::Expand:
return "expand";
case Action::Illegal:
return "illegal";
}
return "unknown";
}
[[nodiscard]] std::string translation_action_text(const InstructionTranslationReport &translation) {
if (translation.copied_original)
return "copy_original";
if (!translation.has_legalization)
return "encode";
std::string text = legalization_action_name(translation.action);
if (translation.semantic_lowering)
text += " semantic";
return text;
}
[[nodiscard]] bool should_show_translation(const InstructionTranslationReport &translation) {
if (translation.copied_original ||
(translation.has_legalization && translation.action == Action::Identity))
return translation.changed || translation.emitted_in_cave;
return true;
}
[[nodiscard]] size_t count_action(const std::vector<InstructionTranslationReport> &translations,
Action action) {
size_t count = 0;
for (const auto &translation : translations) {
if (!translation.copied_original && translation.has_legalization &&
translation.action == action)
++count;
}
return count;
}
[[nodiscard]] size_t
count_runtime_encode(const std::vector<InstructionTranslationReport> &translations) {
size_t count = 0;
for (const auto &translation : translations) {
if (!translation.copied_original && !translation.has_legalization)
++count;
}
return count;
}
[[nodiscard]] size_t
count_copied_original(const std::vector<InstructionTranslationReport> &translations) {
size_t count = 0;
for (const auto &translation : translations) {
if (translation.copied_original)
++count;
}
return count;
}
[[nodiscard]] size_t
count_semantic_lowerings(const std::vector<InstructionTranslationReport> &translations) {
size_t count = 0;
for (const auto &translation : translations) {
if (translation.semantic_lowering)
++count;
}
return count;
}
[[nodiscard]] std::string target_location(const InstructionTranslationReport &translation) {
if (translation.emitted_in_cave)
return ".text(local-cave)+" + hex_offset(translation.target_offset);
return ".text+" + hex_offset(translation.target_offset);
}
void print_code_object_report(std::ostream &os, std::string_view label,
const CodeObjectReport &report) {
os << "\n" << label << ":\n";
if (!report.available) {
os << " unavailable\n";
return;
}
const ReportTotals totals = report_totals(report);
os << " decoder: " << (report.decoder_available ? "available" : "unavailable") << "\n";
os << " sections: " << report.sections.size() << " bytes=" << totals.size_bytes
<< " instructions=" << totals.instruction_count
<< " decode_failures=" << totals.decode_failure_count << "\n";
for (const auto &section : report.sections) {
os << " section " << section.name << " bytes=" << section.size_bytes
<< " instructions=" << section.instruction_count
<< " decode_failures=" << section.decode_failure_count;
if (section.has_first_decode_failure) {
os << " first_failure=0x" << std::hex << section.first_decode_failure_offset << std::dec
<< " reason=" << section.first_decode_failure_message;
}
os << "\n";
}
}
void print_instruction_translation_report(std::ostream &os, const TranslateOutput &output,
bool show_all_translations) {
const auto &translations = output.instruction_translations;
size_t shown = 0;
size_t changed = 0;
for (const auto &translation : translations) {
if (translation.changed)
++changed;
if (show_all_translations || should_show_translation(translation))
++shown;
}
os << "\ninstruction_translations:\n";
os << " total=" << translations.size() << " changed=" << changed << " shown=" << shown << "\n";
os << " actions:" << " copy_original=" << count_copied_original(translations)
<< " encode=" << count_runtime_encode(translations)
<< " identity=" << count_action(translations, Action::Identity)
<< " substitute=" << count_action(translations, Action::Substitute)
<< " lower=" << count_action(translations, Action::Lower)
<< " expand=" << count_action(translations, Action::Expand)
<< " illegal=" << count_action(translations, Action::Illegal)
<< " semantic=" << count_semantic_lowerings(translations) << "\n";
for (const auto &translation : translations) {
if (!show_all_translations && !should_show_translation(translation))
continue;
os << " " << hex_offset(translation.source_offset) << " "
<< translation_action_text(translation) << " .text+" << hex_offset(translation.source_offset)
<< " -> " << target_location(translation) << "\n";
if (!translation.source_words.empty())
os << " source_words: " << words_text(translation.source_words) << "\n";
os << " source: " << translation.source_instruction << "\n";
if (!translation.target_words.empty())
os << " target_words: " << words_text(translation.target_words) << "\n";
for (const auto &target_instruction : translation.target_instructions)
os << " target: " << target_instruction << "\n";
}
}
void print_text_report(std::ostream &os, const CliOptions &options,
const ToolResult<TranslateOutput> &result) {
const TranslateOutput &output = result.value;
os << "rj_dbt_translate: " << (result.ok() ? "ok" : "failed") << "\n";
os << "input: " << options.translate.input_path << "\n";
os << "input_target: " << options.input_target_name << "\n";
os << "source_code_object_id: fnv1a64:" << std::hex << std::setfill('0') << std::setw(16)
<< output.source_code_object_id << std::dec << std::setfill(' ') << "\n";
if (options.saw_input_revision)
os << "input_revision: " << revision_name(output.input_revision) << "\n";
os << "output_target: " << options.output_target_name << "\n";
if (options.saw_output_revision)
os << "output_revision: " << revision_name(output.output_revision) << "\n";
os << "output_elf_bytes: " << output.elf_bytes.size() << "\n";
if (options.translate.verify_idempotence) {
const std::string_view status = !output.idempotence_checked ? "not-checked"
: output.idempotence_verified ? "verified"
: "not-verified";
os << "idempotence: " << status << "\n";
}
print_code_object_report(os, "source", output.source_report);
print_code_object_report(os, "translated", output.translated_report);
print_instruction_translation_report(os, output, options.show_all_translations);
os << "\ndiagnostics: " << output.diagnostics.size() << "\n";
os << "idempotence_diagnostics: " << output.idempotence_diagnostics.size() << "\n";
os << "errors: " << result.errors.size() << "\n";
}
int list_code_objects(const CliOptions &options) {
if (options.translate.input_path.empty()) {
std::cerr << "input path is required with --list-code-objects\n";
return kUsageError;
}
Executable executable(options.translate.input_path);
if (executable.is_valid()) {
for (const TargetInfo &target : kTargetInfos) {
std::cout << target.name << ": " << executable.num_code_objects(target.code_object_target)
<< "\n";
}
return 0;
}
AmdGpuCodeObject code_object(options.translate.input_path);
if (!code_object.is_valid()) {
std::cerr << "failed to parse input as executable or AMDGPU code object\n";
return 2;
}
std::cout << "standalone-code-object: target-id=" << static_cast<int>(code_object.target_id())
<< "\n";
return 0;
}
[[nodiscard]] bool emit_output(const CliOptions &options, const TranslateOutput &output) {
if (options.output_mode == OutputMode::CodeObject) {
std::cout.write(reinterpret_cast<const char *>(output.elf_bytes.data()),
static_cast<std::streamsize>(output.elf_bytes.size()));
return static_cast<bool>(std::cout);
}
std::cout << output.disassembly;
return static_cast<bool>(std::cout);
}
} // namespace
int rocjitsu::tools::detail::run_dbt_translate_cli(int argc, char **argv,
TranslateCodeObjectFn translate) {
CliOptions options;
if (!parse_args(argc, argv, options)) {
print_help();
return kUsageError;
}
if (options.show_help) {
print_help();
return 0;
}
if (options.list_code_objects && options.translate.verify_idempotence) {
std::cerr << "--verify-idempotence cannot be combined with --list-code-objects\n";
return kUsageError;
}
if (options.list_code_objects)
return list_code_objects(options);
if (!options.saw_input || !options.saw_input_target || !options.saw_output_target) {
std::cerr << "input path, --input-target, and --output-target are required\n";
print_help();
return kUsageError;
}
if (const std::optional<std::string_view> request_error =
translation_request_error(options.translate)) {
std::cerr << *request_error << "\n";
return kUsageError;
}
options.translate.collect_diagnostics = options.output_mode == OutputMode::Diff;
options.translate.disassembly = options.output_mode == OutputMode::Disasm
? DisassemblyMode::Translated
: DisassemblyMode::None;
auto result = translate(options.translate);
for (const auto &diagnostic : result.value.diagnostics)
print_diagnostic(std::cerr, diagnostic);
for (const auto &diagnostic : result.value.idempotence_diagnostics)
print_diagnostic(std::cerr, diagnostic, "idempotence");
if (options.output_mode == OutputMode::Diff)
print_text_report(std::cout, options, result);
if (!result.ok()) {
for (const auto &error : result.errors)
std::cerr << "error: " << error.message << "\n";
return result.errors.front().exit_code;
}
// A skipped kernel is a warning, so result.ok() stays true, but its s_endpgm
// stub completes normally without producing the kernel's outputs and would
// silently produce wrong results if dispatched. Refuse to emit an executable
// code object in that case (the HSA hook rejects the same load). Diff/Disasm
// are diagnostic-only inspection modes and remain available.
if (options.output_mode == OutputMode::CodeObject && !result.value.dispatchable()) {
std::cerr << "error: translation skipped one or more kernels; the code object is not "
"dispatchable and will not be written\n";
return kTranslationError;
}
if (options.output_mode != OutputMode::Diff && !emit_output(options, result.value)) {
std::cerr << "error: failed to write output\n";
return kOutputError;
}
return 0;
}