Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 @@ -152,9 +152,14 @@ void flat_global_calculate_addresses(const Inst &inst, amdgpu::Wavefront &wf,
}
uint64_t addr = saddr_val + vaddr + offset;
if (decode_flat_private) {
// CDNA5 section 10.1.1 checks the final address, including IOFFSET.
if (amdgpu::flat_address_in_hole(wf, addr, amdgpu::FlatPrivateLayout::EncodedLane))
d.memviol_lane_mask |= uint64_t{1} << lane;
const auto translated =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::EncodedLane);
if (translated.private_address) {
if (d.atomic_op != amdgpu::AtomicOp::NONE && d.elem_size == sizeof(uint64_t))
d.memviol_lane_mask |= uint64_t{1} << lane;
addr = translated.value;
d.scratch_swizzle = true;
d.requires_scratch_backing = true;
Expand Down Expand Up @@ -196,9 +201,10 @@ std::optional<uint64_t> smem_calculate_address(const SmemMachineInst &inst, amdg
return std::nullopt;
int64_t off = static_cast<int64_t>(signed_ioffset(inst.ioffset));
const bool buffer_load = amdgpu::addr_calc::gfx12_smem_is_buffer_load_op(inst.op);
// CDNA5 section 8.1.1 allows negative ordinary-load offsets, but not buffer IOFFSET.
if (buffer_load && off < 0)
wf.report_undefined_behavior("negative scalar-buffer load immediate");
// CDNA5 section 8.1.1 allows negative ordinary-load offsets; a negative buffer
// IOFFSET returns MEMVIOL.
if (state && buffer_load && off < 0)
state->memviol = true;
const uint64_t align_mask = std::min<uint64_t>(access_size_bytes, 4u) - 1;
const uint32_t scale = inst.scale_offset && !buffer_load ? access_size_bytes : 1;
off &= ~static_cast<int64_t>(align_mask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,23 @@ std::optional<uint32_t> read_smem_offset(uint32_t soffset, amdgpu::Wavefront &wf
std::optional<uint64_t> smem_calculate_address(const SmemMachineInst &inst, amdgpu::Wavefront &wf,
amdgpu::ScalarMemState *state) {
const uint32_t sbase_sel = inst.sbase * 2;
const bool buffer_load = amdgpu::addr_calc::smem_is_buffer_load_op(inst.op);
auto base = amdgpu::try_read_scalar_selector64(wf, sbase_sel);
if (!base)
return std::nullopt;
int64_t off = static_cast<int64_t>(static_cast<int32_t>(inst.offset << 11) >> 11);
const bool negative_immediate = off < 0;
auto soffset = read_smem_offset(inst.soffset, wf);
if (!soffset)
return std::nullopt;
off += *soffset;
// RDNA3 section 8.1.1 permits negative immediates only when the sum is nonnegative.
if (!amdgpu::addr_calc::smem_is_buffer_load_op(inst.op) && off < 0)
if (!buffer_load && off < 0)
wf.report_undefined_behavior("negative combined scalar-memory offset");
if (amdgpu::addr_calc::smem_is_buffer_load_op(inst.op)) {
// A negative immediate applied to S_BUFFER returns MEMVIOL (section 8.1, SMEM fields).
if (state && buffer_load && negative_immediate)
state->memviol = true;
if (buffer_load) {
return amdgpu::addr_calc::scalar_buffer_address(wf, sbase_sel, *base, off, state);
}
return (*base + off) & ~0x3ULL;
Expand Down Expand Up @@ -125,10 +130,19 @@ void flat_calculate_addresses(const FlatMachineInst &inst, amdgpu::Wavefront &wf
} else {
vaddr = vaddr_region.lane64(0, lane);
}
uint64_t addr = saddr_val + vaddr + offset;
if (inst.seg == 0)
addr =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear).value;
const uint64_t base_address = saddr_val + vaddr;
if (inst.seg == 0 &&
amdgpu::flat_address_in_hole(wf, base_address, amdgpu::FlatPrivateLayout::Linear))
d.memviol_lane_mask |= uint64_t{1} << lane;
uint64_t addr = base_address + offset;
if (inst.seg == 0) {
const auto translated =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear);
if (translated.private_address && d.atomic_op != amdgpu::AtomicOp::NONE &&
d.elem_size == sizeof(uint64_t))
d.memviol_lane_mask |= uint64_t{1} << lane;
addr = translated.value;
}
d.per_lane_addr[lane] = addr;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,23 @@ std::optional<uint32_t> read_smem_offset(uint32_t soffset, amdgpu::Wavefront &wf
std::optional<uint64_t> smem_calculate_address(const SmemMachineInst &inst, amdgpu::Wavefront &wf,
amdgpu::ScalarMemState *state) {
const uint32_t sbase_sel = inst.sbase * 2;
const bool buffer_load = amdgpu::addr_calc::smem_is_buffer_load_op(inst.op);
auto base = amdgpu::try_read_scalar_selector64(wf, sbase_sel);
if (!base)
return std::nullopt;
int64_t off = static_cast<int64_t>(static_cast<int32_t>(inst.offset << 11) >> 11);
const bool negative_immediate = off < 0;
auto soffset = read_smem_offset(inst.soffset, wf);
if (!soffset)
return std::nullopt;
off += *soffset;
// Only the non-buffer addressing section defines a negative sum as undefined.
if (!amdgpu::addr_calc::smem_is_buffer_load_op(inst.op) && off < 0)
if (!buffer_load && off < 0)
wf.report_undefined_behavior("negative combined scalar-memory offset");
if (amdgpu::addr_calc::smem_is_buffer_load_op(inst.op))
// A negative immediate applied to S_BUFFER returns MEMVIOL (SMEM OFFSET field).
if (state && buffer_load && negative_immediate)
state->memviol = true;
if (buffer_load)
return amdgpu::addr_calc::scalar_buffer_address(wf, sbase_sel, *base, off, state);
return (*base + off) & ~0x3ULL;
}
Expand Down Expand Up @@ -124,10 +129,19 @@ void flat_calculate_addresses(const FlatMachineInst &inst, amdgpu::Wavefront &wf
} else {
vaddr = vaddr_region.lane64(0, lane);
}
uint64_t addr = saddr_val + vaddr + offset;
if (inst.seg == 0)
addr =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear).value;
const uint64_t base_address = saddr_val + vaddr;
if (inst.seg == 0 &&
amdgpu::flat_address_in_hole(wf, base_address, amdgpu::FlatPrivateLayout::Linear))
d.memviol_lane_mask |= uint64_t{1} << lane;
uint64_t addr = base_address + offset;
if (inst.seg == 0) {
const auto translated =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear);
if (translated.private_address && d.atomic_op != amdgpu::AtomicOp::NONE &&
d.elem_size == sizeof(uint64_t))
d.memviol_lane_mask |= uint64_t{1} << lane;
addr = translated.value;
}
d.per_lane_addr[lane] = addr;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ std::optional<uint64_t> smem_calculate_address(const SmemMachineInst &inst, amdg
// RDNA4 section 8.1.1 restricts the immediate for buffer loads, the sum otherwise.
const bool buffer_load = amdgpu::addr_calc::gfx12_smem_is_buffer_load_op(inst.op);
const bool ordinary_load = amdgpu::addr_calc::gfx12_smem_is_ordinary_load_op(inst.op);
if ((buffer_load && immediate < 0) || (ordinary_load && immediate + *soffset < 0))
wf.report_undefined_behavior(buffer_load ? "negative scalar-buffer load immediate"
: "negative combined scalar-memory offset");
if (ordinary_load && immediate + *soffset < 0)
wf.report_undefined_behavior("negative combined scalar-memory offset");
// A negative IOFFSET applied to S_BUFFER returns MEMVIOL (section 8.1, SMEM fields).
if (state && buffer_load && immediate < 0)
state->memviol = true;
if (buffer_load) {
return amdgpu::addr_calc::scalar_buffer_address(wf, sbase_sel, *base, off, state, align_mask,
align_mask);
Expand Down Expand Up @@ -114,8 +116,17 @@ void flat_calculate_addresses(const VflatMachineInst &inst, amdgpu::Wavefront &w
} else {
vaddr = vaddr_region.lane64(0, lane);
}
uint64_t addr = saddr_val + vaddr + offset;
addr = amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear).value;
// RDNA4 section 11.3 checks the aperture before adding IOFFSET.
const uint64_t base_address = saddr_val + vaddr;
if (amdgpu::flat_address_in_hole(wf, base_address, amdgpu::FlatPrivateLayout::Linear))
d.memviol_lane_mask |= uint64_t{1} << lane;
uint64_t addr = base_address + offset;
const auto translated =
amdgpu::translate_flat_address(wf, addr, lane, amdgpu::FlatPrivateLayout::Linear);
if (translated.private_address && d.atomic_op != amdgpu::AtomicOp::NONE &&
d.elem_size == sizeof(uint64_t))
d.memviol_lane_mask |= uint64_t{1} << lane;
Comment on lines +128 to +130
addr = translated.value;
d.per_lane_addr[lane] = addr;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ inline FlatAddress translate_flat_address(const Wavefront &wf, uint64_t address,
true, true};
}

// Global FLAT addresses are sign-extended from 48 bits on RDNA3-4 and
// 57 bits on CDNA5. Shared and private apertures are valid exceptions.
inline bool flat_address_in_hole(const Wavefront &wf, uint64_t address, FlatPrivateLayout layout) {
const uint64_t high48 = address >> 47;
if (high48 == 0 || high48 == (UINT64_MAX >> 47))
return false;
if (wf.cu().arch() == ROCJITSU_CODE_ARCH_CDNA5) {
const uint64_t high57 = address >> 56;
if (high57 == 0 || high57 == (UINT64_MAX >> 56))
return false;
}
if (wf.shared_aperture_base() != 0 && address >= wf.shared_aperture_base() &&
address <= wf.shared_aperture_limit())
return false;
return !translate_flat_address(wf, address, 0, layout).private_address;
}

inline uint64_t flat_vector_offset(uint64_t value, bool signed_offset, unsigned scale = 1) {
return signed_offset ? static_cast<uint64_t>(int64_t{static_cast<int32_t>(value)} * scale)
: value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ inline util::Result prepare_tensor(TensorDmaState &state, Wavefront &wf) {
if (wf.address_space()) {
state.access = wf.snapshot_vm_access();
if (!state.access)
state.outcome = VmAccessOutcome::Faulted;
state.outcome = VmAccessOutcome::Revoked;
}
return util::Result::success();
}
Expand Down Expand Up @@ -801,7 +801,7 @@ inline VmAccessOutcome resume_tensor_dma_state(TensorDmaState &state, Wavefront
(state.elements.empty() || state.elements.front().completed_bytes == 0)) {
state.access = wf.snapshot_vm_access();
if (!state.access)
return state.outcome = VmAccessOutcome::Faulted;
return state.outcome = VmAccessOutcome::Revoked;
}

while (state.next_element < state.elements.size()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1523,10 +1523,11 @@ void SimulatedKfd::init_command_processors_locked() {
});
cu->set_alu_exception_handler(
[this](amdgpu::Wavefront &wf) { return on_wave_alu_exception(wf); });
cu->set_memory_violation_handler(
[this](amdgpu::Wavefront &wf, uint64_t address, bool is_write, uint64_t issue_pc) {
return on_wave_memory_violation(wf, address, is_write, issue_pc);
});
cu->set_memory_violation_handler([this](amdgpu::Wavefront &wf, uint64_t address,
bool is_write, uint64_t issue_pc,
amdgpu::VmAccessOutcome outcome) {
return on_wave_memory_violation(wf, address, is_write, issue_pc, outcome);
});
}
});
g.cps_initialized = true;
Expand Down Expand Up @@ -5539,9 +5540,15 @@ bool SimulatedKfd::on_wave_execution_failure(amdgpu::Wavefront &wave, bool retai
retain_wave);
}

bool SimulatedKfd::on_wave_memory_violation(amdgpu::Wavefront &wave, uint64_t, bool,
uint64_t issue_pc) {
bool SimulatedKfd::on_wave_memory_violation(amdgpu::Wavefront &wave, uint64_t /*addr*/,
bool /*is_write*/, uint64_t issue_pc,
amdgpu::VmAccessOutcome outcome) {
assert(outcome == amdgpu::VmAccessOutcome::Faulted ||
outcome == amdgpu::VmAccessOutcome::ApertureViolation);
const bool translation_fault = outcome == amdgpu::VmAccessOutcome::Faulted;
constexpr uint32_t kTrapstsMemViol = 1u << 8;
constexpr uint32_t kTrapstsXnackError = 1u << 28;
const uint32_t cause = translation_fault ? kTrapstsXnackError : kTrapstsMemViol;
auto proc = find_process(wave.process_id());
if (!proc)
return false;
Expand All @@ -5565,36 +5572,40 @@ bool SimulatedKfd::on_wave_memory_violation(amdgpu::Wavefront &wave, uint64_t, b
gpu_id = queue->second.gpu_id;
has_runtime_signal = queue->second.has_runtime_exception_signal;
}
constexpr uint64_t kException = KFD_EC_MASK(EC_QUEUE_WAVE_MEMORY_VIOLATION);
// ROCr defers a memory violation to its VM fault handler, which only a
// translation fault wakes; an aperture violation terminates the queue itself.
const uint64_t exception = translation_fault ? KFD_EC_MASK(EC_QUEUE_WAVE_MEMORY_VIOLATION)
: KFD_EC_MASK(EC_QUEUE_WAVE_APERTURE_VIOLATION);
const bool replay = translation_fault && proc->xnack_enabled_.load(std::memory_order_relaxed);
if (!debugger_attached || ctx_base == 0 || !debug_stop_publishable(gpu_id)) {
if (!has_runtime_signal)
return false;
if (proc->xnack_enabled_.load(std::memory_order_relaxed))
if (replay)
wave.pc = issue_pc;
wave.set_trapsts(wave.trapsts() | kTrapstsXnackError);
wave.set_trapsts(wave.trapsts() | cause);
// ROCr removes its async handler after acknowledging the first fault on
// a queue. Later waves transfer ownership on publication, without waiting
// for another acknowledgment or retaining a stop for an absent debugger.
return defer_wave_exception_to_runtime(wave, kException,
return defer_wave_exception_to_runtime(wave, exception,
/*suspend_while_pending=*/true,
/*clear_debug_stop_on_success=*/false,
/*runtime_already_reserved=*/false,
/*runtime_failure_debuggable=*/false,
amdgpu::QueueExceptionDeliveryPolicy::PublishOrFail);
}
const auto saved = wave.debug_stop_state();
if (proc->xnack_enabled_.load(std::memory_order_relaxed))
if (replay)
wave.pc = issue_pc;
wave.set_trapsts(wave.trapsts() | kTrapstsXnackError);
wave.set_trapsts(wave.trapsts() | cause);
wave.debug_trap(0);
bool runtime_failure_debuggable = false;
if (!report_wave_stopped(proc, wave.queue_id(), gpu_id, ctx_base, ctx_size, kException,
if (!report_wave_stopped(proc, wave.queue_id(), gpu_id, ctx_base, ctx_size, exception,
/*retain_on_rejection=*/true, &runtime_failure_debuggable)) {
wave.restore_debug_stop_state(saved);
// Roll back the debugger stop without losing the runtime-owned fault's reason.
if (has_runtime_signal)
wave.set_trapsts(wave.trapsts() | kTrapstsXnackError);
return defer_wave_exception_to_runtime(wave, kException,
wave.set_trapsts(wave.trapsts() | cause);
return defer_wave_exception_to_runtime(wave, exception,
/*suspend_while_pending=*/true,
/*clear_debug_stop_on_success=*/false,
/*runtime_already_reserved=*/true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace rocjitsu {

namespace amdgpu {
class Wavefront;
enum class VmAccessOutcome : uint8_t;
}

namespace kmd {
Expand Down Expand Up @@ -708,11 +709,14 @@ class SimulatedKfd : public LinuxKfd {
bool on_wave_alu_exception(amdgpu::Wavefront &wf);

/// @brief Retain and report a memory fault to its debugger or runtime owner.
/// @details Sets TRAPSTS.xnack_error and uses the issuing PC for XNACK mode.
/// Returns true when an owner claims the wave; otherwise CU cancellation applies.
/// @details A translation fault sets TRAPSTS.xnack_error, reports a memory
/// violation and uses the issuing PC for XNACK mode. MEMVIOL without a
/// translation fault sets TRAPSTS.mem_viol and reports an aperture violation,
/// matching ROCr's trap handler. Returns true when an owner claims the wave;
/// otherwise CU cancellation applies.
/// @param issue_pc Address of the memory instruction that caused the fault.
bool on_wave_memory_violation(amdgpu::Wavefront &wf, uint64_t addr, bool is_write,
uint64_t issue_pc);
uint64_t issue_pc, amdgpu::VmAccessOutcome outcome);

/// @brief Toggle per-access debugger checks on every compute unit.
void set_debug_active_on_all_cus(bool active);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ inline constexpr uint64_t kAqlQueueWaveAbort = uint64_t{1};
/// @details Matches KFD_EC_MASK(EC_QUEUE_WAVE_MEMORY_VIOLATION).
inline constexpr uint64_t kAqlQueueMemoryViolation = uint64_t{1} << 4;

/// @brief ROCr queue exception status for MEMVIOL without a translation fault.
/// @details Matches KFD_EC_MASK(EC_QUEUE_WAVE_APERTURE_VIOLATION), which ROCr's
/// trap handler raises when EXCP_FLAG_PRIV.MEMVIOL is set without XNACK_ERROR.
inline constexpr uint64_t kAqlQueueApertureViolation = uint64_t{1} << 5;

/// @brief Normalized cluster dimensions decoded from an extended AQL dispatch packet.
struct ClusterDispatchShape {
uint32_t count_x = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,10 @@ AqlAdmissionResult admission_from_vm_outcome(VmAccessOutcome outcome) {
case VmAccessOutcome::Unavailable:
return {.status = AqlAdmissionStatus::Blocked};
case VmAccessOutcome::Faulted:
case VmAccessOutcome::ApertureViolation:
return {.status = AqlAdmissionStatus::Faulted};
case VmAccessOutcome::Malformed:
case VmAccessOutcome::Unsupported:
return {.status = AqlAdmissionStatus::Malformed};
case VmAccessOutcome::Revoked:
return {.status = AqlAdmissionStatus::Faulted};
Expand Down
Loading
Loading