mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-19 11:30:15 +00:00
63f57d95d6
* SPIR-V 1.4 headers, add SPV_ENV_UNIVERSAL_1_4 * Support --target-env spv1.4 in help for command line tools * Support asm/dis of UniformId decoration * Validate UniformId decoration * Fix version check on instructions and operands Also register decorations used with OpDecorateId * Extension lists can differ between enums that match Example: SubgroupMaskEq vs SubgroupMaskEqKHR * Validate scope value for Uniform decoration, for SPIR-V 1.4 * More unioning of exts * Preserve grammar order within an enum value * 1.4: Validate OpSelect over composites * Tools default to 1.4 * Add asm/dis test for OpCopyLogical * 1.4: asm/dis tests for PtrEqual, PtrNotEqual, PtrDiff * Basic asm/Dis test for OpCopyMemory * Test asm/dis OpCopyMemory with 2-memory access Add asm/dis tests for OpCopyMemorySized Requires grammar update to add second optional memory access operand to OpCopyMemory and OpCopyMemorySized * Validate one or two memory accesses on OpCopyMemory* * Check av/vis on CopyMemory source and target memory access This is a proposed rule. See https://gitlab.khronos.org/spirv/SPIR-V/issues/413 * Validate operation for OpSpecConstantOp * Validate NonWritable decoration Also permit NonWritable on members of UBO and SSBO. * SPIR-V 1.4: NonWrtiable can decorate Function and Private vars * Update optimizer CLI tests for SPIR-V 1.4 * Testing tools: Give expected SPIR-V version in message * SPIR-V 1.4 validation for entry point interfaces * Allow only unique interfaces * Allow all global variables * Check that all statically used global variables are listed * new tests * Add validation fixture CompileFailure * Add 1.4 validation for pointer comparisons * New tests * Validate with image operands SignExtend, ZeroExtend Since we don't actually know the image texel format, we can't fully validate. We need more context. But we can make sure we allow the new image operands in known-good cases. * Validate OpCopyLogical * Recursively checks subtypes * new tests * Add SPIR-V 1.4 tests for NoSignedWrap, NoUnsignedWrap * Allow scalar conditions in 1.4 with OpSelect * Allows scalar conditions with vector operands * new tests * Validate uniform id scope as an execution scope * Validate the values of memory and execution scopes are valid scope values * new test * Remove SPIR-V 1.4 Vulkan 1.0 environment * SPIR-V 1.4 requires Vulkan 1.1 * FIX: include string for spvLog * FIX: validate nonwritable * FIX: test case suite for member decorate string * FIX: test case for hlsl functionality1 * Validation test fixture: ease debugging * Use binary version for SPIR-V 1.4 specific features * Switch checks based on the SPIR-V version from the target environment to instead use the version from the binary * Moved header parsing into the ValidationState_t constructor (where version based features are set) * Added new versions of tests that assemble a 1.3 binary and validate a 1.4 environment * Fix test for update to SPIR-V 1.4 headers * Fix formatting * Ext inst lookup: Add Vulkan 1.1 env with SPIR-V 1.4 * Update spirv-val help * Operand version checks should use module version Use the module version instead of the target environment version. * Fix comment about two-access form of OpCopyMemory
282 lines
9.9 KiB
C++
282 lines
9.9 KiB
C++
// Copyright (c) 2018 Google LLC
|
|
//
|
|
// 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 <cassert>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <functional>
|
|
|
|
#include "source/opt/build_module.h"
|
|
#include "source/opt/ir_context.h"
|
|
#include "source/opt/log.h"
|
|
#include "source/reduce/reducer.h"
|
|
#include "source/spirv_reducer_options.h"
|
|
#include "source/util/string_utils.h"
|
|
#include "tools/io.h"
|
|
#include "tools/util/cli_consumer.h"
|
|
|
|
namespace {
|
|
|
|
using ErrorOrInt = std::pair<std::string, int>;
|
|
|
|
// Check that the std::system function can actually be used.
|
|
bool CheckExecuteCommand() {
|
|
int res = std::system(nullptr);
|
|
return res != 0;
|
|
}
|
|
|
|
// Execute a command using the shell.
|
|
// Returns true if and only if the command's exit status was 0.
|
|
bool ExecuteCommand(const std::string& command) {
|
|
errno = 0;
|
|
int status = std::system(command.c_str());
|
|
assert(errno == 0 && "failed to execute command");
|
|
// The result returned by 'system' is implementation-defined, but is
|
|
// usually the case that the returned value is 0 when the command's exit
|
|
// code was 0. We are assuming that here, and that's all we depend on.
|
|
return status == 0;
|
|
}
|
|
|
|
// Status and actions to perform after parsing command-line arguments.
|
|
enum ReduceActions { REDUCE_CONTINUE, REDUCE_STOP };
|
|
|
|
struct ReduceStatus {
|
|
ReduceActions action;
|
|
int code;
|
|
};
|
|
|
|
void PrintUsage(const char* program) {
|
|
// NOTE: Please maintain flags in lexicographical order.
|
|
printf(
|
|
R"(%s - Reduce a SPIR-V binary file with respect to a user-provided
|
|
interestingness test.
|
|
|
|
USAGE: %s [options] <input> <interestingness-test>
|
|
|
|
The SPIR-V binary is read from <input>.
|
|
|
|
Whether a binary is interesting is determined by <interestingness-test>, which
|
|
should be the path to a script.
|
|
|
|
* The script must be executable.
|
|
|
|
* The script should take the path to a SPIR-V binary file (.spv) as its single
|
|
argument, and exit with code 0 if and only if the binary file is
|
|
interesting.
|
|
|
|
* Example: an interestingness test for reducing a SPIR-V binary file that
|
|
causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
|
|
standard error should:
|
|
- invoke "foo" on the binary passed as the script argument;
|
|
- capture the return code and standard error from "bar";
|
|
- exit with code 0 if and only if the return code of "foo" was 1 and the
|
|
standard error from "bar" contained "Fatal error: bar".
|
|
|
|
* The reducer does not place a time limit on how long the interestingness test
|
|
takes to run, so it is advisable to use per-command timeouts inside the
|
|
script when invoking SPIR-V-processing tools (such as "foo" in the above
|
|
example).
|
|
|
|
NOTE: The reducer is a work in progress.
|
|
|
|
Options (in lexicographical order):
|
|
|
|
--fail-on-validation-error
|
|
Stop reduction with an error if any reduction step produces a
|
|
SPIR-V module that fails to validate.
|
|
-h, --help
|
|
Print this help.
|
|
--step-limit
|
|
32-bit unsigned integer specifying maximum number of steps the
|
|
reducer will take before giving up.
|
|
--version
|
|
Display reducer version information.
|
|
|
|
Supported validator options are as follows. See `spirv-val --help` for details.
|
|
--relax-logical-pointer
|
|
--relax-block-layout
|
|
--scalar-block-layout
|
|
--skip-block-layout
|
|
--relax-struct-store
|
|
)",
|
|
program, program);
|
|
}
|
|
|
|
// Message consumer for this tool. Used to emit diagnostics during
|
|
// initialization and setup. Note that |source| and |position| are irrelevant
|
|
// here because we are still not processing a SPIR-V input file.
|
|
void ReduceDiagnostic(spv_message_level_t level, const char* /*source*/,
|
|
const spv_position_t& /*position*/, const char* message) {
|
|
if (level == SPV_MSG_ERROR) {
|
|
fprintf(stderr, "error: ");
|
|
}
|
|
fprintf(stderr, "%s\n", message);
|
|
}
|
|
|
|
ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
|
|
const char** interestingness_test,
|
|
spvtools::ReducerOptions* reducer_options,
|
|
spvtools::ValidatorOptions* validator_options) {
|
|
uint32_t positional_arg_index = 0;
|
|
|
|
for (int argi = 1; argi < argc; ++argi) {
|
|
const char* cur_arg = argv[argi];
|
|
if ('-' == cur_arg[0]) {
|
|
if (0 == strcmp(cur_arg, "--version")) {
|
|
spvtools::Logf(ReduceDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
|
|
spvSoftwareVersionDetailsString());
|
|
return {REDUCE_STOP, 0};
|
|
} else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
|
|
PrintUsage(argv[0]);
|
|
return {REDUCE_STOP, 0};
|
|
} else if ('\0' == cur_arg[1]) {
|
|
// We do not support reduction from standard input. We could support
|
|
// this if there was a compelling use case.
|
|
PrintUsage(argv[0]);
|
|
return {REDUCE_STOP, 0};
|
|
} else if (0 == strncmp(cur_arg,
|
|
"--step-limit=", sizeof("--step-limit=") - 1)) {
|
|
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
|
|
char* end = nullptr;
|
|
errno = 0;
|
|
const auto step_limit =
|
|
static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
|
|
assert(end != split_flag.second.c_str() && errno == 0);
|
|
reducer_options->set_step_limit(step_limit);
|
|
}
|
|
} else if (positional_arg_index == 0) {
|
|
// Input file name
|
|
assert(!*in_file);
|
|
*in_file = cur_arg;
|
|
positional_arg_index++;
|
|
} else if (positional_arg_index == 1) {
|
|
assert(!*interestingness_test);
|
|
*interestingness_test = cur_arg;
|
|
positional_arg_index++;
|
|
} else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
|
|
reducer_options->set_fail_on_validation_error(true);
|
|
} else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
|
|
validator_options->SetRelaxLogicalPointer(true);
|
|
} else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
|
|
validator_options->SetRelaxBlockLayout(true);
|
|
} else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
|
|
validator_options->SetScalarBlockLayout(true);
|
|
} else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
|
|
validator_options->SetSkipBlockLayout(true);
|
|
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
|
|
validator_options->SetRelaxStructStore(true);
|
|
} else {
|
|
spvtools::Error(ReduceDiagnostic, nullptr, {},
|
|
"Too many positional arguments specified");
|
|
return {REDUCE_STOP, 1};
|
|
}
|
|
}
|
|
|
|
if (!*in_file) {
|
|
spvtools::Error(ReduceDiagnostic, nullptr, {}, "No input file specified");
|
|
return {REDUCE_STOP, 1};
|
|
}
|
|
|
|
if (!*interestingness_test) {
|
|
spvtools::Error(ReduceDiagnostic, nullptr, {},
|
|
"No interestingness test specified");
|
|
return {REDUCE_STOP, 1};
|
|
}
|
|
|
|
return {REDUCE_CONTINUE, 0};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Dumps |binary| to file |filename|. Useful for interactive debugging.
|
|
void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
|
|
auto write_file_succeeded =
|
|
WriteFile(filename, "wb", &binary[0], binary.size());
|
|
if (!write_file_succeeded) {
|
|
std::cerr << "Failed to dump shader" << std::endl;
|
|
}
|
|
}
|
|
|
|
// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
|
|
// interactive debugging.
|
|
void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
|
|
std::vector<uint32_t> binary;
|
|
context->module()->ToBinary(&binary, false);
|
|
DumpShader(binary, filename);
|
|
}
|
|
|
|
const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
|
|
|
|
int main(int argc, const char** argv) {
|
|
const char* in_file = nullptr;
|
|
const char* interestingness_test = nullptr;
|
|
|
|
spv_target_env target_env = kDefaultEnvironment;
|
|
spvtools::ReducerOptions reducer_options;
|
|
spvtools::ValidatorOptions validator_options;
|
|
|
|
ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
|
|
&reducer_options, &validator_options);
|
|
|
|
if (status.action == REDUCE_STOP) {
|
|
return status.code;
|
|
}
|
|
|
|
if (!CheckExecuteCommand()) {
|
|
std::cerr << "could not find shell interpreter for executing a command"
|
|
<< std::endl;
|
|
return 2;
|
|
}
|
|
|
|
spvtools::reduce::Reducer reducer(target_env);
|
|
|
|
reducer.SetInterestingnessFunction(
|
|
[interestingness_test](std::vector<uint32_t> binary,
|
|
uint32_t reductions_applied) -> bool {
|
|
std::stringstream ss;
|
|
ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
|
|
<< ".spv";
|
|
const auto spv_file = ss.str();
|
|
const std::string command =
|
|
std::string(interestingness_test) + " " + spv_file;
|
|
auto write_file_succeeded =
|
|
WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
|
|
(void)(write_file_succeeded);
|
|
assert(write_file_succeeded);
|
|
return ExecuteCommand(command);
|
|
});
|
|
|
|
reducer.AddDefaultReductionPasses();
|
|
|
|
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
|
|
|
|
std::vector<uint32_t> binary_in;
|
|
if (!ReadFile<uint32_t>(in_file, "rb", &binary_in)) {
|
|
return 1;
|
|
}
|
|
|
|
std::vector<uint32_t> binary_out;
|
|
const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
|
|
reducer_options, validator_options);
|
|
|
|
if (reduction_status == spvtools::reduce::Reducer::ReductionResultStatus::
|
|
kInitialStateNotInteresting ||
|
|
!WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
|
|
binary_out.size())) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|