spirv-fuzz: Return IR and transformation context after replay (#3846)

Before this change, the replayer would return a SPIR-V binary. This
did not allow further transforming the resulting module: it would need
to be re-parsed, and the transformation context arising from the
replayed transformations was not available. This change makes it so
that after replay an IR context and transformation context are
returned instead; the IR context can subsequently be turned into a
binary if desired.

This change paves the way for an upcoming PR to integrate spirv-reduce
with the spirv-fuzz shrinker.
This commit is contained in:
Alastair Donaldson 2020-09-25 09:58:10 +01:00 committed by GitHub
parent e12087d6c3
commit 9e17b9d07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 29 deletions

View File

@ -59,7 +59,7 @@ Replayer::ReplayerResult Replayer::Run() {
"The number of transformations to be replayed must not "
"exceed the size of the transformation sequence.");
return {Replayer::ReplayerResultStatus::kTooManyTransformationsRequested,
std::vector<uint32_t>(), protobufs::TransformationSequence()};
nullptr, nullptr, protobufs::TransformationSequence()};
}
spvtools::SpirvTools tools(target_env_);
@ -67,15 +67,15 @@ Replayer::ReplayerResult Replayer::Run() {
consumer_(SPV_MSG_ERROR, nullptr, {},
"Failed to create SPIRV-Tools interface; stopping.");
return {Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface,
std::vector<uint32_t>(), protobufs::TransformationSequence()};
nullptr, nullptr, protobufs::TransformationSequence()};
}
// Initial binary should be valid.
if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) {
consumer_(SPV_MSG_INFO, nullptr, {},
"Initial binary is invalid; stopping.");
return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid,
std::vector<uint32_t>(), protobufs::TransformationSequence()};
return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid, nullptr,
nullptr, protobufs::TransformationSequence()};
}
// Build the module from the input binary.
@ -140,7 +140,7 @@ Replayer::ReplayerResult Replayer::Run() {
"Binary became invalid during replay (set a "
"breakpoint to inspect); stopping.");
return {Replayer::ReplayerResultStatus::kReplayValidationFailure,
std::vector<uint32_t>(), protobufs::TransformationSequence()};
nullptr, nullptr, protobufs::TransformationSequence()};
}
// The binary was valid, so it becomes the latest valid binary.
@ -149,10 +149,8 @@ Replayer::ReplayerResult Replayer::Run() {
}
}
// Write out the module as a binary.
std::vector<uint32_t> binary_out;
ir_context->module()->ToBinary(&binary_out, false);
return {Replayer::ReplayerResultStatus::kComplete, std::move(binary_out),
return {Replayer::ReplayerResultStatus::kComplete, std::move(ir_context),
std::move(transformation_context),
std::move(transformation_sequence_out)};
}

View File

@ -19,6 +19,8 @@
#include <vector>
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
#include "spirv-tools/libspirv.hpp"
namespace spvtools {
@ -39,7 +41,8 @@ class Replayer {
struct ReplayerResult {
ReplayerResultStatus status;
std::vector<uint32_t> transformed_binary;
std::unique_ptr<opt::IRContext> transformed_module;
std::unique_ptr<TransformationContext> transformation_context;
protobufs::TransformationSequence applied_transformations;
};
@ -70,9 +73,10 @@ class Replayer {
// ids will be available during replay starting from this value.
//
// On success, returns a successful result status together with the
// transformations that were successfully applied and the binary resulting
// from applying them. Otherwise, returns an appropriate result status
// together with an empty binary and empty transformation sequence.
// transformations that were applied, the IR for the transformed module, and
// the transformation context that arises from applying these transformations.
// Otherwise, returns an appropriate result status, an empty transformation
// sequence, and null pointers for the IR context and transformation context.
ReplayerResult Run();
private:

View File

@ -70,7 +70,7 @@ Shrinker::Shrinker(
uint32_t step_limit, bool validate_during_replay,
spv_validator_options validator_options)
: target_env_(target_env),
consumer_(consumer),
consumer_(std::move(consumer)),
binary_in_(binary_in),
initial_facts_(initial_facts),
transformation_sequence_in_(transformation_sequence_in),
@ -120,8 +120,9 @@ Shrinker::ShrinkerResult Shrinker::Run() {
// Get the binary that results from running these transformations, and the
// subsequence of the initial transformations that actually apply (in
// principle this could be a strict subsequence).
std::vector<uint32_t> current_best_binary =
std::move(initial_replay_result.transformed_binary);
std::vector<uint32_t> current_best_binary;
initial_replay_result.transformed_module->module()->ToBinary(
&current_best_binary, false);
protobufs::TransformationSequence current_best_transformations =
std::move(initial_replay_result.applied_transformations);
@ -215,12 +216,14 @@ Shrinker::ShrinkerResult Shrinker::Run() {
"Removing this chunk of transformations should not have an effect "
"on earlier chunks.");
if (interestingness_function_(replay_result.transformed_binary,
attempt)) {
std::vector<uint32_t> transformed_binary;
replay_result.transformed_module->module()->ToBinary(&transformed_binary,
false);
if (interestingness_function_(transformed_binary, attempt)) {
// If the binary arising from the smaller transformation sequence is
// interesting, this becomes our current best binary and transformation
// sequence.
current_best_binary = std::move(replay_result.transformed_binary);
current_best_binary = std::move(transformed_binary);
current_best_transformations =
std::move(replay_result.applied_transformations);
progress_this_round = true;

View File

@ -72,6 +72,13 @@ bool IsEqual(const spv_target_env env, const opt::IRContext* ir_1,
return IsEqual(env, binary_1, binary_2);
}
bool IsEqual(const spv_target_env env, const std::vector<uint32_t>& binary_1,
const opt::IRContext* ir_2) {
std::vector<uint32_t> binary_2;
ir_2->module()->ToBinary(&binary_2, false);
return IsEqual(env, binary_1, binary_2);
}
bool IsValid(spv_target_env env, const opt::IRContext* ir) {
std::vector<uint32_t> binary;
ir->module()->ToBinary(&binary, false);

View File

@ -45,6 +45,11 @@ bool IsEqual(spv_target_env env, const std::string& expected_text,
bool IsEqual(spv_target_env env, const opt::IRContext* ir_1,
const opt::IRContext* ir_2);
// Turns |ir_2| into a binary, then returns true if and only if the resulting
// binary is bit-wise equal to |binary_1|.
bool IsEqual(spv_target_env env, const std::vector<uint32_t>& binary_1,
const opt::IRContext* ir_2);
// Assembles the given IR context and returns true if and only if
// the resulting binary is valid.
bool IsValid(spv_target_env env, const opt::IRContext* ir);

View File

@ -1684,8 +1684,8 @@ void RunFuzzerAndReplayer(const std::string& shader,
replayer_result.applied_transformations.SerializeToString(
&replayer_transformations_string);
ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
ASSERT_EQ(fuzzer_result.transformed_binary,
replayer_result.transformed_binary);
ASSERT_TRUE(IsEqual(env, fuzzer_result.transformed_binary,
replayer_result.transformed_module.get()));
}
}

View File

@ -14,7 +14,12 @@
#include "source/fuzz/replayer.h"
#include "source/fuzz/data_descriptor.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_global_variable.h"
#include "source/fuzz/transformation_add_parameter.h"
#include "source/fuzz/transformation_add_synonym.h"
#include "source/fuzz/transformation_split_block.h"
#include "test/fuzz/fuzz_test_util.h"
@ -169,8 +174,8 @@ TEST(ReplayerTest, PartialReplay) {
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(
IsEqual(env, kFullySplitShader, replayer_result.transformed_binary));
ASSERT_TRUE(IsEqual(env, kFullySplitShader,
replayer_result.transformed_module.get()));
}
{
@ -247,8 +252,8 @@ TEST(ReplayerTest, PartialReplay) {
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(
IsEqual(env, kHalfSplitShader, replayer_result.transformed_binary));
ASSERT_TRUE(IsEqual(env, kHalfSplitShader,
replayer_result.transformed_module.get()));
}
{
@ -263,7 +268,8 @@ TEST(ReplayerTest, PartialReplay) {
replayer_result.status);
// No transformations should be applied
ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size());
ASSERT_TRUE(IsEqual(env, kTestShader, replayer_result.transformed_binary));
ASSERT_TRUE(
IsEqual(env, kTestShader, replayer_result.transformed_module.get()));
}
{
@ -282,10 +288,122 @@ TEST(ReplayerTest, PartialReplay) {
// No transformations should be applied
ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size());
// The output binary should be empty
ASSERT_TRUE(replayer_result.transformed_binary.empty());
ASSERT_EQ(nullptr, replayer_result.transformed_module);
}
}
TEST(ReplayerTest, CheckFactsAfterReplay) {
const std::string kTestShader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%8 = OpTypeInt 32 1
%9 = OpTypePointer Function %8
%50 = OpTypePointer Private %8
%11 = OpConstant %8 1
%4 = OpFunction %2 None %3
%5 = OpLabel
%10 = OpVariable %9 Function
OpStore %10 %11
%12 = OpFunctionCall %2 %6
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
spvtools::ValidatorOptions validator_options;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
t.SetMessageConsumer(kSilentConsumer);
ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption));
ASSERT_TRUE(t.Validate(binary_in));
protobufs::TransformationSequence transformations;
*transformations.add_transformation() =
TransformationAddConstantScalar(100, 8, {42}, true).ToMessage();
*transformations.add_transformation() =
TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 100,
true)
.ToMessage();
*transformations.add_transformation() =
TransformationAddParameter(6, 102, 8, {{12, 100}}, 103).ToMessage();
*transformations.add_transformation() =
TransformationAddSynonym(
11,
protobufs::TransformationAddSynonym::SynonymType::
TransformationAddSynonym_SynonymType_COPY_OBJECT,
104, MakeInstructionDescriptor(12, SpvOpFunctionCall, 0))
.ToMessage();
// Full replay
protobufs::FactSequence empty_facts;
auto replayer_result =
Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations,
transformations.transformation_size(), 0, true,
validator_options)
.Run();
// Replay should succeed.
ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status);
// All transformations should be applied.
ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
transformations, replayer_result.applied_transformations));
const std::string kExpected = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 320
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%8 = OpTypeInt 32 1
%9 = OpTypePointer Function %8
%50 = OpTypePointer Private %8
%11 = OpConstant %8 1
%100 = OpConstant %8 42
%101 = OpVariable %50 Private %100
%103 = OpTypeFunction %2 %8
%4 = OpFunction %2 None %3
%5 = OpLabel
%10 = OpVariable %9 Function
OpStore %10 %11
%104 = OpCopyObject %8 %11
%12 = OpFunctionCall %2 %6 %100
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %103
%102 = OpFunctionParameter %8
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(
IsEqual(env, kExpected, replayer_result.transformed_module.get()));
ASSERT_TRUE(
replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant(
100));
ASSERT_TRUE(replayer_result.transformation_context->GetFactManager()
->PointeeValueIsIrrelevant(101));
ASSERT_TRUE(
replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant(
102));
ASSERT_TRUE(
replayer_result.transformation_context->GetFactManager()->IsSynonymous(
MakeDataDescriptor(11, {}), MakeDataDescriptor(104, {})));
}
} // namespace
} // namespace fuzz
} // namespace spvtools

View File

@ -480,8 +480,7 @@ bool Replay(const spv_target_env& target_env,
initial_facts, transformation_sequence, num_transformations_to_apply,
0, fuzzer_options->replay_validation_enabled, validator_options)
.Run();
*binary_out = std::move(replay_result.transformed_binary);
replay_result.transformed_module->module()->ToBinary(binary_out, false);
*transformations_applied = std::move(replay_result.applied_transformations);
return replay_result.status ==
spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete;