Freeze spec constants to their default values

Add a pass to freeze spec constants to their default values. This pass does
not fold the frozen spec constants and does not handle SpecConstantOp
instructions and SpecConstantComposite instructions.
This commit is contained in:
qining 2016-07-26 12:11:03 -04:00
parent ac46512b05
commit a24506266b
9 changed files with 233 additions and 18 deletions

View File

@ -4,6 +4,9 @@ v2016.2-dev 2016-07-19
- Start v2016.2
- Validator is incomplete
- Checks ID use block is dominated by definition block
- Add optimization passes
- Strip debug info instructions
- Freeze spec constant to their default values
v2016.1 2016-07-19
- Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/261

View File

@ -86,6 +86,11 @@ class Instruction {
std::vector<Instruction>&& dbg_line = {});
SpvOp opcode() const { return opcode_; }
// Sets the opcode of this instruction to a specific opcode. Note this may
// invalidate the instruction.
// TODO(qining): Remove this function when instruction building and insertion
// is well implemented.
void SetOpcode(SpvOp op) { opcode_ = op; }
uint32_t type_id() const { return type_id_; }
uint32_t result_id() const { return result_id_; }
// Returns the vector of line-related debug instructions attached to this

View File

@ -41,5 +41,35 @@ bool StripDebugInfoPass::Process(ir::Module* module) {
return modified;
}
bool FreezeSpecConstantValuePass::Process(ir::Module* module) {
bool modified = false;
module->ForEachInst([&modified](ir::Instruction* inst) {
switch (inst->opcode()) {
case SpvOp::SpvOpSpecConstant:
inst->SetOpcode(SpvOp::SpvOpConstant);
modified = true;
break;
case SpvOp::SpvOpSpecConstantTrue:
inst->SetOpcode(SpvOp::SpvOpConstantTrue);
modified = true;
break;
case SpvOp::SpvOpSpecConstantFalse:
inst->SetOpcode(SpvOp::SpvOpConstantFalse);
modified = true;
break;
case SpvOp::SpvOpDecorate:
if (inst->GetSingleWordInOperand(1) ==
SpvDecoration::SpvDecorationSpecId) {
inst->ToNop();
modified = true;
}
break;
default:
break;
}
});
return modified;
}
} // namespace opt
} // namespace spvtools

View File

@ -46,7 +46,7 @@ class Pass {
// A null pass that does nothing.
class NullPass : public Pass {
const char* name() const override { return "Null"; }
const char* name() const override { return "null"; }
bool Process(ir::Module*) override { return false; }
};
@ -54,10 +54,16 @@ class NullPass : public Pass {
// Section 3.32.2 of the SPIR-V spec).
class StripDebugInfoPass : public Pass {
public:
const char* name() const override { return "StripDebugInfo"; }
const char* name() const override { return "strip-debug"; }
bool Process(ir::Module* module) override;
};
class FreezeSpecConstantValuePass : public Pass {
public:
const char* name() const override { return "freeze-spec-const"; }
bool Process(ir::Module*) override;
};
} // namespace opt
} // namespace spvtools

View File

@ -39,6 +39,11 @@ add_spvtools_unittest(TARGET pass_strip_debug_info
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)
add_spvtools_unittest(TARGET pass_freeze_spec_const
SRCS test_freeze_spec_const.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}
)
add_spvtools_unittest(TARGET pass_utils
SRCS test_utils.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}

View File

@ -29,6 +29,7 @@
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include <gtest/gtest.h>
@ -52,26 +53,50 @@ class PassTest : public TestT {
PassTest()
: tools_(SPV_ENV_UNIVERSAL_1_1), manager_(new opt::PassManager()) {}
// Runs the given |pass| on the binary assembled from the |assembly|, and
// disassebles the optimized binary. Returns a tuple of disassembly string
// and the boolean value returned from pass Process() function.
std::tuple<std::string, bool> OptimizeAndDisassemble(
opt::Pass* pass, const std::string& original, bool skip_nop = false) {
std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
EXPECT_NE(nullptr, module);
if (!module) {
return std::make_tuple(std::string(), false);
}
const bool modified = pass->Process(module.get());
std::vector<uint32_t> binary;
module->ToBinary(&binary, skip_nop);
std::string optimized;
EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
return std::make_tuple(optimized, modified);
}
// Runs a single pass of class |PassT| on the binary assembled from the
// |assembly|, disassembles the optimized binary. Returns a tuple of
// disassembly string and the boolean value from the pass Process() function.
template <typename PassT>
std::tuple<std::string, bool> SinglePassRunAndDisassemble(
const std::string& assembly, bool skip_nop = false) {
auto pass = std::unique_ptr<PassT>(new PassT);
return OptimizeAndDisassemble(pass.get(), assembly, skip_nop);
}
// Runs a single pass of class |PassT| on the binary assembled from the
// |original| assembly, and checks whether the optimized binary can be
// disassembled to the |expected| assembly. This does *not* involve pass
// manager. Callers are suggested to use SCOPED_TRACE() for better messages.
template <typename PassT>
void SinglePassRunAndCheck(const std::string& original,
const std::string& expected) {
std::unique_ptr<ir::Module> module = tools_.BuildModule(original);
ASSERT_NE(nullptr, module);
const bool modified =
std::unique_ptr<PassT>(new PassT)->Process(module.get());
const std::string& expected,
bool skip_nop = false) {
std::string optimized;
bool modified = false;
std::tie(optimized, modified) =
SinglePassRunAndDisassemble<PassT>(original, skip_nop);
// Check whether the pass returns the correct modification indication.
EXPECT_EQ(original != expected, modified);
std::vector<uint32_t> binary;
module->ToBinary(&binary, /* skip_nop = */ false);
std::string optimized;
EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
EXPECT_EQ(expected, optimized);
}

View File

@ -0,0 +1,136 @@
// Copyright (c) 2016 Google Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "pass_fixture.h"
#include "pass_utils.h"
#include <algorithm>
#include <tuple>
#include <vector>
namespace {
using namespace spvtools;
struct FreezeSpecConstantValueTypeTestCase {
const char* type_decl;
const char* spec_const;
const char* expected_frozen_const;
};
using FreezeSpecConstantValueTypeTest =
PassTest<::testing::TestWithParam<FreezeSpecConstantValueTypeTestCase>>;
TEST_P(FreezeSpecConstantValueTypeTest, PrimaryType) {
auto& test_case = GetParam();
std::vector<const char*> text = {"OpCapability Shader",
"OpMemoryModel Logical GLSL450",
test_case.type_decl, test_case.spec_const};
std::vector<const char*> expected = {
"OpCapability Shader", "OpMemoryModel Logical GLSL450",
test_case.type_decl, test_case.expected_frozen_const};
SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
JoinAllInsts(text), JoinAllInsts(expected));
}
// Test each primary type.
INSTANTIATE_TEST_CASE_P(
PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
// Type declaration, original spec constant definition, expected frozen
// spec constants.
{"%int = OpTypeInt 32 1", "%2 = OpSpecConstant %int 1",
"%2 = OpConstant %int 1"},
{"%uint = OpTypeInt 32 0", "%2 = OpSpecConstant %uint 1",
"%2 = OpConstant %uint 1"},
{"%float = OpTypeFloat 32", "%2 = OpSpecConstant %float 3.14",
"%2 = OpConstant %float 3.14"},
{"%double = OpTypeFloat 64", "%2 = OpSpecConstant %double 3.1415926",
"%2 = OpConstant %double 3.1415926"},
{"%bool = OpTypeBool", "%2 = OpSpecConstantTrue %bool",
"%2 = OpConstantTrue %bool"},
{"%bool = OpTypeBool", "%2 = OpSpecConstantFalse %bool",
"%2 = OpConstantFalse %bool"},
})));
using FreezeSpecConstantValueRemoveDecorationTest = PassTest<::testing::Test>;
TEST_F(FreezeSpecConstantValueRemoveDecorationTest,
RemoveDecorationInstWithSpecId) {
std::vector<const char*> text = {
// clang-format off
"OpCapability Shader",
"OpCapability Float64",
"%1 = OpExtInstImport \"GLSL.std.450\"",
"OpMemoryModel Logical GLSL450",
"OpEntryPoint Vertex %main \"main\"",
"OpSource GLSL 450",
"OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"",
"OpSourceExtension \"GL_GOOGLE_include_directive\"",
"OpName %main \"main\"",
"OpDecorate %3 SpecId 200",
"OpDecorate %4 SpecId 201",
"OpDecorate %5 SpecId 202",
"OpDecorate %6 SpecId 203",
"%void = OpTypeVoid",
"%8 = OpTypeFunction %void",
"%int = OpTypeInt 32 1",
"%3 = OpSpecConstant %int 3",
"%float = OpTypeFloat 32",
"%4 = OpSpecConstant %float 3.14",
"%double = OpTypeFloat 64",
"%5 = OpSpecConstant %double 3.14159265358979",
"%bool = OpTypeBool",
"%6 = OpSpecConstantTrue %bool",
"%13 = OpSpecConstantFalse %bool",
"%main = OpFunction %void None %8",
"%14 = OpLabel",
"OpReturn",
"OpFunctionEnd",
// clang-format on
};
std::string expected_disassembly = SelectiveJoin(text, [](const char* line) {
return std::string(line).find("SpecId") != std::string::npos;
});
std::vector<std::pair<const char*, const char*>> opcode_replacement_pairs = {
{" OpSpecConstant ", " OpConstant "},
{" OpSpecConstantTrue ", " OpConstantTrue "},
{" OpSpecConstantFalse ", " OpConstantFalse "},
};
for (auto& p : opcode_replacement_pairs) {
EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second))
<< "text:\n"
<< expected_disassembly << "\n"
<< "find_str:\n"
<< p.first << "\n"
<< "replace_str:\n"
<< p.second << "\n";
}
SinglePassRunAndCheck<opt::FreezeSpecConstantValuePass>(
JoinAllInsts(text), expected_disassembly,
/* skip_nop = */ true);
}
} // anonymous namespace

View File

@ -36,18 +36,18 @@ TEST(PassManager, Interface) {
manager.AddPass<opt::StripDebugInfoPass>();
EXPECT_EQ(1u, manager.NumPasses());
EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
manager.AddPass(std::unique_ptr<opt::NullPass>(new opt::NullPass));
EXPECT_EQ(2u, manager.NumPasses());
EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
EXPECT_STREQ("Null", manager.GetPass(1)->name());
manager.AddPass<opt::StripDebugInfoPass>();
EXPECT_EQ(3u, manager.NumPasses());
EXPECT_STREQ("StripDebugInfo", manager.GetPass(0)->name());
EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
EXPECT_STREQ("Null", manager.GetPass(1)->name());
EXPECT_STREQ("StripDebugInfo", manager.GetPass(2)->name());
EXPECT_STREQ("strip-debug", manager.GetPass(2)->name());
}
// A pass that appends an OpNop instruction to the debug section.

View File

@ -51,6 +51,9 @@ NOTE: The optimizer is a work in progress.
Options:
--strip-debug
Remove all debug instructions.
--freeze-spec-const
Freeze the values of specialization constants to their default
values.
-h, --help Print this help.
--version Display optimizer version information.
)",
@ -83,6 +86,8 @@ int main(int argc, char** argv) {
}
} else if (0 == strcmp(cur_arg, "--strip-debug")) {
pass_manager.AddPass<opt::StripDebugInfoPass>();
} else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
pass_manager.AddPass<opt::FreezeSpecConstantValuePass>();
} else if ('\0' == cur_arg[1]) {
// Setting a filename of "-" to indicate stdin.
if (!in_file) {