Non-Functional: Add postprocess as more robust way to add capabilities

When capabilities are needed for specific SPIR-V instructions, it is
fragile to do so based on GLSL/AST usage; it should be based on actual
instructions they got translated to.
This commit is contained in:
John Kessenich 2018-08-03 15:56:12 -06:00
parent e99a26810f
commit f04c51b3bd
5 changed files with 178 additions and 54 deletions

View File

@ -3,6 +3,7 @@ set(SOURCES
InReadableOrder.cpp
Logger.cpp
SpvBuilder.cpp
SpvPostProcess.cpp
doc.cpp
disassemble.cpp)

View File

@ -1191,6 +1191,7 @@ TGlslangToSpvTraverser::TGlslangToSpvTraverser(unsigned int spvVersion, const gl
// Finish creating SPV, after the traversal is complete.
void TGlslangToSpvTraverser::finishSpv()
{
// Finish the entry point function
if (! entryPointTerminated) {
builder.setBuildPoint(shaderEntry->getLastBlock());
builder.leaveFunction();
@ -1200,7 +1201,9 @@ void TGlslangToSpvTraverser::finishSpv()
for (auto it = iOSet.cbegin(); it != iOSet.cend(); ++it)
entryPoint->addIdOperand(*it);
builder.eliminateDeadDecorations();
// Add capabilities, extensions, remove unneeded decorations, etc.,
// based on the resulting SPIR-V.
builder.postProcess();
}
// Write the SPV into 'out'.
@ -4627,27 +4630,21 @@ spv::Id TGlslangToSpvTraverser::createUnaryOperation(glslang::TOperator op, OpDe
unaryOp = spv::OpFwidth;
break;
case glslang::EOpDPdxFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdxFine;
break;
case glslang::EOpDPdyFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdyFine;
break;
case glslang::EOpFwidthFine:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpFwidthFine;
break;
case glslang::EOpDPdxCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdxCoarse;
break;
case glslang::EOpDPdyCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpDPdyCoarse;
break;
case glslang::EOpFwidthCoarse:
builder.addCapability(spv::CapabilityDerivativeControl);
unaryOp = spv::OpFwidthCoarse;
break;
case glslang::EOpInterpolateAtCentroid:
@ -4655,7 +4652,6 @@ spv::Id TGlslangToSpvTraverser::createUnaryOperation(glslang::TOperator op, OpDe
if (typeProxy == glslang::EbtFloat16)
builder.addExtension(spv::E_SPV_AMD_gpu_shader_half_float);
#endif
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtCentroid;
break;
case glslang::EOpAny:
@ -4791,8 +4787,6 @@ spv::Id TGlslangToSpvTraverser::createUnaryOperation(glslang::TOperator op, OpDe
#endif
#ifdef NV_EXTENSIONS
case glslang::EOpSubgroupPartition:
builder.addExtension(spv::E_SPV_NV_shader_subgroup_partitioned);
builder.addCapability(spv::CapabilityGroupNonUniformPartitionedNV);
unaryOp = spv::OpGroupNonUniformPartitionNV;
break;
#endif
@ -6087,7 +6081,6 @@ spv::Id TGlslangToSpvTraverser::createMiscOperation(glslang::TOperator op, spv::
if (typeProxy == glslang::EbtFloat16)
builder.addExtension(spv::E_SPV_AMD_gpu_shader_half_float);
#endif
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtSample;
break;
case glslang::EOpInterpolateAtOffset:
@ -6095,7 +6088,6 @@ spv::Id TGlslangToSpvTraverser::createMiscOperation(glslang::TOperator op, spv::
if (typeProxy == glslang::EbtFloat16)
builder.addExtension(spv::E_SPV_AMD_gpu_shader_half_float);
#endif
builder.addCapability(spv::CapabilityInterpolationFunction);
libCall = spv::GLSLstd450InterpolateAtOffset;
break;
case glslang::EOpAddCarry:

View File

@ -1774,9 +1774,6 @@ Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse,
// Comments in header
Id Builder::createTextureQueryCall(Op opCode, const TextureParameters& parameters, bool isUnsignedResult)
{
// All these need a capability
addCapability(CapabilityImageQuery);
// Figure out the result type
Id resultType = 0;
switch (opCode) {
@ -2483,42 +2480,6 @@ Id Builder::accessChainGetInferredType()
return type;
}
// comment in header
void Builder::eliminateDeadDecorations() {
std::unordered_set<const Block*> reachable_blocks;
std::unordered_set<Id> unreachable_definitions;
// Collect IDs defined in unreachable blocks. For each function, label the
// reachable blocks first. Then for each unreachable block, collect the
// result IDs of the instructions in it.
for (std::vector<Function*>::const_iterator fi = module.getFunctions().cbegin();
fi != module.getFunctions().cend(); fi++) {
Function* f = *fi;
Block* entry = f->getEntryBlock();
inReadableOrder(entry, [&reachable_blocks](const Block* b) {
reachable_blocks.insert(b);
});
for (std::vector<Block*>::const_iterator bi = f->getBlocks().cbegin();
bi != f->getBlocks().cend(); bi++) {
Block* b = *bi;
if (!reachable_blocks.count(b)) {
for (std::vector<std::unique_ptr<Instruction> >::const_iterator
ii = b->getInstructions().cbegin();
ii != b->getInstructions().cend(); ii++) {
Instruction* i = ii->get();
unreachable_definitions.insert(i->getResultId());
}
}
}
}
decorations.erase(std::remove_if(decorations.begin(), decorations.end(),
[&unreachable_definitions](std::unique_ptr<Instruction>& I) -> bool {
Instruction* inst = I.get();
Id decoration_id = inst->getIdOperand(0);
return unreachable_definitions.count(decoration_id) != 0;
}),
decorations.end());
}
void Builder::dump(std::vector<unsigned int>& out) const
{
// Header, before first instructions:

View File

@ -563,9 +563,15 @@ public:
// based on the type of the base and the chain of dereferences.
Id accessChainGetInferredType();
// Remove OpDecorate instructions whose operands are defined in unreachable
// blocks.
void eliminateDeadDecorations();
// Add capabilities, extensions, remove unneeded decorations, etc.,
// based on the resulting SPIR-V.
void postProcess();
// Hook to visit each instruction in a block in a function
void postProcess(Instruction& inst);
// Hook to visit each instruction in a reachable block in a function.
void postProcessReachable(Instruction& inst);
void dump(std::vector<unsigned int>&) const;
void createBranch(Block* block);

164
SPIRV/SpvPostProcess.cpp Executable file
View File

@ -0,0 +1,164 @@
//
// Copyright (C) 2016-2018 Google, Inc.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Post-processing for SPIR-V IR, in internal form, not standard binary form.
//
#include <cassert>
#include <cstdlib>
#include <unordered_set>
#include <algorithm>
#include "SpvBuilder.h"
#include "spirv.hpp"
#include "GlslangToSpv.h"
#include "SpvBuilder.h"
namespace spv {
#include "GLSL.std.450.h"
#include "GLSL.ext.KHR.h"
#include "GLSL.ext.EXT.h"
#ifdef AMD_EXTENSIONS
#include "GLSL.ext.AMD.h"
#endif
#ifdef NV_EXTENSIONS
#include "GLSL.ext.NV.h"
#endif
}
namespace spv {
// Called for each instruction in a block.
void Builder::postProcess(Instruction& inst)
{
// Add capabilities based simply on the opcode.
switch (inst.getOpCode()) {
case OpExtInst:
switch (inst.getIdOperand(1)) {
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtSample:
case GLSLstd450InterpolateAtOffset:
addCapability(CapabilityInterpolationFunction);
break;
default:
break;
}
break;
case OpDPdxFine:
case OpDPdyFine:
case OpFwidthFine:
case OpDPdxCoarse:
case OpDPdyCoarse:
case OpFwidthCoarse:
addCapability(CapabilityDerivativeControl);
break;
case OpImageQueryLod:
case OpImageQuerySize:
case OpImageQuerySizeLod:
case OpImageQuerySamples:
case OpImageQueryLevels:
addCapability(CapabilityImageQuery);
break;
#ifdef NV_EXTENSIONS
case OpGroupNonUniformPartitionNV:
addExtension(E_SPV_NV_shader_subgroup_partitioned);
addCapability(CapabilityGroupNonUniformPartitionedNV);
break;
#endif
default:
break;
}
}
// Called for each instruction in a reachable block.
void Builder::postProcessReachable(Instruction& inst)
{
// did have code here, but questionable to do so without deleting the instructions
}
// comment in header
void Builder::postProcess()
{
std::unordered_set<const Block*> reachableBlocks;
std::unordered_set<Id> unreachableDefinitions;
// Collect IDs defined in unreachable blocks. For each function, label the
// reachable blocks first. Then for each unreachable block, collect the
// result IDs of the instructions in it.
for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) {
Function* f = *fi;
Block* entry = f->getEntryBlock();
inReadableOrder(entry, [&reachableBlocks](const Block* b) { reachableBlocks.insert(b); });
for (auto bi = f->getBlocks().cbegin(); bi != f->getBlocks().cend(); bi++) {
Block* b = *bi;
if (reachableBlocks.count(b) == 0) {
for (auto ii = b->getInstructions().cbegin(); ii != b->getInstructions().cend(); ii++)
unreachableDefinitions.insert(ii->get()->getResultId());
}
}
}
// Remove unneeded decorations, for unreachable instructions
decorations.erase(std::remove_if(decorations.begin(), decorations.end(),
[&unreachableDefinitions](std::unique_ptr<Instruction>& I) -> bool {
Id decoration_id = I.get()->getIdOperand(0);
return unreachableDefinitions.count(decoration_id) != 0;
}),
decorations.end());
// Add per-instruction capabilities, extensions, etc.,
// process all reachable instructions...
for (auto bi = reachableBlocks.cbegin(); bi != reachableBlocks.cend(); ++bi) {
const Block* block = *bi;
const auto function = [this](const std::unique_ptr<Instruction>& inst) { postProcessReachable(*inst.get()); };
std::for_each(block->getInstructions().begin(), block->getInstructions().end(), function);
}
// process all block-contained instructions
for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) {
Function* f = *fi;
for (auto bi = f->getBlocks().cbegin(); bi != f->getBlocks().cend(); bi++) {
Block* b = *bi;
for (auto ii = b->getInstructions().cbegin(); ii != b->getInstructions().cend(); ii++)
postProcess(*ii->get());
}
}
}
}; // end spv namespace