Commit f3e2cc21 by Ben Clayton

SpirvShader: Move memory ops to new cpp file

Bug: b/145336353 Change-Id: I19b9ee325d349e5a0c6ca79cdac2ee1ac8d227a2 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38813 Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Reviewed-by: 's avatarAlexis Hétu <sugoi@google.com> Tested-by: 's avatarBen Clayton <bclayton@google.com>
parent b4274000
...@@ -42,6 +42,7 @@ swiftshader_source_set("Pipeline") { ...@@ -42,6 +42,7 @@ swiftshader_source_set("Pipeline") {
"SpirvShader.cpp", "SpirvShader.cpp",
"SpirvShaderControlFlow.cpp", "SpirvShaderControlFlow.cpp",
"SpirvShaderGLSLstd450.cpp", "SpirvShaderGLSLstd450.cpp",
"SpirvShaderMemory.cpp",
"SpirvShaderSampling.cpp", "SpirvShaderSampling.cpp",
"VertexProgram.cpp", "VertexProgram.cpp",
"VertexRoutine.cpp", "VertexRoutine.cpp",
......
...@@ -936,59 +936,7 @@ namespace sw ...@@ -936,59 +936,7 @@ namespace sw
} }
} }
bool SpirvShader::StoresInHelperInvocation(spv::StorageClass storageClass) int SpirvShader::VisitInterfaceInner(Type::ID id, Decorations d, const InterfaceVisitor &f) const
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassImage:
return false;
default:
return true;
}
}
bool SpirvShader::IsExplicitLayout(spv::StorageClass storageClass)
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassPushConstant:
return true;
default:
return false;
}
}
sw::SIMD::Pointer SpirvShader::InterleaveByLane(sw::SIMD::Pointer p)
{
p *= sw::SIMD::Width;
p.staticOffsets[0] += 0 * sizeof(float);
p.staticOffsets[1] += 1 * sizeof(float);
p.staticOffsets[2] += 2 * sizeof(float);
p.staticOffsets[3] += 3 * sizeof(float);
return p;
}
bool SpirvShader::IsStorageInterleavedByLane(spv::StorageClass storageClass)
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassPushConstant:
case spv::StorageClassWorkgroup:
case spv::StorageClassImage:
return false;
default:
return true;
}
}
template<typename F>
int SpirvShader::VisitInterfaceInner(Type::ID id, Decorations d, F f) const
{ {
// Recursively walks variable definition and its type tree, taking into account // Recursively walks variable definition and its type tree, taking into account
// any explicit Location or Component decorations encountered; where explicit // any explicit Location or Component decorations encountered; where explicit
...@@ -1008,19 +956,19 @@ namespace sw ...@@ -1008,19 +956,19 @@ namespace sw
switch(obj.opcode()) switch(obj.opcode())
{ {
case spv::OpTypePointer: case spv::OpTypePointer:
return VisitInterfaceInner<F>(obj.definition.word(3), d, f); return VisitInterfaceInner(obj.definition.word(3), d, f);
case spv::OpTypeMatrix: case spv::OpTypeMatrix:
for (auto i = 0u; i < obj.definition.word(3); i++, d.Location++) for (auto i = 0u; i < obj.definition.word(3); i++, d.Location++)
{ {
// consumes same components of N consecutive locations // consumes same components of N consecutive locations
VisitInterfaceInner<F>(obj.definition.word(2), d, f); VisitInterfaceInner(obj.definition.word(2), d, f);
} }
return d.Location; return d.Location;
case spv::OpTypeVector: case spv::OpTypeVector:
for (auto i = 0u; i < obj.definition.word(3); i++, d.Component++) for (auto i = 0u; i < obj.definition.word(3); i++, d.Component++)
{ {
// consumes N consecutive components in the same location // consumes N consecutive components in the same location
VisitInterfaceInner<F>(obj.definition.word(2), d, f); VisitInterfaceInner(obj.definition.word(2), d, f);
} }
return d.Location + 1; return d.Location + 1;
case spv::OpTypeFloat: case spv::OpTypeFloat:
...@@ -1038,7 +986,7 @@ namespace sw ...@@ -1038,7 +986,7 @@ namespace sw
for (auto i = 0u; i < obj.definition.wordCount() - 2; i++) for (auto i = 0u; i < obj.definition.wordCount() - 2; i++)
{ {
ApplyDecorationsForIdMember(&d, id, i); ApplyDecorationsForIdMember(&d, id, i);
d.Location = VisitInterfaceInner<F>(obj.definition.word(i + 2), d, f); d.Location = VisitInterfaceInner(obj.definition.word(i + 2), d, f);
d.Component = 0; // Implicit locations always have component=0 d.Component = 0; // Implicit locations always have component=0
} }
return d.Location; return d.Location;
...@@ -1048,7 +996,7 @@ namespace sw ...@@ -1048,7 +996,7 @@ namespace sw
auto arraySize = GetConstScalarInt(obj.definition.word(3)); auto arraySize = GetConstScalarInt(obj.definition.word(3));
for (auto i = 0u; i < arraySize; i++) for (auto i = 0u; i < arraySize; i++)
{ {
d.Location = VisitInterfaceInner<F>(obj.definition.word(2), d, f); d.Location = VisitInterfaceInner(obj.definition.word(2), d, f);
} }
return d.Location; return d.Location;
} }
...@@ -1058,8 +1006,7 @@ namespace sw ...@@ -1058,8 +1006,7 @@ namespace sw
} }
} }
template<typename F> void SpirvShader::VisitInterface(Object::ID id, const InterfaceVisitor &f) const
void SpirvShader::VisitInterface(Object::ID id, F f) const
{ {
// Walk a variable definition and call f for each component in it. // Walk a variable definition and call f for each component in it.
Decorations d{}; Decorations d{};
...@@ -1067,146 +1014,7 @@ namespace sw ...@@ -1067,146 +1014,7 @@ namespace sw
auto def = getObject(id).definition; auto def = getObject(id).definition;
ASSERT(def.opcode() == spv::OpVariable); ASSERT(def.opcode() == spv::OpVariable);
VisitInterfaceInner<F>(def.word(1), d, f); VisitInterfaceInner(def.word(1), d, f);
}
template<typename F>
void SpirvShader::VisitMemoryObjectInner(sw::SpirvShader::Type::ID id, sw::SpirvShader::Decorations d, uint32_t& index, uint32_t offset, F f) const
{
// Walk a type tree in an explicitly laid out storage class, calling
// a functor for each scalar element within the object.
// The functor's first parameter is the index of the scalar element;
// the second parameter is the offset (in bytes) from the base of the
// object.
ApplyDecorationsForId(&d, id);
auto const &type = getType(id);
if (d.HasOffset)
{
offset += d.Offset;
d.HasOffset = false;
}
switch (type.opcode())
{
case spv::OpTypePointer:
VisitMemoryObjectInner<F>(type.definition.word(3), d, index, offset, f);
break;
case spv::OpTypeInt:
case spv::OpTypeFloat:
f(index++, offset);
break;
case spv::OpTypeVector:
{
auto elemStride = (d.InsideMatrix && d.HasRowMajor && d.RowMajor) ? d.MatrixStride : static_cast<int32_t>(sizeof(float));
for (auto i = 0u; i < type.definition.word(3); i++)
{
VisitMemoryObjectInner(type.definition.word(2), d, index, offset + elemStride * i, f);
}
break;
}
case spv::OpTypeMatrix:
{
auto columnStride = (d.HasRowMajor && d.RowMajor) ? static_cast<int32_t>(sizeof(float)) : d.MatrixStride;
d.InsideMatrix = true;
for (auto i = 0u; i < type.definition.word(3); i++)
{
ASSERT(d.HasMatrixStride);
VisitMemoryObjectInner(type.definition.word(2), d, index, offset + columnStride * i, f);
}
break;
}
case spv::OpTypeStruct:
for (auto i = 0u; i < type.definition.wordCount() - 2; i++)
{
ApplyDecorationsForIdMember(&d, id, i);
VisitMemoryObjectInner<F>(type.definition.word(i + 2), d, index, offset, f);
}
break;
case spv::OpTypeArray:
{
auto arraySize = GetConstScalarInt(type.definition.word(3));
for (auto i = 0u; i < arraySize; i++)
{
ASSERT(d.HasArrayStride);
VisitMemoryObjectInner<F>(type.definition.word(2), d, index, offset + i * d.ArrayStride, f);
}
break;
}
default:
UNREACHABLE("%s", OpcodeName(type.opcode()).c_str());
}
}
template<typename F>
void SpirvShader::VisitMemoryObject(sw::SpirvShader::Object::ID id, F f) const
{
auto typeId = getObject(id).type;
auto const & type = getType(typeId);
if (IsExplicitLayout(type.storageClass))
{
Decorations d{};
ApplyDecorationsForId(&d, id);
uint32_t index = 0;
VisitMemoryObjectInner<F>(typeId, d, index, 0, f);
}
else
{
// Objects without explicit layout are tightly packed.
for (auto i = 0u; i < getType(type.element).sizeInComponents; i++)
{
f(i, i * sizeof(float));
}
}
}
SIMD::Pointer SpirvShader::GetPointerToData(Object::ID id, int arrayIndex, EmitState const *state) const
{
auto routine = state->routine;
auto &object = getObject(id);
switch (object.kind)
{
case Object::Kind::Pointer:
case Object::Kind::InterfaceVariable:
return state->getPointer(id);
case Object::Kind::DescriptorSet:
{
const auto &d = descriptorDecorations.at(id);
ASSERT(d.DescriptorSet >= 0 && d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS);
ASSERT(d.Binding >= 0);
auto set = state->getPointer(id);
auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
ASSERT_MSG(setLayout->hasBinding(d.Binding), "Descriptor set %d does not contain binding %d", int(d.DescriptorSet), int(d.Binding));
int bindingOffset = static_cast<int>(setLayout->getBindingOffset(d.Binding, arrayIndex));
Pointer<Byte> descriptor = set.base + bindingOffset; // BufferDescriptor*
Pointer<Byte> data = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::BufferDescriptor, ptr)); // void*
Int size = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, sizeInBytes));
if (setLayout->isBindingDynamic(d.Binding))
{
uint32_t dynamicBindingIndex =
routine->pipelineLayout->getDynamicOffsetBase(d.DescriptorSet) +
setLayout->getDynamicDescriptorOffset(d.Binding) +
arrayIndex;
Int offset = routine->descriptorDynamicOffsets[dynamicBindingIndex];
Int robustnessSize = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, robustnessSize));
return SIMD::Pointer(data + offset, Min(size, robustnessSize - offset));
}
else
{
return SIMD::Pointer(data, size);
}
}
default:
UNREACHABLE("Invalid pointer kind %d", int(object.kind));
return SIMD::Pointer(Pointer<Byte>(), 0);
}
} }
void SpirvShader::ApplyDecorationsForAccessChain(Decorations *d, DescriptorDecorations *dd, Object::ID baseId, uint32_t numIndexes, uint32_t const *indexIds) const void SpirvShader::ApplyDecorationsForAccessChain(Decorations *d, DescriptorDecorations *dd, Object::ID baseId, uint32_t numIndexes, uint32_t const *indexIds) const
...@@ -2119,240 +1927,6 @@ namespace sw ...@@ -2119,240 +1927,6 @@ namespace sw
return EmitResult::Continue; return EmitResult::Continue;
} }
SpirvShader::EmitResult SpirvShader::EmitVariable(InsnIterator insn, EmitState *state) const
{
auto routine = state->routine;
Object::ID resultId = insn.word(2);
auto &object = getObject(resultId);
auto &objectTy = getType(object.type);
switch (objectTy.storageClass)
{
case spv::StorageClassOutput:
case spv::StorageClassPrivate:
case spv::StorageClassFunction:
{
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->getVariable(resultId)[0];
auto elementTy = getType(objectTy.element);
auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
state->createPointer(resultId, SIMD::Pointer(base, size));
break;
}
case spv::StorageClassWorkgroup:
{
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->workgroupMemory[0];
auto size = workgroupMemory.size();
state->createPointer(resultId, SIMD::Pointer(base, size, workgroupMemory.offsetOf(resultId)));
break;
}
case spv::StorageClassInput:
{
if (object.kind == Object::Kind::InterfaceVariable)
{
auto &dst = routine->getVariable(resultId);
int offset = 0;
VisitInterface(resultId,
[&](Decorations const &d, AttribType type) {
auto scalarSlot = d.Location << 2 | d.Component;
dst[offset++] = routine->inputs[scalarSlot];
});
}
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->getVariable(resultId)[0];
auto elementTy = getType(objectTy.element);
auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
state->createPointer(resultId, SIMD::Pointer(base, size));
break;
}
case spv::StorageClassUniformConstant:
{
const auto &d = descriptorDecorations.at(resultId);
ASSERT(d.DescriptorSet >= 0);
ASSERT(d.Binding >= 0);
uint32_t arrayIndex = 0; // TODO(b/129523279)
auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
if (setLayout->hasBinding(d.Binding))
{
uint32_t bindingOffset = static_cast<uint32_t>(setLayout->getBindingOffset(d.Binding, arrayIndex));
Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet]; // DescriptorSet*
Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset); // vk::SampledImageDescriptor*
auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
state->createPointer(resultId, SIMD::Pointer(binding, size));
}
else
{
// TODO: Error if the variable with the non-existant binding is
// used? Or perhaps strip these unused variable declarations as
// a preprocess on the SPIR-V?
}
break;
}
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
{
const auto &d = descriptorDecorations.at(resultId);
ASSERT(d.DescriptorSet >= 0);
auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
// Note: the module may contain descriptor set references that are not suitable for this implementation -- using a set index higher than the number
// of descriptor set binding points we support. As long as the selected entrypoint doesn't actually touch the out of range binding points, this
// is valid. In this case make the value nullptr to make it easier to diagnose an attempt to dereference it.
if (d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS)
{
state->createPointer(resultId, SIMD::Pointer(routine->descriptorSets[d.DescriptorSet], size));
}
else
{
state->createPointer(resultId, SIMD::Pointer(nullptr, 0));
}
break;
}
case spv::StorageClassPushConstant:
{
state->createPointer(resultId, SIMD::Pointer(routine->pushConstants, vk::MAX_PUSH_CONSTANT_SIZE));
break;
}
default:
UNREACHABLE("Storage class %d", objectTy.storageClass);
break;
}
if (insn.wordCount() > 4)
{
Object::ID initializerId = insn.word(4);
if (getObject(initializerId).kind != Object::Kind::Constant)
{
UNIMPLEMENTED("Non-constant initializers not yet implemented");
}
switch (objectTy.storageClass)
{
case spv::StorageClassOutput:
case spv::StorageClassPrivate:
case spv::StorageClassFunction:
{
bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
auto ptr = GetPointerToData(resultId, 0, state);
GenericValue initialValue(this, state, initializerId);
VisitMemoryObject(resultId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
auto robustness = OutOfBoundsBehavior::UndefinedBehavior; // Local variables are always within bounds.
p.Store(initialValue.Float(i), robustness, state->activeLaneMask());
});
break;
}
default:
ASSERT_MSG(initializerId == 0, "Vulkan does not permit variables of storage class %d to have initializers", int(objectTy.storageClass));
}
}
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitLoad(InsnIterator insn, EmitState *state) const
{
bool atomic = (insn.opcode() == spv::OpAtomicLoad);
Object::ID resultId = insn.word(2);
Object::ID pointerId = insn.word(3);
auto &result = getObject(resultId);
auto &resultTy = getType(result.type);
auto &pointer = getObject(pointerId);
auto &pointerTy = getType(pointer.type);
std::memory_order memoryOrder = std::memory_order_relaxed;
ASSERT(getType(pointer.type).element == result.type);
ASSERT(Type::ID(insn.word(1)) == result.type);
ASSERT(!atomic || getType(getType(pointer.type).element).opcode() == spv::OpTypeInt); // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
if(pointerTy.storageClass == spv::StorageClassUniformConstant)
{
// Just propagate the pointer.
auto &ptr = state->getPointer(pointerId);
state->createPointer(resultId, ptr);
return EmitResult::Continue;
}
if(atomic)
{
Object::ID semanticsId = insn.word(5);
auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
memoryOrder = MemoryOrder(memorySemantics);
}
auto ptr = GetPointerToData(pointerId, 0, state);
bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
auto &dst = state->createIntermediate(resultId, resultTy.sizeInComponents);
auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); } // TODO: Interleave once, then add offset?
dst.move(i, p.Load<SIMD::Float>(robustness, state->activeLaneMask(), atomic, memoryOrder));
});
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitStore(InsnIterator insn, EmitState *state) const
{
bool atomic = (insn.opcode() == spv::OpAtomicStore);
Object::ID pointerId = insn.word(1);
Object::ID objectId = insn.word(atomic ? 4 : 2);
auto &object = getObject(objectId);
auto &pointer = getObject(pointerId);
auto &pointerTy = getType(pointer.type);
auto &elementTy = getType(pointerTy.element);
std::memory_order memoryOrder = std::memory_order_relaxed;
if(atomic)
{
Object::ID semanticsId = insn.word(3);
auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
memoryOrder = MemoryOrder(memorySemantics);
}
ASSERT(!atomic || elementTy.opcode() == spv::OpTypeInt); // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
auto ptr = GetPointerToData(pointerId, 0, state);
bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
SIMD::Int mask = state->activeLaneMask();
if (!StoresInHelperInvocation(pointerTy.storageClass))
{
mask = mask & state->storesAndAtomicsMask();
}
if (object.kind == Object::Kind::Constant)
{
// Constant source data.
const uint32_t *src = object.constantValue.get();
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
p.Store(SIMD::Int(src[i]), robustness, mask, atomic, memoryOrder);
});
}
else
{
// Intermediate source data.
auto &src = state->getIntermediate(objectId);
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
p.Store(src.Float(i), robustness, mask, atomic, memoryOrder);
});
}
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitAccessChain(InsnIterator insn, EmitState *state) const SpirvShader::EmitResult SpirvShader::EmitAccessChain(InsnIterator insn, EmitState *state) const
{ {
Type::ID typeId = insn.word(1); Type::ID typeId = insn.word(1);
...@@ -3051,29 +2625,6 @@ namespace sw ...@@ -3051,29 +2625,6 @@ namespace sw
return EmitResult::Continue; return EmitResult::Continue;
} }
std::memory_order SpirvShader::MemoryOrder(spv::MemorySemanticsMask memorySemantics)
{
auto control = static_cast<uint32_t>(memorySemantics) & static_cast<uint32_t>(
spv::MemorySemanticsAcquireMask |
spv::MemorySemanticsReleaseMask |
spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsSequentiallyConsistentMask
);
switch (control)
{
case spv::MemorySemanticsMaskNone: return std::memory_order_relaxed;
case spv::MemorySemanticsAcquireMask: return std::memory_order_acquire;
case spv::MemorySemanticsReleaseMask: return std::memory_order_release;
case spv::MemorySemanticsAcquireReleaseMask: return std::memory_order_acq_rel;
case spv::MemorySemanticsSequentiallyConsistentMask: return std::memory_order_acq_rel; // Vulkan 1.1: "SequentiallyConsistent is treated as AcquireRelease"
default:
// "it is invalid for more than one of these four bits to be set:
// Acquire, Release, AcquireRelease, or SequentiallyConsistent."
UNREACHABLE("MemorySemanticsMask: %x", int(control));
return std::memory_order_acq_rel;
}
}
SIMD::Float SpirvShader::Dot(unsigned numComponents, GenericValue const & x, GenericValue const & y) const SIMD::Float SpirvShader::Dot(unsigned numComponents, GenericValue const & x, GenericValue const & y) const
{ {
SIMD::Float d = x.Float(0) * y.Float(0); SIMD::Float d = x.Float(0) * y.Float(0);
...@@ -4217,52 +3768,6 @@ namespace sw ...@@ -4217,52 +3768,6 @@ namespace sw
return EmitResult::Continue; return EmitResult::Continue;
} }
SpirvShader::EmitResult SpirvShader::EmitCopyMemory(InsnIterator insn, EmitState *state) const
{
Object::ID dstPtrId = insn.word(1);
Object::ID srcPtrId = insn.word(2);
auto &dstPtrTy = getType(getObject(dstPtrId).type);
auto &srcPtrTy = getType(getObject(srcPtrId).type);
ASSERT(dstPtrTy.element == srcPtrTy.element);
bool dstInterleavedByLane = IsStorageInterleavedByLane(dstPtrTy.storageClass);
bool srcInterleavedByLane = IsStorageInterleavedByLane(srcPtrTy.storageClass);
auto dstPtr = GetPointerToData(dstPtrId, 0, state);
auto srcPtr = GetPointerToData(srcPtrId, 0, state);
std::unordered_map<uint32_t, uint32_t> srcOffsets;
VisitMemoryObject(srcPtrId, [&](uint32_t i, uint32_t srcOffset) { srcOffsets[i] = srcOffset; });
VisitMemoryObject(dstPtrId, [&](uint32_t i, uint32_t dstOffset)
{
auto it = srcOffsets.find(i);
ASSERT(it != srcOffsets.end());
auto srcOffset = it->second;
auto dst = dstPtr + dstOffset;
auto src = srcPtr + srcOffset;
if (dstInterleavedByLane) { dst = InterleaveByLane(dst); }
if (srcInterleavedByLane) { src = InterleaveByLane(src); }
// TODO(b/131224163): Optimize based on src/dst storage classes.
auto robustness = OutOfBoundsBehavior::RobustBufferAccess;
auto value = src.Load<SIMD::Float>(robustness, state->activeLaneMask());
dst.Store(value, robustness, state->activeLaneMask());
});
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitMemoryBarrier(InsnIterator insn, EmitState *state) const
{
auto semantics = spv::MemorySemanticsMask(GetConstScalarInt(insn.word(2)));
// TODO: We probably want to consider the memory scope here. For now,
// just always emit the full fence.
Fence(semantics);
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitGroupNonUniform(InsnIterator insn, EmitState *state) const SpirvShader::EmitResult SpirvShader::EmitGroupNonUniform(InsnIterator insn, EmitState *state) const
{ {
static_assert(SIMD::Width == 4, "EmitGroupNonUniform makes many assumptions that the SIMD vector width is 4"); static_assert(SIMD::Width == 4, "EmitGroupNonUniform makes many assumptions that the SIMD vector width is 4");
......
...@@ -731,17 +731,17 @@ namespace sw ...@@ -731,17 +731,17 @@ namespace sw
// Output storage buffers and images should not be affected by helper invocations // Output storage buffers and images should not be affected by helper invocations
static bool StoresInHelperInvocation(spv::StorageClass storageClass); static bool StoresInHelperInvocation(spv::StorageClass storageClass);
template<typename F> using InterfaceVisitor = std::function<void(Decorations const, AttribType)>;
int VisitInterfaceInner(Type::ID id, Decorations d, F f) const;
template<typename F> void VisitInterface(Object::ID id, const InterfaceVisitor& v) const;
void VisitInterface(Object::ID id, F f) const;
template<typename F> int VisitInterfaceInner(Type::ID id, Decorations d, const InterfaceVisitor& v) const;
void VisitMemoryObject(Object::ID id, F f) const;
template<typename F> using MemoryVisitor = std::function<void(uint32_t index, uint32_t offset)>;
void VisitMemoryObjectInner(Type::ID id, Decorations d, uint32_t &index, uint32_t offset, F f) const;
void VisitMemoryObject(Object::ID id, const MemoryVisitor& v) const;
void VisitMemoryObjectInner(Type::ID id, Decorations d, uint32_t &index, uint32_t offset, const MemoryVisitor& v) const;
Object& CreateConstant(InsnIterator it); Object& CreateConstant(InsnIterator it);
......
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "SpirvShader.hpp"
#include "ShaderCore.hpp"
#include "Vulkan/VkPipelineLayout.hpp"
#include <spirv/unified1/spirv.hpp>
#include <spirv/unified1/GLSL.std.450.h>
namespace sw {
SpirvShader::EmitResult SpirvShader::EmitLoad(InsnIterator insn, EmitState *state) const
{
bool atomic = (insn.opcode() == spv::OpAtomicLoad);
Object::ID resultId = insn.word(2);
Object::ID pointerId = insn.word(3);
auto &result = getObject(resultId);
auto &resultTy = getType(result.type);
auto &pointer = getObject(pointerId);
auto &pointerTy = getType(pointer.type);
std::memory_order memoryOrder = std::memory_order_relaxed;
ASSERT(getType(pointer.type).element == result.type);
ASSERT(Type::ID(insn.word(1)) == result.type);
ASSERT(!atomic || getType(getType(pointer.type).element).opcode() == spv::OpTypeInt); // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
if(pointerTy.storageClass == spv::StorageClassUniformConstant)
{
// Just propagate the pointer.
auto &ptr = state->getPointer(pointerId);
state->createPointer(resultId, ptr);
return EmitResult::Continue;
}
if(atomic)
{
Object::ID semanticsId = insn.word(5);
auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
memoryOrder = MemoryOrder(memorySemantics);
}
auto ptr = GetPointerToData(pointerId, 0, state);
bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
auto &dst = state->createIntermediate(resultId, resultTy.sizeInComponents);
auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); } // TODO: Interleave once, then add offset?
dst.move(i, p.Load<SIMD::Float>(robustness, state->activeLaneMask(), atomic, memoryOrder));
});
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitStore(InsnIterator insn, EmitState *state) const
{
bool atomic = (insn.opcode() == spv::OpAtomicStore);
Object::ID pointerId = insn.word(1);
Object::ID objectId = insn.word(atomic ? 4 : 2);
auto &object = getObject(objectId);
auto &pointer = getObject(pointerId);
auto &pointerTy = getType(pointer.type);
auto &elementTy = getType(pointerTy.element);
std::memory_order memoryOrder = std::memory_order_relaxed;
if(atomic)
{
Object::ID semanticsId = insn.word(3);
auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
memoryOrder = MemoryOrder(memorySemantics);
}
ASSERT(!atomic || elementTy.opcode() == spv::OpTypeInt); // Vulkan 1.1: "Atomic instructions must declare a scalar 32-bit integer type, for the value pointed to by Pointer."
auto ptr = GetPointerToData(pointerId, 0, state);
bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
SIMD::Int mask = state->activeLaneMask();
if (!StoresInHelperInvocation(pointerTy.storageClass))
{
mask = mask & state->storesAndAtomicsMask();
}
if (object.kind == Object::Kind::Constant)
{
// Constant source data.
const uint32_t *src = object.constantValue.get();
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
p.Store(SIMD::Int(src[i]), robustness, mask, atomic, memoryOrder);
});
}
else
{
// Intermediate source data.
auto &src = state->getIntermediate(objectId);
VisitMemoryObject(pointerId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
p.Store(src.Float(i), robustness, mask, atomic, memoryOrder);
});
}
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitVariable(InsnIterator insn, EmitState *state) const
{
auto routine = state->routine;
Object::ID resultId = insn.word(2);
auto &object = getObject(resultId);
auto &objectTy = getType(object.type);
switch (objectTy.storageClass)
{
case spv::StorageClassOutput:
case spv::StorageClassPrivate:
case spv::StorageClassFunction:
{
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->getVariable(resultId)[0];
auto elementTy = getType(objectTy.element);
auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
state->createPointer(resultId, SIMD::Pointer(base, size));
break;
}
case spv::StorageClassWorkgroup:
{
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->workgroupMemory[0];
auto size = workgroupMemory.size();
state->createPointer(resultId, SIMD::Pointer(base, size, workgroupMemory.offsetOf(resultId)));
break;
}
case spv::StorageClassInput:
{
if (object.kind == Object::Kind::InterfaceVariable)
{
auto &dst = routine->getVariable(resultId);
int offset = 0;
VisitInterface(resultId,
[&](Decorations const &d, AttribType type) {
auto scalarSlot = d.Location << 2 | d.Component;
dst[offset++] = routine->inputs[scalarSlot];
});
}
ASSERT(objectTy.opcode() == spv::OpTypePointer);
auto base = &routine->getVariable(resultId)[0];
auto elementTy = getType(objectTy.element);
auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
state->createPointer(resultId, SIMD::Pointer(base, size));
break;
}
case spv::StorageClassUniformConstant:
{
const auto &d = descriptorDecorations.at(resultId);
ASSERT(d.DescriptorSet >= 0);
ASSERT(d.Binding >= 0);
uint32_t arrayIndex = 0; // TODO(b/129523279)
auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
if (setLayout->hasBinding(d.Binding))
{
uint32_t bindingOffset = static_cast<uint32_t>(setLayout->getBindingOffset(d.Binding, arrayIndex));
Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet]; // DescriptorSet*
Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset); // vk::SampledImageDescriptor*
auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
state->createPointer(resultId, SIMD::Pointer(binding, size));
}
else
{
// TODO: Error if the variable with the non-existant binding is
// used? Or perhaps strip these unused variable declarations as
// a preprocess on the SPIR-V?
}
break;
}
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
{
const auto &d = descriptorDecorations.at(resultId);
ASSERT(d.DescriptorSet >= 0);
auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
// Note: the module may contain descriptor set references that are not suitable for this implementation -- using a set index higher than the number
// of descriptor set binding points we support. As long as the selected entrypoint doesn't actually touch the out of range binding points, this
// is valid. In this case make the value nullptr to make it easier to diagnose an attempt to dereference it.
if (d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS)
{
state->createPointer(resultId, SIMD::Pointer(routine->descriptorSets[d.DescriptorSet], size));
}
else
{
state->createPointer(resultId, SIMD::Pointer(nullptr, 0));
}
break;
}
case spv::StorageClassPushConstant:
{
state->createPointer(resultId, SIMD::Pointer(routine->pushConstants, vk::MAX_PUSH_CONSTANT_SIZE));
break;
}
default:
UNREACHABLE("Storage class %d", objectTy.storageClass);
break;
}
if (insn.wordCount() > 4)
{
Object::ID initializerId = insn.word(4);
if (getObject(initializerId).kind != Object::Kind::Constant)
{
UNIMPLEMENTED("Non-constant initializers not yet implemented");
}
switch (objectTy.storageClass)
{
case spv::StorageClassOutput:
case spv::StorageClassPrivate:
case spv::StorageClassFunction:
{
bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
auto ptr = GetPointerToData(resultId, 0, state);
GenericValue initialValue(this, state, initializerId);
VisitMemoryObject(resultId, [&](uint32_t i, uint32_t offset)
{
auto p = ptr + offset;
if (interleavedByLane) { p = InterleaveByLane(p); }
auto robustness = OutOfBoundsBehavior::UndefinedBehavior; // Local variables are always within bounds.
p.Store(initialValue.Float(i), robustness, state->activeLaneMask());
});
break;
}
default:
ASSERT_MSG(initializerId == 0, "Vulkan does not permit variables of storage class %d to have initializers", int(objectTy.storageClass));
}
}
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitCopyMemory(InsnIterator insn, EmitState *state) const
{
Object::ID dstPtrId = insn.word(1);
Object::ID srcPtrId = insn.word(2);
auto &dstPtrTy = getType(getObject(dstPtrId).type);
auto &srcPtrTy = getType(getObject(srcPtrId).type);
ASSERT(dstPtrTy.element == srcPtrTy.element);
bool dstInterleavedByLane = IsStorageInterleavedByLane(dstPtrTy.storageClass);
bool srcInterleavedByLane = IsStorageInterleavedByLane(srcPtrTy.storageClass);
auto dstPtr = GetPointerToData(dstPtrId, 0, state);
auto srcPtr = GetPointerToData(srcPtrId, 0, state);
std::unordered_map<uint32_t, uint32_t> srcOffsets;
VisitMemoryObject(srcPtrId, [&](uint32_t i, uint32_t srcOffset) { srcOffsets[i] = srcOffset; });
VisitMemoryObject(dstPtrId, [&](uint32_t i, uint32_t dstOffset)
{
auto it = srcOffsets.find(i);
ASSERT(it != srcOffsets.end());
auto srcOffset = it->second;
auto dst = dstPtr + dstOffset;
auto src = srcPtr + srcOffset;
if (dstInterleavedByLane) { dst = InterleaveByLane(dst); }
if (srcInterleavedByLane) { src = InterleaveByLane(src); }
// TODO(b/131224163): Optimize based on src/dst storage classes.
auto robustness = OutOfBoundsBehavior::RobustBufferAccess;
auto value = src.Load<SIMD::Float>(robustness, state->activeLaneMask());
dst.Store(value, robustness, state->activeLaneMask());
});
return EmitResult::Continue;
}
SpirvShader::EmitResult SpirvShader::EmitMemoryBarrier(InsnIterator insn, EmitState *state) const
{
auto semantics = spv::MemorySemanticsMask(GetConstScalarInt(insn.word(2)));
// TODO: We probably want to consider the memory scope here. For now,
// just always emit the full fence.
Fence(semantics);
return EmitResult::Continue;
}
void SpirvShader::VisitMemoryObjectInner(sw::SpirvShader::Type::ID id, sw::SpirvShader::Decorations d, uint32_t& index, uint32_t offset, const MemoryVisitor &f) const
{
// Walk a type tree in an explicitly laid out storage class, calling
// a functor for each scalar element within the object.
// The functor's first parameter is the index of the scalar element;
// the second parameter is the offset (in bytes) from the base of the
// object.
ApplyDecorationsForId(&d, id);
auto const &type = getType(id);
if (d.HasOffset)
{
offset += d.Offset;
d.HasOffset = false;
}
switch (type.opcode())
{
case spv::OpTypePointer:
VisitMemoryObjectInner(type.definition.word(3), d, index, offset, f);
break;
case spv::OpTypeInt:
case spv::OpTypeFloat:
f(index++, offset);
break;
case spv::OpTypeVector:
{
auto elemStride = (d.InsideMatrix && d.HasRowMajor && d.RowMajor) ? d.MatrixStride : static_cast<int32_t>(sizeof(float));
for (auto i = 0u; i < type.definition.word(3); i++)
{
VisitMemoryObjectInner(type.definition.word(2), d, index, offset + elemStride * i, f);
}
break;
}
case spv::OpTypeMatrix:
{
auto columnStride = (d.HasRowMajor && d.RowMajor) ? static_cast<int32_t>(sizeof(float)) : d.MatrixStride;
d.InsideMatrix = true;
for (auto i = 0u; i < type.definition.word(3); i++)
{
ASSERT(d.HasMatrixStride);
VisitMemoryObjectInner(type.definition.word(2), d, index, offset + columnStride * i, f);
}
break;
}
case spv::OpTypeStruct:
for (auto i = 0u; i < type.definition.wordCount() - 2; i++)
{
ApplyDecorationsForIdMember(&d, id, i);
VisitMemoryObjectInner(type.definition.word(i + 2), d, index, offset, f);
}
break;
case spv::OpTypeArray:
{
auto arraySize = GetConstScalarInt(type.definition.word(3));
for (auto i = 0u; i < arraySize; i++)
{
ASSERT(d.HasArrayStride);
VisitMemoryObjectInner(type.definition.word(2), d, index, offset + i * d.ArrayStride, f);
}
break;
}
default:
UNREACHABLE("%s", OpcodeName(type.opcode()).c_str());
}
}
void SpirvShader::VisitMemoryObject(sw::SpirvShader::Object::ID id, const MemoryVisitor &f) const
{
auto typeId = getObject(id).type;
auto const & type = getType(typeId);
if (IsExplicitLayout(type.storageClass))
{
Decorations d{};
ApplyDecorationsForId(&d, id);
uint32_t index = 0;
VisitMemoryObjectInner(typeId, d, index, 0, f);
}
else
{
// Objects without explicit layout are tightly packed.
for (auto i = 0u; i < getType(type.element).sizeInComponents; i++)
{
f(i, i * sizeof(float));
}
}
}
SIMD::Pointer SpirvShader::GetPointerToData(Object::ID id, int arrayIndex, EmitState const *state) const
{
auto routine = state->routine;
auto &object = getObject(id);
switch (object.kind)
{
case Object::Kind::Pointer:
case Object::Kind::InterfaceVariable:
return state->getPointer(id);
case Object::Kind::DescriptorSet:
{
const auto &d = descriptorDecorations.at(id);
ASSERT(d.DescriptorSet >= 0 && d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS);
ASSERT(d.Binding >= 0);
auto set = state->getPointer(id);
auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
ASSERT_MSG(setLayout->hasBinding(d.Binding), "Descriptor set %d does not contain binding %d", int(d.DescriptorSet), int(d.Binding));
int bindingOffset = static_cast<int>(setLayout->getBindingOffset(d.Binding, arrayIndex));
Pointer<Byte> descriptor = set.base + bindingOffset; // BufferDescriptor*
Pointer<Byte> data = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::BufferDescriptor, ptr)); // void*
Int size = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, sizeInBytes));
if (setLayout->isBindingDynamic(d.Binding))
{
uint32_t dynamicBindingIndex =
routine->pipelineLayout->getDynamicOffsetBase(d.DescriptorSet) +
setLayout->getDynamicDescriptorOffset(d.Binding) +
arrayIndex;
Int offset = routine->descriptorDynamicOffsets[dynamicBindingIndex];
Int robustnessSize = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, robustnessSize));
return SIMD::Pointer(data + offset, Min(size, robustnessSize - offset));
}
else
{
return SIMD::Pointer(data, size);
}
}
default:
UNREACHABLE("Invalid pointer kind %d", int(object.kind));
return SIMD::Pointer(Pointer<Byte>(), 0);
}
}
std::memory_order SpirvShader::MemoryOrder(spv::MemorySemanticsMask memorySemantics)
{
auto control = static_cast<uint32_t>(memorySemantics) & static_cast<uint32_t>(
spv::MemorySemanticsAcquireMask |
spv::MemorySemanticsReleaseMask |
spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsSequentiallyConsistentMask
);
switch (control)
{
case spv::MemorySemanticsMaskNone: return std::memory_order_relaxed;
case spv::MemorySemanticsAcquireMask: return std::memory_order_acquire;
case spv::MemorySemanticsReleaseMask: return std::memory_order_release;
case spv::MemorySemanticsAcquireReleaseMask: return std::memory_order_acq_rel;
case spv::MemorySemanticsSequentiallyConsistentMask: return std::memory_order_acq_rel; // Vulkan 1.1: "SequentiallyConsistent is treated as AcquireRelease"
default:
// "it is invalid for more than one of these four bits to be set:
// Acquire, Release, AcquireRelease, or SequentiallyConsistent."
UNREACHABLE("MemorySemanticsMask: %x", int(control));
return std::memory_order_acq_rel;
}
}
bool SpirvShader::StoresInHelperInvocation(spv::StorageClass storageClass)
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassImage:
return false;
default:
return true;
}
}
bool SpirvShader::IsExplicitLayout(spv::StorageClass storageClass)
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassPushConstant:
return true;
default:
return false;
}
}
sw::SIMD::Pointer SpirvShader::InterleaveByLane(sw::SIMD::Pointer p)
{
p *= sw::SIMD::Width;
p.staticOffsets[0] += 0 * sizeof(float);
p.staticOffsets[1] += 1 * sizeof(float);
p.staticOffsets[2] += 2 * sizeof(float);
p.staticOffsets[3] += 3 * sizeof(float);
return p;
}
bool SpirvShader::IsStorageInterleavedByLane(spv::StorageClass storageClass)
{
switch (storageClass)
{
case spv::StorageClassUniform:
case spv::StorageClassStorageBuffer:
case spv::StorageClassPushConstant:
case spv::StorageClassWorkgroup:
case spv::StorageClassImage:
return false;
default:
return true;
}
}
} // namespace sw
\ No newline at end of file
...@@ -167,6 +167,7 @@ IF EXIST "$(SolutionDir)..\deqp\build\external\vulkancts\modules\vulkan\Release" ...@@ -167,6 +167,7 @@ IF EXIST "$(SolutionDir)..\deqp\build\external\vulkancts\modules\vulkan\Release"
<ClCompile Include="..\Pipeline\SpirvShader.cpp" /> <ClCompile Include="..\Pipeline\SpirvShader.cpp" />
<ClCompile Include="..\Pipeline\SpirvShaderControlFlow.cpp" /> <ClCompile Include="..\Pipeline\SpirvShaderControlFlow.cpp" />
<ClCompile Include="..\Pipeline\SpirvShaderGLSLstd450.cpp" /> <ClCompile Include="..\Pipeline\SpirvShaderGLSLstd450.cpp" />
<ClCompile Include="..\Pipeline\SpirvShaderMemory.cpp" />
<ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp" /> <ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp" />
<ClCompile Include="..\Pipeline\SpirvShader_dbg.cpp" /> <ClCompile Include="..\Pipeline\SpirvShader_dbg.cpp" />
<ClCompile Include="..\Pipeline\VertexProgram.cpp" /> <ClCompile Include="..\Pipeline\VertexProgram.cpp" />
......
...@@ -252,6 +252,9 @@ ...@@ -252,6 +252,9 @@
<ClCompile Include="..\Pipeline\SpirvShaderGLSLstd450.cpp"> <ClCompile Include="..\Pipeline\SpirvShaderGLSLstd450.cpp">
<Filter>Source Files\Pipeline</Filter> <Filter>Source Files\Pipeline</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\Pipeline\SpirvShaderMemory.cpp">
<Filter>Source Files\Pipeline</Filter>
</ClCompile>
<ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp"> <ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp">
<Filter>Source Files\Pipeline</Filter> <Filter>Source Files\Pipeline</Filter>
</ClCompile> </ClCompile>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment