1 // Copyright 2019 The SwiftShader Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "SpirvShader.hpp"
16
17 #include "ShaderCore.hpp"
18
19 #include "Vulkan/VkPipelineLayout.hpp"
20
21 #include <spirv/unified1/spirv.hpp>
22
23 namespace sw {
24
EmitLoad(InsnIterator insn,EmitState * state) const25 SpirvShader::EmitResult SpirvShader::EmitLoad(InsnIterator insn, EmitState *state) const
26 {
27 bool atomic = (insn.opcode() == spv::OpAtomicLoad);
28 Object::ID resultId = insn.word(2);
29 Object::ID pointerId = insn.word(3);
30 auto &result = getObject(resultId);
31 auto &resultTy = getType(result.type);
32 auto &pointer = getObject(pointerId);
33 auto &pointerTy = getType(pointer.type);
34 std::memory_order memoryOrder = std::memory_order_relaxed;
35
36 ASSERT(getType(pointer.type).element == result.type);
37 ASSERT(Type::ID(insn.word(1)) == result.type);
38 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."
39
40 if(pointerTy.storageClass == spv::StorageClassUniformConstant)
41 {
42 // Just propagate the pointer.
43 auto &ptr = state->getPointer(pointerId);
44 state->createPointer(resultId, ptr);
45 return EmitResult::Continue;
46 }
47
48 if(atomic)
49 {
50 Object::ID semanticsId = insn.word(5);
51 auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
52 memoryOrder = MemoryOrder(memorySemantics);
53 }
54
55 auto ptr = GetPointerToData(pointerId, 0, state);
56 bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
57 auto &dst = state->createIntermediate(resultId, resultTy.sizeInComponents);
58 auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
59
60 VisitMemoryObject(pointerId, [&](const MemoryElement &el) {
61 auto p = ptr + el.offset;
62 if(interleavedByLane) { p = InterleaveByLane(p); } // TODO: Interleave once, then add offset?
63 dst.move(el.index, p.Load<SIMD::Float>(robustness, state->activeLaneMask(), atomic, memoryOrder));
64 });
65
66 return EmitResult::Continue;
67 }
68
EmitStore(InsnIterator insn,EmitState * state) const69 SpirvShader::EmitResult SpirvShader::EmitStore(InsnIterator insn, EmitState *state) const
70 {
71 bool atomic = (insn.opcode() == spv::OpAtomicStore);
72 Object::ID pointerId = insn.word(1);
73 Object::ID objectId = insn.word(atomic ? 4 : 2);
74 auto &object = getObject(objectId);
75 auto &pointer = getObject(pointerId);
76 auto &pointerTy = getType(pointer.type);
77 auto &elementTy = getType(pointerTy.element);
78 std::memory_order memoryOrder = std::memory_order_relaxed;
79
80 if(atomic)
81 {
82 Object::ID semanticsId = insn.word(3);
83 auto memorySemantics = static_cast<spv::MemorySemanticsMask>(getObject(semanticsId).constantValue[0]);
84 memoryOrder = MemoryOrder(memorySemantics);
85 }
86
87 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."
88
89 auto ptr = GetPointerToData(pointerId, 0, state);
90 bool interleavedByLane = IsStorageInterleavedByLane(pointerTy.storageClass);
91 auto robustness = state->getOutOfBoundsBehavior(pointerTy.storageClass);
92
93 SIMD::Int mask = state->activeLaneMask();
94 if(!StoresInHelperInvocation(pointerTy.storageClass))
95 {
96 mask = mask & state->storesAndAtomicsMask();
97 }
98
99 if(object.kind == Object::Kind::Constant)
100 {
101 // Constant source data.
102 const uint32_t *src = object.constantValue.get();
103 VisitMemoryObject(pointerId, [&](const MemoryElement &el) {
104 auto p = ptr + el.offset;
105 if(interleavedByLane) { p = InterleaveByLane(p); }
106 p.Store(SIMD::Int(src[el.index]), robustness, mask, atomic, memoryOrder);
107 });
108 }
109 else
110 {
111 // Intermediate source data.
112 auto &src = state->getIntermediate(objectId);
113 VisitMemoryObject(pointerId, [&](const MemoryElement &el) {
114 auto p = ptr + el.offset;
115 if(interleavedByLane) { p = InterleaveByLane(p); }
116 p.Store(src.Float(el.index), robustness, mask, atomic, memoryOrder);
117 });
118 }
119
120 return EmitResult::Continue;
121 }
122
EmitVariable(InsnIterator insn,EmitState * state) const123 SpirvShader::EmitResult SpirvShader::EmitVariable(InsnIterator insn, EmitState *state) const
124 {
125 auto routine = state->routine;
126 Object::ID resultId = insn.word(2);
127 auto &object = getObject(resultId);
128 auto &objectTy = getType(object.type);
129
130 switch(objectTy.storageClass)
131 {
132 case spv::StorageClassOutput:
133 case spv::StorageClassPrivate:
134 case spv::StorageClassFunction:
135 {
136 ASSERT(objectTy.opcode() == spv::OpTypePointer);
137 auto base = &routine->getVariable(resultId)[0];
138 auto elementTy = getType(objectTy.element);
139 auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
140 state->createPointer(resultId, SIMD::Pointer(base, size));
141 break;
142 }
143 case spv::StorageClassWorkgroup:
144 {
145 ASSERT(objectTy.opcode() == spv::OpTypePointer);
146 auto base = &routine->workgroupMemory[0];
147 auto size = workgroupMemory.size();
148 state->createPointer(resultId, SIMD::Pointer(base, size, workgroupMemory.offsetOf(resultId)));
149 break;
150 }
151 case spv::StorageClassInput:
152 {
153 if(object.kind == Object::Kind::InterfaceVariable)
154 {
155 auto &dst = routine->getVariable(resultId);
156 int offset = 0;
157 VisitInterface(resultId,
158 [&](Decorations const &d, AttribType type) {
159 auto scalarSlot = d.Location << 2 | d.Component;
160 dst[offset++] = routine->inputs[scalarSlot];
161 });
162 }
163 ASSERT(objectTy.opcode() == spv::OpTypePointer);
164 auto base = &routine->getVariable(resultId)[0];
165 auto elementTy = getType(objectTy.element);
166 auto size = elementTy.sizeInComponents * static_cast<uint32_t>(sizeof(float)) * SIMD::Width;
167 state->createPointer(resultId, SIMD::Pointer(base, size));
168 break;
169 }
170 case spv::StorageClassUniformConstant:
171 {
172 const auto &d = descriptorDecorations.at(resultId);
173 ASSERT(d.DescriptorSet >= 0);
174 ASSERT(d.Binding >= 0);
175
176 uint32_t arrayIndex = 0; // TODO(b/129523279)
177 auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
178 if(setLayout->hasBinding(d.Binding))
179 {
180 uint32_t bindingOffset = static_cast<uint32_t>(setLayout->getBindingOffset(d.Binding, arrayIndex));
181 Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet]; // DescriptorSet*
182 Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset); // vk::SampledImageDescriptor*
183 auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
184 state->createPointer(resultId, SIMD::Pointer(binding, size));
185 }
186 else
187 {
188 // TODO: Error if the variable with the non-existant binding is
189 // used? Or perhaps strip these unused variable declarations as
190 // a preprocess on the SPIR-V?
191 }
192 break;
193 }
194 case spv::StorageClassUniform:
195 case spv::StorageClassStorageBuffer:
196 {
197 const auto &d = descriptorDecorations.at(resultId);
198 ASSERT(d.DescriptorSet >= 0);
199 auto size = 0; // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
200 // Note: the module may contain descriptor set references that are not suitable for this implementation -- using a set index higher than the number
201 // of descriptor set binding points we support. As long as the selected entrypoint doesn't actually touch the out of range binding points, this
202 // is valid. In this case make the value nullptr to make it easier to diagnose an attempt to dereference it.
203 if(d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS)
204 {
205 state->createPointer(resultId, SIMD::Pointer(routine->descriptorSets[d.DescriptorSet], size));
206 }
207 else
208 {
209 state->createPointer(resultId, SIMD::Pointer(nullptr, 0));
210 }
211 break;
212 }
213 case spv::StorageClassPushConstant:
214 {
215 state->createPointer(resultId, SIMD::Pointer(routine->pushConstants, vk::MAX_PUSH_CONSTANT_SIZE));
216 break;
217 }
218 default:
219 UNREACHABLE("Storage class %d", objectTy.storageClass);
220 break;
221 }
222
223 if(insn.wordCount() > 4)
224 {
225 Object::ID initializerId = insn.word(4);
226 if(getObject(initializerId).kind != Object::Kind::Constant)
227 {
228 UNIMPLEMENTED("b/148241854: Non-constant initializers not yet implemented"); // FIXME(b/148241854)
229 }
230
231 switch(objectTy.storageClass)
232 {
233 case spv::StorageClassOutput:
234 case spv::StorageClassPrivate:
235 case spv::StorageClassFunction:
236 {
237 bool interleavedByLane = IsStorageInterleavedByLane(objectTy.storageClass);
238 auto ptr = GetPointerToData(resultId, 0, state);
239 GenericValue initialValue(this, state, initializerId);
240 VisitMemoryObject(resultId, [&](const MemoryElement &el) {
241 auto p = ptr + el.offset;
242 if(interleavedByLane) { p = InterleaveByLane(p); }
243 auto robustness = OutOfBoundsBehavior::UndefinedBehavior; // Local variables are always within bounds.
244 p.Store(initialValue.Float(el.index), robustness, state->activeLaneMask());
245 });
246 break;
247 }
248 default:
249 ASSERT_MSG(initializerId == 0, "Vulkan does not permit variables of storage class %d to have initializers", int(objectTy.storageClass));
250 }
251 }
252
253 return EmitResult::Continue;
254 }
255
EmitCopyMemory(InsnIterator insn,EmitState * state) const256 SpirvShader::EmitResult SpirvShader::EmitCopyMemory(InsnIterator insn, EmitState *state) const
257 {
258 Object::ID dstPtrId = insn.word(1);
259 Object::ID srcPtrId = insn.word(2);
260 auto &dstPtrTy = getType(getObject(dstPtrId).type);
261 auto &srcPtrTy = getType(getObject(srcPtrId).type);
262 ASSERT(dstPtrTy.element == srcPtrTy.element);
263
264 bool dstInterleavedByLane = IsStorageInterleavedByLane(dstPtrTy.storageClass);
265 bool srcInterleavedByLane = IsStorageInterleavedByLane(srcPtrTy.storageClass);
266 auto dstPtr = GetPointerToData(dstPtrId, 0, state);
267 auto srcPtr = GetPointerToData(srcPtrId, 0, state);
268
269 std::unordered_map<uint32_t, uint32_t> srcOffsets;
270
271 VisitMemoryObject(srcPtrId, [&](const MemoryElement &el) { srcOffsets[el.index] = el.offset; });
272
273 VisitMemoryObject(dstPtrId, [&](const MemoryElement &el) {
274 auto it = srcOffsets.find(el.index);
275 ASSERT(it != srcOffsets.end());
276 auto srcOffset = it->second;
277 auto dstOffset = el.offset;
278
279 auto dst = dstPtr + dstOffset;
280 auto src = srcPtr + srcOffset;
281 if(dstInterleavedByLane) { dst = InterleaveByLane(dst); }
282 if(srcInterleavedByLane) { src = InterleaveByLane(src); }
283
284 // TODO(b/131224163): Optimize based on src/dst storage classes.
285 auto robustness = OutOfBoundsBehavior::RobustBufferAccess;
286
287 auto value = src.Load<SIMD::Float>(robustness, state->activeLaneMask());
288 dst.Store(value, robustness, state->activeLaneMask());
289 });
290 return EmitResult::Continue;
291 }
292
EmitMemoryBarrier(InsnIterator insn,EmitState * state) const293 SpirvShader::EmitResult SpirvShader::EmitMemoryBarrier(InsnIterator insn, EmitState *state) const
294 {
295 auto semantics = spv::MemorySemanticsMask(GetConstScalarInt(insn.word(2)));
296 // TODO: We probably want to consider the memory scope here. For now,
297 // just always emit the full fence.
298 Fence(semantics);
299 return EmitResult::Continue;
300 }
301
VisitMemoryObjectInner(sw::SpirvShader::Type::ID id,sw::SpirvShader::Decorations d,uint32_t & index,uint32_t offset,const MemoryVisitor & f) const302 void SpirvShader::VisitMemoryObjectInner(sw::SpirvShader::Type::ID id, sw::SpirvShader::Decorations d, uint32_t &index, uint32_t offset, const MemoryVisitor &f) const
303 {
304 ApplyDecorationsForId(&d, id);
305 auto const &type = getType(id);
306
307 if(d.HasOffset)
308 {
309 offset += d.Offset;
310 d.HasOffset = false;
311 }
312
313 switch(type.opcode())
314 {
315 case spv::OpTypePointer:
316 VisitMemoryObjectInner(type.definition.word(3), d, index, offset, f);
317 break;
318 case spv::OpTypeInt:
319 case spv::OpTypeFloat:
320 case spv::OpTypeRuntimeArray:
321 f(MemoryElement{ index++, offset, type });
322 break;
323 case spv::OpTypeVector:
324 {
325 auto elemStride = (d.InsideMatrix && d.HasRowMajor && d.RowMajor) ? d.MatrixStride : static_cast<int32_t>(sizeof(float));
326 for(auto i = 0u; i < type.definition.word(3); i++)
327 {
328 VisitMemoryObjectInner(type.definition.word(2), d, index, offset + elemStride * i, f);
329 }
330 break;
331 }
332 case spv::OpTypeMatrix:
333 {
334 auto columnStride = (d.HasRowMajor && d.RowMajor) ? static_cast<int32_t>(sizeof(float)) : d.MatrixStride;
335 d.InsideMatrix = true;
336 for(auto i = 0u; i < type.definition.word(3); i++)
337 {
338 ASSERT(d.HasMatrixStride);
339 VisitMemoryObjectInner(type.definition.word(2), d, index, offset + columnStride * i, f);
340 }
341 break;
342 }
343 case spv::OpTypeStruct:
344 for(auto i = 0u; i < type.definition.wordCount() - 2; i++)
345 {
346 ApplyDecorationsForIdMember(&d, id, i);
347 VisitMemoryObjectInner(type.definition.word(i + 2), d, index, offset, f);
348 }
349 break;
350 case spv::OpTypeArray:
351 {
352 auto arraySize = GetConstScalarInt(type.definition.word(3));
353 for(auto i = 0u; i < arraySize; i++)
354 {
355 ASSERT(d.HasArrayStride);
356 VisitMemoryObjectInner(type.definition.word(2), d, index, offset + i * d.ArrayStride, f);
357 }
358 break;
359 }
360 default:
361 UNREACHABLE("%s", OpcodeName(type.opcode()).c_str());
362 }
363 }
364
VisitMemoryObject(sw::SpirvShader::Object::ID id,const MemoryVisitor & f) const365 void SpirvShader::VisitMemoryObject(sw::SpirvShader::Object::ID id, const MemoryVisitor &f) const
366 {
367 auto typeId = getObject(id).type;
368 auto const &type = getType(typeId);
369 if(IsExplicitLayout(type.storageClass))
370 {
371 Decorations d{};
372 ApplyDecorationsForId(&d, id);
373 uint32_t index = 0;
374 VisitMemoryObjectInner(typeId, d, index, 0, f);
375 }
376 else
377 {
378 // Objects without explicit layout are tightly packed.
379 auto &elType = getType(type.element);
380 for(auto index = 0u; index < elType.sizeInComponents; index++)
381 {
382 auto offset = static_cast<uint32_t>(index * sizeof(float));
383 f({ index, offset, elType });
384 }
385 }
386 }
387
GetPointerToData(Object::ID id,int arrayIndex,EmitState const * state) const388 SIMD::Pointer SpirvShader::GetPointerToData(Object::ID id, int arrayIndex, EmitState const *state) const
389 {
390 auto routine = state->routine;
391 auto &object = getObject(id);
392 switch(object.kind)
393 {
394 case Object::Kind::Pointer:
395 case Object::Kind::InterfaceVariable:
396 return state->getPointer(id);
397
398 case Object::Kind::DescriptorSet:
399 {
400 const auto &d = descriptorDecorations.at(id);
401 ASSERT(d.DescriptorSet >= 0 && d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS);
402 ASSERT(d.Binding >= 0);
403
404 auto set = state->getPointer(id);
405
406 auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
407 ASSERT_MSG(setLayout->hasBinding(d.Binding), "Descriptor set %d does not contain binding %d", int(d.DescriptorSet), int(d.Binding));
408 int bindingOffset = static_cast<int>(setLayout->getBindingOffset(d.Binding, arrayIndex));
409
410 Pointer<Byte> descriptor = set.base + bindingOffset; // BufferDescriptor*
411 Pointer<Byte> data = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::BufferDescriptor, ptr)); // void*
412 Int size = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, sizeInBytes));
413 if(setLayout->isBindingDynamic(d.Binding))
414 {
415 uint32_t dynamicBindingIndex =
416 routine->pipelineLayout->getDynamicOffsetBase(d.DescriptorSet) +
417 setLayout->getDynamicDescriptorOffset(d.Binding) +
418 arrayIndex;
419 Int offset = routine->descriptorDynamicOffsets[dynamicBindingIndex];
420 Int robustnessSize = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, robustnessSize));
421 return SIMD::Pointer(data + offset, Min(size, robustnessSize - offset));
422 }
423 else
424 {
425 return SIMD::Pointer(data, size);
426 }
427 }
428
429 default:
430 UNREACHABLE("Invalid pointer kind %d", int(object.kind));
431 return SIMD::Pointer(Pointer<Byte>(), 0);
432 }
433 }
434
MemoryOrder(spv::MemorySemanticsMask memorySemantics)435 std::memory_order SpirvShader::MemoryOrder(spv::MemorySemanticsMask memorySemantics)
436 {
437 auto control = static_cast<uint32_t>(memorySemantics) & static_cast<uint32_t>(
438 spv::MemorySemanticsAcquireMask |
439 spv::MemorySemanticsReleaseMask |
440 spv::MemorySemanticsAcquireReleaseMask |
441 spv::MemorySemanticsSequentiallyConsistentMask);
442 switch(control)
443 {
444 case spv::MemorySemanticsMaskNone: return std::memory_order_relaxed;
445 case spv::MemorySemanticsAcquireMask: return std::memory_order_acquire;
446 case spv::MemorySemanticsReleaseMask: return std::memory_order_release;
447 case spv::MemorySemanticsAcquireReleaseMask: return std::memory_order_acq_rel;
448 case spv::MemorySemanticsSequentiallyConsistentMask: return std::memory_order_acq_rel; // Vulkan 1.1: "SequentiallyConsistent is treated as AcquireRelease"
449 default:
450 // "it is invalid for more than one of these four bits to be set:
451 // Acquire, Release, AcquireRelease, or SequentiallyConsistent."
452 UNREACHABLE("MemorySemanticsMask: %x", int(control));
453 return std::memory_order_acq_rel;
454 }
455 }
456
StoresInHelperInvocation(spv::StorageClass storageClass)457 bool SpirvShader::StoresInHelperInvocation(spv::StorageClass storageClass)
458 {
459 switch(storageClass)
460 {
461 case spv::StorageClassUniform:
462 case spv::StorageClassStorageBuffer:
463 case spv::StorageClassImage:
464 return false;
465 default:
466 return true;
467 }
468 }
469
IsExplicitLayout(spv::StorageClass storageClass)470 bool SpirvShader::IsExplicitLayout(spv::StorageClass storageClass)
471 {
472 switch(storageClass)
473 {
474 case spv::StorageClassUniform:
475 case spv::StorageClassStorageBuffer:
476 case spv::StorageClassPushConstant:
477 return true;
478 default:
479 return false;
480 }
481 }
482
InterleaveByLane(sw::SIMD::Pointer p)483 sw::SIMD::Pointer SpirvShader::InterleaveByLane(sw::SIMD::Pointer p)
484 {
485 p *= sw::SIMD::Width;
486 p.staticOffsets[0] += 0 * sizeof(float);
487 p.staticOffsets[1] += 1 * sizeof(float);
488 p.staticOffsets[2] += 2 * sizeof(float);
489 p.staticOffsets[3] += 3 * sizeof(float);
490 return p;
491 }
492
IsStorageInterleavedByLane(spv::StorageClass storageClass)493 bool SpirvShader::IsStorageInterleavedByLane(spv::StorageClass storageClass)
494 {
495 switch(storageClass)
496 {
497 case spv::StorageClassUniform:
498 case spv::StorageClassStorageBuffer:
499 case spv::StorageClassPushConstant:
500 case spv::StorageClassWorkgroup:
501 case spv::StorageClassImage:
502 return false;
503 default:
504 return true;
505 }
506 }
507
508 } // namespace sw