diff --git a/BUILD.gn b/BUILD.gn index 400936626d..9dbeb92171 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -646,6 +646,7 @@ source_set("tint_unittests_spv_reader_src") { "src/reader/spirv/function_cfg_test.cc", "src/reader/spirv/function_conversion_test.cc", "src/reader/spirv/function_decl_test.cc", + "src/reader/spirv/function_glsl_std_450_test.cc", "src/reader/spirv/function_logical_test.cc", "src/reader/spirv/function_memory_test.cc", "src/reader/spirv/function_var_test.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 01e8a0063e..55a24a6c54 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -322,6 +322,7 @@ if(${TINT_BUILD_SPV_READER}) reader/spirv/function_cfg_test.cc reader/spirv/function_conversion_test.cc reader/spirv/function_decl_test.cc + reader/spirv/function_glsl_std_450_test.cc reader/spirv/function_logical_test.cc reader/spirv/function_var_test.cc reader/spirv/function_memory_test.cc diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index a5cefcbf50..f9aca7edbc 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -24,11 +24,13 @@ #include "source/opt/function.h" #include "source/opt/instruction.h" #include "source/opt/module.h" +#include "spirv/unified1/GLSL.std.450.h" #include "src/ast/array_accessor_expression.h" #include "src/ast/as_expression.h" #include "src/ast/assignment_statement.h" #include "src/ast/binary_expression.h" #include "src/ast/break_statement.h" +#include "src/ast/call_expression.h" #include "src/ast/case_statement.h" #include "src/ast/continue_statement.h" #include "src/ast/else_statement.h" @@ -271,6 +273,32 @@ ast::BinaryOp NegatedFloatCompare(SpvOp opcode) { return ast::BinaryOp::kNone; } +// Returns the WGSL standard library function for the given +// GLSL.std.450 extended instruction operation code. Unknown +// and invalid opcodes map to the empty string. +// @returns the WGSL standard function name, or an empty string. +std::string GetGlslStd450FuncName(uint32_t ext_opcode) { + switch (ext_opcode) { + case GLSLstd450Atan2: + return "atan2"; + case GLSLstd450Cos: + return "cos"; + case GLSLstd450Sin: + return "sin"; + case GLSLstd450Distance: + return "distance"; + case GLSLstd450Normalize: + return "normalize"; + case GLSLstd450FClamp: + return "fclamp"; + case GLSLstd450Length: + return "length"; + default: + break; + } + return ""; +} + // @returns the merge block ID for the given basic block, or 0 if there is none. uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) { // Get the OpSelectionMerge or OpLoopMerge instruction, if any. @@ -2353,6 +2381,15 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue( return {ast_type, std::move(negated_expr)}; } + if (opcode == SpvOpExtInst) { + const auto import = inst.GetSingleWordInOperand(0); + if (parser_impl_.glsl_std_450_imports().count(import) == 0) { + Fail() << "unhandled extended instruction import with ID " << import; + return {}; + } + return EmitGlslStd450ExtInst(inst); + } + // builtin readonly function // glsl.std.450 readonly function @@ -2384,6 +2421,27 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue( return {}; } +TypedExpression FunctionEmitter::EmitGlslStd450ExtInst( + const spvtools::opt::Instruction& inst) { + const auto ext_opcode = inst.GetSingleWordInOperand(1); + const auto name = GetGlslStd450FuncName(ext_opcode); + if (name.empty()) { + Fail() << "unhandled GLSL.std.450 instruction " << ext_opcode; + return {}; + } + auto func = std::make_unique( + std::vector{parser_impl_.GlslStd450Prefix(), name}); + ast::ExpressionList operands; + // All parameters to GLSL.std.450 extended instructions are IDs. + for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) { + operands.emplace_back(MakeOperand(inst, iarg).expr); + } + auto* ast_type = parser_impl_.ConvertType(inst.type_id()); + auto call = std::make_unique(std::move(func), + std::move(operands)); + return {ast_type, std::move(call)}; +} + TypedExpression FunctionEmitter::MakeAccessChain( const spvtools::opt::Instruction& inst) { if (inst.NumInOperands() < 1) { diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h index 1719defdef..0e9290726f 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -427,6 +427,12 @@ class FunctionEmitter { TypedExpression MaybeEmitCombinatorialValue( const spvtools::opt::Instruction& inst); + /// Creates an expression and supporting statements for the a GLSL.std.450 + /// extended instruction. + /// @param inst a SPIR-V OpExtInst instruction from GLSL.std.450 + /// @returns an AST expression for the instruction, or nullptr. + TypedExpression EmitGlslStd450ExtInst(const spvtools::opt::Instruction& inst); + /// Gets the block info for a block ID, if any exists /// @param id the SPIR-V ID of the OpLabel instruction starting the block /// @returns the block info for the given ID, if it exists, or nullptr diff --git a/src/reader/spirv/function_glsl_std_450_test.cc b/src/reader/spirv/function_glsl_std_450_test.cc new file mode 100644 index 0000000000..a73187efc9 --- /dev/null +++ b/src/reader/spirv/function_glsl_std_450_test.cc @@ -0,0 +1,465 @@ +// 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 +#include + +#include "gmock/gmock.h" +#include "src/reader/spirv/fail_stream.h" +#include "src/reader/spirv/function.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 Preamble() { + return R"( + OpCapability Shader + %glsl = OpExtInstImport "GLSL.std.450" + + %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 %float 50 + %float_60 = OpConstant %float 60 + %float_70 = OpConstant %float 70 + + %v2uint = OpTypeVector %uint 2 + %v2int = OpTypeVector %int 2 + %v2float = OpTypeVector %float 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 + %v2float_50_60 = OpConstantComposite %v2float %float_50 %float_60 + %v2float_60_50 = OpConstantComposite %v2float %float_60 %float_50 + %v2float_70_70 = OpConstantComposite %v2float %float_70 %float_70 +)"; +} + +struct GlslStd450Case { + std::string opcode; + std::string wgsl_func; +}; +inline std::ostream& operator<<(std::ostream& out, GlslStd450Case c) { + out << "GlslStd450Case(" << c.opcode << " " << c.wgsl_func << ")"; + return out; +} + +// Nomenclature: +// Float = scalar float +// Floating = scalar float or vector-of-float + +using SpvParserTest_GlslStd450_Float_Floating = + SpvParserTestBase<::testing::TestWithParam>; +using SpvParserTest_GlslStd450_Float_FloatingFloating = + SpvParserTestBase<::testing::TestWithParam>; +using SpvParserTest_GlslStd450_Floating_Floating = + SpvParserTestBase<::testing::TestWithParam>; +using SpvParserTest_GlslStd450_Floating_FloatingFloating = + SpvParserTestBase<::testing::TestWithParam>; +using SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating = + SpvParserTestBase<::testing::TestWithParam>; + +TEST_P(SpvParserTest_GlslStd450_Float_Floating, Scalar) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %float_50 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly; + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + ScalarConstructor{50.000000} + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Float_Floating, Vector) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %v2float_50_60 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + } + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Scalar) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %float_50 %float_60 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly; + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Float_FloatingFloating, Vector) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %v2float_50_60 %v2float_60_50 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + } + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{60.000000} + ScalarConstructor{50.000000} + } + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Scalar) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %float_50 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + ScalarConstructor{50.000000} + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_Floating, Vector) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %v2float %glsl )" + + GetParam().opcode + R"( %v2float_50_60 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __vec_2__f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + } + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %float_50 %float_60 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Vector) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %v2float %glsl )" + + GetParam().opcode + R"( %v2float_50_60 %v2float_60_50 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __vec_2__f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + } + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{60.000000} + ScalarConstructor{50.000000} + } + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Scalar) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %float %glsl )" + + GetParam().opcode + R"( %float_50 %float_60 %float_70 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + ScalarConstructor{70.000000} + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, Vector) { + const auto assembly = Preamble() + R"( + %100 = OpFunction %void None %voidfn + %entry = OpLabel + %1 = OpExtInst %v2float %glsl )" + + GetParam().opcode + + R"( %v2float_50_60 %v2float_60_50 %v2float_70_70 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()); + FunctionEmitter fe(p, *spirv_function(100)); + EXPECT_TRUE(fe.EmitBody()) << p->error(); + EXPECT_THAT(ToString(fe.ast_body()), HasSubstr(R"( + Variable{ + x_1 + none + __vec_2__f32 + { + Call{ + Identifier{)" + GetParam().wgsl_func + R"(} + ( + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{50.000000} + ScalarConstructor{60.000000} + } + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{60.000000} + ScalarConstructor{50.000000} + } + TypeConstructor{ + __vec_2__f32 + ScalarConstructor{70.000000} + ScalarConstructor{70.000000} + } + ) + } + } + })")) + << ToString(fe.ast_body()); +} + +INSTANTIATE_TEST_SUITE_P(Samples, + SpvParserTest_GlslStd450_Float_Floating, + ::testing::Values(GlslStd450Case{ + "Length", "std::glsl::length"})); + +INSTANTIATE_TEST_SUITE_P(Samples, + SpvParserTest_GlslStd450_Float_FloatingFloating, + ::testing::Values(GlslStd450Case{ + "Distance", "std::glsl::distance"})); + +INSTANTIATE_TEST_SUITE_P( + Samples, + SpvParserTest_GlslStd450_Floating_Floating, + ::testing::Values(GlslStd450Case{"Sin", "std::glsl::sin"}, + GlslStd450Case{"Cos", "std::glsl::cos"}, + GlslStd450Case{"Normalize", "std::glsl::normalize"})); + +INSTANTIATE_TEST_SUITE_P(Samples, + SpvParserTest_GlslStd450_Floating_FloatingFloating, + ::testing::Values(GlslStd450Case{"Atan2", + "std::glsl::atan2"})); + +INSTANTIATE_TEST_SUITE_P( + Samples, + SpvParserTest_GlslStd450_Floating_FloatingFloatingFloating, + ::testing::Values(GlslStd450Case{"FClamp", "std::glsl::fclamp"})); + +} // namespace +} // namespace spirv +} // namespace reader +} // namespace tint diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc index 623ac51d72..f778c61ad6 100644 --- a/src/reader/spirv/parser_impl.cc +++ b/src/reader/spirv/parser_impl.cc @@ -454,7 +454,7 @@ bool ParserImpl::RegisterExtendedInstructionImports() { // This is a canonicalization. if (glsl_std_450_imports_.empty()) { auto ast_import = - std::make_unique(name, "std::glsl"); + std::make_unique(name, GlslStd450Prefix()); import_map_[import.result_id()] = ast_import.get(); ast_module_.AddImport(std::move(ast_import)); } diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h index d99b73cdac..f7330a8f56 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -128,6 +128,9 @@ class ParserImpl : Reader { return glsl_std_450_imports_; } + /// @returns the import prefix to use for the GLSL.std.450 import. + std::string GlslStd450Prefix() const { return "std::glsl"; } + /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup. /// On failure, logs an error and returns null. This should only be called /// after the internal representation of the module has been built.