[spirv-reader] Support IAdd

Also adds generic support for generating a combinatorial value
as a const definition.

Bug: tint:3
Change-Id: Idae758d146264491679710967e48ea270436be91
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/19107
Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
David Neto 2020-04-09 20:45:31 +00:00
parent 4d32be4f1b
commit 3743c5ee65
5 changed files with 330 additions and 22 deletions

View File

@ -328,6 +328,7 @@ if(${TINT_BUILD_SPV_READER})
list(APPEND TINT_TEST_SRCS list(APPEND TINT_TEST_SRCS
reader/spirv/enum_converter_test.cc reader/spirv/enum_converter_test.cc
reader/spirv/fail_stream_test.cc reader/spirv/fail_stream_test.cc
reader/spirv/function_arithmetic_test.cc
reader/spirv/function_decl_test.cc reader/spirv/function_decl_test.cc
reader/spirv/function_var_test.cc reader/spirv/function_var_test.cc
reader/spirv/function_memory_test.cc reader/spirv/function_memory_test.cc

View File

@ -21,6 +21,7 @@
#include "source/opt/instruction.h" #include "source/opt/instruction.h"
#include "source/opt/module.h" #include "source/opt/module.h"
#include "src/ast/assignment_statement.h" #include "src/ast/assignment_statement.h"
#include "src/ast/binary_expression.h"
#include "src/ast/identifier_expression.h" #include "src/ast/identifier_expression.h"
#include "src/ast/scalar_constructor_expression.h" #include "src/ast/scalar_constructor_expression.h"
#include "src/ast/storage_class.h" #include "src/ast/storage_class.h"
@ -34,6 +35,19 @@ namespace tint {
namespace reader { namespace reader {
namespace spirv { namespace spirv {
namespace {
// @returns the AST binary op for the given opcode, or kNone
ast::BinaryOp ConvertBinaryOp(SpvOp opcode) {
switch (opcode) {
case SpvOpIAdd:
return ast::BinaryOp::kAdd;
default:
break;
}
return ast::BinaryOp::kNone;
}
} // namespace
FunctionEmitter::FunctionEmitter(ParserImpl* pi, FunctionEmitter::FunctionEmitter(ParserImpl* pi,
const spvtools::opt::Function& function) const spvtools::opt::Function& function)
: parser_impl_(*pi), : parser_impl_(*pi),
@ -180,6 +194,11 @@ std::unique_ptr<ast::Expression> FunctionEmitter::MakeExpression(uint32_t id) {
if (identifier_values_.count(id)) { if (identifier_values_.count(id)) {
return std::make_unique<ast::IdentifierExpression>(namer_.Name(id)); return std::make_unique<ast::IdentifierExpression>(namer_.Name(id));
} }
if (singly_used_values_.count(id)) {
auto expr = std::move(singly_used_values_[id]);
singly_used_values_.erase(id);
return expr;
}
const auto* spirv_constant = constant_mgr_->FindDeclaredConstant(id); const auto* spirv_constant = constant_mgr_->FindDeclaredConstant(id);
if (spirv_constant) { if (spirv_constant) {
return parser_impl_.MakeConstantExpression(id); return parser_impl_.MakeConstantExpression(id);
@ -222,7 +241,45 @@ bool FunctionEmitter::EmitStatementsInBasicBlock(
return true; return true;
} }
bool FunctionEmitter::EmitConstDefinition(
const spvtools::opt::Instruction& inst,
std::unique_ptr<ast::Expression> ast_expr) {
if (!ast_expr) {
return false;
}
auto ast_const =
parser_impl_.MakeVariable(inst.result_id(), ast::StorageClass::kNone,
parser_impl_.ConvertType(inst.type_id()));
if (!ast_const) {
return false;
}
ast_const->set_constructor(std::move(ast_expr));
ast_const->set_is_const(true);
ast_body_.emplace_back(
std::make_unique<ast::VariableDeclStatement>(std::move(ast_const)));
// Save this as an already-named value.
identifier_values_.insert(inst.result_id());
return success();
}
bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
// Handle combinatorial instructions first.
auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
if (combinatorial_expr != nullptr) {
if (def_use_mgr_->NumUses(&inst) == 1) {
// If it's used once, then defer emitting the expression until it's used.
// Any supporting statements have already been emitted.
singly_used_values_[inst.result_id()] = std::move(combinatorial_expr);
return success();
}
// Otherwise, generate a const definition for it now and later use
// the const's name at the uses of the value.
return EmitConstDefinition(inst, std::move(combinatorial_expr));
}
if (failed()) {
return false;
}
switch (inst.opcode()) { switch (inst.opcode()) {
case SpvOpStore: { case SpvOpStore: {
// TODO(dneto): Order of evaluation? // TODO(dneto): Order of evaluation?
@ -232,27 +289,11 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
std::move(lhs), std::move(rhs))); std::move(lhs), std::move(rhs)));
return success(); return success();
} }
case SpvOpLoad: { case SpvOpLoad:
// Memory accesses must be issued in SPIR-V program order. // Memory accesses must be issued in SPIR-V program order.
// So represent a load by a new const definition. // So represent a load by a new const definition.
auto ast_initializer = MakeExpression(inst.GetSingleWordInOperand(0)); return EmitConstDefinition(
if (!ast_initializer) { inst, MakeExpression(inst.GetSingleWordInOperand(0)));
return false;
}
auto ast_const =
parser_impl_.MakeVariable(inst.result_id(), ast::StorageClass::kNone,
parser_impl_.ConvertType(inst.type_id()));
if (!ast_const) {
return false;
}
ast_const->set_constructor(std::move(ast_initializer));
ast_const->set_is_const(true);
ast_body_.emplace_back(
std::make_unique<ast::VariableDeclStatement>(std::move(ast_const)));
// Save this as an already-named value.
identifier_values_.insert(inst.result_id());
return success();
}
case SpvOpFunctionCall: case SpvOpFunctionCall:
// TODO(dneto): Fill this out. Make this pass, for existing tests // TODO(dneto): Fill this out. Make this pass, for existing tests
return success(); return success();
@ -262,6 +303,58 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
return Fail() << "unhandled instruction with opcode " << inst.opcode(); return Fail() << "unhandled instruction with opcode " << inst.opcode();
} }
std::unique_ptr<ast::Expression> FunctionEmitter::MaybeEmitCombinatorialValue(
const spvtools::opt::Instruction& inst) {
if (inst.result_id() == 0) {
return nullptr;
}
// TODO(dneto): Fill in the following cases.
auto operand = [this, &inst](uint32_t operand_index) {
return this->MakeExpression(inst.GetSingleWordInOperand(operand_index));
};
auto binary_op = ConvertBinaryOp(inst.opcode());
if (binary_op != ast::BinaryOp::kNone) {
return std::make_unique<ast::BinaryExpression>(binary_op, operand(0),
operand(1));
}
// binary operator
// unary operator
// builtin readonly function
// glsl.std.450 readonly function
// Instructions:
// OpCopyObject
// OpUndef
// OpBitcast
// OpSatConvertSToU
// OpSatConvertUToS
// OpSatConvertFToS
// OpSatConvertFToU
// OpSatConvertSToF
// OpSatConvertUToF
// OpUConvert
// OpSConvert
// OpFConvert
// OpConvertPtrToU // Not in WebGPU
// OpConvertUToPtr // Not in WebGPU
// OpPtrCastToGeneric // Not in Vulkan
// OpGenericCastToPtr // Not in Vulkan
// OpGenericCastToPtrExplicit // Not in Vulkan
//
// OpAccessChain
// OpInBoundsAccessChain
// OpArrayLength
// OpVectorExtractDynamic
// OpVectorInsertDynamic
// OpCompositeExtract
// OpCompositeInsert
return nullptr;
}
} // namespace spirv } // namespace spirv
} // namespace reader } // namespace reader
} // namespace tint } // namespace tint

View File

@ -90,11 +90,30 @@ class FunctionEmitter {
/// @returns false if emission failed. /// @returns false if emission failed.
bool EmitStatement(const spvtools::opt::Instruction& inst); bool EmitStatement(const spvtools::opt::Instruction& inst);
/// Emits a const definition for a SPIR-V value.
/// @param inst the SPIR-V instruction defining the value
/// @param ast_expr the already-computed AST expression for the value
/// @returns false if emission failed.
bool EmitConstDefinition(const spvtools::opt::Instruction& inst,
std::unique_ptr<ast::Expression> ast_expr);
/// Makes an expression /// Makes an expression
/// @param id the SPIR-V ID of the value /// @param id the SPIR-V ID of the value
/// @returns true if emission has not yet failed. /// @returns true if emission has not yet failed.
std::unique_ptr<ast::Expression> MakeExpression(uint32_t id); std::unique_ptr<ast::Expression> MakeExpression(uint32_t id);
/// Creates an expression and supporting statements for a combinatorial
/// instruction, or returns null. A SPIR-V instruction is combinatorial
/// if it has no side effects and its result depends only on its operands,
/// and not on accessing external state like memory or the state of other
/// invocations. Statements are only created if required to provide values
/// to the expression. Supporting statements are not required to be
/// combinatorial.
/// @param inst a SPIR-V instruction representing an exrpression
/// @returns an AST expression for the instruction, or nullptr.
std::unique_ptr<ast::Expression> MaybeEmitCombinatorialValue(
const spvtools::opt::Instruction& inst);
private: private:
/// @returns the store type for the OpVariable instruction, or /// @returns the store type for the OpVariable instruction, or
/// null on failure. /// null on failure.
@ -113,6 +132,9 @@ class FunctionEmitter {
ast::StatementList ast_body_; ast::StatementList ast_body_;
// The set of IDs that have already had an identifier name generated for it. // The set of IDs that have already had an identifier name generated for it.
std::unordered_set<uint32_t> identifier_values_; std::unordered_set<uint32_t> identifier_values_;
// Mapping from SPIR-V ID that is used at most once, to its AST expression.
std::unordered_map<uint32_t, std::unique_ptr<ast::Expression>>
singly_used_values_;
}; };
} // namespace spirv } // namespace spirv

View File

@ -0,0 +1,188 @@
// Copyright 2020 The Tint Authors.
//
// 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 "src/reader/spirv/function.h"
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "src/reader/spirv/parser_impl.h"
#include "src/reader/spirv/parser_impl_test_helper.h"
#include "src/reader/spirv/spirv_tools_helpers_test.h"
namespace tint {
namespace reader {
namespace spirv {
namespace {
using ::testing::HasSubstr;
std::string CommonTypes() {
return R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%uint = OpTypeInt 32 0
%int = OpTypeInt 32 1
%float = OpTypeFloat 32
%uint_10 = OpConstant %uint 10
%uint_20 = OpConstant %uint 20
%int_30 = OpConstant %int 30
%int_40 = OpConstant %int 40
%float_50 = OpConstant %uint 50
%float_60 = OpConstant %uint 60
%ptr_uint = OpTypePointer Function %uint
%ptr_int = OpTypePointer Function %int
%ptr_float = OpTypePointer Function %float
%v2uint = OpTypeVector %uint 2
%v2int = OpTypeVector %int 2
%v2uint_10_20 = OpConstantComposite %v2uint %uint_10 %uint_20
%v2uint_20_10 = OpConstantComposite %v2uint %uint_20 %uint_10
%v2int_30_40 = OpConstantComposite %v2int %int_30 %int_40
%v2int_40_30 = OpConstantComposite %v2int %int_40 %int_30
)";
}
// Returns the AST dump for a given SPIR-V assembly constant.
std::string AstFor(std::string assembly) {
if (assembly == "v2uint_10_20") {
return R"(TypeConstructor{
__vec_2__u32
ScalarConstructor{10}
ScalarConstructor{20}
})";
}
if (assembly == "v2uint_20_10") {
return R"(TypeConstructor{
__vec_2__u32
ScalarConstructor{20}
ScalarConstructor{10}
})";
}
if (assembly == "v2int_30_40") {
return R"(TypeConstructor{
__vec_2__i32
ScalarConstructor{30}
ScalarConstructor{40}
})";
}
if (assembly == "v2int_40_30") {
return R"(TypeConstructor{
__vec_2__i32
ScalarConstructor{40}
ScalarConstructor{30}
})";
}
return "bad case";
}
struct BinaryData {
const std::string res_type;
const std::string lhs;
const std::string op;
const std::string rhs;
const std::string ast_type;
const std::string ast_lhs;
const std::string ast_op;
const std::string ast_rhs;
};
inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
out << "BinaryData{" << data.res_type << "," << data.lhs << "," << data.op
<< "," << data.rhs << "," << data.ast_type << "," << data.ast_lhs << ","
<< data.ast_op << "," << data.ast_rhs << "}";
return out;
}
using SpvBinaryTest = SpvParserTestBase<::testing::TestWithParam<BinaryData>>;
TEST_P(SpvBinaryTest, EmitExpression) {
const auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%1 = )" + GetParam().op +
" %" + GetParam().res_type + " %" + GetParam().lhs +
" %" + GetParam().rhs + R"(
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
<< p->error() << "\n"
<< assembly;
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
std::ostringstream ss;
ss << R"(Variable{
x_1
none
)"
<< GetParam().ast_type << "\n {\n Binary{"
<< "\n " << GetParam().ast_lhs << "\n " << GetParam().ast_op
<< "\n " << GetParam().ast_rhs;
EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(ss.str())) << assembly;
}
INSTANTIATE_TEST_SUITE_P(SpvParserTest,
SpvBinaryTest,
::testing::Values(
// Both uint
BinaryData{
"uint", "uint_10", "OpIAdd", "uint_20", "__u32",
"ScalarConstructor{10}", "add",
"ScalarConstructor{20}"},
// Both int
BinaryData{
"int", "int_30", "OpIAdd", "int_40", "__i32",
"ScalarConstructor{30}", "add",
"ScalarConstructor{40}"},
// Mixed, returning uint
BinaryData{
"uint", "int_30", "OpIAdd", "uint_10", "__u32",
"ScalarConstructor{30}", "add",
"ScalarConstructor{10}"},
// Mixed, returning int
BinaryData{
"int", "int_30", "OpIAdd", "uint_10", "__i32",
"ScalarConstructor{30}", "add",
"ScalarConstructor{10}"},
// Both v2uint
BinaryData{
"v2uint", "v2uint_10_20", "OpIAdd", "v2uint_20_10", "__vec_2__u32",
AstFor("v2uint_10_20"), "add",
AstFor("v2uint_20_10")},
// Both v2int
BinaryData{
"v2int", "v2int_30_40", "OpIAdd", "v2int_40_30", "__vec_2__i32",
AstFor("v2int_30_40"), "add",
AstFor("v2int_40_30")},
// Mixed, returning v2uint
BinaryData{
"v2uint", "v2int_30_40", "OpIAdd", "v2uint_10_20", "__vec_2__u32",
AstFor("v2int_30_40"), "add",
AstFor("v2uint_10_20")},
// Mixed, returning v2int
BinaryData{
"v2int", "v2int_40_30", "OpIAdd", "v2uint_20_10", "__vec_2__i32",
AstFor("v2int_40_30"), "add",
AstFor("v2uint_20_10")}
));
} // namespace
} // namespace spirv
} // namespace reader
} // namespace tint

View File

@ -30,10 +30,11 @@ namespace reader {
namespace spirv { namespace spirv {
/// SPIR-V Parser test class /// SPIR-V Parser test class
class SpvParserTest : public testing::Test { template <typename T>
class SpvParserTestBase : public T {
public: public:
SpvParserTest() = default; SpvParserTestBase() = default;
~SpvParserTest() = default; ~SpvParserTestBase() = default;
/// Sets up the test helper /// Sets up the test helper
void SetUp() { ctx_.Reset(); } void SetUp() { ctx_.Reset(); }
@ -63,6 +64,9 @@ class SpvParserTest : public testing::Test {
Context ctx_; Context ctx_;
}; };
// Use this form when you don't need to template any further.
using SpvParserTest = SpvParserTestBase<::testing::Test>;
/// Returns the string dump of a function body. /// Returns the string dump of a function body.
/// @param body the statement in the body /// @param body the statement in the body
/// @returnss the string dump of a function body. /// @returnss the string dump of a function body.