mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 20:06:54 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc3cd2d2c9 | |||
| e6fce45d3a | |||
| 5b826c6e4a | |||
| 77d7722173 | |||
| 266c12cec6 | |||
| 7a0e1dafd7 | |||
| 1d41ddb378 | |||
| 5c83060481 |
@@ -1962,6 +1962,11 @@ bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::IsRegionRegistered(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
return !FindImagesInRegion(address, size, false).empty();
|
||||
}
|
||||
|
||||
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
uint32_t packed_clear);
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionRegistered(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
||||
|
||||
@@ -226,43 +226,37 @@ void CommandScheduler::PopPendingOperations() {
|
||||
}
|
||||
|
||||
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
|
||||
QueueOperation(std::move(operation), false);
|
||||
}
|
||||
|
||||
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
|
||||
QueueOperation(std::move(operation), true);
|
||||
}
|
||||
|
||||
void CommandScheduler::QueueOperation(Common::UniqueFunction<void>&& operation, bool priority) {
|
||||
CheckActive();
|
||||
EXIT_IF(!operation);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
if (m_operation_state == OperationState::Open) {
|
||||
m_pending_operations.push({std::move(operation), CurrentTick()});
|
||||
return;
|
||||
}
|
||||
if (g_deferred_callback_scheduler == this) {
|
||||
auto& queue = priority ? m_priority_operations : m_pending_operations;
|
||||
queue.push({std::move(operation), CurrentTick()});
|
||||
lock.unlock();
|
||||
operation();
|
||||
if (priority) {
|
||||
m_operation_available.notify_one();
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_operation_available.wait(lock,
|
||||
[this] { return m_operation_state == OperationState::Closed; });
|
||||
if (g_deferred_callback_scheduler != this) {
|
||||
m_operation_available.wait(lock,
|
||||
[this] { return m_operation_state == OperationState::Closed; });
|
||||
}
|
||||
lock.unlock();
|
||||
operation();
|
||||
}
|
||||
|
||||
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
|
||||
CheckActive();
|
||||
EXIT_IF(!operation);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
if (m_operation_state == OperationState::Open) {
|
||||
m_priority_operations.push({std::move(operation), CurrentTick()});
|
||||
lock.unlock();
|
||||
m_operation_available.notify_one();
|
||||
return;
|
||||
}
|
||||
if (g_deferred_callback_scheduler == this) {
|
||||
lock.unlock();
|
||||
operation();
|
||||
return;
|
||||
}
|
||||
m_operation_available.wait(lock,
|
||||
[this] { return m_operation_state == OperationState::Closed; });
|
||||
lock.unlock();
|
||||
operation();
|
||||
bool CommandScheduler::HasPendingPriorityOperations() {
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
return !m_priority_operations.empty() || m_priority_active;
|
||||
}
|
||||
|
||||
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
|
||||
|
||||
@@ -38,8 +38,10 @@ public:
|
||||
void PopPendingOperations();
|
||||
void DrainPriorityOperations();
|
||||
void WaitPriorityOperations(uint64_t tick);
|
||||
// Guest-memory completions use the priority queue; normal callbacks maintain GPU resources.
|
||||
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] bool HasPendingPriorityOperations();
|
||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
|
||||
@@ -82,6 +84,7 @@ private:
|
||||
|
||||
void BeginNext();
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void QueueOperation(Common::UniqueFunction<void>&& operation, bool priority);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
|
||||
MasterSemaphore m_master;
|
||||
|
||||
@@ -99,7 +99,12 @@ void RenderContext::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
vaddr, size);
|
||||
}
|
||||
const auto unmap = [this, vaddr, size] {
|
||||
if (m_command_scheduler.Active()) {
|
||||
// Check cache ownership on the GPU thread. Guest-memory callbacks can still
|
||||
// access a range with no cached data, so they must finish before it is unmapped.
|
||||
if (m_command_scheduler.Active() &&
|
||||
(m_buffer_cache.IsRegionRegistered(vaddr, size) ||
|
||||
m_texture_cache.IsRegionRegistered(vaddr, size) ||
|
||||
m_command_scheduler.HasPendingPriorityOperations())) {
|
||||
const auto tick = m_command_scheduler.CurrentTick();
|
||||
m_command_scheduler.Finish();
|
||||
m_command_scheduler.WaitPriorityOperations(tick);
|
||||
|
||||
@@ -17,21 +17,8 @@ Pair ExtractPair(EmitterState& state, uint32_t value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU64(state), result, low, high);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
|
||||
bool not_equal) {
|
||||
const auto compare = Binary(state, not_equal ? spv::OpINotEqual : spv::OpIEqual,
|
||||
TypeBoolVector(state, 2), lhs_value, rhs_value);
|
||||
return Unary(state, not_equal ? spv::OpAny : spv::OpAll, TypeBool(state), compare);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxF64(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto bits_type = TypeU64(state);
|
||||
const auto bits_type = TypeU32Vector(state, 2);
|
||||
const auto lhs_bits = Unary(state, spv::OpBitcast, bits_type, lhs);
|
||||
const auto rhs_bits = Unary(state, spv::OpBitcast, bits_type, rhs);
|
||||
const auto ordered = Binary(state, max_value ? spv::OpFOrdGreaterThan : spv::OpFOrdLessThan,
|
||||
@@ -52,17 +39,6 @@ uint32_t EmitMinMaxF64(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max
|
||||
rhs, result);
|
||||
}
|
||||
|
||||
uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
|
||||
spv::Op high_compare, spv::Op low_compare) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto high_equal = Binary(state, spv::OpIEqual, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto high_result = Binary(state, high_compare, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto low_result = Binary(state, low_compare, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto low_path = Binary(state, spv::OpLogicalAnd, TypeBool(state), high_equal, low_result);
|
||||
return Binary(state, spv::OpLogicalOr, TypeBool(state), high_result, low_path);
|
||||
}
|
||||
|
||||
uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signed_value) {
|
||||
const auto operand_type = signed_value ? TypeI32(state) : TypeU32(state);
|
||||
const auto pair_type = signed_value ? TypeI32Pair(state) : TypeU32Pair(state);
|
||||
@@ -80,86 +56,6 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
|
||||
return signed_value ? Unary(state, spv::OpBitcast, TypeU32(state), high) : high;
|
||||
}
|
||||
|
||||
uint32_t EmitShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32_t shift) {
|
||||
const auto pair = ExtractPair(state, value);
|
||||
const auto amount = EmitAndConstant(state, shift, 63u);
|
||||
const auto word_shift = EmitAndConstant(state, amount, 31u);
|
||||
const auto at_least_32 =
|
||||
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), amount, ConstantU32(state, 32));
|
||||
const auto nonzero =
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), amount, ConstantU32(state, 0));
|
||||
const auto carry_count = EmitAndConstant(
|
||||
state, Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 32), word_shift), 31u);
|
||||
if (opcode == spv::OpShiftLeftLogical) {
|
||||
const auto low = Binary(state, opcode, TypeU32(state), pair.low, word_shift);
|
||||
const auto carry =
|
||||
Select(state, TypeU32(state), nonzero,
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), pair.low, carry_count),
|
||||
ConstantU32(state, 0));
|
||||
const auto high =
|
||||
Binary(state, spv::OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, opcode, TypeU32(state), pair.high, word_shift), carry);
|
||||
return MakePair(state,
|
||||
Select(state, TypeU32(state), at_least_32, ConstantU32(state, 0), low),
|
||||
Select(state, TypeU32(state), at_least_32, low, high));
|
||||
}
|
||||
const auto high = Binary(state, opcode, TypeU32(state), pair.high, word_shift);
|
||||
const auto carry =
|
||||
Select(state, TypeU32(state), nonzero,
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), pair.high, carry_count),
|
||||
ConstantU32(state, 0));
|
||||
const auto low = Binary(
|
||||
state, spv::OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), pair.low, word_shift), carry);
|
||||
const auto fill = opcode == spv::OpShiftRightArithmetic
|
||||
? Binary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, 31))
|
||||
: ConstantU32(state, 0);
|
||||
return MakePair(state, Select(state, TypeU32(state), at_least_32, high, low),
|
||||
Select(state, TypeU32(state), at_least_32, fill, high));
|
||||
}
|
||||
|
||||
uint32_t EmitConstantShift64(EmitterState& state, spv::Op opcode, uint32_t value, uint32_t shift) {
|
||||
shift &= 63u;
|
||||
if (shift == 0u) {
|
||||
return value;
|
||||
}
|
||||
const auto pair = ExtractPair(state, value);
|
||||
if (opcode == spv::OpShiftLeftLogical) {
|
||||
if (shift < 32u) {
|
||||
const auto low = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), pair.low,
|
||||
ConstantU32(state, shift));
|
||||
const auto high = Binary(state, spv::OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
pair.high, ConstantU32(state, shift)),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state),
|
||||
pair.low, ConstantU32(state, 32u - shift)));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
return MakePair(state, ConstantU32(state, 0),
|
||||
shift == 32u ? pair.low
|
||||
: Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
pair.low, ConstantU32(state, shift - 32u)));
|
||||
}
|
||||
if (shift < 32u) {
|
||||
const auto low = Binary(state, spv::OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), pair.low,
|
||||
ConstantU32(state, shift)),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), pair.high,
|
||||
ConstantU32(state, 32u - shift)));
|
||||
const auto high =
|
||||
Binary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, shift));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
const auto high = opcode == spv::OpShiftRightArithmetic
|
||||
? Binary(state, spv::OpShiftRightArithmetic, TypeU32(state), pair.high,
|
||||
ConstantU32(state, 31u))
|
||||
: ConstantU32(state, 0);
|
||||
const auto low = shift == 32u ? pair.high
|
||||
: Binary(state, opcode, TypeU32(state), pair.high,
|
||||
ConstantU32(state, shift - 32u));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
|
||||
bool max_value) {
|
||||
const auto ab = signed_value ? EmitMinMaxI32Value(state, a, b, max_value)
|
||||
@@ -231,15 +127,18 @@ uint32_t EmitFPFma32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
}
|
||||
|
||||
uint32_t EmitFPMad32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
// Legacy MAD/MAC round and flush between multiply and add, irrespective of SP_DENORM.
|
||||
a = EmitFlushF32DenormToSignedZero(state, a);
|
||||
b = EmitFlushF32DenormToSignedZero(state, b);
|
||||
c = EmitFlushF32DenormToSignedZero(state, c);
|
||||
// Explicit denormal checks typically drop 3DMiniGolf menu FPS from 28-29 to 21-24
|
||||
// on NVIDIA RTX 5080 Laptop GPU, so leave them disabled for this experiment.
|
||||
// a = EmitFlushF32DenormToSignedZero(state, a);
|
||||
// b = EmitFlushF32DenormToSignedZero(state, b);
|
||||
// c = EmitFlushF32DenormToSignedZero(state, c);
|
||||
const auto product = EmitFPMul32(state, a, b);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, product, spv::DecorationNoContraction);
|
||||
const auto sum = EmitFPAdd32(state, EmitFlushF32DenormToSignedZero(state, product), c);
|
||||
// const auto sum = EmitFPAdd32(state, EmitFlushF32DenormToSignedZero(state, product), c);
|
||||
const auto sum = EmitFPAdd32(state, product, c);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, sum, spv::DecorationNoContraction);
|
||||
return EmitFlushF32DenormToSignedZero(state, sum);
|
||||
// return EmitFlushF32DenormToSignedZero(state, sum);
|
||||
return sum;
|
||||
}
|
||||
|
||||
uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
@@ -256,57 +155,15 @@ uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c)
|
||||
}
|
||||
|
||||
uint32_t EmitFindUMsb64(EmitterState& state, uint32_t value) {
|
||||
const auto pair = ExtractPair(state, value);
|
||||
const auto high_i = state.builder.AllocateId();
|
||||
const auto low_i = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), high_i, GlslStd450(state),
|
||||
GLSLstd450FindUMsb, pair.high);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeI32(state), low_i, GlslStd450(state),
|
||||
GLSLstd450FindUMsb, pair.low);
|
||||
const auto high = Unary(state, spv::OpBitcast, TypeU32(state), high_i);
|
||||
const auto low = Unary(state, spv::OpBitcast, TypeU32(state), low_i);
|
||||
const auto high_nonzero =
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), high_nonzero,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), high, ConstantU32(state, 32)), low);
|
||||
}
|
||||
|
||||
uint32_t EmitIMul64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto low = Binary(state, spv::OpIMul, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto high0 = EmitMulHigh(state, lhs.low, rhs.low, false);
|
||||
const auto high1 = Binary(state, spv::OpIMul, TypeU32(state), lhs.low, rhs.high);
|
||||
const auto high2 = Binary(state, spv::OpIMul, TypeU32(state), lhs.high, rhs.low);
|
||||
return MakePair(state, low,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), high0, high1), high2));
|
||||
}
|
||||
|
||||
uint32_t EmitISub64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto low = Binary(state, spv::OpISub, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto borrow = Binary(state, spv::OpULessThan, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto borrow_u32 =
|
||||
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0));
|
||||
const auto high0 = Binary(state, spv::OpISub, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = Binary(state, spv::OpISub, TypeU32(state), high0, borrow_u32);
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
|
||||
uint32_t EmitIAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto low_pair = state.builder.AllocateId();
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto carry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, low_pair, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), carry, low_pair, 1);
|
||||
const auto high0 = Binary(state, spv::OpIAdd, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = Binary(state, spv::OpIAdd, TypeU32(state), high0, carry);
|
||||
return MakePair(state, low, high);
|
||||
// GLSL FindUMsb is restricted to 32-bit components.
|
||||
const auto unpacked = Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), value);
|
||||
const auto pair =
|
||||
ExtractPair(state, EmitExt(state, TypeU32Vector(state, 2), GLSLstd450FindUMsb, {unpacked}));
|
||||
const auto high_found =
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, UINT32_MAX));
|
||||
return Select(state, TypeU32(state), high_found,
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), pair.high, ConstantU32(state, 32)),
|
||||
pair.low);
|
||||
}
|
||||
|
||||
uint32_t EmitConvertU16U32(EmitterState& state, uint32_t arg0) {
|
||||
@@ -335,7 +192,7 @@ uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0) {
|
||||
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0) {
|
||||
const auto converted = Unary(state, spv::OpFConvert, TypeF32(state), arg0);
|
||||
const auto source = EmitNative<spv::OpCompositeExtract, IR::Type::U32>(
|
||||
state, Unary(state, spv::OpBitcast, TypeU64(state), arg0), 1u);
|
||||
state, Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), arg0), 1u);
|
||||
const auto exponent = EmitAndConstant(state, source, 0x7ff00000u);
|
||||
const auto overflow =
|
||||
Binary(state, spv::OpLogicalAnd, TypeBool(state),
|
||||
@@ -353,7 +210,21 @@ uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0) {
|
||||
EmitFlushF32DenormToSignedZero(state, arg0));
|
||||
}
|
||||
|
||||
uint32_t EmitCompositeConstructU64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
|
||||
arg1 = arg1.Resolve();
|
||||
return arg1.IsImmediate() && arg1.U32() == 0u
|
||||
? Unary(ctx.state, spv::OpUConvert, TypeU64(ctx.state), arg0)
|
||||
: PackU64(ctx.state, arg0, ctx.Def(arg1));
|
||||
}
|
||||
|
||||
uint32_t EmitCompositeExtractU64(EmitterState& state, uint32_t arg0, IR::Value arg1) {
|
||||
const auto value = arg1.U32() == 0u ? arg0
|
||||
: Binary(state, spv::OpShiftRightLogical, TypeU64(state),
|
||||
arg0, ConstantU32(state, 32));
|
||||
return Unary(state, spv::OpUConvert, TypeU32(state), value);
|
||||
}
|
||||
|
||||
uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value arg1) {
|
||||
return EmitNative<spv::OpCompositeExtract, IR::Type::U32>(state, arg0, arg1.U32());
|
||||
}
|
||||
|
||||
@@ -385,33 +256,11 @@ uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0) {
|
||||
return Select(state, TypeU32(state), negative, neg, value);
|
||||
}
|
||||
|
||||
uint32_t EmitShiftLeftLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
|
||||
auto& state = ctx.state;
|
||||
return arg1.Resolve().IsImmediate()
|
||||
? EmitConstantShift64(state, spv::OpShiftLeftLogical, arg0, arg1.Resolve().U32())
|
||||
: EmitShift64(state, spv::OpShiftLeftLogical, arg0, ctx.Def(arg1));
|
||||
}
|
||||
|
||||
uint32_t EmitShiftRightLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
|
||||
auto& state = ctx.state;
|
||||
return arg1.Resolve().IsImmediate()
|
||||
? EmitConstantShift64(state, spv::OpShiftRightLogical, arg0, arg1.Resolve().U32())
|
||||
: EmitShift64(state, spv::OpShiftRightLogical, arg0, ctx.Def(arg1));
|
||||
}
|
||||
|
||||
uint32_t EmitShiftRightArithmetic64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
|
||||
auto& state = ctx.state;
|
||||
return arg1.Resolve().IsImmediate()
|
||||
? EmitConstantShift64(state, spv::OpShiftRightArithmetic, arg0, arg1.Resolve().U32())
|
||||
: EmitShift64(state, spv::OpShiftRightArithmetic, arg0, ctx.Def(arg1));
|
||||
}
|
||||
|
||||
uint32_t EmitBitwiseAnd64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return Binary(state, spv::OpBitwiseAnd, TypeU64(state), arg0, arg1);
|
||||
}
|
||||
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0) {
|
||||
const auto pair = ExtractPair(state, Unary(state, spv::OpBitCount, TypeU64(state), arg0));
|
||||
// Vulkan bit-count operands remain 32-bit, including when counting a U64.
|
||||
const auto unpacked = Unary(state, spv::OpBitcast, TypeU32Vector(state, 2), arg0);
|
||||
const auto pair =
|
||||
ExtractPair(state, Unary(state, spv::OpBitCount, TypeU32Vector(state, 2), unpacked));
|
||||
return Binary(state, spv::OpIAdd, TypeU32(state), pair.low, pair.high);
|
||||
}
|
||||
|
||||
@@ -465,38 +314,6 @@ uint32_t EmitUMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32
|
||||
return EmitMed3(state, arg0, arg1, arg2, false);
|
||||
}
|
||||
|
||||
uint32_t EmitIEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareEqual64(state, arg0, arg1, false);
|
||||
}
|
||||
|
||||
uint32_t EmitINotEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareEqual64(state, arg0, arg1, true);
|
||||
}
|
||||
|
||||
uint32_t EmitULessThan64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpULessThan, spv::OpULessThan);
|
||||
}
|
||||
|
||||
uint32_t EmitSLessThan64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpSLessThan, spv::OpULessThan);
|
||||
}
|
||||
|
||||
uint32_t EmitUGreaterThan64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpUGreaterThan, spv::OpUGreaterThan);
|
||||
}
|
||||
|
||||
uint32_t EmitSLessThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpSLessThan, spv::OpULessThanEqual);
|
||||
}
|
||||
|
||||
uint32_t EmitULessThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpULessThan, spv::OpULessThanEqual);
|
||||
}
|
||||
|
||||
uint32_t EmitUGreaterThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return CompareOrdered64(state, arg0, arg1, spv::OpUGreaterThan, spv::OpUGreaterThanEqual);
|
||||
}
|
||||
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0) {
|
||||
return EmitNative<spv::OpFUnordNotEqual, IR::Type::U1>(state, arg0, arg0);
|
||||
}
|
||||
|
||||
@@ -125,20 +125,23 @@ uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
return EmitLogicalAndBool(state, selected, class_match);
|
||||
}
|
||||
|
||||
uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) {
|
||||
const auto bits = EmitBitcastF32ToU32(state, value);
|
||||
const auto sign_bits = EmitAndConstant(state, bits, 0x80000000u);
|
||||
const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu);
|
||||
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
|
||||
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
|
||||
const auto quiet_bits = EmitAndConstant(state, mantissa_bits, 0x00400000u);
|
||||
static uint32_t EmitClassMaskBits(EmitterState& state, uint32_t bits, uint32_t mask, bool half) {
|
||||
const uint32_t sign_mask = half ? 0x8000u : 0x80000000u;
|
||||
const uint32_t exponent_mask = half ? 0x7c00u : 0x7f800000u;
|
||||
const uint32_t mantissa_mask = half ? 0x03ffu : 0x007fffffu;
|
||||
const uint32_t quiet_mask = half ? 0x0200u : 0x00400000u;
|
||||
const auto sign_bits = EmitAndConstant(state, bits, sign_mask);
|
||||
const auto abs_bits = EmitAndConstant(state, bits, sign_mask - 1u);
|
||||
const auto exponent_bits = EmitAndConstant(state, abs_bits, exponent_mask);
|
||||
const auto mantissa_bits = EmitAndConstant(state, abs_bits, mantissa_mask);
|
||||
const auto quiet_bits = EmitAndConstant(state, mantissa_bits, quiet_mask);
|
||||
|
||||
const auto sign = EmitCompareU32Constant(state, spv::OpINotEqual, sign_bits, 0);
|
||||
const auto positive = EmitLogicalNotBool(state, sign);
|
||||
const auto exponent_zero = EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0);
|
||||
const auto exponent_nonzero = EmitLogicalNotBool(state, exponent_zero);
|
||||
const auto exponent_inf =
|
||||
EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, 0x7f800000u);
|
||||
EmitCompareU32Constant(state, spv::OpIEqual, exponent_bits, exponent_mask);
|
||||
const auto finite_exponent = EmitLogicalNotBool(state, exponent_inf);
|
||||
const auto mantissa_zero = EmitCompareU32Constant(state, spv::OpIEqual, mantissa_bits, 0);
|
||||
const auto mantissa_nonzero = EmitLogicalNotBool(state, mantissa_zero);
|
||||
@@ -178,6 +181,14 @@ uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) {
|
||||
EmitClassMaskBitMatch(state, mask, 9, EmitLogicalAndBool(state, inf, positive)));
|
||||
}
|
||||
|
||||
uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) {
|
||||
return EmitClassMaskBits(state, EmitBitcastF32ToU32(state, value), mask, false);
|
||||
}
|
||||
|
||||
uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask) {
|
||||
return EmitClassMaskBits(state, bits, mask, true);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto lhs_class = EmitClassifyF32(state, lhs);
|
||||
const auto rhs_class = EmitClassifyF32(state, rhs);
|
||||
|
||||
@@ -136,8 +136,8 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
const auto scalar_type = image.atomic64 ? TypeScalarU64(state)
|
||||
: ImageScalarType(state, image.numeric_class);
|
||||
const auto scalar_type =
|
||||
image.atomic64 ? TypeU64(state) : ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
info.spirv_dimension, image.depth_compare ? 1u : 0u, info.arrayed,
|
||||
info.multisampled, sampled, format);
|
||||
|
||||
@@ -19,8 +19,8 @@ uint32_t Vector(EmitterState& s, uint32_t type, std::span<const uint32_t> compon
|
||||
|
||||
uint32_t NodeWord(EmitterState& s, uint32_t block, uint32_t word) {
|
||||
const auto offset = Binary(s, spv::OpShiftLeftLogical, TypeU32(s), word, ConstantU32(s, 2));
|
||||
const auto address = Binary(s, spv::OpIAdd, TypeScalarU64(s), block,
|
||||
Unary(s, spv::OpUConvert, TypeScalarU64(s), offset));
|
||||
const auto address =
|
||||
Binary(s, spv::OpIAdd, TypeU64(s), block, Unary(s, spv::OpUConvert, TypeU64(s), offset));
|
||||
const auto pointer = Unary(s, spv::OpConvertUToPtr, TypePhysicalU32Pointer(s), address);
|
||||
const auto result = s.builder.AllocateId();
|
||||
s.builder.AddFunction(spv::OpLoad, TypeU32(s), result, pointer, spv::MemoryAccessAlignedMask, 4u);
|
||||
@@ -224,7 +224,7 @@ uint32_t Boxes(EmitterState& s, uint32_t first, uint32_t second, uint32_t half,
|
||||
|
||||
void DefineBvhIntersect(EmitterState& s) {
|
||||
if (!s.requirements.bvh) return;
|
||||
const auto u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), wide = TypeScalarU64(s);
|
||||
const auto u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), wide = TypeU64(s);
|
||||
const auto vec4 = TypeU32Vector(s, 4), vec3 = TypeF32Vector(s, 3);
|
||||
const auto signature = s.builder.Type(spv::OpTypeFunction, vec4, vec4, wide, f, vec3, vec3, vec3);
|
||||
s.bvh_intersect_function = s.builder.AllocateId();
|
||||
@@ -260,22 +260,24 @@ void DefineBvhIntersect(EmitterState& s) {
|
||||
const auto bary = Binary(s, spv::OpINotEqual, b, and_bits(words[3], 0x01000000), ConstantU32(s, 0));
|
||||
const auto sort = Binary(s, spv::OpINotEqual, b, and_bits(words[1], 0x80000000), ConstantU32(s, 0));
|
||||
const auto grow = and_bits(shr(words[1], 23), 0xff);
|
||||
const auto base = Binary(s, spv::OpShiftLeftLogical, wide,
|
||||
DeviceAddressFromWords(s, words[0], and_bits(words[1], 0xff)), ConstantDeviceAddress(s, 8));
|
||||
const auto index = Binary(s, spv::OpShiftRightLogical, wide, node, ConstantDeviceAddress(s, 3));
|
||||
const auto base = Binary(s, spv::OpShiftLeftLogical, wide,
|
||||
PackU64(s, words[0], and_bits(words[1], 0xff)), ConstantU32(s, 8));
|
||||
const auto index = Binary(s, spv::OpShiftRightLogical, wide, node, ConstantU32(s, 3));
|
||||
const auto last_index = Binary(s, spv::OpIAdd, wide, index,
|
||||
Select(s, wide, full, ConstantDeviceAddress(s, 1), ConstantDeviceAddress(s, 0)));
|
||||
Select(s, wide, full, ConstantU64(s, 1), ConstantU64(s, 0)));
|
||||
const auto address = Binary(s, spv::OpIAdd, wide, base,
|
||||
Binary(s, spv::OpShiftLeftLogical, wide, index, ConstantDeviceAddress(s, 6)));
|
||||
const auto last_address = Binary(s, spv::OpIAdd, wide, base,
|
||||
Binary(s, spv::OpShiftLeftLogical, wide, last_index, ConstantDeviceAddress(s, 6)));
|
||||
Binary(s, spv::OpShiftLeftLogical, wide, index, ConstantU32(s, 6)));
|
||||
const auto last_address =
|
||||
Binary(s, spv::OpIAdd, wide, base,
|
||||
Binary(s, spv::OpShiftLeftLogical, wide, last_index, ConstantU32(s, 6)));
|
||||
auto valid = Binary(s, spv::OpIEqual, b, shr(words[3], 28), ConstantU32(s, 8));
|
||||
const auto require = [&](uint32_t condition) {
|
||||
valid = Binary(s, spv::OpLogicalAnd, b, valid, condition);
|
||||
};
|
||||
require(Binary(s, spv::OpULessThanEqual, b, kind, ConstantU32(s, 5)));
|
||||
require(Binary(s, spv::OpULessThanEqual, b, last_index, DeviceAddressFromWords(s, words[2], and_bits(words[3], 0x3ff))));
|
||||
require(Binary(s, spv::OpULessThan, b, last_address, ConstantDeviceAddress(s, uint64_t{1} << 48)));
|
||||
require(Binary(s, spv::OpULessThanEqual, b, last_index,
|
||||
PackU64(s, words[2], and_bits(words[3], 0x3ff))));
|
||||
require(Binary(s, spv::OpULessThan, b, last_address, ConstantU64(s, uint64_t {1} << 48)));
|
||||
require(Unary(s, spv::OpLogicalNot, b, Unary(s, spv::OpIsNan, b, extent)));
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
for (const auto value: {origin[i], direction[i], inverse[i]})
|
||||
@@ -292,13 +294,13 @@ void DefineBvhIntersect(EmitterState& s) {
|
||||
const auto invalid = Vector(s, vec4, invalid_words);
|
||||
const auto result = EmitValueOrDefaultIfCondition(s, valid, vec4, invalid, [&] {
|
||||
const auto first = GetBdaPointer(s, address);
|
||||
const auto second = EmitValueOrDefaultIfCondition(s, full, wide, ConstantDeviceAddress(s, 0), [&] {
|
||||
return GetBdaPointer(s, Binary(s, spv::OpIAdd, wide, address, ConstantDeviceAddress(s, 64)));
|
||||
const auto second = EmitValueOrDefaultIfCondition(s, full, wide, ConstantU64(s, 0), [&] {
|
||||
return GetBdaPointer(s, Binary(s, spv::OpIAdd, wide, address, ConstantU64(s, 64)));
|
||||
});
|
||||
const auto present = Binary(s, spv::OpLogicalAnd, b,
|
||||
Binary(s, spv::OpINotEqual, b, first, ConstantDeviceAddress(s, 0)),
|
||||
const auto present = Binary(
|
||||
s, spv::OpLogicalAnd, b, Binary(s, spv::OpINotEqual, b, first, ConstantU64(s, 0)),
|
||||
Binary(s, spv::OpLogicalOr, b, Unary(s, spv::OpLogicalNot, b, full),
|
||||
Binary(s, spv::OpINotEqual, b, second, ConstantDeviceAddress(s, 0))));
|
||||
Binary(s, spv::OpINotEqual, b, second, ConstantU64(s, 0))));
|
||||
return EmitValueOrDefaultIfCondition(s, present, vec4, invalid, [&] {
|
||||
const auto tri_label = s.builder.AllocateId(), box_label = s.builder.AllocateId();
|
||||
const auto merge = s.builder.AllocateId();
|
||||
@@ -336,9 +338,8 @@ uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return Vector(s, TypeF32Vector(s, 3), values);
|
||||
};
|
||||
const auto descriptor = ctx.Arg(inst, 0);
|
||||
const auto node = node_words == 2u
|
||||
? DeviceAddressFromWords(s, ctx.Arg(*ray, 0), ctx.Arg(*ray, 1))
|
||||
: Unary(s, spv::OpUConvert, TypeScalarU64(s), ctx.Arg(*ray, 0));
|
||||
const auto node = node_words == 2u ? PackU64(s, ctx.Arg(*ray, 0), ctx.Arg(*ray, 1))
|
||||
: Unary(s, spv::OpUConvert, TypeU64(s), ctx.Arg(*ray, 0));
|
||||
const auto extent = Unary(s, spv::OpBitcast, TypeF32(s), ctx.Arg(*ray, node_words));
|
||||
const auto origin = vector(node_words + 1u), direction = vector(node_words + 4u),
|
||||
inverse = vector(node_words + 7u);
|
||||
|
||||
@@ -430,16 +430,16 @@ uint32_t EmitWqmU64(EmitterState& state, uint32_t value) {
|
||||
const auto quad_bits = state.builder.AllocateId();
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_one, value,
|
||||
ConstantU64(state, 0x0000000100000001ull));
|
||||
ConstantU32(state, 1));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_one, value, shifted_one);
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU64(state), shifted_two, merged_one,
|
||||
ConstantU64(state, 0x0000000200000002ull));
|
||||
ConstantU32(state, 2));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU64(state), merged_two, merged_one,
|
||||
shifted_two);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU64(state), quad_bits, merged_two,
|
||||
ConstantU64(state, 0x1111111111111111ull));
|
||||
state.builder.AddFunction(spv::OpIMul, TypeU64(state), result, quad_bits,
|
||||
ConstantU64(state, 0x0000000f0000000full));
|
||||
ConstantU64(state, 15));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -930,9 +930,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Define(inst, EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), result_type, zero, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassImage,
|
||||
image.atomic64 ? TypeScalarU64(state) : TypeU32(state));
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassImage,
|
||||
image.atomic64 ? TypeU64(state) : TypeU32(state));
|
||||
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
|
||||
ImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
|
||||
@@ -34,16 +34,16 @@ uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
|
||||
EMIT_NATIVE(ConvertF64U32, OpConvertUToF, F64, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructU64, OpCompositeConstruct, U64, uint32_t, uint32_t)
|
||||
uint32_t EmitCompositeConstructU64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
|
||||
EMIT_NATIVE(CompositeConstructU32x2, OpCompositeConstruct, U32x2, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructU32x3, OpCompositeConstruct, U32x3, uint32_t, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructF32x2, OpCompositeConstruct, F32x2, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructU32x4, OpCompositeConstruct, U32x4, uint32_t, uint32_t, uint32_t,
|
||||
uint32_t)
|
||||
uint32_t EmitCompositeExtractU64(EmitterState& state, uint32_t arg0, IR::Value arg1);
|
||||
inline constexpr auto EmitCompositeExtractU32x2 = EmitCompositeExtractU64;
|
||||
inline constexpr auto EmitCompositeExtractU32x3 = EmitCompositeExtractU64;
|
||||
inline constexpr auto EmitCompositeExtractU32x4 = EmitCompositeExtractU64;
|
||||
uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value arg1);
|
||||
inline constexpr auto EmitCompositeExtractU32x3 = EmitCompositeExtractU32x2;
|
||||
inline constexpr auto EmitCompositeExtractU32x4 = EmitCompositeExtractU32x2;
|
||||
inline constexpr auto EmitPackHalf2x16 = EmitGlsl<GLSLstd450PackHalf2x16, IR::Type::U32, uint32_t>;
|
||||
inline constexpr auto EmitPackSnorm2x16 =
|
||||
EmitGlsl<GLSLstd450PackSnorm2x16, IR::Type::U32, uint32_t>;
|
||||
@@ -63,9 +63,9 @@ EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ISub32, OpISub, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IMul32, OpIMul, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(UDiv32, OpUDiv, U32, uint32_t, uint32_t)
|
||||
uint32_t EmitIAdd64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitISub64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitIMul64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
EMIT_NATIVE(IAdd64, OpIAdd, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ISub64, OpISub, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IMul64, OpIMul, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IAddCarry32, OpIAddCarry, U32x2, uint32_t, uint32_t)
|
||||
uint32_t EmitSMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
@@ -73,14 +73,26 @@ uint32_t EmitIAbs32(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(ShiftLeftLogical32, OpShiftLeftLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightLogical32, OpShiftRightLogical, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ShiftRightArithmetic32, OpShiftRightArithmetic, U32, uint32_t, uint32_t)
|
||||
uint32_t EmitShiftLeftLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
|
||||
uint32_t EmitShiftRightLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
|
||||
uint32_t EmitShiftRightArithmetic64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
|
||||
template <spv::Op opcode>
|
||||
uint32_t EmitShift64(ValueEmitContext& ctx, uint32_t value, IR::Value shift) {
|
||||
shift = shift.Resolve();
|
||||
if (shift.IsImmediate()) {
|
||||
const auto amount = shift.U32() & 63u;
|
||||
return amount == 0u ? value
|
||||
: Binary(ctx.state, opcode, TypeU64(ctx.state), value,
|
||||
ConstantU32(ctx.state, amount));
|
||||
}
|
||||
return Binary(ctx.state, opcode, TypeU64(ctx.state), value,
|
||||
EmitAndConstant(ctx.state, ctx.Def(shift), 63u));
|
||||
}
|
||||
inline constexpr auto EmitShiftLeftLogical64 = EmitShift64<spv::OpShiftLeftLogical>;
|
||||
inline constexpr auto EmitShiftRightLogical64 = EmitShift64<spv::OpShiftRightLogical>;
|
||||
inline constexpr auto EmitShiftRightArithmetic64 = EmitShift64<spv::OpShiftRightArithmetic>;
|
||||
EMIT_NATIVE(BitwiseAnd32, OpBitwiseAnd, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitwiseOr32, OpBitwiseOr, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitwiseXor32, OpBitwiseXor, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitwiseNot32, OpNot, U32, uint32_t)
|
||||
uint32_t EmitBitwiseAnd64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
EMIT_NATIVE(BitwiseAnd64, OpBitwiseAnd, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(BitReverse32, OpBitReverse, U32, uint32_t)
|
||||
EMIT_NATIVE(BitCount32, OpBitCount, U32, uint32_t)
|
||||
uint32_t EmitBitCount64(EmitterState& state, uint32_t arg0);
|
||||
@@ -107,14 +119,14 @@ EMIT_NATIVE(UGreaterThan32, OpUGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(INotEqual32, OpINotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SGreaterThanEqual32, OpSGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(UGreaterThanEqual32, OpUGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
uint32_t EmitIEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitINotEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitULessThan64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitSLessThan64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitSLessThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitULessThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUGreaterThanEqual64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
uint32_t EmitUGreaterThan64(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
EMIT_NATIVE(IEqual64, OpIEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(INotEqual64, OpINotEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ULessThan64, OpULessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SLessThan64, OpSLessThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SLessThanEqual64, OpSLessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ULessThanEqual64, OpULessThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(UGreaterThanEqual64, OpUGreaterThanEqual, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(UGreaterThan64, OpUGreaterThan, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalOr, OpLogicalOr, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalAnd, OpLogicalAnd, U1, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(LogicalXor, OpLogicalNotEqual, U1, uint32_t, uint32_t)
|
||||
@@ -152,6 +164,7 @@ inline constexpr auto EmitFPOrdGreaterThanEqual32 = EmitFloatCompare32<spv::OpFO
|
||||
inline constexpr auto EmitFPUnordGreaterThanEqual32 = EmitFloatCompare32<spv::OpFUnordGreaterThanEqual>;
|
||||
uint32_t EmitFPIsNan32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
|
||||
inline constexpr auto EmitFPCmpClass16 = EmitClassMaskF16;
|
||||
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPSub32, OpFSub, F32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPMul32, OpFMul, F32, uint32_t, uint32_t)
|
||||
|
||||
@@ -98,7 +98,7 @@ struct EmitterState {
|
||||
uint32_t void_type = 0;
|
||||
uint32_t bool_type = 0;
|
||||
uint32_t u32_type = 0;
|
||||
uint32_t native_u64_type = 0;
|
||||
uint32_t u64_type = 0;
|
||||
uint32_t i32_type = 0;
|
||||
uint32_t f32_type = 0;
|
||||
uint32_t f64_type = 0;
|
||||
@@ -168,7 +168,6 @@ uint32_t TypeBool(EmitterState& state);
|
||||
uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeScalarU64(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
@@ -221,6 +220,12 @@ inline uint32_t Unary(EmitterState& state, spv::Op opcode, uint32_t type, uint32
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t PackU64(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto pair = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 2), pair, low, high);
|
||||
return Unary(state, spv::OpBitcast, TypeU64(state), pair);
|
||||
}
|
||||
|
||||
inline uint32_t Binary(EmitterState& state, spv::Op opcode, uint32_t type, uint32_t lhs,
|
||||
uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
@@ -510,6 +515,7 @@ uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
uint32_t class_match);
|
||||
|
||||
uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask);
|
||||
uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask);
|
||||
|
||||
uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value);
|
||||
|
||||
@@ -530,8 +536,6 @@ void EmitProgram(EmitterState& state);
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
void DefineBvhIntersect(EmitterState& state);
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value);
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high);
|
||||
|
||||
// These templates accept local lambdas from several emitter translation units.
|
||||
template <typename Fn>
|
||||
|
||||
@@ -133,22 +133,22 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem
|
||||
}
|
||||
uint32_t address = 0;
|
||||
if (mem.address_is_full) {
|
||||
address = DeviceAddressFromWords(state, low, ctx.Arg(inst, 2));
|
||||
address = PackU64(state, low, ctx.Arg(inst, 2));
|
||||
} else {
|
||||
const auto* handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetAddressResource ||
|
||||
handle->NumArgs() != 2) {
|
||||
ctx.Fail(inst, "has no address base pair");
|
||||
return ConstantDeviceAddress(state, 0);
|
||||
return ConstantU64(state, 0);
|
||||
}
|
||||
auto base_low = ctx.Arg(*handle, 0);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
base_low = Binary(state, spv::OpBitwiseAnd, TypeU32(state), base_low,
|
||||
ConstantU32(state, ~3u));
|
||||
}
|
||||
const auto base = DeviceAddressFromWords(state, base_low, ctx.Arg(*handle, 1));
|
||||
address = Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), low));
|
||||
const auto base = PackU64(state, base_low, ctx.Arg(*handle, 1));
|
||||
address = Binary(state, spv::OpIAdd, TypeU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeU64(state), low));
|
||||
}
|
||||
auto immediate = static_cast<int32_t>(mem.offset);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
@@ -156,9 +156,8 @@ uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Mem
|
||||
}
|
||||
return immediate == 0
|
||||
? address
|
||||
: Binary(state, spv::OpIAdd, TypeScalarU64(state), address,
|
||||
ConstantDeviceAddress(
|
||||
state, static_cast<uint64_t>(static_cast<int64_t>(immediate))));
|
||||
: Binary(state, spv::OpIAdd, TypeU64(state), address,
|
||||
ConstantU64(state, static_cast<uint64_t>(static_cast<int64_t>(immediate))));
|
||||
}
|
||||
|
||||
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
|
||||
@@ -185,7 +184,7 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto bda = GetBdaPointer(state, address);
|
||||
const auto present =
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), bda, ConstantDeviceAddress(state, 0));
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), bda, ConstantU64(state, 0));
|
||||
return EmitValueOrZeroIfCondition(state, present, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer,
|
||||
@@ -201,8 +200,8 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, active, [&]() {
|
||||
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
|
||||
ConstantU64(state, ~uint64_t {3}));
|
||||
const auto first = LoadBdaDword(ctx, aligned);
|
||||
const auto byte =
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
@@ -212,8 +211,8 @@ uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint3
|
||||
: Binary(state, bits == 16u ? spv::OpUGreaterThan : spv::OpINotEqual,
|
||||
TypeBool(state), byte, ConstantU32(state, bits == 16u ? 2u : 0u));
|
||||
const auto second = EmitValueOrZeroIfCondition(state, crosses, [&]() {
|
||||
return LoadBdaDword(ctx, Binary(state, spv::OpIAdd, TypeScalarU64(state), aligned,
|
||||
ConstantDeviceAddress(state, sizeof(uint32_t))));
|
||||
return LoadBdaDword(ctx, Binary(state, spv::OpIAdd, TypeU64(state), aligned,
|
||||
ConstantU64(state, sizeof(uint32_t))));
|
||||
});
|
||||
const auto shift =
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, ConstantU32(state, 3));
|
||||
@@ -805,26 +804,25 @@ uint32_t LoadIndirectScalarBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto word1 = ctx.Arg(handle, 1);
|
||||
const auto stride = EmitBitFieldUExtract(state, word1, ConstantU32(state, 16),
|
||||
ConstantU32(state, 14));
|
||||
const auto size = Binary(
|
||||
state, spv::OpIMul, TypeScalarU64(state),
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state),
|
||||
EmitUMax32(state, stride, ConstantU32(state, 1))),
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), ctx.Arg(handle, 2)));
|
||||
const auto offset = Binary(
|
||||
state, spv::OpIAdd, TypeScalarU64(state),
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), ctx.Arg(inst, 1),
|
||||
ConstantU32(state, ~3u))),
|
||||
ConstantDeviceAddress(state, ctx.Memory(inst).offset & ~3u));
|
||||
const auto end = Binary(state, spv::OpIAdd, TypeScalarU64(state), offset,
|
||||
ConstantDeviceAddress(state, sizeof(uint32_t)));
|
||||
const auto size = Binary(state, spv::OpIMul, TypeU64(state),
|
||||
Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
EmitUMax32(state, stride, ConstantU32(state, 1))),
|
||||
Unary(state, spv::OpUConvert, TypeU64(state), ctx.Arg(handle, 2)));
|
||||
const auto offset = Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
ctx.Arg(inst, 1), ConstantU32(state, ~3u))),
|
||||
ConstantU64(state, ctx.Memory(inst).offset & ~3u));
|
||||
const auto end =
|
||||
Binary(state, spv::OpIAdd, TypeU64(state), offset, ConstantU64(state, sizeof(uint32_t)));
|
||||
return EmitValueOrZeroIfCondition(
|
||||
state, Binary(state, spv::OpULessThanEqual, TypeBool(state), end, size), [&]() {
|
||||
const auto base = DeviceAddressFromWords(
|
||||
state, Binary(state, spv::OpBitwiseAnd, TypeU32(state), ctx.Arg(handle, 0),
|
||||
ConstantU32(state, ~3u)),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), word1, ConstantU32(state, 0xffffu)));
|
||||
return LoadBdaDword(ctx, Binary(state, spv::OpIAdd, TypeScalarU64(state), base, offset));
|
||||
const auto base = PackU64(state,
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state),
|
||||
ctx.Arg(handle, 0), ConstantU32(state, ~3u)),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), word1,
|
||||
ConstantU32(state, 0xffffu)));
|
||||
return LoadBdaDword(ctx, Binary(state, spv::OpIAdd, TypeU64(state), base, offset));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -865,21 +863,21 @@ IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst
|
||||
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
|
||||
ctx.Memory(inst).offset, stride, swizzle,
|
||||
field(word3, 21, 2));
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(handle, 0), field(word1, 0, 16));
|
||||
const auto base = PackU64(state, ctx.Arg(handle, 0), field(word1, 0, 16));
|
||||
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
|
||||
return {
|
||||
.address = Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), address.byte)),
|
||||
.offset = address.offset,
|
||||
.stride = stride,
|
||||
.swizzle = swizzle,
|
||||
.format = field(word3, 12, 7),
|
||||
.selector = ctx.Memory(inst).formatted ? field(word3, 0, 3) : 0u,
|
||||
.mode = field(word3, 28, 2),
|
||||
.records = records,
|
||||
.index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records),
|
||||
.address = Binary(state, spv::OpIAdd, TypeU64(state), base,
|
||||
Unary(state, spv::OpUConvert, TypeU64(state), address.byte)),
|
||||
.offset = address.offset,
|
||||
.stride = stride,
|
||||
.swizzle = swizzle,
|
||||
.format = field(word3, 12, 7),
|
||||
.selector = ctx.Memory(inst).formatted ? field(word3, 0, 3) : 0u,
|
||||
.mode = field(word3, 28, 2),
|
||||
.records = records,
|
||||
.index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records),
|
||||
.scalar_in_bounds = Binary(state, spv::OpULessThanEqual, TypeBool(state), soffset, records),
|
||||
.raw_records = raw_records,
|
||||
.raw_records = raw_records,
|
||||
.raw_index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, raw_records),
|
||||
};
|
||||
}
|
||||
@@ -925,13 +923,13 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_
|
||||
const auto buffer = PrepareIndirectBuffer(ctx, inst);
|
||||
const auto valid_format = Binary(state, spv::OpINotEqual, TypeBool(state), buffer.format,
|
||||
ConstantU32(state, 0));
|
||||
const auto base = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), buffer.address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||
ConstantU64(state, ~uint64_t {3}));
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; ++component) {
|
||||
const auto address = component == 0u ? base : Binary(
|
||||
state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
ConstantDeviceAddress(state, component * 4u));
|
||||
const auto address = component == 0u ? base
|
||||
: Binary(state, spv::OpIAdd, TypeU64(state), base,
|
||||
ConstantU64(state, component * 4u));
|
||||
values[component] = EmitValueOrZeroIfCondition(
|
||||
state, AndCondition(state, valid_format,
|
||||
IndirectBufferInBounds(state, buffer, component * 4u, 4u, false)),
|
||||
@@ -959,13 +957,14 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
TypeU32(state), constant, [&]() {
|
||||
const auto in_bounds = IndirectBufferInBounds(state, buffer, 0u, info.byte_size, true);
|
||||
const auto base = Binary(
|
||||
state, spv::OpBitwiseAnd, TypeScalarU64(state), buffer.address,
|
||||
ConstantDeviceAddress(state, ~(uint64_t(std::min(4u, info.byte_size)) - 1u)));
|
||||
state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
|
||||
ConstantU64(state, ~(uint64_t(std::min(4u, info.byte_size)) - 1u)));
|
||||
const auto load = [&](uint32_t component, uint32_t bits) {
|
||||
const auto offset = Format::GetFormatComponentByteOffset(info, component);
|
||||
const auto address = offset == 0u ? base : Binary(
|
||||
state, spv::OpIAdd, TypeScalarU64(state), base,
|
||||
ConstantDeviceAddress(state, offset));
|
||||
const auto address = offset == 0u
|
||||
? base
|
||||
: Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
base, ConstantU64(state, offset));
|
||||
return LoadBda(ctx, address, in_bounds, bits);
|
||||
};
|
||||
const auto emit_component = [&](uint32_t component) {
|
||||
@@ -1085,23 +1084,9 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
|
||||
return state.builder.Constant(spv::OpConstant, TypeScalarU64(state),
|
||||
static_cast<uint32_t>(value),
|
||||
static_cast<uint32_t>(value >> 32u));
|
||||
}
|
||||
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto low64 = Unary(state, spv::OpUConvert, TypeScalarU64(state), low);
|
||||
const auto high64 = Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state),
|
||||
Unary(state, spv::OpUConvert, TypeScalarU64(state), high),
|
||||
ConstantDeviceAddress(state, 32));
|
||||
return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low64, high64);
|
||||
}
|
||||
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), result,
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
|
||||
state.bda_pointer_function, address);
|
||||
return result;
|
||||
}
|
||||
@@ -1110,7 +1095,7 @@ void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
}
|
||||
const auto type = TypeScalarU64(state);
|
||||
const auto type = TypeU64(state);
|
||||
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type);
|
||||
state.bda_pointer_function = state.builder.AllocateId();
|
||||
const auto address = state.builder.AllocateId();
|
||||
@@ -1122,14 +1107,14 @@ void DefineGetBdaPointer(EmitterState& state) {
|
||||
EmitLabel(state, entry_label);
|
||||
|
||||
const auto extended = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), address,
|
||||
ConstantDeviceAddress(state, LibKernel::Memory::kExtendedMemoryBase));
|
||||
const auto packed = Select(state, type, extended,
|
||||
Binary(state, spv::OpISub, type, address,
|
||||
ConstantDeviceAddress(state,
|
||||
LibKernel::Memory::kExtendedMemoryBase - LOWER_ADDRESS_SIZE)),
|
||||
address);
|
||||
ConstantU64(state, LibKernel::Memory::kExtendedMemoryBase));
|
||||
const auto packed = Select(
|
||||
state, type, extended,
|
||||
Binary(state, spv::OpISub, type, address,
|
||||
ConstantU64(state, LibKernel::Memory::kExtendedMemoryBase - LOWER_ADDRESS_SIZE)),
|
||||
address);
|
||||
const auto page64 = Binary(state, spv::OpShiftRightLogical, type, packed,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
|
||||
ConstantU32(state, BufferCache::CACHING_PAGEBITS));
|
||||
const auto page = Unary(state, spv::OpUConvert, TypeU32(state), page64);
|
||||
const auto entry_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state, 64),
|
||||
@@ -1137,8 +1122,7 @@ void DefineGetBdaPointer(EmitterState& state) {
|
||||
page);
|
||||
const auto base = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, type, base, entry_pointer);
|
||||
const auto missing =
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
|
||||
const auto missing = Binary(state, spv::OpIEqual, TypeBool(state), base, ConstantU64(state, 0));
|
||||
const auto fault_label = state.builder.AllocateId();
|
||||
const auto available_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
@@ -1151,14 +1135,14 @@ void DefineGetBdaPointer(EmitterState& state) {
|
||||
|
||||
EmitLabel(state, available_label);
|
||||
const auto offset = Binary(state, spv::OpBitwiseAnd, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
|
||||
ConstantU64(state, BufferCache::CACHING_PAGESIZE - 1));
|
||||
const auto available = Binary(state, spv::OpIAdd, type, base, offset);
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
|
||||
EmitLabel(state, merge_label);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpPhi, type, result, ConstantDeviceAddress(state, 0),
|
||||
fault_label, available, available_label);
|
||||
state.builder.AddFunction(spv::OpPhi, type, result, ConstantU64(state, 0), fault_label,
|
||||
available, available_label);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
@@ -1216,12 +1200,12 @@ void EmitSharedAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
semantics = spv::MemorySemanticsUniformMemoryMask;
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, state.lds_storage_class, TypeScalarU64(state)),
|
||||
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
|
||||
const auto value = Unary(state, spv::OpBitcast, TypeScalarU64(state), ctx.Arg(inst, 1));
|
||||
state.builder.AddFunction(
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), state.builder.AllocateId(),
|
||||
spv::OpAccessChain, TypePointer(state, state.lds_storage_class, TypeU64(state)),
|
||||
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
|
||||
const auto value = ctx.Arg(inst, 1);
|
||||
state.builder.AddFunction(
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeU64(state), state.builder.AllocateId(),
|
||||
pointer, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | semantics), value);
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ void EnsureLdsStorage(EmitterState& state) {
|
||||
};
|
||||
if (state.requirements.shared_int64_atomics) {
|
||||
state.lds_variable = define(TypeU32(state), 4u);
|
||||
state.lds_u64_variable = define(TypeScalarU64(state), 8u);
|
||||
state.lds_u64_variable = define(TypeU64(state), 8u);
|
||||
state.builder.AddName(state.lds_u64_variable, "lds_qwords");
|
||||
} else {
|
||||
state.lds_variable = state.builder.DefineGlobalVariable(
|
||||
@@ -410,7 +410,7 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
const auto opcode = SpirvAtomicOpcode(inst.GetOpcode());
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
const bool wide = inst.GetType() == IR::Type::U64;
|
||||
const auto type = wide ? TypeScalarU64(ctx.state) : TypeU32(ctx.state);
|
||||
const auto type = wide ? TypeU64(ctx.state) : TypeU32(ctx.state);
|
||||
if (opcode == spv::OpAtomicCompareExchange) {
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
@@ -419,13 +419,12 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator);
|
||||
} else {
|
||||
auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
if (wide) value = Unary(ctx.state, spv::OpBitcast, type, value);
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(opcode, type, old, pointer,
|
||||
ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), value);
|
||||
}
|
||||
return wide ? Unary(ctx.state, spv::OpBitcast, TypeU64(ctx.state), old) : old;
|
||||
return old;
|
||||
}
|
||||
|
||||
void EmitAtomicMemoryBarrier(EmitterState& state, IR::ResourceKind kind) {
|
||||
|
||||
@@ -35,14 +35,11 @@ uint32_t TypeU32(EmitterState& state) {
|
||||
}
|
||||
|
||||
uint32_t TypeU64(EmitterState& state) {
|
||||
return TypeU32Vector(state, 2);
|
||||
}
|
||||
|
||||
uint32_t TypeScalarU64(EmitterState& state) {
|
||||
if (state.native_u64_type == 0) {
|
||||
state.native_u64_type = state.builder.Type(spv::OpTypeInt, 64, 0);
|
||||
if (state.u64_type == 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.u64_type = state.builder.Type(spv::OpTypeInt, 64, 0);
|
||||
}
|
||||
return state.native_u64_type;
|
||||
return state.u64_type;
|
||||
}
|
||||
|
||||
uint32_t TypeU32Pair(EmitterState& state) {
|
||||
@@ -124,8 +121,9 @@ uint32_t TypeFunction(EmitterState& state) {
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferElement(EmitterState& state, uint32_t bits) {
|
||||
return bits == 32u ? TypeU32(state) : bits == 64u ? TypeScalarU64(state)
|
||||
: state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
return bits == 32u ? TypeU32(state)
|
||||
: bits == 64u ? TypeU64(state)
|
||||
: state.builder.Type(spv::OpTypeInt, bits, 0);
|
||||
}
|
||||
|
||||
uint32_t StorageRuntimeArrayType(EmitterState& state, uint32_t bits) {
|
||||
@@ -331,9 +329,8 @@ uint32_t ConstantBool(EmitterState& state, bool value) {
|
||||
}
|
||||
|
||||
uint32_t ConstantU64(EmitterState& state, uint64_t value) {
|
||||
return state.builder.Constant(spv::OpConstantComposite, TypeU64(state),
|
||||
ConstantU32(state, static_cast<uint32_t>(value)),
|
||||
ConstantU32(state, static_cast<uint32_t>(value >> 32u)));
|
||||
return state.builder.Constant(spv::OpConstant, TypeU64(state), static_cast<uint32_t>(value),
|
||||
static_cast<uint32_t>(value >> 32u));
|
||||
}
|
||||
|
||||
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components) {
|
||||
|
||||
@@ -397,6 +397,7 @@ enum class Opcode {
|
||||
V_CMP_T_I32,
|
||||
V_CMP_CLASS_F32,
|
||||
V_CMPX_CLASS_F32,
|
||||
V_CMPX_CLASS_F16,
|
||||
V_CMP_LT_I16,
|
||||
V_CMP_EQ_I16,
|
||||
V_CMP_LE_I16,
|
||||
|
||||
@@ -35,7 +35,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
|
||||
{0x04u, Opcode::V_SUB_F32, Vop2SdwaProfile::Float32},
|
||||
{0x05u, Opcode::V_SUBREV_F32},
|
||||
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
|
||||
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
|
||||
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerFullDestination},
|
||||
{0x0fu, Opcode::V_MIN_F32},
|
||||
{0x10u, Opcode::V_MAX_F32},
|
||||
@@ -252,6 +252,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x99u, Opcode::V_CMPX_LT_I16, false}, {0x9au, Opcode::V_CMPX_EQ_I16, false},
|
||||
{0x9bu, Opcode::V_CMPX_LE_I16, false}, {0x9cu, Opcode::V_CMPX_GT_I16, false},
|
||||
{0x9du, Opcode::V_CMPX_NE_I16, false}, {0x9eu, Opcode::V_CMPX_GE_I16, false},
|
||||
{0x9fu, Opcode::V_CMPX_CLASS_F16, false},
|
||||
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
|
||||
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
|
||||
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
|
||||
@@ -917,6 +918,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMPX_NEQ_F16:
|
||||
case Opcode::V_CMPX_NLT_F16:
|
||||
case Opcode::V_CMP_CLASS_F32:
|
||||
case Opcode::V_CMPX_CLASS_F16:
|
||||
case Opcode::V_CMPX_CLASS_F32: return true;
|
||||
default: return false;
|
||||
}
|
||||
@@ -1540,6 +1542,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_NE_I32:
|
||||
case Opcode::V_CMPX_GE_I32:
|
||||
case Opcode::V_CMPX_CLASS_F32:
|
||||
case Opcode::V_CMPX_CLASS_F16:
|
||||
case Opcode::V_CMPX_LT_U32:
|
||||
case Opcode::V_CMPX_EQ_U32:
|
||||
case Opcode::V_CMPX_LE_U32:
|
||||
|
||||
@@ -72,11 +72,13 @@ void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx) {
|
||||
const auto value = ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto mask = ReadOperand(inst.src1, IR::Type::U32);
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(IR::ValueOpcode::FPCmpClass32, {value, mask})), false,
|
||||
cmpx);
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half) {
|
||||
const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false))
|
||||
: ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
||||
: ReadOperand(inst.src1, IR::Type::U32);
|
||||
const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32;
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false, cmpx);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
|
||||
@@ -248,6 +248,7 @@ void Translator::S_CSELECT_B32(const Decoder::Instruction& inst) {
|
||||
|
||||
void Translator::ScalarSelect64(const Decoder::Instruction& inst,
|
||||
const Decoder::Operand& false_source) {
|
||||
// Preserve per-word expressions for descriptor tracking and mask provenance.
|
||||
const auto condition = ir.GetScc();
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
const auto rhs = ReadU32Pair(false_source);
|
||||
|
||||
@@ -8,13 +8,13 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
void Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool accumulator, bool quiet_snan) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadF16LaneAsF32(inst.src0, high, true);
|
||||
const auto rhs = ReadF16LaneAsF32(inst.src1, high, true);
|
||||
const auto lhs = ReadF16LaneAsF32(inst.src0, high);
|
||||
const auto rhs = ReadF16LaneAsF32(inst.src1, high);
|
||||
IR::F32 result;
|
||||
if (accumulator) {
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.dst, high, true)}));
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.dst, high)}));
|
||||
} else if (inst.src_count == 3u) {
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.src2, high, true)}));
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs, ReadF16LaneAsF32(inst.src2, high)}));
|
||||
} else {
|
||||
result = IR::F32(ir.Emit(opcode, {lhs, rhs}));
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
return;
|
||||
}
|
||||
|
||||
// Descriptor tracking reasons about each scalar word independently.
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
auto mask_valid = ReadMaskValid(inst.src0);
|
||||
if (inst.src_count > 1u) {
|
||||
@@ -269,22 +270,18 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
|
||||
}
|
||||
|
||||
void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU32(inst.src0);
|
||||
const auto rhs = ReadU32(inst.src1);
|
||||
const auto add = ExtractU64(ReadU64(inst.src2));
|
||||
const auto mul_low = ir.IMul(lhs, rhs);
|
||||
const auto mul_high = IR::U32(ir.Emit(IR::ValueOpcode::UMulHi, {lhs, rhs}));
|
||||
const auto low = ir.IAdd(mul_low, add[0]);
|
||||
const auto carry_low = ir.ULessThan(low, mul_low);
|
||||
const auto high0 = ir.IAdd(mul_high, add[1]);
|
||||
const auto carry0 = ir.ULessThan(high0, mul_high);
|
||||
const auto high =
|
||||
ir.IAdd(high0, ir.Select(carry_low, IR::U32(IR::Value(1u)), IR::U32(IR::Value(0u))));
|
||||
const auto carry1 = ir.ULessThan(high, high0);
|
||||
WriteOperand(DestinationOperand(inst), ir.ConstructU64(low, high));
|
||||
const auto lhs = ReadU32(inst.src0);
|
||||
const auto rhs = ReadU32(inst.src1);
|
||||
// Keep the 32x32 product in words: native widening IMul64 fails NVIDIA compilation
|
||||
// in large vertex shaders (Astro's Playroom). The 64-bit addition stays native.
|
||||
const auto product = ir.ConstructU64(
|
||||
ir.IMul(lhs, rhs), IR::U32(ir.Emit(IR::ValueOpcode::UMulHi, {lhs, rhs})));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::IAdd64, {product, ReadU64(inst.src2)});
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||
WriteMask(inst.dst2, ir.LogicalOr(carry0, carry1));
|
||||
// The 32x32 product fits in 64 bits, so only the addition can carry.
|
||||
WriteMask(inst.dst2, IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product})));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,9 +404,12 @@ IR::U64 Translator::RightMask64(IR::U32 count) {
|
||||
}
|
||||
|
||||
void Translator::S_BFM_B64(const Decoder::Instruction& inst) {
|
||||
const auto count = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(63u)));
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {RightMask64(count), offset});
|
||||
const auto count = ReadU32(inst.src0);
|
||||
const auto offset = ReadU32(inst.src1);
|
||||
const auto one = IR::Value(uint64_t {1});
|
||||
const auto limit = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {one, count});
|
||||
const auto mask = ir.Emit(IR::ValueOpcode::ISub64, {limit, one});
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {mask, offset});
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,9 +165,16 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_BCNT1_I32_B32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::BitCount32, IR::Type::U32, false, false,
|
||||
true);
|
||||
case O::S_BCNT1_I32_B64:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::BitCount64, IR::Type::U64, false, false,
|
||||
true);
|
||||
case O::S_BCNT1_I32_B64: {
|
||||
// Vulkan bit counts operate on 32-bit words; avoid packing only to split again.
|
||||
const auto value = ReadU32Pair(inst.src0);
|
||||
const auto low = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {value[0]}));
|
||||
const auto high = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {value[1]}));
|
||||
const auto result = ir.IAdd(low, high);
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
return;
|
||||
}
|
||||
case O::S_FF1_I32_B32:
|
||||
return SimpleInteger(inst, IR::ValueOpcode::FindILsb32, IR::Type::U32, false, false,
|
||||
false);
|
||||
|
||||
@@ -503,41 +503,11 @@ IR::U64 Translator::ReadU64(const Decoder::Operand& operand) {
|
||||
return IR::U64(ReadOperand(operand, IR::Type::U64));
|
||||
}
|
||||
|
||||
IR::F32 Translator::ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane, bool packed) {
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const bool use_zero = packed && (high_lane ? operand.op_sel_hi : operand.op_sel);
|
||||
auto value = use_zero ? IR::F32(IR::Value::F32(0.0f))
|
||||
: ir.BitCastF32(IR::U32(IR::Value(operand.value)));
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {value}));
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half}));
|
||||
if (operand.absolute) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value}));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value}));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
auto raw_operand = operand;
|
||||
raw_operand.sdwa_sel = 6;
|
||||
raw_operand.sdwa_sext = false;
|
||||
const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand));
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
const auto half_u32 = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(16u)}));
|
||||
const auto half_u16 = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32, {half_u32}));
|
||||
IR::F32 Translator::ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane) {
|
||||
const auto bits = Read16LaneBits(operand, high_lane);
|
||||
const auto half_u16 = IR::U16(ir.Emit(IR::ValueOpcode::ConvertU16U32, {bits}));
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::BitCastF16U16, {half_u16}));
|
||||
auto value = IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half}));
|
||||
if (operand.absolute) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPAbs32, {value}));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = IR::F32(ir.Emit(IR::ValueOpcode::FPNeg32, {value}));
|
||||
}
|
||||
return value;
|
||||
return IR::F32(ir.Emit(IR::ValueOpcode::ConvertF32F16, {half}));
|
||||
}
|
||||
|
||||
IR::F32 Translator::ReadF16AsF32(const Decoder::Operand& operand) {
|
||||
@@ -555,29 +525,6 @@ IR::F32 Translator::ReadMixF32(const Decoder::Operand& operand) {
|
||||
return IR::F32(ReadOperand(value_operand, IR::Type::F32));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane) {
|
||||
auto raw_operand = operand;
|
||||
raw_operand.sdwa_sel = 6;
|
||||
raw_operand.sdwa_sext = false;
|
||||
const auto bits = ApplyBitSourceModifiers(raw_operand, ReadRawU32(operand));
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
uint32_t width = 16u;
|
||||
if (operand.sdwa_sel <= 3u) {
|
||||
offset = operand.sdwa_sel * 8u;
|
||||
width = 8u;
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
if (width == 8u && operand.sdwa_sext) {
|
||||
// Extend the selected byte into the 16-bit operand before halfword modifiers.
|
||||
const auto value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
return ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane,
|
||||
bool sign_extend) {
|
||||
auto value = Read16LaneBits(operand, high_lane);
|
||||
@@ -593,12 +540,36 @@ IR::U32 Translator::ReadU16AsU32(const Decoder::Operand& operand, bool sign_exte
|
||||
}
|
||||
|
||||
IR::U32 Translator::Read16LaneBits(const Decoder::Operand& operand, bool high_lane) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
auto raw_operand = operand;
|
||||
raw_operand.sdwa_sel = 6;
|
||||
raw_operand.sdwa_sext = false;
|
||||
auto bits = ReadRawU32(operand);
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
// Inline floats encode the operand's width before SDWA or OP_SEL selects bits.
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {ir.BitCastF32(bits)}));
|
||||
const auto half_bits = IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {half}));
|
||||
bits = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {half_bits}));
|
||||
}
|
||||
bits = ApplyBitSourceModifiers(raw_operand, bits);
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
uint32_t width = 16u;
|
||||
if (operand.sdwa_sel <= 3u) {
|
||||
offset = operand.sdwa_sel * 8u;
|
||||
width = 8u;
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
const auto extract = width == 8u && operand.sdwa_sext
|
||||
? IR::ValueOpcode::BitFieldSExtract
|
||||
: IR::ValueOpcode::BitFieldUExtract;
|
||||
auto value = IR::U32(ir.Emit(extract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
if (width == 8u && operand.sdwa_sext) {
|
||||
value = ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
if (operand.absolute) {
|
||||
value = ir.BitwiseAnd(value, IR::U32(IR::Value(0x7fffu)));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
// RDNA2 source NEG flips the sign bit, including packed integer operands.
|
||||
value = ir.BitwiseXor(value, IR::U32(IR::Value(0x8000u)));
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -40,10 +40,9 @@ private:
|
||||
IR::U32 ReadU32(const Decoder::Operand& operand);
|
||||
std::array<IR::U32, 2> ReadU32Pair(const Decoder::Operand& operand);
|
||||
IR::U64 ReadU64(const Decoder::Operand& operand);
|
||||
IR::F32 ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane, bool packed = false);
|
||||
IR::F32 ReadF16LaneAsF32(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::F32 ReadF16AsF32(const Decoder::Operand& operand);
|
||||
IR::F32 ReadMixF32(const Decoder::Operand& operand);
|
||||
IR::U32 ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::U32 ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane, bool sign_extend);
|
||||
IR::U32 ReadU16AsU32(const Decoder::Operand& operand, bool sign_extend);
|
||||
IR::U32 Read16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
@@ -127,7 +126,7 @@ private:
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
||||
bool cmpx);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false);
|
||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
||||
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
||||
void V_CVT_F32_I32(const Decoder::Instruction& inst);
|
||||
|
||||
@@ -346,6 +346,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
|
||||
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
|
||||
|
||||
@@ -493,6 +493,7 @@ struct DescriptorSource {
|
||||
uint32_t table_stride = 0;
|
||||
uint32_t workgroup_axis = UINT32_MAX;
|
||||
uint32_t selector_shift = 0;
|
||||
uint32_t selector_bits = UINT32_MAX;
|
||||
Value key_count;
|
||||
Value selector_first;
|
||||
Value selector_mask;
|
||||
|
||||
@@ -175,6 +175,7 @@ VALUE_OPCODE(FPOrdGreaterThanEqual32, U1, F32, F32)
|
||||
VALUE_OPCODE(FPUnordGreaterThanEqual32, U1, F32, F32)
|
||||
VALUE_OPCODE(FPIsNan32, U1, F32)
|
||||
VALUE_OPCODE(FPCmpClass32, U1, F32, U32)
|
||||
VALUE_OPCODE(FPCmpClass16, U1, U32, U32)
|
||||
|
||||
VALUE_OPCODE(FPAdd32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPSub32, F32, F32, F32)
|
||||
|
||||
@@ -347,6 +347,17 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
if (IsImmediate(low, Type::U32) && IsImmediate(high, Type::U32)) {
|
||||
Replace(inst, Value(static_cast<uint64_t>(low.U32()) |
|
||||
(static_cast<uint64_t>(high.U32()) << 32u)));
|
||||
} else {
|
||||
const auto* low_extract = low.TryInstruction();
|
||||
const auto* high_extract = high.TryInstruction();
|
||||
if (low_extract != nullptr && high_extract != nullptr &&
|
||||
low_extract->GetOpcode() == ValueOpcode::CompositeExtractU64 &&
|
||||
high_extract->GetOpcode() == ValueOpcode::CompositeExtractU64 &&
|
||||
Arg(*low_extract, 1) == Value(0u) && Arg(*high_extract, 1) == Value(1u) &&
|
||||
Arg(*low_extract, 0) == Arg(*high_extract, 0)) {
|
||||
// Guest register pairs need not unpack and repack between wide operations.
|
||||
Replace(inst, Arg(*low_extract, 0));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,7 +306,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
ShaderBufferResource material;
|
||||
uint32_t first = 0, count = 0;
|
||||
if (!DecodeBufferDescriptor(material_value, material) || material.Type() != 0u ||
|
||||
(indirect.selector_shift != 0u && (material.Base48() & 3u) != 0u) ||
|
||||
((indirect.selector_shift != 0u || indirect.selector_bits != UINT32_MAX) &&
|
||||
(material.Base48() & 3u) != 0u) ||
|
||||
material.SwizzleEnabled() ||
|
||||
material.AddTid() || material.OutOfBounds() != 0u ||
|
||||
uint64_t {indirect.selector_offset} + 4u > material.Stride() ||
|
||||
@@ -362,8 +363,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
if (indirect.selector_shift == 0u) keys.push_back(0u);
|
||||
}
|
||||
if (indirect.material_source != UINT32_MAX) {
|
||||
if (indirect.selector_shift != 0u) {
|
||||
for (auto& key: keys) key >>= indirect.selector_shift;
|
||||
for (auto& key: keys) key = (key >> indirect.selector_shift) & indirect.selector_bits;
|
||||
if (indirect.selector_shift != 0u || indirect.selector_bits != UINT32_MAX) {
|
||||
keys.push_back(0u); // An out-of-range material load returns zero.
|
||||
}
|
||||
std::ranges::sort(keys);
|
||||
@@ -663,7 +664,6 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
||||
}
|
||||
}
|
||||
std::vector<ResourceBlock> blocks(program.blocks.size());
|
||||
std::vector<std::vector<uint32_t>> predecessors(blocks.size());
|
||||
for (uint32_t i = 0; i < blocks.size(); i++) {
|
||||
auto& block = blocks[i];
|
||||
const auto& info = program.block_info[i];
|
||||
@@ -687,7 +687,6 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
||||
return {};
|
||||
}
|
||||
block.successors.push_back(found->second);
|
||||
predecessors[found->second].push_back(i);
|
||||
}
|
||||
for (const auto& inst: *program.blocks[i]) {
|
||||
const auto op = inst.GetOpcode();
|
||||
@@ -717,30 +716,7 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
||||
block.sources.erase(std::unique(block.sources.begin(), block.sources.end()),
|
||||
block.sources.end());
|
||||
}
|
||||
// Host branches only matter when they guard a later resource access. Leave
|
||||
// data-only branches (for example a mutable flag checked before a trap) on the GPU.
|
||||
std::vector<uint8_t> resource_reachable(blocks.size());
|
||||
std::vector<uint32_t> pending;
|
||||
for (uint32_t i = 0; i < blocks.size(); ++i) {
|
||||
if (!blocks[i].sources.empty() || !blocks[i].srt_reads.empty()) {
|
||||
resource_reachable[i] = 1;
|
||||
pending.push_back(i);
|
||||
}
|
||||
}
|
||||
while (!pending.empty()) {
|
||||
const auto index = pending.back();
|
||||
pending.pop_back();
|
||||
for (const auto predecessor: predecessors[index]) {
|
||||
if (resource_reachable[predecessor]) continue;
|
||||
resource_reachable[predecessor] = 1;
|
||||
pending.push_back(predecessor);
|
||||
}
|
||||
}
|
||||
for (auto& block: blocks) {
|
||||
block.condition = std::ranges::any_of(block.successors, [&](uint32_t successor) {
|
||||
return resource_reachable[successor] != 0;
|
||||
}) ? predicate(block.condition) : Value {};
|
||||
}
|
||||
for (auto& block: blocks) block.condition = predicate(block.condition);
|
||||
if (std::ranges::none_of(
|
||||
blocks, [](const ResourceBlock& block) { return !block.condition.IsEmpty(); })) {
|
||||
return {};
|
||||
|
||||
@@ -742,6 +742,15 @@ private:
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
uint32_t index = 0;
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 &&
|
||||
ScalarReadMemory(inst, index) != nullptr && inst.Arg(1).Resolve().IsImmediate() &&
|
||||
ValidateRuntimeValue(m_program, Value(&inst)))
|
||||
CollectScalarRead(Value(&inst), inst.Flags<MemoryFlags>().pc);
|
||||
}
|
||||
}
|
||||
for (auto* read: m_scalar_reads) {
|
||||
const auto flags = read->Flags<MemoryFlags>();
|
||||
auto& memory = m_program.memory_info[flags.index];
|
||||
@@ -835,7 +844,7 @@ private:
|
||||
const auto& b = *descriptor.indirect_descriptor;
|
||||
if (a.material_source != b.material_source || a.table_source != b.table_source ||
|
||||
a.selector_stride != b.selector_stride || a.selector_offset != b.selector_offset ||
|
||||
a.selector_shift != b.selector_shift ||
|
||||
a.selector_shift != b.selector_shift || a.selector_bits != b.selector_bits ||
|
||||
a.table_offset != b.table_offset || a.table_stride != b.table_stride ||
|
||||
a.workgroup_axis != b.workgroup_axis || a.sources != b.sources ||
|
||||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
|
||||
@@ -968,11 +977,24 @@ private:
|
||||
return stride != 0u && value.GetType() == Type::U32 && !value.IsImmediate();
|
||||
}
|
||||
|
||||
const Inst* MaterialKeyShift(Value key, uint32_t& amount) const {
|
||||
const auto* shift = key.Resolve().TryInstruction();
|
||||
return shift != nullptr && shift->GetOpcode() == ValueOpcode::ShiftRightLogical32 &&
|
||||
ImmediateU32(shift->Arg(1), amount) && amount != 0u && amount < 32u
|
||||
? shift : nullptr;
|
||||
Value MaterialKey(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
|
||||
indirect.selector_shift = 0u;
|
||||
indirect.selector_bits = UINT32_MAX;
|
||||
const auto* inst = key.Resolve().TryInstruction();
|
||||
uint32_t immediate;
|
||||
if (inst != nullptr && inst->GetOpcode() == ValueOpcode::BitwiseAnd32) {
|
||||
if (ImmediateU32(inst->Arg(1), immediate)) key = inst->Arg(0);
|
||||
else if (ImmediateU32(inst->Arg(0), immediate)) key = inst->Arg(1);
|
||||
else return key;
|
||||
indirect.selector_bits = immediate;
|
||||
inst = key.Resolve().TryInstruction();
|
||||
}
|
||||
if (inst != nullptr && inst->GetOpcode() == ValueOpcode::ShiftRightLogical32 &&
|
||||
ImmediateU32(inst->Arg(1), immediate) && immediate != 0u && immediate < 32u) {
|
||||
indirect.selector_shift = immediate;
|
||||
key = inst->Arg(0);
|
||||
}
|
||||
return key.Resolve();
|
||||
}
|
||||
|
||||
enum class LaneQuantifier { Any, All };
|
||||
@@ -1647,10 +1669,9 @@ private:
|
||||
DescriptorSource& material_source) {
|
||||
const auto* selected = UniformizedMaterialValue(key, image);
|
||||
if (selected == nullptr) return false;
|
||||
uint32_t amount = 0;
|
||||
const auto* shift = MaterialKeyShift(selected->Arg(1), amount);
|
||||
if (shift == nullptr) return false;
|
||||
const auto* loaded = shift->Arg(0).Resolve().TryInstruction();
|
||||
const auto value = MaterialKey(selected->Arg(1), indirect);
|
||||
if (indirect.selector_shift == 0u && indirect.selector_bits == UINT32_MAX) return false;
|
||||
const auto* loaded = value.TryInstruction();
|
||||
if (loaded == nullptr || loaded->GetOpcode() != ValueOpcode::SelectU32 ||
|
||||
!EquivalentValue(m_program, selected->Arg(0), loaded->Arg(0))) return false;
|
||||
const auto* read = loaded->Arg(1).Resolve().TryInstruction();
|
||||
@@ -1668,7 +1689,6 @@ private:
|
||||
indirect.selector_first = Value(0u);
|
||||
indirect.key_count = material_source.dwords[2];
|
||||
indirect.selector_offset = memory.offset;
|
||||
indirect.selector_shift = amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1690,9 +1710,8 @@ private:
|
||||
// The descriptor also supplies dimensions to shader arithmetic. Keep its reads;
|
||||
// only the image handle is projected onto the bounded workgroup key.
|
||||
plan.reads.fill(nullptr);
|
||||
} else if (table_stride != 32u) {
|
||||
return false;
|
||||
} else if (table_source.dword_count == 2u) {
|
||||
if (table_stride != 32u) return false;
|
||||
const auto* selector = key.Resolve().TryInstruction();
|
||||
const bool bitscan = selector != nullptr && selector->GetOpcode() == ValueOpcode::FindILsb32 &&
|
||||
selector->NumArgs() == 1u && !m_shader_writes &&
|
||||
@@ -1710,13 +1729,11 @@ private:
|
||||
if ((table_offset & 3u) != 0u ||
|
||||
(bitscan && table_offset > UINT32_MAX - (32u * 32u - 1u))) return false;
|
||||
} else if (!MatchUniformizedBufferKey(key, handle, indirect, material_source)) {
|
||||
const auto* key_shift = MaterialKeyShift(key, indirect.selector_shift);
|
||||
auto* material_read =
|
||||
(key_shift != nullptr ? key_shift->Arg(0) : key).Resolve().TryInstruction();
|
||||
auto* material_read = MaterialKey(key, indirect).TryInstruction();
|
||||
uint32_t material_memory_index = 0;
|
||||
const auto* memory = material_read != nullptr
|
||||
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
|
||||
if (table_offset != 0u || memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
|
||||
if (memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
|
||||
memory->offset > INT32_MAX || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1735,6 +1752,12 @@ private:
|
||||
}
|
||||
indirect.material_source = InternSource(material_source);
|
||||
}
|
||||
// This plan combines shader byte-offset additions with the scalar immediate.
|
||||
// A nonzero combined offset is exact only when neither can cross U32 wrap.
|
||||
const auto maximum_key = (UINT32_MAX >> indirect.selector_shift) & indirect.selector_bits;
|
||||
if (indirect.workgroup_axis == UINT32_MAX && table_source.dword_count == 4u &&
|
||||
table_offset != 0u && uint64_t {maximum_key} * table_stride + table_offset + 28u > UINT32_MAX)
|
||||
return false;
|
||||
indirect.table_source = InternSource(table_source);
|
||||
DescriptorSource image_source;
|
||||
image_source.dword_count = 8u;
|
||||
@@ -2032,6 +2055,8 @@ private:
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, inst.GetOpcode() == ValueOpcode::StoreBufferU32 ? 4u : 8u,
|
||||
false, false, memory.resource * 4u, descriptor, flags.pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (ValidateSource(descriptor, bad_dword)) continue;
|
||||
IndirectDescriptorPlan plan;
|
||||
if (TryMakeIndirectImage(*handle, descriptor, plan) || TryMakeFiniteImage(*handle, plan) ||
|
||||
TryMakeIndirectBuffer(*handle, descriptor, plan)) {
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace {
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
|
||||
bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
|
||||
if (base > AddressMask) {
|
||||
return false;
|
||||
}
|
||||
if (offset < 0) {
|
||||
const auto magnitude = uint64_t {0} - static_cast<uint64_t>(offset);
|
||||
if (magnitude > base) {
|
||||
@@ -32,7 +35,7 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
|
||||
return true;
|
||||
}
|
||||
const auto magnitude = static_cast<uint64_t>(offset);
|
||||
if (magnitude > UINT64_MAX - base) {
|
||||
if (magnitude > AddressMask - base) {
|
||||
return false;
|
||||
}
|
||||
result = base + magnitude;
|
||||
@@ -477,7 +480,7 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
if (!Arg(*handle, 0, low) || !Arg(*handle, 1, high) || !Arg(inst, 1, offset)) {
|
||||
return false;
|
||||
}
|
||||
const auto base = (high << 32u) | static_cast<uint32_t>(low);
|
||||
const auto base = ((high << 32u) | static_cast<uint32_t>(low)) & AddressMask;
|
||||
const auto immediate = static_cast<int64_t>(static_cast<int32_t>(mem.offset));
|
||||
uint64_t address = 0;
|
||||
if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer || vector) {
|
||||
@@ -507,7 +510,7 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
||||
if (byte_offset > size || size - byte_offset < sizeof(uint32_t)) {
|
||||
return false;
|
||||
}
|
||||
address = (base & AddressMask & ~uint64_t {3}) + byte_offset;
|
||||
address = (base & ~uint64_t {3}) + byte_offset;
|
||||
} else {
|
||||
const auto relative = (immediate & ~int64_t {3}) +
|
||||
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
|
||||
|
||||
+55
-23
@@ -599,34 +599,66 @@ static int KYTY_SYSV_ABI HttpUriBuild(char* out, size_t* require, size_t prepare
|
||||
return HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
std::string uri;
|
||||
if (src_element->scheme != nullptr) {
|
||||
uri.append(src_element->scheme);
|
||||
uri.push_back(':');
|
||||
}
|
||||
constexpr uint32_t BUILD_WITH_SCHEME = 0x01;
|
||||
constexpr uint32_t BUILD_WITH_HOSTNAME = 0x02;
|
||||
constexpr uint32_t BUILD_WITH_PORT = 0x04;
|
||||
constexpr uint32_t BUILD_WITH_PATH = 0x08;
|
||||
constexpr uint32_t BUILD_WITH_USERNAME = 0x10;
|
||||
constexpr uint32_t BUILD_WITH_PASSWORD = 0x20;
|
||||
constexpr uint32_t BUILD_WITH_QUERY = 0x40;
|
||||
constexpr uint32_t BUILD_WITH_FRAGMENT = 0x80;
|
||||
constexpr uint32_t BUILD_WITH_ALL = 0xff;
|
||||
|
||||
if (src_element->opaque == 0) {
|
||||
if (src_element->hostname != nullptr) {
|
||||
// No component bits preserves the legacy full-URI build.
|
||||
const uint32_t parts = (option & BUILD_WITH_ALL) == 0 ? BUILD_WITH_ALL : option;
|
||||
const bool hierarchical = src_element->opaque == 0 && src_element->hostname != nullptr;
|
||||
const bool with_hostname = (parts & BUILD_WITH_HOSTNAME) != 0 && hierarchical;
|
||||
|
||||
std::string uri;
|
||||
if ((parts & BUILD_WITH_SCHEME) != 0) {
|
||||
if (src_element->scheme != nullptr) {
|
||||
uri.append(src_element->scheme);
|
||||
uri.push_back(':');
|
||||
}
|
||||
// Scheme-only builds include the authority marker.
|
||||
if (hierarchical) {
|
||||
uri.append("//");
|
||||
if (src_element->username != nullptr) {
|
||||
uri.append(src_element->username);
|
||||
if (src_element->password != nullptr) {
|
||||
uri.push_back(':');
|
||||
uri.append(src_element->password);
|
||||
}
|
||||
uri.push_back('@');
|
||||
}
|
||||
uri.append(src_element->hostname);
|
||||
if (src_element->port != 0) {
|
||||
uri.push_back(':');
|
||||
uri.append(std::to_string(src_element->port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppendUriPart(&uri, src_element->path);
|
||||
AppendUriPart(&uri, src_element->query);
|
||||
AppendUriPart(&uri, src_element->fragment);
|
||||
const bool with_username = (parts & BUILD_WITH_USERNAME) != 0 && src_element->username != nullptr;
|
||||
const bool with_password = (parts & BUILD_WITH_PASSWORD) != 0 && src_element->password != nullptr;
|
||||
if (with_username) {
|
||||
uri.append(src_element->username);
|
||||
}
|
||||
if (with_password) {
|
||||
if (with_username) {
|
||||
uri.push_back(':');
|
||||
}
|
||||
uri.append(src_element->password);
|
||||
}
|
||||
if (with_hostname) {
|
||||
if (with_username || with_password) {
|
||||
uri.push_back('@');
|
||||
}
|
||||
uri.append(src_element->hostname);
|
||||
}
|
||||
if ((parts & BUILD_WITH_PORT) != 0 && hierarchical && src_element->port != 0) {
|
||||
if (with_hostname) {
|
||||
uri.push_back(':');
|
||||
}
|
||||
uri.append(std::to_string(src_element->port));
|
||||
}
|
||||
|
||||
if ((parts & BUILD_WITH_PATH) != 0) {
|
||||
AppendUriPart(&uri, src_element->path);
|
||||
}
|
||||
if ((parts & BUILD_WITH_QUERY) != 0) {
|
||||
AppendUriPart(&uri, src_element->query);
|
||||
}
|
||||
if ((parts & BUILD_WITH_FRAGMENT) != 0) {
|
||||
AppendUriPart(&uri, src_element->fragment);
|
||||
}
|
||||
|
||||
const auto needed = uri.size() + 1;
|
||||
if (require != nullptr) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace Libs::LibHttp {
|
||||
void InitNet_1_Http(Loader::SymbolDatabase *symbols);
|
||||
@@ -40,14 +41,14 @@ int failures = 0;
|
||||
using HttpUriParse = int(KYTY_SYSV_ABI *)(SceHttpUriElement *, const char *,
|
||||
void *, size_t *, size_t);
|
||||
|
||||
HttpUriParse GetHttpUriParse() {
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibHttp::InitNet_1_Http(&symbols);
|
||||
const auto *record =
|
||||
symbols.FindByNid("IWalAn-guFs", Loader::SymbolType::Func);
|
||||
using HttpUriBuild = int(KYTY_SYSV_ABI *)(char *, size_t *, size_t,
|
||||
const SceHttpUriElement *, uint32_t);
|
||||
|
||||
template <typename T>
|
||||
T GetHttpFunction(const Loader::SymbolDatabase &symbols, const char *nid) {
|
||||
const auto *record = symbols.FindByNid(nid, Loader::SymbolType::Func);
|
||||
CHECK(record != nullptr);
|
||||
return record != nullptr ? reinterpret_cast<HttpUriParse>(record->vaddr)
|
||||
: nullptr;
|
||||
return record != nullptr ? reinterpret_cast<T>(record->vaddr) : nullptr;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
@@ -117,13 +118,61 @@ void TestPresentQuery(HttpUriParse parse) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildWith(HttpUriBuild build, const SceHttpUriElement &element,
|
||||
uint32_t option) {
|
||||
size_t required = 0;
|
||||
CHECK(build(nullptr, &required, 0, &element, option) == 0);
|
||||
std::array<char, 256> out{};
|
||||
size_t filled = 0;
|
||||
CHECK(required <= out.size());
|
||||
CHECK(build(out.data(), &filled, required, &element, option) == 0);
|
||||
CHECK(filled == required);
|
||||
return std::string(out.data());
|
||||
}
|
||||
|
||||
void TestBuildHonoursOption(HttpUriParse parse, HttpUriBuild build) {
|
||||
constexpr char url[] =
|
||||
"https://user:pw@gssdk1.gamesci.com.cn:8443/VersionServerImpl?x=1#frag";
|
||||
size_t required = 0;
|
||||
CHECK(parse(nullptr, url, nullptr, &required, 0) == 0);
|
||||
std::array<char, 256> pool{};
|
||||
SceHttpUriElement element{};
|
||||
CHECK(parse(&element, url, pool.data(), &required, required) == 0);
|
||||
|
||||
constexpr uint32_t scheme = 0x01, hostname = 0x02, port = 0x04, path = 0x08,
|
||||
username = 0x10, password = 0x20, query = 0x40,
|
||||
fragment = 0x80;
|
||||
|
||||
CHECK(BuildWith(build, element, scheme) == "https://");
|
||||
CHECK(BuildWith(build, element, hostname) == "gssdk1.gamesci.com.cn");
|
||||
CHECK(BuildWith(build, element, port) == "8443");
|
||||
CHECK(BuildWith(build, element, path) == "/VersionServerImpl");
|
||||
CHECK(BuildWith(build, element, username) == "user");
|
||||
CHECK(BuildWith(build, element, password) == "pw");
|
||||
CHECK(BuildWith(build, element, query) == "?x=1");
|
||||
CHECK(BuildWith(build, element, fragment) == "#frag");
|
||||
CHECK(BuildWith(build, element, scheme | hostname) ==
|
||||
"https://gssdk1.gamesci.com.cn");
|
||||
CHECK(BuildWith(build, element, hostname | path) ==
|
||||
"gssdk1.gamesci.com.cn/VersionServerImpl");
|
||||
CHECK(BuildWith(build, element, hostname | port) ==
|
||||
"gssdk1.gamesci.com.cn:8443");
|
||||
|
||||
CHECK(BuildWith(build, element, 0xff) == url);
|
||||
CHECK(BuildWith(build, element, 0) == url);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const auto parse = GetHttpUriParse();
|
||||
if (parse == nullptr) {
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibHttp::InitNet_1_Http(&symbols);
|
||||
const auto parse = GetHttpFunction<HttpUriParse>(symbols, "IWalAn-guFs");
|
||||
const auto build = GetHttpFunction<HttpUriBuild>(symbols, "5LZA+KPISVA");
|
||||
if (parse == nullptr || build == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
TestBuildHonoursOption(parse, build);
|
||||
TestAbsentQuery(parse);
|
||||
TestEmptyUri(parse);
|
||||
TestPresentQuery(parse);
|
||||
|
||||
@@ -32,8 +32,7 @@ AddValueBlock(Libs::Graphics::ShaderRecompiler::IR::Program &program) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address,
|
||||
bool buffer = false) {
|
||||
Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
Program program;
|
||||
program.stage = Libs::Graphics::ShaderType::Compute;
|
||||
@@ -42,22 +41,16 @@ Libs::Graphics::ShaderRecompiler::IR::ResourcePlan SrtPlan(uint64_t address,
|
||||
auto &value_block = AddValueBlock(program);
|
||||
|
||||
MemoryInfo memory;
|
||||
memory.kind = buffer ? ResourceKind::ScalarBuffer : ResourceKind::ScalarAddress;
|
||||
memory.kind = ResourceKind::ScalarAddress;
|
||||
memory.planning_only = true;
|
||||
program.memory_info.push_back(memory);
|
||||
const auto low = Value(static_cast<uint32_t>(address));
|
||||
const auto high = Value(static_cast<uint32_t>(address >> 32u));
|
||||
auto &handle = buffer
|
||||
? value_block.AppendNewInst(ValueOpcode::GetBufferResource,
|
||||
{low, high, Value(4u), Value(0u)})
|
||||
: value_block.AppendNewInst(ValueOpcode::GetAddressResource,
|
||||
{low, high});
|
||||
auto &raw = buffer
|
||||
? value_block.AppendNewInst(ValueOpcode::ReadConstBuffer,
|
||||
{Value(&handle), Value(0u)})
|
||||
: value_block.AppendNewInst(
|
||||
ValueOpcode::LoadAddressU32,
|
||||
{Value(&handle), Value(0u), Value(0u), Value(true)});
|
||||
auto &handle =
|
||||
value_block.AppendNewInst(ValueOpcode::GetAddressResource, {low, high});
|
||||
auto &raw = value_block.AppendNewInst(
|
||||
ValueOpcode::LoadAddressU32,
|
||||
{Value(&handle), Value(0u), Value(0u), Value(true)});
|
||||
raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40});
|
||||
program.srt_reads.push_back({Value(&raw), 0});
|
||||
|
||||
@@ -175,32 +168,6 @@ void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
}
|
||||
|
||||
void TestRawScalarAddressPreservesHighBits() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
constexpr uint64_t address = 0xabcd000156780003ull;
|
||||
for (const bool buffer : {false, true}) {
|
||||
auto plan = SrtPlan(address, buffer);
|
||||
uint64_t observed = 0;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = [](void *userdata, uint64_t requested,
|
||||
std::span<uint32_t> words) {
|
||||
if (words.size() != 1) return false;
|
||||
*static_cast<uint64_t *>(userdata) = requested;
|
||||
words[0] = 0x12345678;
|
||||
return true;
|
||||
},
|
||||
.userdata = &observed};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"scalar address materialization failed");
|
||||
Check(observed == (buffer ? 0x0000000156780000ull : address & ~uint64_t{3}),
|
||||
"raw pointer high bits and buffer descriptor flags were conflated");
|
||||
Check(snapshot.flattened_srt == std::vector<uint32_t>{0x12345678},
|
||||
"scalar address read did not reach its flattened slot");
|
||||
}
|
||||
}
|
||||
|
||||
void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
auto plan = SrtPlan(0x10000);
|
||||
@@ -599,7 +566,6 @@ void DbgExit(int) { std::abort(); }
|
||||
|
||||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestRawScalarAddressPreservesHighBits();
|
||||
TestIntegerRuntimeValueFollowsSrtReads();
|
||||
TestUniformVectorDescriptorRead();
|
||||
TestExactReciprocalDescriptorArithmetic();
|
||||
|
||||
@@ -208,7 +208,8 @@ bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span<uint32_t>
|
||||
std::unique_ptr<Fixture>
|
||||
MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 4,
|
||||
bool memory_backed_material = false, uint32_t member_offset = 0,
|
||||
uint32_t material_stride = 224) {
|
||||
uint32_t material_stride = 224, uint32_t selector_bits = UINT32_MAX,
|
||||
uint32_t table_stride = 32, uint32_t table_offset = 0) {
|
||||
auto fixture = std::make_unique<Fixture>();
|
||||
std::array<Value, 4> material_words;
|
||||
std::array<Value, 4> heap_words;
|
||||
@@ -257,17 +258,19 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 4,
|
||||
MemoryInfo material_scalar;
|
||||
material_scalar.kind = ResourceKind::ScalarBuffer;
|
||||
material_scalar.offset = material_immediate;
|
||||
const auto key =
|
||||
auto key =
|
||||
fixture->Emit(ValueOpcode::ReadConstBuffer, {material, member},
|
||||
fixture->AddMemory(material_scalar, 0x10d8));
|
||||
if (selector_bits != UINT32_MAX)
|
||||
key = fixture->Emit(ValueOpcode::BitwiseAnd32, {key, Value(selector_bits)});
|
||||
const auto heap_offset =
|
||||
fixture->Emit(ValueOpcode::ShiftLeftLogical32, {key, Value(5u)});
|
||||
fixture->Emit(ValueOpcode::IMul32, {key, Value(table_stride)});
|
||||
std::array<Value, 8> image_words;
|
||||
MemoryInfo heap_scalar;
|
||||
heap_scalar.kind = ResourceKind::ScalarBuffer;
|
||||
for (uint32_t dword = 0; dword < image_words.size(); dword++) {
|
||||
auto component = heap_scalar;
|
||||
component.offset = dword * sizeof(uint32_t);
|
||||
component.offset = table_offset + dword * sizeof(uint32_t);
|
||||
if (malformed && dword == image_words.size() - 1u) {
|
||||
component.offset += sizeof(uint32_t);
|
||||
}
|
||||
@@ -579,6 +582,43 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
split_memory.watched_reads == 1u,
|
||||
"indirect scalar offsets did not align and bound their components independently");
|
||||
}
|
||||
|
||||
// The shader packs flags above a 15-bit cube-image key in the final DWORD of
|
||||
// each 64-byte material record; the image occupies bytes 16..47 of its record.
|
||||
auto packed = MakeIndirectImageFixture(false, 60u, false, 0u, 64u, 0x7fffu, 48u, 16u);
|
||||
packed->PlanAndTrack();
|
||||
const auto packed_plan = ExtractResourcePlan(packed->program);
|
||||
LinearTestMemory packed_memory;
|
||||
std::array<uint32_t, 8> packed_data{0x1000u, 64u << 16u, 3u, 0u,
|
||||
0x2000u, 48u << 16u, 2u, 0u};
|
||||
for (uint32_t i = 0; i < 3u; ++i)
|
||||
packed_memory.words[(i * 64u + 60u) / 4u] = (0xffffu << 15u) | (i & 1u);
|
||||
for (uint32_t i = 0; i < 2u; ++i) {
|
||||
auto descriptor = image_descriptor;
|
||||
descriptor[0] += i;
|
||||
std::copy(descriptor.begin(), descriptor.end(),
|
||||
packed_memory.words.begin() + (0x1000u + i * 48u + 16u) / 4u);
|
||||
}
|
||||
SrtRuntime packed_runtime{.user_data = packed_data, .userdata = &packed_memory,
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
Check(MaterializeResources(packed_plan, packed_runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 2u && snapshot.images[0].dwords == image_descriptor &&
|
||||
snapshot.images[1].dwords[0] == image_descriptor[0] + 1u &&
|
||||
snapshot.flattened_srt[specialization.images[0].indirect_mapping_offset] == 2u,
|
||||
"packed material flags changed the table key or 48-byte descriptor addressing");
|
||||
packed_memory.words[60u / 4u] = 0xffff8002u;
|
||||
packed_memory.fail_address = 0x2070u;
|
||||
Check(MaterializeResources(packed_plan, packed_runtime, snapshot, specialization) &&
|
||||
snapshot.images.size() == 3u &&
|
||||
std::ranges::all_of(snapshot.images[2].dwords, [](uint32_t word) { return word == 0u; }),
|
||||
"masked out-of-range descriptor did not return zero without reading past table bounds");
|
||||
packed_memory.fail_address = 0x205cu;
|
||||
Check(!MaterializeResources(packed_plan, packed_runtime, snapshot, specialization),
|
||||
"unreadable in-range DWORD in a 48-byte descriptor record was accepted");
|
||||
auto overflowing = MakeIndirectImageFixture(false, 60u, false, 0u, 64u,
|
||||
UINT32_MAX, 48u, 16u);
|
||||
CheckFatal([&] { overflowing->PlanAndTrack(); }, "not a valid runtime value",
|
||||
"unbounded table offset conflated shader U32 wrap with scalar immediate addition");
|
||||
}
|
||||
|
||||
void TestGuardedDirectImageTable() {
|
||||
@@ -1529,6 +1569,9 @@ void TestUniformScalarBufferImage() {
|
||||
fixture.PlanAndTrack();
|
||||
const auto plan = ExtractResourcePlan(fixture.program);
|
||||
|
||||
Check(std::ranges::none_of(plan.descriptor_sources, [](const DescriptorSource& source) {
|
||||
return source.indirect_descriptor.has_value();
|
||||
}), "draw-uniform scalar image was expanded to GPU-selected candidates");
|
||||
std::array<uint32_t, 9> user_data{0x1000u, 0u, 0x100u, 0u,
|
||||
0x2000u, 0u, 0x100u, 0u, 0u};
|
||||
LinearTestMemory memory;
|
||||
@@ -3053,79 +3096,6 @@ ResourcePlan ConditionalBufferPlan(ConditionalBufferUse use) {
|
||||
return ExtractResourcePlan(fixture.program);
|
||||
}
|
||||
|
||||
void TestNativeScalarAtomicDataDoesNotEnterResourcePlan() {
|
||||
namespace CFG = Libs::Graphics::ShaderRecompiler::CFG;
|
||||
Fixture fixture;
|
||||
auto *entry = fixture.block;
|
||||
for (uint32_t i = 1; i < 5; ++i) fixture.AddBlock();
|
||||
fixture.program.block_info[0].terminator = {
|
||||
.kind = CFG::TerminatorKind::ConditionalBranch, .true_block = 1, .false_block = 4};
|
||||
for (uint32_t i = 1; i < 4; ++i) {
|
||||
fixture.program.block_info[i].terminator = {
|
||||
.kind = CFG::TerminatorKind::Branch, .true_block = i + 1};
|
||||
}
|
||||
fixture.program.block_info[4].terminator.kind = CFG::TerminatorKind::Return;
|
||||
|
||||
const auto table = fixture.Address(fixture.UserData(0), fixture.UserData(1));
|
||||
MemoryInfo scalar;
|
||||
scalar.kind = ResourceKind::ScalarAddress;
|
||||
scalar.component_count = 2;
|
||||
scalar.offset = 136;
|
||||
const auto low = fixture.Emit(ValueOpcode::LoadAddressU32,
|
||||
{table, Value(0u), Value(0u), Value(true)}, fixture.AddMemory(scalar, 0x560));
|
||||
scalar.offset = 140;
|
||||
scalar.component_index = 1;
|
||||
const auto high = fixture.Emit(ValueOpcode::LoadAddressU32,
|
||||
{table, Value(0u), Value(0u), Value(true)}, fixture.AddMemory(scalar, 0x560));
|
||||
const auto address = fixture.Address(low, high);
|
||||
scalar.offset = 24;
|
||||
scalar.component_count = 1;
|
||||
scalar.component_index = 0;
|
||||
const auto data_flags = fixture.AddMemory(scalar, 0x584);
|
||||
const auto data = fixture.Emit(ValueOpcode::LoadAddressU32,
|
||||
{address, Value(0u), Value(0u), Value(true)}, data_flags);
|
||||
const auto descriptor = fixture.Buffer({low,
|
||||
fixture.Emit(ValueOpcode::BitwiseOr32, {high, Value(256u << 16u)}),
|
||||
Value(1u), Value(90628u)}, 0x58c);
|
||||
MemoryInfo vector;
|
||||
vector.kind = ResourceKind::Buffer;
|
||||
vector.offset = 24;
|
||||
vector.data_dwords = 2;
|
||||
fixture.Emit(ValueOpcode::BufferAtomicOr64,
|
||||
{descriptor, Value(0u), Value(0u), Value(0u), Value(uint64_t{1}), Value(true)},
|
||||
fixture.AddMemory(vector, 0x58c));
|
||||
const auto flag = fixture.Emit(ValueOpcode::BitwiseAnd32, {data, Value(16u)});
|
||||
fixture.program.block_info[0].condition =
|
||||
fixture.Emit(ValueOpcode::INotEqual32, {flag, Value(0u)});
|
||||
fixture.PlanAndTrack();
|
||||
Check(data.Instruction()->Parent() == entry &&
|
||||
!fixture.program.memory_info[data_flags.index].planning_only &&
|
||||
fixture.program.info.buffers.size() == 1 && fixture.program.info.buffers[0].atomic,
|
||||
"mutable scalar data was replaced by a host snapshot before its native atomic write");
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
Check(plan.srt_reads.size() == 2 && plan.control_flow.empty() &&
|
||||
!plan.capture_specialization_reads,
|
||||
"resource-free successor chain retained the shader's scalar data predicate");
|
||||
struct Reads { uint32_t descriptors = 0; uint32_t data = 0; } reads;
|
||||
const auto Read = +[](void *userdata, uint64_t address, std::span<uint32_t> words) {
|
||||
auto &reads = *static_cast<Reads *>(userdata);
|
||||
if (address == 0x2018u) { ++reads.data; return false; }
|
||||
if (words.size() != 1 || (address != 0x1088u && address != 0x108cu)) return false;
|
||||
++reads.descriptors;
|
||||
words[0] = address == 0x1088u ? 0x2000u : 0u;
|
||||
return true;
|
||||
};
|
||||
const std::array<uint32_t, 2> user_data{0x1000u, 0u};
|
||||
const SrtRuntime runtime{.user_data = user_data, .read_memory = Read, .userdata = &reads,
|
||||
.read_specialization_memory = Read};
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
reads.descriptors == 2 && reads.data == 0 && snapshot.specialization_reads.empty() &&
|
||||
snapshot.buffers[0].dwords[0] == 0x2000u,
|
||||
"resource materialization read mutable scalar data or lost its descriptor pointer");
|
||||
}
|
||||
|
||||
void TestConditionalBufferMaterialization() {
|
||||
auto plan = ConditionalBufferPlan(ConditionalBufferUse::Optional);
|
||||
// GTA III leaves packet words in s[12:15] when its scalar control word is zero.
|
||||
@@ -3685,7 +3655,6 @@ int main() {
|
||||
Run("DMA address materialization", TestDmaAddressMaterialization);
|
||||
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
|
||||
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
|
||||
Run("native scalar atomic data", TestNativeScalarAtomicDataDoesNotEnterResourcePlan);
|
||||
Run("conditional buffer materialization", TestConditionalBufferMaterialization);
|
||||
Run("guarded scalar descriptor reads", TestGuardedScalarDescriptorReads);
|
||||
Run("conservative buffer reachability", TestConservativeBufferReachability);
|
||||
|
||||
@@ -2341,6 +2341,7 @@ public:
|
||||
|
||||
constexpr uint64_t base = 0x0000000200000000ull;
|
||||
constexpr uint64_t page = 0x4000;
|
||||
const auto cpu_only_tick = scheduler.CurrentTick();
|
||||
resources.MapMemory(base, page * 4);
|
||||
resources.MapMemory(base + page * 2, page * 4);
|
||||
Require("GpuMappedRangeLifecycle", "union",
|
||||
@@ -2373,8 +2374,9 @@ public:
|
||||
resources.MapMemory(new_prt, page * 6);
|
||||
Require("GpuMappedRangeLifecycle", "PRT replacement",
|
||||
!resources.IsMapped(old_prt, page * 4) &&
|
||||
resources.IsMapped(new_prt, page * 6),
|
||||
"old-unmap/new-map did not replace full PRT coverage");
|
||||
resources.IsMapped(new_prt, page * 6) &&
|
||||
scheduler.CurrentTick() == cpu_only_tick,
|
||||
"CPU-only unmaps submitted GPU work or lost full PRT coverage");
|
||||
|
||||
scheduler.Finish();
|
||||
context.ShutdownGpu();
|
||||
@@ -2818,10 +2820,20 @@ public:
|
||||
alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX;
|
||||
alignas(uint64_t) uint64_t cb_db_release_label = UINT64_MAX;
|
||||
bool parser_kept_nonblocking_boundaries = false;
|
||||
bool host_resource_retired = false;
|
||||
bool cpu_unmap_did_not_submit = false;
|
||||
std::jthread no_gpu_wait([&] {
|
||||
gpu.SendCommandSync([&] {
|
||||
processor->BufferInit();
|
||||
const auto tick_before = gpu_scheduler.CurrentTick();
|
||||
gpu_scheduler.DeferOperation([&] { host_resource_retired = true; });
|
||||
constexpr uint64_t cpu_only_address = 0x0000000200400000ull;
|
||||
context.MapMemory(cpu_only_address, 0x4000);
|
||||
context.UnmapMemory(cpu_only_address, 0x4000);
|
||||
cpu_unmap_did_not_submit =
|
||||
gpu_scheduler.CurrentTick() == tick_before &&
|
||||
!host_resource_retired &&
|
||||
!context.IsMapped(cpu_only_address, 0x4000);
|
||||
processor->TriggerEvent(0x16u, 0u);
|
||||
auto packet = make_release_mem(0, 0, nullptr, 0);
|
||||
Pm4Execution execution;
|
||||
@@ -2887,6 +2899,10 @@ public:
|
||||
no_gpu_wait.join();
|
||||
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
|
||||
m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr);
|
||||
Require("GpuCommandLane", "CPU-only unmap with pending host retirement",
|
||||
parser_did_not_wait_gpu && cpu_unmap_did_not_submit &&
|
||||
host_resource_retired,
|
||||
"CPU-only unmap submitted or waited for unrelated native work");
|
||||
Require("GpuCommandLane", "nonblocking GPU packets",
|
||||
parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries,
|
||||
"a cache event, GL2 writeback, DE counter, or interrupt boundary "
|
||||
@@ -3321,13 +3337,14 @@ public:
|
||||
"an unrelated unmap waited for a blocked PM4 submission");
|
||||
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
std::atomic<bool> normal_completed{false};
|
||||
gpu.SendCommandSync(
|
||||
[&] { scheduler.DeferOperation([&] { normal_completed = true; }); });
|
||||
std::atomic<bool> completion_published{false};
|
||||
gpu.SendCommandSync([&] {
|
||||
scheduler.DeferPriorityOperation([&] { completion_published = true; });
|
||||
});
|
||||
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
||||
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
||||
Require("GpuCommandLane", "unmap native completion",
|
||||
normal_completed.load() &&
|
||||
completion_published.load() &&
|
||||
!resources.IsMapped(empty_unmap_base, empty_unmap_size),
|
||||
"unmap returned before an earlier native guest-memory callback");
|
||||
|
||||
@@ -4100,6 +4117,13 @@ public:
|
||||
cache.IsRegionRegistered(index_begin - 1, index_span + 2),
|
||||
"indexed lookup mishandled a half-open boundary, gap, or broad "
|
||||
"overlap");
|
||||
const auto clean_unmap_tick = scheduler.CurrentTick();
|
||||
resources.UnmapMemory(index_begin, index_page);
|
||||
Require(name, "clean cached buffer unmap drain",
|
||||
scheduler.CurrentTick() == clean_unmap_tick + 1 &&
|
||||
!resources.IsMapped(index_begin, index_page),
|
||||
"unmapping a clean cached buffer did not drain native work");
|
||||
resources.MapMemory(index_begin, index_page);
|
||||
const auto index_bridge =
|
||||
cache.FindBuffer(index_begin + index_page - 1, index_page + 2);
|
||||
Require(name, "registered-range merge",
|
||||
@@ -4613,6 +4637,24 @@ public:
|
||||
sizeof(partial_unmap_survivor_value),
|
||||
partial_unmap_survivor_value, false);
|
||||
resources.UnmapMemory(base + 0x8000, 0x4000);
|
||||
Require(
|
||||
name, "partial-invalidation keeps disjoint bytes dirty",
|
||||
resources.IsMapped(base + partial_unmap_survivor_offset,
|
||||
sizeof(partial_unmap_survivor_value)) &&
|
||||
!cache.IsRegionCpuModified(
|
||||
base + partial_unmap_survivor_offset,
|
||||
sizeof(partial_unmap_survivor_value)) &&
|
||||
cache.IsRegionGpuModified(base + partial_unmap_survivor_offset,
|
||||
sizeof(partial_unmap_survivor_value)) &&
|
||||
cache.HasGpuDirtyBytes(base + partial_unmap_survivor_offset,
|
||||
sizeof(partial_unmap_survivor_value)) &&
|
||||
!cache.HasGpuDirtyBytes(base + partial_unmap_offset,
|
||||
sizeof(partial_unmap_value)),
|
||||
"partial invalidation read back an unrelated mapped page");
|
||||
Require(name, "partial-invalidation survivor read fault",
|
||||
resources.HandleFault(PageFaultAccess::Read,
|
||||
base + partial_unmap_survivor_offset),
|
||||
"CPU read did not publish the retained GPU-dirty page");
|
||||
uint32_t partial_unmap_survivor_backing = 0;
|
||||
std::memcpy(&partial_unmap_survivor_backing,
|
||||
memory + partial_unmap_survivor_offset,
|
||||
@@ -4630,7 +4672,8 @@ public:
|
||||
!cache.HasGpuDirtyBytes(base + partial_unmap_survivor_offset,
|
||||
sizeof(partial_unmap_survivor_value)) &&
|
||||
partial_unmap_survivor_backing == partial_unmap_survivor_value,
|
||||
"widened partial invalidation mishandled ownership or backing");
|
||||
"partial invalidation or subsequent CPU read mishandled ownership or "
|
||||
"backing");
|
||||
resources.MapMemory(base + 0x8000, 0x4000);
|
||||
auto survivor =
|
||||
cache.ObtainBuffer(base + partial_unmap_survivor_offset,
|
||||
@@ -7119,12 +7162,24 @@ public:
|
||||
Prospero::ImageType::kColor2D, {2048, 1, 1}, 1, 4, 1);
|
||||
const auto partial_unmap_image_id =
|
||||
texture_cache.FindImage(partial_unmap_image);
|
||||
texture_cache.UnmapMemory(partial_unmap_image.info.data.address, 0x1000);
|
||||
const auto image_address = partial_unmap_image.info.data.address;
|
||||
Require(name, "registered image boundaries",
|
||||
texture_cache.IsRegionRegistered(image_address, 1) &&
|
||||
texture_cache.IsRegionRegistered(image_address + 0x1fff, 1) &&
|
||||
!texture_cache.IsRegionRegistered(image_address - 1, 1) &&
|
||||
!texture_cache.IsRegionRegistered(image_address + 0x2000, 1) &&
|
||||
texture_cache.IsRegionRegistered(image_address - 1, 0x2002),
|
||||
"image lookup mishandled a partial overlap or adjacent range");
|
||||
scheduler.Finish();
|
||||
scheduler.DrainPriorityOperations();
|
||||
const auto image_unmap_tick = scheduler.CurrentTick();
|
||||
resources.UnmapMemory(image_address + 0x1000, 0x1000);
|
||||
Require(name, "partial image unmap tracking",
|
||||
partial_unmap_image_id &&
|
||||
!texture_cache.FindImageFromRange(
|
||||
partial_unmap_image.info.data.address, 0x2000, false),
|
||||
"partial unmap left the deleted image's mapped tail tracked");
|
||||
scheduler.CurrentTick() == image_unmap_tick + 1 &&
|
||||
!texture_cache.IsRegionRegistered(image_address, 0x2000) &&
|
||||
!texture_cache.FindImageFromRange(image_address, 0x2000, false),
|
||||
"partial image unmap skipped its drain or retained the image");
|
||||
|
||||
constexpr uint64_t unformatted_alias_offset = 0x2500000;
|
||||
auto unformatted_alias =
|
||||
@@ -18598,6 +18653,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
||||
case Opcode::V_CMPX_NLT_F32:
|
||||
case Opcode::V_CMP_CLASS_F32:
|
||||
case Opcode::V_CMPX_CLASS_F32:
|
||||
case Opcode::V_CMPX_CLASS_F16:
|
||||
case Opcode::V_CMP_LT_F16:
|
||||
case Opcode::V_CMP_EQ_F16:
|
||||
case Opcode::V_CMP_LE_F16:
|
||||
@@ -20311,6 +20367,114 @@ TestCase Scalar64BitOps() {
|
||||
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarDynamic64BitOps() {
|
||||
using O = ShaderOpcode;
|
||||
struct Input {
|
||||
uint64_t lhs;
|
||||
uint64_t rhs;
|
||||
u32 count;
|
||||
u32 offset;
|
||||
};
|
||||
constexpr Input inputs[] = {
|
||||
{0, UINT64_MAX, 0, 0},
|
||||
{UINT64_MAX, 0, 63, 0},
|
||||
{0x8000000000000000ull, 1, 64, 0},
|
||||
{0x0123456789abcdefull, 0xfedcba9876543210ull, 127, 0},
|
||||
{UINT64_MAX, 0xffffffffull, 64, 1},
|
||||
{0x0123456789abcdefull, 0x0123456789abcdefull, 63, 32},
|
||||
{UINT64_MAX, 0x100000000ull, 127, 63},
|
||||
{0, 0, 0, 127},
|
||||
{0x8000000000000001ull, UINT64_MAX, UINT32_MAX, 64},
|
||||
};
|
||||
TestCase test;
|
||||
test.name = "ScalarDynamic64BitOps";
|
||||
for (const auto &input : inputs) {
|
||||
test.initial.insert(test.initial.end(),
|
||||
{u32(input.lhs), u32(input.lhs >> 32), u32(input.rhs),
|
||||
u32(input.rhs >> 32), input.count, input.offset,
|
||||
((input.count & 127u) << 16) | input.offset});
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
const auto store = [&](uint64_t value, bool scc) {
|
||||
const auto output = static_cast<u32>(test.expected.size());
|
||||
code.push_back(EncodeSMovB32(18, 253));
|
||||
AppendStoreSgprPair(&code, 16, output);
|
||||
AppendStoreSgpr(&code, 18, output + 2);
|
||||
test.expected.insert(test.expected.end(),
|
||||
{u32(value), u32(value >> 32), u32(scc)});
|
||||
};
|
||||
for (u32 i = 0; i < std::size(inputs); ++i) {
|
||||
const auto [lhs, rhs, count, offset] = inputs[i];
|
||||
for (u32 word = 0; word < 7; ++word) {
|
||||
AppendVMovU32(&code, 30, (i * 7 + word) * sizeof(u32));
|
||||
AppendBufferLoadDword(&code, 1, 30);
|
||||
code.push_back(EncodeVop1(0x02, 8 + word, Vgpr(1)));
|
||||
}
|
||||
for (const auto [opcode, value] :
|
||||
std::array<std::pair<u32, uint64_t>, 8>{{{0x0f, lhs & rhs},
|
||||
{0x11, lhs | rhs},
|
||||
{0x13, lhs ^ rhs},
|
||||
{0x15, lhs & ~rhs},
|
||||
{0x17, lhs | ~rhs},
|
||||
{0x19, ~(lhs & rhs)},
|
||||
{0x1b, ~(lhs | rhs)},
|
||||
{0x1d, ~(lhs ^ rhs)}}}) {
|
||||
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(value != 0)));
|
||||
code.push_back(EncodeSop2(opcode, 16, 8, 10));
|
||||
store(value, value != 0);
|
||||
}
|
||||
for (const bool select_lhs : {false, true}) {
|
||||
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(!select_lhs)));
|
||||
code.push_back(EncodeSop2(0x0b, 16, 8, 10));
|
||||
store(select_lhs ? lhs : rhs, select_lhs);
|
||||
}
|
||||
// BFM preserves SCC; BFE sets it from the entire extracted result.
|
||||
code.push_back(EncodeSop2(0x25, 16, 12, 13));
|
||||
store(((uint64_t{1} << (count & 63u)) - 1u) << (offset & 63u), true);
|
||||
code.push_back(EncodeSop2(0x29, 16, 8, 14));
|
||||
const auto width = std::min(count & 127u, 64u - (offset & 63u));
|
||||
const auto mask = width == 64 ? UINT64_MAX : (uint64_t{1} << width) - 1u;
|
||||
const auto extracted = (lhs >> (offset & 63u)) & mask;
|
||||
store(extracted, extracted != 0);
|
||||
for (const bool set : {false, true}) {
|
||||
code.push_back(EncodeSop1(0x04, 16, 8));
|
||||
code.push_back(EncodeSop1(set ? 0x1e : 0x1c, 16, 12));
|
||||
const auto bit = uint64_t{1} << (count & 63u);
|
||||
store(set ? lhs | bit : lhs & ~bit, extracted != 0);
|
||||
}
|
||||
code.push_back(EncodeSop1(0x2d, 16, 8));
|
||||
uint64_t quads = 0;
|
||||
for (u32 quad = 0; quad < 16; ++quad) {
|
||||
quads |= uint64_t((lhs >> (quad * 4) & 15u) != 0) << quad;
|
||||
}
|
||||
store(quads, quads != 0);
|
||||
code.push_back(EncodeSop1(0x3b, 16, 8));
|
||||
uint64_t replicated = 0;
|
||||
for (u32 bit = 0; bit < 32; ++bit) {
|
||||
replicated |= ((lhs >> bit) & 1u) * (uint64_t{3} << (2 * bit));
|
||||
}
|
||||
store(replicated, quads != 0);
|
||||
code.push_back(EncodeSMovB32(17, InlineU32(0)));
|
||||
code.push_back(EncodeSop1(0x10, 16, 8));
|
||||
store(std::popcount(lhs), lhs != 0);
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {
|
||||
O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32,
|
||||
O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_AND_B64,
|
||||
O::S_OR_B64, O::S_XOR_B64, O::S_ANDN2_B64,
|
||||
O::S_ORN2_B64, O::S_NAND_B64, O::S_NOR_B64,
|
||||
O::S_XNOR_B64, O::S_CSELECT_B64, O::S_BFM_B64,
|
||||
O::S_BFE_U64, O::S_MOV_B64, O::S_BITSET0_B64,
|
||||
O::S_BITSET1_B64, O::S_QUADMASK_B64, O::S_BITREPLICATE_B64_B32,
|
||||
O::S_BCNT1_I32_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpTypeInt 64 0", "OpBitwiseAnd", "OpBitwiseOr",
|
||||
"OpBitwiseXor"};
|
||||
return test;
|
||||
}
|
||||
|
||||
uint64_t ReferenceAshrI64(uint64_t bits, u32 count) {
|
||||
const auto shift = count & 63u;
|
||||
// Complementing negative values expresses sign fill using unsigned shifts.
|
||||
@@ -20384,8 +20548,7 @@ TestCase ScalarAshrI64Edges(bool dynamic) {
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {
|
||||
{"S_ASHR_I64", std::size(sources) * std::size(counts)}};
|
||||
test.required_spirv = {"OpShiftRightArithmetic"};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.required_spirv = {"OpShiftRightArithmetic", "OpTypeInt 64 0"};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -20486,7 +20649,7 @@ TestCase ScalarAshrI64OperandsAndAliases() {
|
||||
O::S_ASHR_I64, O::V_MOV_B32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"S_ASHR_I64", instructions}};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.required_spirv.push_back("OpTypeInt 64 0");
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -20548,7 +20711,6 @@ TestCase ScalarAshrI64Masks(u32 wave_size) {
|
||||
O::S_ENDPGM};
|
||||
test.decoded_counts = {{"S_ASHR_I64 vcc_lo, vcc_lo", std::size(cases)},
|
||||
{"S_ASHR_I64 exec_lo, exec_lo", std::size(cases)}};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.compute_info.threads_num[0] = wave_size;
|
||||
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
@@ -21531,6 +21693,65 @@ TestCase Vop2SdwaMaxI32CapturedHighWord(u32 wave_size) {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop2SdwaMulI24Destinations(u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
constexpr std::array<u32, 5> sources{
|
||||
0xdead1234u, 0xabcdffffu, 0x98768001u, 0x34560000u, 0x56789abcu};
|
||||
constexpr std::array<u32, 10> captured_expected{
|
||||
0xdead91a0u, 0xdead3400u, 0xabcdfff8u, 0xabcdff00u,
|
||||
0x98760008u, 0x98760100u, 0x34560000u, 0x34560000u,
|
||||
0x56789abcu, 0x56789abcu};
|
||||
constexpr std::array<u32, 18> partial_expected{
|
||||
0x00000080u, 0xffffff80u, 0xa1b2c380u,
|
||||
0x00008000u, 0xffff8000u, 0xa1b280d4u,
|
||||
0x00800000u, 0xff800000u, 0xa180c3d4u,
|
||||
0x80000000u, 0x80000000u, 0x80b2c3d4u,
|
||||
0x0000a180u, 0xffffa180u, 0xa1b2a180u,
|
||||
0xa1800000u, 0xa1800000u, 0xa180c3d4u};
|
||||
TestCase test;
|
||||
test.name = wave_size == 64 ? "Vop2SdwaMulI24DestinationsWave64"
|
||||
: "Vop2SdwaMulI24DestinationsWave32";
|
||||
test.initial.assign(sources.begin(), sources.end());
|
||||
test.initial.push_back(0xabcde080u);
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
for (u32 index = 0; index < sources.size(); ++index) {
|
||||
AppendVMovU32(&code, 30, index * sizeof(u32));
|
||||
AppendBufferLoadDword(&code, 12, 30);
|
||||
AppendBufferLoadDword(&code, 26, 30);
|
||||
AppendSMovLiteral(&code, 106, 256);
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(index + 1u == sources.size() ? 0 : 1)));
|
||||
// Captured WORD0/PRESERVE encodings alias their source and destination.
|
||||
code.insert(code.end(), {0x121818f9u, 0x04861488u,
|
||||
0x123434f9u, 0x0486146au});
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
AppendStoreVgpr(&code, 12, static_cast<u32>(test.expected.size()));
|
||||
AppendStoreVgpr(&code, 26, static_cast<u32>(test.expected.size()) + 1u);
|
||||
test.expected.push_back(captured_expected[index * 2u]);
|
||||
test.expected.push_back(captured_expected[index * 2u + 1u]);
|
||||
}
|
||||
AppendVMovU32(&code, 30, sources.size() * sizeof(u32));
|
||||
AppendBufferLoadDword(&code, 12, 30);
|
||||
for (u32 selector = 0; selector < 6; ++selector) {
|
||||
for (u32 unused = 0; unused < 3; ++unused) {
|
||||
AppendVMovLiteral(&code, 26, 0xa1b2c3d4u);
|
||||
code.push_back(EncodeVop2(0x09, 26, 249, 12));
|
||||
code.push_back(EncodeVop2Sdwa(InlineU32(3), selector, unused, 6, 4,
|
||||
0, 0, 0, 0, 0, 0, 1));
|
||||
AppendStoreVgpr(&code, 26, static_cast<u32>(test.expected.size()));
|
||||
test.expected.push_back(partial_expected[selector * 3u + unused]);
|
||||
}
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_MUL_I32_I24, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpIMul", "OpBitFieldUExtract"};
|
||||
test.compute_info.wave_size = wave_size;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop2SdwaLshrrevCapturedByte1Source() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -21811,8 +22032,7 @@ TestCase VectorVop3LshrrevB64Captured() {
|
||||
test.expected = {0x789abcdeu, 0x00123456u};
|
||||
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::V_LSHRREV_B64,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpShiftRightLogical", "OpSelect"};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.required_spirv = {"OpShiftRightLogical"};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -21839,8 +22059,7 @@ TestCase VectorVop3LshlrevB64Captured() {
|
||||
test.expected = {33u, 0u, 2u, 1u, 0u};
|
||||
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_LSHLREV_B64, O::V_ADD_NC_U32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpShiftLeftLogical", "OpSelect"};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.required_spirv = {"OpShiftLeftLogical", "OpTypeInt 64 0"};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -21848,7 +22067,7 @@ TestCase VectorDynamicU64ShiftEdges() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = "VectorDynamicU64ShiftEdges";
|
||||
test.initial = {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u};
|
||||
test.initial = {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u, 127u, UINT32_MAX};
|
||||
test.expected = test.initial;
|
||||
auto& code = test.code;
|
||||
for (const uint64_t source : {uint64_t{0}, uint64_t{1},
|
||||
@@ -21877,8 +22096,8 @@ TestCase VectorDynamicU64ShiftEdges() {
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_LSHLREV_B64,
|
||||
O::V_LSHRREV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpShiftLeftLogical", "OpShiftRightLogical", "OpSelect"};
|
||||
test.forbidden_spirv = {"OpTypeInt 64"};
|
||||
test.required_spirv = {"OpShiftLeftLogical", "OpShiftRightLogical",
|
||||
"OpTypeInt 64 0"};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -22411,23 +22630,53 @@ TestCase VectorVop3BSubCoU32UsesRdna2Opcode310() {
|
||||
|
||||
TestCase VectorMadU64U32UnsignedCarryOut() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
AppendVMovLiteral(&code, 1, 0xffffffffu);
|
||||
AppendVMovU32(&code, 2, 2);
|
||||
AppendVMovU32(&code, 4, 2);
|
||||
AppendVMovLiteral(&code, 5, 0xffffffffu);
|
||||
AppendVop3B(&code, 0x176u, 10, 20, Vgpr(1), Vgpr(2), Vgpr(4));
|
||||
AppendStoreVgpr(&code, 10, 0);
|
||||
AppendStoreVgpr(&code, 11, 1);
|
||||
AppendStoreSgprPair(&code, 20, 2);
|
||||
struct Input {
|
||||
u32 lhs;
|
||||
u32 rhs;
|
||||
uint64_t addend;
|
||||
};
|
||||
constexpr Input inputs[] = {
|
||||
{0, UINT32_MAX, UINT64_MAX},
|
||||
{UINT32_MAX, 2, 0xffffffff00000002ull},
|
||||
{UINT32_MAX, UINT32_MAX, UINT64_MAX},
|
||||
{0x80000000u, 0x80000000u, 0x4000000000000000ull},
|
||||
{3, 5, 7},
|
||||
};
|
||||
TestCase test;
|
||||
test.name = "VectorMadU64U32UnsignedCarryOut";
|
||||
for (const auto &input : inputs) {
|
||||
test.initial.insert(
|
||||
test.initial.end(),
|
||||
{input.lhs, input.rhs, u32(input.addend), u32(input.addend >> 32)});
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
for (u32 i = 0; i < std::size(inputs); ++i) {
|
||||
for (u32 word = 0; word < 4; ++word) {
|
||||
AppendVMovU32(&code, 30, (i * 4 + word) * sizeof(u32));
|
||||
AppendBufferLoadDword(&code, word < 2 ? word + 1 : word + 2, 30);
|
||||
}
|
||||
AppendVop3B(&code, 0x176u, 10, 20, Vgpr(1), Vgpr(2), Vgpr(4));
|
||||
const auto output = static_cast<u32>(test.expected.size());
|
||||
AppendStoreVgpr(&code, 10, output);
|
||||
AppendStoreVgpr(&code, 11, output + 1);
|
||||
AppendStoreSgprPair(&code, 20, output + 2);
|
||||
const auto product = uint64_t(inputs[i].lhs) * inputs[i].rhs;
|
||||
const auto sum = product + inputs[i].addend;
|
||||
test.expected.insert(test.expected.end(),
|
||||
{u32(sum), u32(sum >> 32), u32(sum < product), 0});
|
||||
}
|
||||
AppendEnd(&code);
|
||||
|
||||
return {"VectorMadU64U32UnsignedCarryOut",
|
||||
code,
|
||||
{},
|
||||
{0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u},
|
||||
{O::V_MOV_B32, O::V_MAD_U64_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAD_U64_U32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
// NVIDIA rejected the native widening multiply in a large Astro's Playroom
|
||||
// vertex shader. Keep its 32x32 product portable and its 64-bit addition
|
||||
// native.
|
||||
test.required_spirv = {"OpTypeInt 64 0", "OpUMulExtended", "OpIMul %uint",
|
||||
"OpIAdd %ulong", "OpULessThan"};
|
||||
test.forbidden_spirv = {"OpIMul %ulong", "OpIMul %long"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorLaneAndPackedOps() {
|
||||
@@ -23848,23 +24097,33 @@ TestCase FloatInlineConstantF16UsesNumericValue() {
|
||||
AppendVMovLiteral(&code, 2, 0x3c003c00u); // 1.0h, 1.0h
|
||||
AppendVMovLiteral(&code, 10, 0xaaaa0000u);
|
||||
AppendVMovLiteral(&code, 12, 0xbbbb0000u);
|
||||
AppendVMovLiteral(&code, 14, 0xcccc0000u);
|
||||
AppendVMovLiteral(&code, 15, 0xdddd0000u);
|
||||
|
||||
AppendVop3(&code, 0x34bu, 10, 246u, Vgpr(1), Vgpr(2));
|
||||
AppendVop3p(&code, 0x0eu, 11, 246u, Vgpr(1), Vgpr(2), 0x1u);
|
||||
AppendVop3(&code, 0x34bu, 12, 248u, 242u, 128u);
|
||||
AppendVop3(&code, 0x311u, 13, 242u, 248u);
|
||||
AppendVop3(&code, 0x34bu, 14, 246u, Vgpr(1), Vgpr(2), 0, 1);
|
||||
AppendVop3(&code, 0x303u, 15, 240u, InlineU32(1));
|
||||
|
||||
AppendStoreVgpr(&code, 10, 0);
|
||||
AppendStoreVgpr(&code, 11, 1);
|
||||
AppendStoreVgpr(&code, 12, 2);
|
||||
AppendStoreVgpr(&code, 13, 3);
|
||||
AppendStoreVgpr(&code, 14, 4);
|
||||
AppendStoreVgpr(&code, 15, 5);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "FloatInlineConstantF16UsesNumericValue";
|
||||
test.code = std::move(code);
|
||||
test.expected = {0xaaaa4200u, 0x3c004200u, 0xbbbb3118u};
|
||||
test.expected = {0xaaaa4200u, 0x3c004200u, 0xbbbb3118u,
|
||||
0x31183c00u, 0xcccc3c00u, 0xdddd3801u};
|
||||
test.opcodes = {O::V_MOV_B32, O::V_FMA_F16, O::V_PK_FMA_F16,
|
||||
O::V_PACK_B32_F16, O::V_ADD_NC_U16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"4.000000", 2}, {"0.159155", 1}};
|
||||
test.decoded_counts = {{"4.000000", 3}, {"0.159155", 2}};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -25617,6 +25876,77 @@ TestCase VectorVopcSdwaCmpxClassF32CapturedExecMask() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorCmpxClassF16(u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
// One raw half value for each architectural class bit, in bit order.
|
||||
constexpr std::array<u32, 10> values{
|
||||
0x7c01u, 0x7e01u, 0xfc00u, 0xbc00u, 0x8001u,
|
||||
0x8000u, 0x0000u, 0x0001u, 0x3c00u, 0x7c00u};
|
||||
TestCase test;
|
||||
test.name = wave_size == 64 ? "VectorCmpxClassF16Wave64" : "VectorCmpxClassF16Wave32";
|
||||
for (const auto value : values) {
|
||||
test.initial.insert(test.initial.end(), {0xa55a0000u | value, (value << 16u) | 0x7e01u});
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
AppendSMovLiteral(&code, 106, 0x12345678u);
|
||||
AppendSMovLiteral(&code, 107, 0x87654321u);
|
||||
code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
||||
const auto compare = [&](std::initializer_list<u32> words, u32 expected) {
|
||||
code.insert(code.end(), words);
|
||||
code.push_back(EncodeSMovB32(20, 126));
|
||||
code.push_back(EncodeSMovB32(21, 127));
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
AppendStoreSgpr(&code, 20, static_cast<u32>(test.expected.size()));
|
||||
AppendStoreSgpr(&code, 21, static_cast<u32>(test.expected.size()) + 1u);
|
||||
test.expected.insert(test.expected.end(), {expected, 0});
|
||||
};
|
||||
for (u32 index = 0; index < values.size(); ++index) {
|
||||
AppendVMovU32(&code, 30, index * 8u);
|
||||
AppendBufferLoadDword(&code, 0, 30);
|
||||
AppendVMovU32(&code, 30, index * 8u + 4u);
|
||||
AppendBufferLoadDword(&code, 2, 30);
|
||||
for (u32 bit = 0; bit < 12; ++bit) {
|
||||
AppendVMovU32(&code, 1, bit == 11 ? 0 : 1u << bit);
|
||||
const u32 expected = bit == index ? 1 : 0;
|
||||
if ((bit & 1u) == 0) {
|
||||
compare({EncodeVopc(0x9f, Vgpr(0), 1)}, expected);
|
||||
} else {
|
||||
compare({EncodeVop3Word0(0x9f, 126), EncodeVop3Word1(Vgpr(0), Vgpr(1))}, expected);
|
||||
}
|
||||
}
|
||||
const u32 finite = index >= 3 && index <= 8 ? 1 : 0;
|
||||
compare({0x7d3f70f9u, 0x86360000u}, finite); // Captured -abs(v0), inline56.
|
||||
compare({0x7d3f70f9u, 0x86350002u}, finite); // Captured -abs(v2.word1).
|
||||
}
|
||||
AppendVMovU32(&code, 1, 1u << 8u);
|
||||
compare({EncodeVopc(0x9f, 242, 1)}, 1); // Inline1.0 encodes half0x3c00.
|
||||
AppendVMovU32(&code, 1, 1u << 6u);
|
||||
compare({EncodeVopc(0x9f, 249, 1),
|
||||
EncodeVopcSdwa(242, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 1)}, 1);
|
||||
AppendVMovU32(&code, 1, 1u << 7u);
|
||||
compare({EncodeVopc(0x9f, 249, 1),
|
||||
EncodeVopcSdwa(242, 0, 0, 1, 6, 0, 0, 0, 0, 0, 0, 1)}, 1);
|
||||
AppendVMovU32(&code, 30, 8u * 8u);
|
||||
AppendBufferLoadDword(&code, 0, 30);
|
||||
compare({EncodeVopc(0x9f, 249, 248),
|
||||
EncodeVopcSdwa(0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 1)}, 1);
|
||||
// INV_2PI as a half class mask is0x3118, including positive normal bit8.
|
||||
AppendVMovU32(&code, 1, 1u << 8u);
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(0)));
|
||||
compare({EncodeVopc(0x9f, Vgpr(0), 1)}, 0);
|
||||
AppendStoreSgpr(&code, 106, static_cast<u32>(test.expected.size()));
|
||||
AppendStoreSgpr(&code, 107, static_cast<u32>(test.expected.size()) + 1u);
|
||||
test.expected.insert(test.expected.end(), {0x12345678u, 0x87654321u});
|
||||
AppendEnd(&code);
|
||||
test.initial.resize(test.expected.size());
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_CMPX_CLASS_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.compute_info.wave_size = wave_size;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Wave32VccMasksPreserveOtherHalf() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
@@ -26880,18 +27210,15 @@ TestCase ScalarMemoryLoadVariants() {
|
||||
std::vector<u32> expected = initial;
|
||||
expected.insert(expected.end(), initial.begin(), initial.end());
|
||||
|
||||
TestCase test{"ScalarMemoryLoadVariants",
|
||||
code,
|
||||
initial,
|
||||
expected,
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::S_LOAD_DWORDX2, O::S_LOAD_DWORDX4,
|
||||
O::S_LOAD_DWORDX8, O::S_LOAD_DWORDX16, O::S_BUFFER_LOAD_DWORD,
|
||||
O::S_BUFFER_LOAD_DWORDX2, O::S_BUFFER_LOAD_DWORDX4,
|
||||
O::S_BUFFER_LOAD_DWORDX8, O::S_BUFFER_LOAD_DWORDX16, O::V_MOV_B32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
test.bda_mappings = {{0, 0}};
|
||||
test.required_spirv = {"get_bda_pointer"};
|
||||
return test;
|
||||
return {"ScalarMemoryLoadVariants",
|
||||
code,
|
||||
initial,
|
||||
expected,
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::S_LOAD_DWORDX2, O::S_LOAD_DWORDX4,
|
||||
O::S_LOAD_DWORDX8, O::S_LOAD_DWORDX16, O::S_BUFFER_LOAD_DWORD,
|
||||
O::S_BUFFER_LOAD_DWORDX2, O::S_BUFFER_LOAD_DWORDX4,
|
||||
O::S_BUFFER_LOAD_DWORDX8, O::S_BUFFER_LOAD_DWORDX16, O::V_MOV_B32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarBufferOffsetAlignmentAndCarry() {
|
||||
@@ -26935,15 +27262,11 @@ TestCase ScalarLoadSignedImmediateOffsetAddsSoffset() {
|
||||
AppendStoreSgpr(&code, 1, 0);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test{"ScalarLoadSignedImmediateOffsetAddsSoffset",
|
||||
code,
|
||||
{0x11111111u, 0x22222222u},
|
||||
{0x22222222u, 0x22222222u},
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
test.bda_mappings = {{0, 0}};
|
||||
test.required_spirv = {"get_bda_pointer"};
|
||||
test.forbidden_spirv = {"flattened_srt"};
|
||||
return test;
|
||||
return {"ScalarLoadSignedImmediateOffsetAddsSoffset",
|
||||
code,
|
||||
{0x11111111u, 0x22222222u},
|
||||
{0x22222222u, 0x22222222u},
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase BufferLoadStore() {
|
||||
@@ -29317,27 +29640,23 @@ TestCase BranchVccnzUsesCarryProducedWaveMask() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ScalarLoadAlignsComponents() {
|
||||
TestCase ScalarLoadAlignsComponentsAndMasksAddress() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
AppendSMovLiteral(&code, 0, 1u);
|
||||
AppendSMovLiteral(&code, 2, 3u);
|
||||
AppendSMovLiteral(&code, 3, 0u);
|
||||
AppendSMovLiteral(&code, 3, 0xffff0000u);
|
||||
code.push_back(EncodeSmem0(0x00, 1, 1));
|
||||
code.push_back(EncodeSmem1(3, 0));
|
||||
AppendStoreSgpr(&code, 1, 0);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test{"ScalarLoadAlignsComponents",
|
||||
code,
|
||||
{0x11111111u, 0x22222222u},
|
||||
{0x11111111u, 0x22222222u},
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
test.bda_mappings = {{0, 0}};
|
||||
test.required_spirv = {"get_bda_pointer"};
|
||||
test.forbidden_spirv = {"flattened_srt"};
|
||||
return test;
|
||||
return {"ScalarLoadAlignsComponentsAndMasksAddress",
|
||||
code,
|
||||
{0x11111111u, 0x22222222u},
|
||||
{0x11111111u, 0x22222222u},
|
||||
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarLoadAlignsDynamicBase() {
|
||||
@@ -34559,6 +34878,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ScalarBfeI32CapturedRawSignExtends);
|
||||
AddCase(BitfieldExtractWidthPastEndEdges);
|
||||
AddCase(Scalar64BitOps);
|
||||
AddCase(ScalarDynamic64BitOps);
|
||||
cases.push_back(ScalarAshrI64Edges(false));
|
||||
cases.push_back(ScalarAshrI64Edges(true));
|
||||
AddCase(ScalarAshrI64OperandsAndAliases);
|
||||
@@ -34595,6 +34915,8 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends);
|
||||
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(32));
|
||||
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64));
|
||||
cases.push_back(Vop2SdwaMulI24Destinations(32));
|
||||
cases.push_back(Vop2SdwaMulI24Destinations(64));
|
||||
AddCase(Vop2SdwaLshrrevCapturedByte1Source);
|
||||
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
|
||||
AddCase(Vop3CvtPkI16I32Captured);
|
||||
@@ -34702,6 +35024,8 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(VectorVop3CmpxNeI64CapturedExecMask);
|
||||
AddCase(VectorCompareClassF32);
|
||||
AddCase(VectorVopcSdwaCmpxClassF32CapturedExecMask);
|
||||
cases.push_back(VectorCmpxClassF16(32));
|
||||
cases.push_back(VectorCmpxClassF16(64));
|
||||
AddCase(VectorCompareF16Ops);
|
||||
AddCase(Wave32VccMasksPreserveOtherHalf);
|
||||
AddCase(Vop2SdwaCndmaskSourceModifier);
|
||||
@@ -34736,7 +35060,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ScalarMemoryLoadVariants);
|
||||
AddCase(ScalarBufferOffsetAlignmentAndCarry);
|
||||
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
|
||||
AddCase(ScalarLoadAlignsComponents);
|
||||
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
|
||||
AddCase(ScalarLoadAlignsDynamicBase);
|
||||
cases.push_back(ScalarBufferFromLoopReadlane(32));
|
||||
cases.push_back(ScalarBufferFromLoopReadlane(64));
|
||||
@@ -37647,19 +37971,17 @@ void CheckResourcePlanHandoff() {
|
||||
ValidateSpirv(name, compiled.spirv);
|
||||
const auto &bindings = compiled.program.bindings;
|
||||
Require(name, "GPU data requirements",
|
||||
bindings.user_data_registers == (numeric_read ? std::vector<u32>{0, 1}
|
||||
: std::vector<u32>{}) &&
|
||||
bindings.memory_offset_count == 1 &&
|
||||
FindBinding(bindings, DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
||||
(FindBinding(bindings, DescriptorBindingKind::BdaPagetable) != nullptr) == numeric_read,
|
||||
"descriptor-only shader retained SRT uploads or native scalar data lost its address");
|
||||
bindings.user_data_registers.empty() && bindings.memory_offset_count == 1 &&
|
||||
(FindBinding(bindings, DescriptorBindingKind::FlattenedSrt) != nullptr) ==
|
||||
numeric_read,
|
||||
"descriptor-only shader retained SRT uploads or live scalar data was removed");
|
||||
|
||||
memory[0] = 0x2000u;
|
||||
memory[4] = 13u;
|
||||
Require(name, "independent host plan",
|
||||
MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||
snapshot.buffers[0].dwords[0] == 0x2000u &&
|
||||
snapshot.flattened_srt.size() == 4,
|
||||
(!numeric_read || snapshot.flattened_srt.back() == 13u),
|
||||
"compiled shader cleanup invalidated or froze the host resource plan");
|
||||
}
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
@@ -40044,6 +40366,17 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, VectorVopcSdwaCmpxClassF32CapturedExecMask());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cmpx-class-f16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorCmpxClassF16(32));
|
||||
RunCase(&vulkan, VectorCmpxClassF16(64));
|
||||
RunCase(&vulkan, VectorCompareClassF32());
|
||||
RunCase(&vulkan, VectorVopcSdwaCmpxClassF32CapturedExecMask());
|
||||
RunCase(&vulkan, FloatInlineConstantF16UsesNumericValue());
|
||||
RunCase(&vulkan, VectorCompareF16Ops());
|
||||
RunCase(&vulkan, VectorMinMaxF16Ops());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cmp-eq-f64-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorCompareF64Edges());
|
||||
@@ -40351,6 +40684,18 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckGpuSuspendPoint();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--native64-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, ScalarDynamic64BitOps());
|
||||
RunCase(&vulkan, VectorDynamicU64ShiftEdges());
|
||||
RunCase(&vulkan, ScalarAshrI64Edges(false));
|
||||
RunCase(&vulkan, ScalarAshrI64Edges(true));
|
||||
RunCase(&vulkan, VectorCompareInteger64Edges());
|
||||
RunCase(&vulkan, VectorMadU64U32UnsignedCarryOut());
|
||||
RunCase(&vulkan, ScalarWqmB64SelectsSccDomain());
|
||||
RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorAlignByteUsesTwoBitByteOffset());
|
||||
@@ -40415,6 +40760,13 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--sdwa-mul-i24-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop2SdwaMulI24Destinations(32));
|
||||
RunCase(&vulkan, Vop2SdwaMulI24Destinations(64));
|
||||
RunCase(&vulkan, VectorIntegerOps());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop3CvtPkI16I32Captured());
|
||||
|
||||
+163
-19
@@ -3668,6 +3668,39 @@ void TestNewShaderRecompilerVop1SdwaBfrev() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestVop2SdwaMulI24Destination() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
const uint32_t captured[][2] = {
|
||||
{0x121818f9u, 0x04861488u}, {0x123434f9u, 0x0486146au}};
|
||||
for (uint32_t index = 0; index < 2; ++index) {
|
||||
Instruction decoded;
|
||||
DecodeInstruction(captured[index], 0, decoded);
|
||||
const uint32_t reg = index == 0 ? 12u : 26u;
|
||||
Check(decoded.opcode == Opcode::V_MUL_I32_I24 && decoded.word_count == 2 &&
|
||||
decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == reg &&
|
||||
decoded.dst.sdwa_sel == 4 && decoded.dst.sdwa_dst_unused == 2 &&
|
||||
decoded.dst.explicit_sdwa_dst && !decoded.dst.clamp &&
|
||||
decoded.dst.omod == 0 && decoded.src0.sdwa_sel == 6 &&
|
||||
decoded.src1.kind == OperandKind::Vgpr && decoded.src1.reg == reg &&
|
||||
decoded.src1.sdwa_sel == 4 && !decoded.src1.sdwa_sext,
|
||||
"captured V_MUL_I32_I24 SDWA destination/source metadata is incorrect");
|
||||
Check(index == 0 ? decoded.src0.kind == OperandKind::IntegerInlineConstant &&
|
||||
decoded.src0.value == 8
|
||||
: decoded.src0.kind == OperandKind::VccLo,
|
||||
"captured V_MUL_I32_I24 SDWA scalar source is incorrect");
|
||||
}
|
||||
for (const uint32_t modifier : {
|
||||
0x04861788u, 0x04861c88u, 0x04871488u, 0x07861488u,
|
||||
0x04961488u, 0x04a61488u, 0x14861488u, 0x24861488u,
|
||||
0x04863488u, 0x04865488u}) {
|
||||
const uint32_t invalid[] = {captured[0][0], modifier};
|
||||
Instruction decoded;
|
||||
DecodeInstruction(invalid, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.word_count == 2,
|
||||
"V_MUL_I32_I24 SDWA accepted reserved selectors or float modifiers");
|
||||
}
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerVop1SdwaNotDestination() {
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
|
||||
@@ -4861,6 +4894,46 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestVopcCmpxClassF16Decoder() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
for (const uint32_t modifier : {0x86360000u, 0x86350002u}) {
|
||||
const uint32_t captured[] = {0x7d3f70f9u, modifier};
|
||||
Instruction decoded;
|
||||
DecodeInstruction(captured, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::V_CMPX_CLASS_F16 && decoded.word_count == 2 &&
|
||||
decoded.dst.kind == OperandKind::ExecLo &&
|
||||
decoded.src0.kind == OperandKind::Vgpr &&
|
||||
decoded.src0.reg == (modifier == 0x86360000u ? 0u : 2u) &&
|
||||
decoded.src0.sdwa_sel == (modifier == 0x86360000u ? 6u : 5u) &&
|
||||
decoded.src0.absolute && decoded.src0.negate &&
|
||||
decoded.src1.kind == OperandKind::IntegerInlineConstant &&
|
||||
decoded.src1.value == 56 && decoded.src1.sdwa_sel == 6,
|
||||
"captured V_CMPX_CLASS_F16 SDWA fields are incorrect");
|
||||
const uint32_t shader[] = {captured[0], captured[1], EncodeSopp(0x01)};
|
||||
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel));
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
const uint32_t compact[] = {EncodeVopc(0x9f, 256, 1)};
|
||||
const uint32_t vop3[] = {EncodeVop3Word0(0x9f, 126),
|
||||
EncodeVop3Word1(256, 257, 0)};
|
||||
for (auto words : {std::span<const uint32_t>(compact),
|
||||
std::span<const uint32_t>(vop3)}) {
|
||||
Instruction decoded;
|
||||
DecodeInstruction(words, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::V_CMPX_CLASS_F16 &&
|
||||
decoded.dst.kind == OperandKind::ExecLo && decoded.src0.reg == 0 &&
|
||||
decoded.src1.kind == OperandKind::Vgpr && decoded.src1.reg == 1,
|
||||
"compact/VOP3 V_CMPX_CLASS_F16 did not decode to an EXEC compare");
|
||||
}
|
||||
for (const uint32_t modifier : {0x86370000u, 0x87360000u}) {
|
||||
const uint32_t invalid[] = {0x7d3f70f9u, modifier};
|
||||
Instruction decoded;
|
||||
DecodeInstruction(invalid, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.word_count == 2,
|
||||
"V_CMPX_CLASS_F16 accepted reserved SDWA selector 7");
|
||||
}
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16() {
|
||||
using namespace ShaderRecompiler;
|
||||
|
||||
@@ -7869,7 +7942,7 @@ void TestNewShaderRecompilerStructuredU64Phi() {
|
||||
const auto after = MeasureSpirv(spirv);
|
||||
Check(after.phis == before.phis + 1u &&
|
||||
after.function_variables == before.function_variables,
|
||||
"structured U64 Phi was not emitted as a native vector OpPhi");
|
||||
"structured U64 Phi was not emitted as a native scalar OpPhi");
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgTerminalExitMergePS() {
|
||||
@@ -9793,7 +9866,7 @@ void TestNewShaderRecompilerPlanningOnlyLoads() {
|
||||
"planning-only loads or references emitted GPU operands/instructions");
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerU64PairTranslation() {
|
||||
void TestNewShaderRecompilerNativeU64Translation() {
|
||||
using namespace ShaderRecompiler;
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
@@ -9923,20 +9996,23 @@ void TestNewShaderRecompilerU64PairTranslation() {
|
||||
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, options.input_info);
|
||||
CheckSpirvBinaryValidates(spirv);
|
||||
const auto source = DisassembleSpirvBinary(spirv);
|
||||
Check((source.find("OpCapability Int64") == std::string::npos) &&
|
||||
(source.find("OpTypeInt 64") == std::string::npos),
|
||||
"portable pair-U64 translation introduced native shader Int64");
|
||||
Check(SpirvInstructionOpcodeCount(spirv, 149u) == 1u,
|
||||
"pair-U64 addition did not use exactly one carry instruction");
|
||||
Check(SpirvInstructionOpcodeCount(spirv, 154u) == 1u &&
|
||||
SpirvInstructionOpcodeCount(spirv, 155u) == 1u,
|
||||
"pair-U64 equality did not reduce its vector comparison with Any/All");
|
||||
Check(source.find("OpCapability Int64") != std::string::npos &&
|
||||
source.find("OpTypeInt 64 0") != std::string::npos &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpIAdd", "%ulong") &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpIMul", "%ulong") &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpBitwiseAnd", "%ulong"),
|
||||
"U64 arithmetic did not emit native 64-bit scalar instructions");
|
||||
Check(SpirvInstructionOpcodeCount(spirv, spv::OpIAddCarry) == 0u &&
|
||||
SpirvInstructionOpcodeCount(spirv, spv::OpUMulExtended) == 0u &&
|
||||
SpirvInstructionOpcodeCount(spirv, spv::OpAny) == 0u &&
|
||||
SpirvInstructionOpcodeCount(spirv, spv::OpAll) == 0u,
|
||||
"native U64 arithmetic retained split-word operations");
|
||||
const auto direct_metrics = MeasureSpirv(spirv);
|
||||
Check(
|
||||
direct_metrics.words <= 940u && direct_metrics.instructions <= 199u &&
|
||||
direct_metrics.type_vectors == 2u && direct_metrics.phis == 0u &&
|
||||
direct_metrics.function_variables == 0u,
|
||||
"portable pair-U64 translation exceeded its declaration/code-size ratchet");
|
||||
// The same fixture required 864 words and 182 instructions with split U64s.
|
||||
Check(direct_metrics.words <= 285u && direct_metrics.instructions <= 67u &&
|
||||
direct_metrics.phis == 0u &&
|
||||
direct_metrics.function_variables == 0u,
|
||||
"native U64 translation exceeded its declaration/code-size ratchet");
|
||||
|
||||
const uint32_t dispatcher_shader[] = {
|
||||
EncodeSopp(0x05, 2), EncodeSopp(0x02, 0), EncodeSopp(0x05, 0xfffeu),
|
||||
@@ -9961,7 +10037,39 @@ void TestNewShaderRecompilerU64PairTranslation() {
|
||||
Check(after.function_variables == before.function_variables + 1u &&
|
||||
after.loads == before.loads + 1u &&
|
||||
after.stores == before.stores + 1u,
|
||||
"dispatcher did not use one canonical U64 vector spill slot");
|
||||
"dispatcher did not use one canonical U64 scalar spill slot");
|
||||
}
|
||||
|
||||
|
||||
void TestVertexMadU64UsesPortableProduct() {
|
||||
using StageInputKind = ShaderRecompiler::IR::StageInputKind;
|
||||
for (const bool dynamic_addend : {false, true}) {
|
||||
const uint32_t shader[] = {
|
||||
EncodeVop1(0x01, 6,
|
||||
8 + 256), // Dynamic addend is [VertexIndex, InstanceIndex].
|
||||
EncodeVop3Word0Sdst(0x176, 0, 20),
|
||||
EncodeVop3Word1(5 + 256, 8 + 256, dynamic_addend ? 5 + 256 : 128),
|
||||
EncodeExp0(0x0c, 0x3),
|
||||
EncodeExp1(0, 1, 0, 0), // POS0.xy keeps both product words live.
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const auto result =
|
||||
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
|
||||
Check(ProgramHasInput(result.program, StageInputKind::VertexIndex) &&
|
||||
ProgramHasInput(result.program, StageInputKind::InstanceIndex),
|
||||
"vertex MAD fixture lost its runtime multiplication operands");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
// Large vertex shaders on NVIDIA rejected native widening multiplication.
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpUMulExtended) ==
|
||||
1u &&
|
||||
SpirvSourceHasInstructionUsing(source, "OpIMul", "%uint") &&
|
||||
!SpirvSourceHasInstructionUsing(source, "OpIMul", "%ulong"),
|
||||
"vertex MAD lost its portable 32x32 product");
|
||||
Check(!dynamic_addend ||
|
||||
SpirvSourceHasInstructionUsing(source, "OpIAdd", "%ulong"),
|
||||
"vertex MAD split its live 64-bit addition into word operations");
|
||||
}
|
||||
}
|
||||
|
||||
void TestComputeDispatchWaveSize() {
|
||||
@@ -12058,7 +12166,8 @@ void TestU64ShiftConstantPropagation() {
|
||||
std::vector<ShiftCase> shift_cases;
|
||||
for (const uint64_t source :
|
||||
{uint64_t{0x0123456789abcdefull}, uint64_t{0xf123456789abcdefull}}) {
|
||||
for (const uint32_t count : {0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u}) {
|
||||
for (const uint32_t count :
|
||||
{0u, 1u, 31u, 32u, 33u, 63u, 64u, 65u, 127u, UINT32_MAX}) {
|
||||
const uint32_t amount = count & 63u;
|
||||
shift_cases.push_back({shift_ir.Emit(ValueOpcode::ShiftLeftLogical64,
|
||||
{Value(source), Value(count)}),
|
||||
@@ -12077,10 +12186,41 @@ void TestU64ShiftConstantPropagation() {
|
||||
const auto value = test.value.Resolve();
|
||||
Check(value.IsImmediate() && value.GetType() == Type::U64 &&
|
||||
value.U64() == test.expected,
|
||||
"pair-U64 shift propagation violated the masked RDNA2 count");
|
||||
"U64 shift propagation violated the masked RDNA2 count");
|
||||
}
|
||||
}
|
||||
|
||||
void TestU64RegisterPairRoundTrip() {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
Program program;
|
||||
program.block_storage.push_back(std::make_unique<Block>());
|
||||
program.blocks.push_back(program.block_storage.back().get());
|
||||
IREmitter ir(program.blocks.front());
|
||||
const auto lane = ir.Emit(ValueOpcode::LaneId);
|
||||
const auto pair = ir.Emit(ValueOpcode::CompositeConstructU64, {lane, lane});
|
||||
const auto first = ir.Emit(ValueOpcode::IAdd64, {pair, Value(uint64_t{1})});
|
||||
const auto second = ir.Emit(ValueOpcode::IAdd64, {pair, Value(uint64_t{2})});
|
||||
const auto low =
|
||||
ir.Emit(ValueOpcode::CompositeExtractU64, {first, Value(0u)});
|
||||
const auto high =
|
||||
ir.Emit(ValueOpcode::CompositeExtractU64, {first, Value(1u)});
|
||||
const auto other_high =
|
||||
ir.Emit(ValueOpcode::CompositeExtractU64, {second, Value(1u)});
|
||||
const auto restored =
|
||||
ir.Emit(ValueOpcode::CompositeConstructU64, {low, high});
|
||||
const auto swapped = ir.Emit(ValueOpcode::CompositeConstructU64, {high, low});
|
||||
const auto mixed =
|
||||
ir.Emit(ValueOpcode::CompositeConstructU64, {low, other_high});
|
||||
ConstantPropagationPass(program.blocks);
|
||||
Check(restored.Resolve() == first.Resolve(),
|
||||
"unchanged SGPR pair did not retain its native U64 value");
|
||||
Check(swapped.Resolve().Instruction()->GetOpcode() ==
|
||||
ValueOpcode::CompositeConstructU64 &&
|
||||
mixed.Resolve().Instruction()->GetOpcode() ==
|
||||
ValueOpcode::CompositeConstructU64,
|
||||
"modified SGPR pair incorrectly reused an unrelated native U64 value");
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
void TestNativeWideValueValidation() {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
@@ -14784,6 +14924,7 @@ int main() {
|
||||
TestNewShaderRecompilerF64AluEncodings();
|
||||
TestNewShaderRecompilerVop1SdwaBfrev();
|
||||
TestNewShaderRecompilerVop1SdwaNotDestination();
|
||||
TestVop2SdwaMulI24Destination();
|
||||
TestNewShaderRecompilerScalarMemoryBindingDomains();
|
||||
// Opcode semantics and optimized SPIR-V are exercised by
|
||||
// ShaderRecompilerComputeTests; keep the distinct decoder contract checks
|
||||
@@ -14796,6 +14937,7 @@ int main() {
|
||||
TestSopkCompareImmediateExtension();
|
||||
TestNativeStoreCompletion();
|
||||
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
|
||||
TestVopcCmpxClassF16Decoder();
|
||||
TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16();
|
||||
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
|
||||
TestNewShaderRecompilerRejectsDppOn64BitCompares();
|
||||
@@ -14856,7 +14998,8 @@ int main() {
|
||||
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
||||
TestNewShaderRecompilerDispatcherSpillsU32x3();
|
||||
TestNewShaderRecompilerPlanningOnlyLoads();
|
||||
TestNewShaderRecompilerU64PairTranslation();
|
||||
TestNewShaderRecompilerNativeU64Translation();
|
||||
TestVertexMadU64UsesPortableProduct();
|
||||
TestComputeDispatchWaveSize();
|
||||
TestNewShaderRecompilerBufferLoadsGuardedByExec();
|
||||
TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
|
||||
@@ -14881,6 +15024,7 @@ int main() {
|
||||
TestValuePhiValidation();
|
||||
TestWave32MaskProjection();
|
||||
TestU64ShiftConstantPropagation();
|
||||
TestU64RegisterPairRoundTrip();
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
TestNativeWideValueValidation();
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user