Compare commits

..

8 Commits

Author SHA1 Message Date
Rohit Mahto dc3cd2d2c9 libNet: honour the option mask in sceHttpUriBuild (#1069)
* libNet: honour the option mask in sceHttpUriBuild

sceHttpUriBuild's `option` argument (SCE_HTTP_URI_BUILD_WITH_*) selects which
components of the SceHttpUriElement end up in the output. HttpUriBuild logged
the value and ignored it, always returning the full URL.

Black Myth: Wukong calls it with SCHEME alone and requires "https://" (or
"http://") back before it starts an HTTP request. The full URL fails that
check, so the request starter returns false. The caller's "run on game thread
and wait" helper then returns early while its task stays queued, still holding
by-reference captures of a stack frame that is already gone. About 70 seconds
into boot the game thread runs that task against the dead stack and faults
(null read at eboot+0x59662f). With the mask honoured the game gets past that
point and reaches its title cinematic and menu.

Bits: SCHEME 0x01, HOSTNAME 0x02, PORT 0x04, PATH 0x08, USERNAME 0x10,
PASSWORD 0x20, QUERY 0x40, FRAGMENT 0x80. A mask with none of the eight bits
set keeps the previous "everything" behaviour for callers that never pass one.
A scheme-only build returns "https://", the authority marker belonging to the
scheme chunk.

Adds TestBuildHonoursOption to http_uri_parse covering scheme-only, hostname,
port, path, and the 0xff / 0 cases.

* libNet: simplify URI build code and tests

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-05 21:31:45 +04:00
nmzik e6fce45d3a gpu: skip drains for CPU-only memory unmaps 2026-10-05 21:00:12 +04:00
nmzik 5b826c6e4a shader: emit native 64-bit operations where compatible 2026-10-05 20:41:03 +04:00
nmzik 77d7722173 shader: disable explicit MAD denormal checks for performance testing
Comment out the five explicit flush checks and record the substantial 3DMiniGolf menu FPS drop on NVIDIA RTX 5080 Laptop GPU. Preserve separate multiply/add rounding and both NoContraction decorations.

The matched runs used silent logging, no debugger, no Vulkan validation, and the cd9fc6ff revert on both revisions. Native denormal behavior remains GPU-dependent.
2026-10-05 18:39:31 +04:00
nmzik 266c12cec6 Revert "shader: keep scalar data native and preserve raw addresses"
This reverts commit cd9fc6ff2d.
2026-10-05 18:39:31 +04:00
nmzik 7a0e1dafd7 shader: classify native half values with shared operand decoding 2026-10-05 18:39:31 +04:00
nmzik 1d41ddb378 shader: allow RDNA2 SDWA partial destinations for i24 multiply 2026-10-05 18:39:31 +04:00
nmzik 5c83060481 shader: resolve masked image keys in strided descriptor records 2026-10-05 14:43:21 +04:00
38 changed files with 1149 additions and 801 deletions
+5
View File
@@ -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");
+1
View File
@@ -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
View File
@@ -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) {
+58 -9
View File
@@ -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);
+7 -41
View File
@@ -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();
+47 -78
View File
@@ -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);
+432 -80
View File
@@ -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
View File
@@ -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