Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
#include "rocjitsu/isa/arch/amdgpu/generated/rdna3_5/opcodes.h"
#include "rocjitsu/isa/arch/amdgpu/generated/rdna4/builders.h"
#include "rocjitsu/isa/arch/amdgpu/generated/rdna4/opcodes.h"
#include "util/except.h"
#include "util/diagnostic.h"
#include "util/result.h"

namespace rocjitsu {

Expand All @@ -56,57 +57,64 @@ inline constexpr uint16_t kVectorSrcLiteral = cdna1::OPR_SRC_SRC_LITERAL;
/// value, so the immediate travels in a second word. Emitting the pair as one
/// unit keeps the caller from having to know that.
///
/// @returns Failure when the target ISA has no VOP1 encoding.
/// @param error Optional caller-owned diagnostic emitter, used only on failure.
///
/// @note On gfx1250 an encoded @p vdst names a register within the bank
/// MODE.VGPR_MSB selects, not a fixed physical VGPR. That constrains callers
/// that must name a specific register, not this encoder
[[nodiscard]] inline std::array<uint32_t, 2> build_v_mov_b32_imm(uint16_t vdst, uint32_t imm,
rj_code_arch_t arch) {
inline util::FailureOr<std::array<uint32_t, 2>>
build_v_mov_b32_imm(uint16_t vdst, uint32_t imm, rj_code_arch_t arch,
const util::DiagnosticEmitter *error = nullptr) {
const uint8_t dst = static_cast<uint8_t>(vdst);
switch (arch) {
case ROCJITSU_CODE_ARCH_CDNA1:
return {
return std::array<uint32_t, 2>{
cdna1::build_vop1(cdna1::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_CDNA2:
return {
return std::array<uint32_t, 2>{
cdna2::build_vop1(cdna2::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_CDNA3:
return {
return std::array<uint32_t, 2>{
cdna3::build_vop1(cdna3::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_CDNA4:
return {
return std::array<uint32_t, 2>{
cdna4::build_vop1(cdna4::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_CDNA5:
return {
return std::array<uint32_t, 2>{
cdna5::build_vop1(cdna5::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_RDNA1:
return {
return std::array<uint32_t, 2>{
rdna1::build_vop1(rdna1::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_RDNA2:
return {
return std::array<uint32_t, 2>{
rdna2::build_vop1(rdna2::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_RDNA3:
return {
return std::array<uint32_t, 2>{
rdna3::build_vop1(rdna3::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
case ROCJITSU_CODE_ARCH_RDNA3_5:
return {rdna3_5::build_vop1(rdna3_5::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst})
.front(),
imm};
return std::array<uint32_t, 2>{
rdna3_5::build_vop1(rdna3_5::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst})
.front(),
imm};
case ROCJITSU_CODE_ARCH_RDNA4:
return {
return std::array<uint32_t, 2>{
rdna4::build_vop1(rdna4::kVMovB32Vop1, {.src0 = kVectorSrcLiteral, .vdst = dst}).front(),
imm};
default:
// RV32I/RV64I and ROCJITSU_CODE_ARCH_INVALID reach here: not AMDGPU, so
// there is no VOP1 encoding to pick.
throw util::UnimplementedInst("v_mov_b32 with a literal for target architecture");
return error != nullptr
? (error->emit() << "unsupported target architecture for v_mov_b32 with a literal")
: util::Result::failure();
}
}

Expand All @@ -119,17 +127,24 @@ inline constexpr uint16_t kVectorSrcLiteral = cdna1::OPR_SRC_SRC_LITERAL;
/// Broadcasting a scalar this way is how the trampoline hands a probe a value it
/// holds in an SGPR rather than as a build-time constant.
///
/// @throws util::InvalidInst if @p src0 names the literal constant. That form
/// @returns Failure when the target ISA has no VOP1 encoding or @p src0 names
/// the literal constant. That form
/// needs a trailing word this encoder has no way to return, so accepting it
/// would emit an instruction that reads whatever follows it.
///
/// @param error Optional caller-owned diagnostic emitter, used only on failure.
///
/// @note On gfx1250 an encoded @p vdst names a register within the bank
/// MODE.VGPR_MSB selects, not a fixed physical VGPR. That constrains callers
/// that must name a specific register, not this encoder
[[nodiscard]] inline uint32_t build_v_mov_b32_src(uint16_t vdst, uint16_t src0,
rj_code_arch_t arch) {
if (src0 == kVectorSrcLiteral)
throw util::InvalidInst("v_mov_b32 from a literal needs build_v_mov_b32_imm");
inline util::FailureOr<uint32_t>
build_v_mov_b32_src(uint16_t vdst, uint16_t src0, rj_code_arch_t arch,
const util::DiagnosticEmitter *error = nullptr) {
if (src0 == kVectorSrcLiteral) {
Comment thread
IanWood1 marked this conversation as resolved.
Resolved
return error != nullptr
? (error->emit() << "v_mov_b32 from a literal needs build_v_mov_b32_imm")
: util::Result::failure();
}
const uint8_t dst = static_cast<uint8_t>(vdst);
switch (arch) {
case ROCJITSU_CODE_ARCH_CDNA1:
Expand All @@ -155,7 +170,9 @@ inline constexpr uint16_t kVectorSrcLiteral = cdna1::OPR_SRC_SRC_LITERAL;
default:
// RV32I/RV64I and ROCJITSU_CODE_ARCH_INVALID reach here: not AMDGPU, so
// there is no VOP1 encoding to pick.
throw util::UnimplementedInst("v_mov_b32 from a register for target architecture");
return error != nullptr
? (error->emit() << "unsupported target architecture for v_mov_b32 from a register")
: util::Result::failure();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "rocjitsu/code/kernel_descriptor_scan.h"
#include "rocjitsu/code/patch/error_report.h"
#include "rocjitsu/isa/isa_traits.h"
#include "util/diagnostic.h"

#include <algorithm>
#include <array>
Expand Down Expand Up @@ -523,6 +524,13 @@ std::optional<TrampolineBytes> TrampolineBuilder::emit_probe_call(const Trampoli
// Spill saves: store each live+clobbered register before the call.
env.insert(env.end(), spill.prologue.begin(), spill.prologue.end());

// TODO: Accept a DiagnosticEmitter in emit_probe_call and remove this error_out adapter.
Comment thread
IanWood1 marked this conversation as resolved.
Resolved
auto report_builder_error = [&](std::string_view message) {
if (error_out != nullptr)
error_out->assign(message);
};
const util::DiagnosticEmitter error(report_builder_error);

// Argument materialization: consecutive VGPRs from the ABI's base, in order.
//
// Three things fix this position. It follows the spill prologue, so an argument
Expand Down Expand Up @@ -552,27 +560,47 @@ std::optional<TrampolineBytes> TrampolineBuilder::emit_probe_call(const Trampoli
// reserves the temp, and the guard above fails closed if it is somehow absent.
switch (arg.source) {
case ProbeArgSource::Immediate: {
const auto words = build_v_mov_b32_imm(vdst, arg.immediate, plan.arch);
const auto maybe_words = build_v_mov_b32_imm(vdst, arg.immediate, plan.arch, &error);
if (maybe_words.failed())
return std::nullopt;
const auto &words = maybe_words.value();
env.insert(env.end(), words.begin(), words.end());
break;
}
case ProbeArgSource::AnchorExecLo:
env.push_back(build_v_mov_b32_src(vdst, exec_temp, plan.arch));
case ProbeArgSource::AnchorExecLo: {
const auto word = build_v_mov_b32_src(vdst, exec_temp, plan.arch, &error);
if (word.failed())
return std::nullopt;
env.push_back(word.value());
break;
case ProbeArgSource::AnchorExecHi:
env.push_back(build_v_mov_b32_src(vdst, static_cast<uint16_t>(exec_temp + 1), plan.arch));
}
case ProbeArgSource::AnchorExecHi: {
const auto word =
build_v_mov_b32_src(vdst, static_cast<uint16_t>(exec_temp + 1), plan.arch, &error);
if (word.failed())
return std::nullopt;
env.push_back(word.value());
break;
}
// The pair is read, never written, so it needs no save/restore here: the
// prologue defined it at kernel entry and planning kept the envelope and the
// probe body off it.
case ProbeArgSource::LogBufferPtrLo:
env.push_back(build_v_mov_b32_src(vdst, *plan.entry_storage_base, plan.arch));
case ProbeArgSource::LogBufferPtrLo: {
const auto word = build_v_mov_b32_src(vdst, *plan.entry_storage_base, plan.arch, &error);
if (word.failed())
return std::nullopt;
env.push_back(word.value());
break;
case ProbeArgSource::LogBufferPtrHi:
env.push_back(build_v_mov_b32_src(vdst, static_cast<uint16_t>(*plan.entry_storage_base + 1),
plan.arch));
}
case ProbeArgSource::LogBufferPtrHi: {
const auto word = build_v_mov_b32_src(
vdst, static_cast<uint16_t>(*plan.entry_storage_base + 1), plan.arch, &error);
if (word.failed())
return std::nullopt;
env.push_back(word.value());
break;
}
}
}

// Restore the anchor EXEC before the call so the probe runs under the anchor mask,
Expand Down
4 changes: 3 additions & 1 deletion emulation/rocjitsu/tests/dbi/dbi_arg_sim_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ class DbiArgSimBase : public ::testing::Test {
// whatever the guest happened to leave in v0. Nop the two v_mov_b32 v0, literal
// words and the probe should read the guest's value instead.
void expect_without_the_argument_write_probe_reads_the_guest_value() {
const auto arg_write = build_v_mov_b32_imm(0, kArgSentinel, a_.arch);
const auto maybe_arg_write = build_v_mov_b32_imm(0, kArgSentinel, a_.arch);
ASSERT_TRUE(maybe_arg_write.succeeded());
const auto arg_write = maybe_arg_write.value();
const uint32_t nop = build_s_nop(0, a_.arch);

std::vector<uint32_t> sabotaged = patched_text_;
Expand Down
38 changes: 24 additions & 14 deletions emulation/rocjitsu/tests/dbi/dbi_entry_prologue_sim_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,45 +194,55 @@ class DbiEntryPrologueSimBase : public ::testing::Test {
// Inline constant 0 is scalar-source code 128.
const uint32_t clobber_s0 = build_s_mov_b32(0, 128, a_.arch);
const uint32_t clobber_s1 = build_s_mov_b32(1, 128, a_.arch);
const uint32_t guest_anchor = build_v_mov_b32_src(1, kVgprSrcBase + 0, a_.arch);

const std::vector<uint32_t> publish{build_v_mov_b32_src(2, kVgprSrcBase + 0, a_.arch),
build_v_mov_b32_src(3, kVgprSrcBase + 1, a_.arch), setpc};
const auto maybe_guest_anchor = build_v_mov_b32_src(1, kVgprSrcBase + 0, a_.arch);
ASSERT_TRUE(maybe_guest_anchor.succeeded());
const uint32_t guest_anchor = maybe_guest_anchor.value();

const auto maybe_publish_lo = build_v_mov_b32_src(2, kVgprSrcBase + 0, a_.arch);
const auto maybe_publish_hi = build_v_mov_b32_src(3, kVgprSrcBase + 1, a_.arch);
const auto maybe_restore_lo = build_v_mov_b32_src(2, 0, a_.arch);
const auto maybe_restore_hi = build_v_mov_b32_src(3, 1, a_.arch);
ASSERT_TRUE(maybe_publish_lo.succeeded());
const uint32_t publish_lo = maybe_publish_lo.value();
ASSERT_TRUE(maybe_publish_hi.succeeded());
const uint32_t publish_hi = maybe_publish_hi.value();
ASSERT_TRUE(maybe_restore_lo.succeeded());
const uint32_t restore_lo = maybe_restore_lo.value();
ASSERT_TRUE(maybe_restore_hi.succeeded());
const uint32_t restore_hi = maybe_restore_hi.value();
const std::vector<uint32_t> publish{publish_lo, publish_hi, setpc};
auto probe = test::make_amdgpu_probe_elf("rj_test_log_probe", publish, a_.e_flags);
AmdGpuCodeObject probe_obj(probe.data(), probe.size());
ASSERT_TRUE(probe_obj.is_valid());

ASSERT_NO_FATAL_FAILURE(patch({clobber_s0, clobber_s1, guest_anchor, endpgm},
/*anchor_offset=*/8, probe_obj, delivery_));
ASSERT_NO_FATAL_FAILURE(
patch({build_s_nop(0, a_.arch), guest_anchor, build_v_mov_b32_src(2, 0, a_.arch),
build_v_mov_b32_src(3, 1, a_.arch), endpgm},
patch({build_s_nop(0, a_.arch), guest_anchor, restore_lo, restore_hi, endpgm},
/*anchor_offset=*/4, probe_obj, restore_));

// s_call_b64's immediate is a signed dword offset from the next instruction.
const uint32_t ret = build_s_setpc_b64(/*s[4:5]=*/4, a_.arch);
ASSERT_NO_FATAL_FAILURE(
patch({clobber_s0, clobber_s1, build_s_call_b64(4, 2, a_.arch), guest_anchor, endpgm, ret},
/*anchor_offset=*/12, probe_obj, call_delivery_));
ASSERT_NO_FATAL_FAILURE(
patch({build_s_nop(0, a_.arch), build_s_call_b64(4, 4, a_.arch), guest_anchor,
build_v_mov_b32_src(2, 0, a_.arch), build_v_mov_b32_src(3, 1, a_.arch), endpgm, ret},
/*anchor_offset=*/8, probe_obj, call_restore_));
ASSERT_NO_FATAL_FAILURE(patch({build_s_nop(0, a_.arch), build_s_call_b64(4, 4, a_.arch),
guest_anchor, restore_lo, restore_hi, endpgm, ret},
/*anchor_offset=*/8, probe_obj, call_restore_));

// A point on the entry itself: the stub runs the prologue, then branches to
// the entry, which is spliced to the site's trampoline.
ASSERT_NO_FATAL_FAILURE(
patch({guest_anchor, endpgm}, /*anchor_offset=*/0, probe_obj, entry_point_delivery_));
ASSERT_NO_FATAL_FAILURE(patch({guest_anchor, build_v_mov_b32_src(2, 0, a_.arch),
build_v_mov_b32_src(3, 1, a_.arch), endpgm},
ASSERT_NO_FATAL_FAILURE(patch({guest_anchor, restore_lo, restore_hi, endpgm},
/*anchor_offset=*/0, probe_obj, entry_point_restore_));

// Re-entry kernels. s6 counts passes through the entry; the second pass
// leaves the loop or skips the call or jump.
const uint32_t count_pass = build_s_add_u32(6, 6, kInline1, a_.arch);
const uint32_t second_pass = build_s_cmp_lg_u32(6, kInline2, a_.arch);
const uint32_t read_s0 = build_v_mov_b32_src(2, 0, a_.arch);
const uint32_t read_s1 = build_v_mov_b32_src(3, 1, a_.arch);
const uint32_t read_s0 = restore_lo;
const uint32_t read_s1 = restore_hi;
const uint32_t loop_to_entry = test::build_s_cbranch_scc(/*scc1=*/true, -4, a_.arch);
ASSERT_NO_FATAL_FAILURE(patch({count_pass, guest_anchor, second_pass, loop_to_entry, endpgm},
/*anchor_offset=*/4, probe_obj, loop_delivery_));
Expand Down
7 changes: 4 additions & 3 deletions emulation/rocjitsu/tests/dbi/dbi_log_record_sim_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ class DbiLogRecordSimBase : public ::testing::Test {
/*granulated_sgpr_count=*/kKernelSgprs / 8 - 1, /*entry_text_offset=*/0,
/*granulated_vgpr_count=*/kKernelVgprs / 8 - 1,
/*accum_offset=*/t_.wave32 ? 0 : kKernelVgprs / 4 - 1);
const auto probe = test::make_amdgpu_probe_elf(
kProbeSymbol, test::build_log_record_probe_body(t_.arch, kRecordType, kSlotCount),
t_.e_flags);
std::vector<uint32_t> probe_body;
ASSERT_NO_FATAL_FAILURE(
test::build_log_record_probe_body(t_.arch, kRecordType, kSlotCount, probe_body));
const auto probe = test::make_amdgpu_probe_elf(kProbeSymbol, probe_body, t_.e_flags);
AmdGpuCodeObject obj(target.data(), target.size());
AmdGpuCodeObject probe_obj(probe.data(), probe.size());
ASSERT_TRUE(obj.is_valid());
Expand Down
19 changes: 11 additions & 8 deletions emulation/rocjitsu/tests/dbi/dbi_sim_memory_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,11 @@ constexpr uint16_t kInlineZero = scalar_positive_inline_u32(0);
// Copies the dword at kSource to kSource + kDestinationDelta, addressing both
// through s[2:3] and a zero per-lane offset in v0. Without @p with_store the
// store is replaced by s_nops of the same length.
std::vector<uint32_t> make_copy_kernel(rj_code_arch_t arch, bool with_store) {
std::vector<uint32_t> code{build_s_mov_b32(2, kScalarSrcLiteral, arch),
static_cast<uint32_t>(kSource), build_s_mov_b32(3, kInlineZero, arch),
build_v_mov_b32_src(0, kInlineZero, arch)};
void make_copy_kernel(rj_code_arch_t arch, bool with_store, std::vector<uint32_t> &code) {
Comment thread
IanWood1 marked this conversation as resolved.
Resolved
const auto zero = build_v_mov_b32_src(0, kInlineZero, arch);
ASSERT_TRUE(zero.succeeded());
code = {build_s_mov_b32(2, kScalarSrcLiteral, arch), static_cast<uint32_t>(kSource),
build_s_mov_b32(3, kInlineZero, arch), zero.value()};
const std::vector<uint32_t> load = build_global_load_dword(1, 0, 2, 0, arch);
code.insert(code.end(), load.begin(), load.end());
code.push_back(build_wait_loads_complete(arch));
Expand All @@ -180,7 +181,6 @@ std::vector<uint32_t> make_copy_kernel(rj_code_arch_t arch, bool with_store) {
else
code.insert(code.end(), store.size(), build_s_nop(0, arch));
code.push_back(build_s_endpgm(arch));
return code;
}

// Seeds kSource, runs the copy kernel, and returns the destination dword, or
Expand All @@ -193,9 +193,12 @@ std::optional<uint32_t> run_copy(const SimArch &target, bool with_store) {
std::array<uint8_t, sizeof(kSeed)> seed{};
std::memcpy(seed.data(), &kSeed, sizeof(kSeed));
sim.write_memory(kSource, seed);
const std::optional<std::vector<uint8_t>> bytes =
sim.run_and_read_memory(make_copy_kernel(target.arch, with_store), /*private_bytes=*/0,
kSource + kDestinationDelta, sizeof(uint32_t));
std::vector<uint32_t> code;
make_copy_kernel(target.arch, with_store, code);
if (testing::Test::HasFatalFailure())
return std::nullopt;
const std::optional<std::vector<uint8_t>> bytes = sim.run_and_read_memory(
code, /*private_bytes=*/0, kSource + kDestinationDelta, sizeof(uint32_t));
if (!bytes)
return std::nullopt;
uint32_t value = 0;
Expand Down
Loading
Loading