Skip to content

Commit d3bf5cf

Browse files
committed
spirv-opt: Run tests second time with lower max id bound
The SinglePassRunAndCheck function duplicated logic from SinglePassRunAndDisassemble. This change refactors the former to call the latter, simplifying the implementation and improving maintainability. This allows us to have a central place where we can run tests a second time with a lower id bound that will guarentee an id overflow. We can test that the pass does not crash. This does not necessarily cover all code paths, but should cover most.
1 parent f856af4 commit d3bf5cf

File tree

1 file changed

+78
-39
lines changed

1 file changed

+78
-39
lines changed

test/opt/pass_fixture.h

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -75,27 +75,20 @@ class PassTest : public TestT {
7575
disassemble_options_(SpirvTools::kDefaultDisassembleOption),
7676
env_(SPV_ENV_UNIVERSAL_1_3) {}
7777

78-
// Runs the given |pass| on the binary assembled from the |original|.
79-
// Returns a tuple of the optimized binary and the boolean value returned
80-
// from pass Process() function.
81-
std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
82-
Pass* pass, const std::string& original, bool skip_nop) {
83-
context_ = BuildModule(env_, consumer_, original, assemble_options_);
84-
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
85-
<< original << std::endl;
86-
if (!context()) {
87-
return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
88-
}
89-
90-
context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
91-
context()->set_preserve_spec_constants(
78+
// Runs the given |pass| on the given |context|. Returns a tuple of the
79+
// optimized binary and the boolean value returned from pass Process()
80+
// function.
81+
std::tuple<std::vector<uint32_t>, Pass::Status> RunPassAndGetBinary(
82+
Pass* pass, IRContext* context, bool skip_nop) {
83+
context->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
84+
context->set_preserve_spec_constants(
9285
OptimizerOptions()->preserve_spec_constants_);
9386

94-
const auto status = pass->Run(context());
87+
const auto status = pass->Run(context);
9588

9689
std::vector<uint32_t> binary;
9790
if (status != Pass::Status::Failure) {
98-
context()->module()->ToBinary(&binary, skip_nop);
91+
context->module()->ToBinary(&binary, skip_nop);
9992
}
10093
return std::make_tuple(binary, status);
10194
}
@@ -106,9 +99,71 @@ class PassTest : public TestT {
10699
template <typename PassT, typename... Args>
107100
std::tuple<std::vector<uint32_t>, Pass::Status> SinglePassRunToBinary(
108101
const std::string& assembly, bool skip_nop, Args&&... args) {
109-
auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
102+
// Copy the arguments so they can be used to create two instances of the
103+
// pass.
104+
std::tuple<std::decay_t<Args>...> copied_args(std::forward<Args>(args)...);
105+
106+
auto pass = std::apply(
107+
[&](const auto&... an_arg) { return MakeUnique<PassT>(an_arg...); },
108+
copied_args);
110109
pass->SetMessageConsumer(consumer_);
111-
return OptimizeToBinary(pass.get(), assembly, skip_nop);
110+
111+
context_ = BuildModule(env_, consumer_, assembly, assemble_options_);
112+
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
113+
<< assembly << std::endl;
114+
if (!context()) {
115+
return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
116+
}
117+
118+
const uint32_t original_id_bound = context()->module()->id_bound();
119+
120+
auto result = RunPassAndGetBinary(pass.get(), context_.get(), skip_nop);
121+
122+
const uint32_t optimized_id_bound = context()->module()->id_bound();
123+
124+
// Second run (if needed) to test for id overflow.
125+
if (std::get<1>(result) == Pass::Status::SuccessWithChange) {
126+
for (uint32_t new_bound = original_id_bound;
127+
new_bound < optimized_id_bound; ++new_bound) {
128+
auto null_message_consumer = [](spv_message_level_t, const char*,
129+
const spv_position_t&, const char*) {};
130+
std::unique_ptr<IRContext> context2 = BuildModule(
131+
env_, null_message_consumer, assembly, assemble_options_);
132+
EXPECT_NE(nullptr, context2)
133+
<< "Assembling failed for shader (2nd run):\n"
134+
<< assembly << std::endl;
135+
if (context2) {
136+
auto pass2 = std::apply(
137+
[&](const auto&... an_arg) {
138+
return MakeUnique<PassT>(an_arg...);
139+
},
140+
copied_args);
141+
pass2->SetMessageConsumer(null_message_consumer);
142+
143+
// const uint32_t new_bound = (original_id_bound + optimized_id_bound)
144+
// / 2;
145+
context2->set_max_id_bound(new_bound);
146+
147+
// We don't care about the status, just that it doesn't crash.
148+
(void)RunPassAndGetBinary(pass2.get(), context2.get(), skip_nop);
149+
}
150+
}
151+
}
152+
return result;
153+
}
154+
155+
// Runs the given |pass| on the binary assembled from the |original|.
156+
// Returns a tuple of the optimized binary and the boolean value returned
157+
// from pass Process() function.
158+
std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
159+
Pass* pass, const std::string& original, bool skip_nop) {
160+
context_ = BuildModule(env_, consumer_, original, assemble_options_);
161+
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
162+
<< original << std::endl;
163+
if (!context()) {
164+
return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
165+
}
166+
return RunPassAndGetBinary(pass, context_.get(), skip_nop);
112167
}
113168

114169
// Runs a single pass of class |PassT| on the binary assembled from the
@@ -152,31 +207,15 @@ class PassTest : public TestT {
152207
void SinglePassRunAndCheck(const std::string& original,
153208
const std::string& expected, bool skip_nop,
154209
bool do_validation, Args&&... args) {
155-
std::vector<uint32_t> optimized_bin;
156-
auto status = Pass::Status::SuccessWithoutChange;
157-
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
158-
original, skip_nop, std::forward<Args>(args)...);
210+
std::string optimized_asm;
211+
Pass::Status status;
212+
std::tie(optimized_asm, status) = SinglePassRunAndDisassemble<PassT>(
213+
original, skip_nop, do_validation, std::forward<Args>(args)...);
214+
159215
// Check whether the pass returns the correct modification indication.
160216
EXPECT_NE(Pass::Status::Failure, status);
161217
EXPECT_EQ(original == expected,
162218
status == Pass::Status::SuccessWithoutChange);
163-
if (do_validation) {
164-
spv_context spvContext = spvContextCreate(env_);
165-
spv_diagnostic diagnostic = nullptr;
166-
spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
167-
spv_result_t error = spvValidateWithOptions(
168-
spvContext, ValidatorOptions(), &binary, &diagnostic);
169-
EXPECT_EQ(error, 0);
170-
if (error != 0) spvDiagnosticPrint(diagnostic);
171-
spvDiagnosticDestroy(diagnostic);
172-
spvContextDestroy(spvContext);
173-
}
174-
std::string optimized_asm;
175-
SpirvTools tools(env_);
176-
EXPECT_TRUE(
177-
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
178-
<< "Disassembling failed for shader:\n"
179-
<< original << std::endl;
180219
EXPECT_EQ(expected, optimized_asm);
181220
}
182221

0 commit comments

Comments
 (0)