diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn index bd4c605d12..f0a3ee7571 100644 --- a/src/tint/BUILD.gn +++ b/src/tint/BUILD.gn @@ -1859,6 +1859,7 @@ if (tint_build_unittests) { if (tint_build_ir) { sources += [ + "writer/spirv/generator_impl_function_test.cc", "writer/spirv/generator_impl_ir_test.cc", "writer/spirv/generator_impl_type_test.cc", ] diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index 8b8daa4ecc..26995d3d64 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -1227,6 +1227,7 @@ if(TINT_BUILD_TESTS) if(${TINT_BUILD_IR}) list(APPEND TINT_TEST_SRCS + writer/spirv/generator_impl_function_test.cc writer/spirv/generator_impl_ir_test.cc writer/spirv/generator_impl_type_test.cc writer/spirv/test_helper_ir.h diff --git a/src/tint/writer/spirv/generator_impl_function_test.cc b/src/tint/writer/spirv/generator_impl_function_test.cc new file mode 100644 index 0000000000..e4cb71bdd9 --- /dev/null +++ b/src/tint/writer/spirv/generator_impl_function_test.cc @@ -0,0 +1,50 @@ +// Copyright 2023 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/tint/writer/spirv/test_helper_ir.h" + +namespace tint::writer::spirv { +namespace { + +TEST_F(SpvGeneratorImplTest, Function_Empty) { + auto* func = CreateFunction(); + func->name = ir.symbols.Register("foo"); + func->return_type = ir.types.Get(); + + generator_.EmitFunction(func); + EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %2 None %3 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"); +} + +// Test that we do not emit the same function type more than once. +TEST_F(SpvGeneratorImplTest, Function_DeduplicateType) { + auto* func = CreateFunction(); + func->return_type = ir.types.Get(); + + generator_.EmitFunction(func); + generator_.EmitFunction(func); + generator_.EmitFunction(func); + EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeVoid +%3 = OpTypeFunction %2 +)"); +} + +} // namespace +} // namespace tint::writer::spirv diff --git a/src/tint/writer/spirv/generator_impl_ir.cc b/src/tint/writer/spirv/generator_impl_ir.cc index fabb3a9164..e947e6c419 100644 --- a/src/tint/writer/spirv/generator_impl_ir.cc +++ b/src/tint/writer/spirv/generator_impl_ir.cc @@ -43,8 +43,10 @@ bool GeneratorImplIr::Generate() { // TODO(crbug.com/tint/1906): Emit variables. (void)zero_init_workgroup_memory_; - // TODO(crbug.com/tint/1906): Emit functions. - (void)ir_; + // Emit functions. + for (auto* func : ir_->functions) { + EmitFunction(func); + } // Serialize the module into binary SPIR-V. writer_.WriteHeader(module_.IdBound()); @@ -79,4 +81,46 @@ uint32_t GeneratorImplIr::Type(const type::Type* ty) { }); } +void GeneratorImplIr::EmitFunction(const ir::Function* func) { + // Make an ID for the function. + auto id = module_.NextId(); + + // Emit the function name. + module_.PushDebug(spv::Op::OpName, {id, Operand(func->name.Name())}); + + // TODO(jrprice): Emit OpEntryPoint and OpExecutionMode declarations if needed. + + // Get the ID for the return type. + auto return_type_id = Type(func->return_type); + + // Get the ID for the function type (creating it if needed). + // TODO(jrprice): Add the parameter types when they are supported in the IR. + FunctionType function_type{return_type_id, {}}; + auto function_type_id = function_types_.GetOrCreate(function_type, [&]() { + auto func_ty_id = module_.NextId(); + OperandList operands = {func_ty_id, return_type_id}; + operands.insert(operands.end(), function_type.param_type_ids.begin(), + function_type.param_type_ids.end()); + module_.PushType(spv::Op::OpTypeFunction, operands); + return func_ty_id; + }); + + // Declare the function. + auto decl = Instruction{spv::Op::OpFunction, + {return_type_id, id, SpvFunctionControlMaskNone, function_type_id}}; + + // Create a function that we will add instructions to. + // TODO(jrprice): Add the parameter declarations when they are supported in the IR. + auto entry_block = module_.NextId(); + Function current_function_(decl, entry_block, {}); + + // TODO(jrprice): Emit the body of the function. + + // TODO(jrprice): Remove this when we start emitting OpReturn for branches to the terminator. + current_function_.push_inst(spv::Op::OpReturn, {}); + + // Add the function to the module. + module_.PushFunction(current_function_); +} + } // namespace tint::writer::spirv diff --git a/src/tint/writer/spirv/generator_impl_ir.h b/src/tint/writer/spirv/generator_impl_ir.h index 44fefb5d53..d9aab3cb02 100644 --- a/src/tint/writer/spirv/generator_impl_ir.h +++ b/src/tint/writer/spirv/generator_impl_ir.h @@ -19,11 +19,13 @@ #include "src/tint/diagnostic/diagnostic.h" #include "src/tint/utils/hashmap.h" +#include "src/tint/utils/vector.h" #include "src/tint/writer/spirv/binary_writer.h" #include "src/tint/writer/spirv/module.h" // Forward declarations namespace tint::ir { +class Function; class Module; } // namespace tint::ir namespace tint::type { @@ -58,15 +60,47 @@ class GeneratorImplIr { /// @returns the result ID of the type uint32_t Type(const type::Type* ty); + /// Emit a function. + /// @param func the function to emit + void EmitFunction(const ir::Function* func); + private: const ir::Module* ir_; spirv::Module module_; BinaryWriter writer_; diag::List diagnostics_; + /// A function type used for an OpTypeFunction declaration. + struct FunctionType { + uint32_t return_type_id; + utils::Vector param_type_ids; + + /// Hasher provides a hash function for the FunctionType. + struct Hasher { + /// @param ft the FunctionType to create a hash for + /// @return the hash value + inline std::size_t operator()(const FunctionType& ft) const { + size_t hash = utils::Hash(ft.return_type_id); + for (auto& p : ft.param_type_ids) { + hash = utils::HashCombine(hash, p); + } + return hash; + } + }; + + /// Equality operator for FunctionType. + bool operator==(const FunctionType& other) const { + return (param_type_ids == other.param_type_ids) && + (return_type_id == other.return_type_id); + } + }; + /// The map of types to their result IDs. utils::Hashmap types_; + /// The map of function types to their result IDs. + utils::Hashmap function_types_; + bool zero_init_workgroup_memory_ = false; }; diff --git a/src/tint/writer/spirv/spv_dump.cc b/src/tint/writer/spirv/spv_dump.cc index 6ffc56bf8b..9a637862e3 100644 --- a/src/tint/writer/spirv/spv_dump.cc +++ b/src/tint/writer/spirv/spv_dump.cc @@ -56,9 +56,13 @@ std::string Disassemble(const std::vector& data) { } std::string DumpBuilder(Builder& builder) { + return DumpModule(builder.Module()); +} + +std::string DumpModule(Module& module) { BinaryWriter writer; - writer.WriteHeader(builder.Module().IdBound()); - writer.WriteModule(&builder.Module()); + writer.WriteHeader(module.IdBound()); + writer.WriteModule(&module); return Disassemble(writer.result()); } diff --git a/src/tint/writer/spirv/spv_dump.h b/src/tint/writer/spirv/spv_dump.h index a0dec2c8d4..359f0cbc8a 100644 --- a/src/tint/writer/spirv/spv_dump.h +++ b/src/tint/writer/spirv/spv_dump.h @@ -32,6 +32,11 @@ std::string Disassemble(const std::vector& data); /// @returns the builder as a SPIR-V disassembly string std::string DumpBuilder(Builder& builder); +/// Dumps the given module to a SPIR-V disassembly string +/// @param module the module to convert +/// @returns the module as a SPIR-V disassembly string +std::string DumpModule(Module& module); + /// Dumps the given instruction to a SPIR-V disassembly string /// @param inst the instruction to dump /// @returns the instruction as a SPIR-V disassembly string