// 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 #include "source/opt/basic_block.h" #include "source/opt/function.h" #include "source/opt/instruction.h" #include "source/opt/module.h" #include "src/ast/assignment_statement.h" #include "src/ast/binary_expression.h" #include "src/ast/identifier_expression.h" #include "src/ast/scalar_constructor_expression.h" #include "src/ast/storage_class.h" #include "src/ast/uint_literal.h" #include "src/ast/variable.h" #include "src/ast/variable_decl_statement.h" #include "src/reader/spirv/fail_stream.h" #include "src/reader/spirv/parser_impl.h" namespace tint { namespace reader { 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, const spvtools::opt::Function& function) : parser_impl_(*pi), ast_module_(pi->get_module()), ir_context_(*(pi->ir_context())), def_use_mgr_(ir_context_.get_def_use_mgr()), constant_mgr_(ir_context_.get_constant_mgr()), type_mgr_(ir_context_.get_type_mgr()), fail_stream_(pi->fail_stream()), namer_(pi->namer()), function_(function) {} FunctionEmitter::~FunctionEmitter() = default; bool FunctionEmitter::Emit() { if (failed()) { return false; } // We only care about functions with bodies. if (function_.cbegin() == function_.cend()) { return true; } if (!EmitFunctionDeclaration()) { return false; } if (!EmitBody()) { return false; } // Set the body of the AST function node. parser_impl_.get_module().functions().back()->set_body(std::move(ast_body_)); return success(); } bool FunctionEmitter::EmitFunctionDeclaration() { if (failed()) { return false; } const auto name = namer_.Name(function_.result_id()); // Surprisingly, the "type id" on an OpFunction is the result type of the // function, not the type of the function. This is the one exceptional case // in SPIR-V where the type ID is not the type of the result ID. auto* ret_ty = parser_impl_.ConvertType(function_.type_id()); if (failed()) { return false; } if (ret_ty == nullptr) { return Fail() << "internal error: unregistered return type for function with ID " << function_.result_id(); } ast::VariableList ast_params; function_.ForEachParam( [this, &ast_params](const spvtools::opt::Instruction* param) { auto* ast_type = parser_impl_.ConvertType(param->type_id()); if (ast_type != nullptr) { ast_params.emplace_back(parser_impl_.MakeVariable( param->result_id(), ast::StorageClass::kNone, ast_type)); } else { // We've already logged an error and emitted a diagnostic. Do nothing // here. } }); if (failed()) { return false; } auto ast_fn = std::make_unique(name, std::move(ast_params), ret_ty); ast_module_.AddFunction(std::move(ast_fn)); return success(); } ast::type::Type* FunctionEmitter::GetVariableStoreType( const spvtools::opt::Instruction& var_decl_inst) { const auto type_id = var_decl_inst.type_id(); auto* var_ref_type = type_mgr_->GetType(type_id); if (!var_ref_type) { Fail() << "internal error: variable type id " << type_id << " has no registered type"; return nullptr; } auto* var_ref_ptr_type = var_ref_type->AsPointer(); if (!var_ref_ptr_type) { Fail() << "internal error: variable type id " << type_id << " is not a pointer type"; return nullptr; } auto var_store_type_id = type_mgr_->GetId(var_ref_ptr_type->pointee_type()); return parser_impl_.ConvertType(var_store_type_id); } bool FunctionEmitter::EmitBody() { if (!EmitFunctionVariables()) { return false; } if (!EmitFunctionBodyStatements()) { return false; } return success(); } bool FunctionEmitter::EmitFunctionVariables() { if (failed()) { return false; } for (auto& inst : *function_.entry()) { if (inst.opcode() != SpvOpVariable) { continue; } auto* var_store_type = GetVariableStoreType(inst); if (failed()) { return false; } auto var = parser_impl_.MakeVariable( inst.result_id(), ast::StorageClass::kFunction, var_store_type); if (inst.NumInOperands() > 1) { // SPIR-V initializers are always constants. // (OpenCL also allows the ID of an OpVariable, but we don't handle that // here.) var->set_constructor( parser_impl_.MakeConstantExpression(inst.GetSingleWordInOperand(1))); } // TODO(dneto): Add the initializer via Variable::set_constructor. auto var_decl_stmt = std::make_unique(std::move(var)); ast_body_.emplace_back(std::move(var_decl_stmt)); // Save this as an already-named value. identifier_values_.insert(inst.result_id()); } return success(); } std::unique_ptr FunctionEmitter::MakeExpression(uint32_t id) { if (failed()) { return nullptr; } if (identifier_values_.count(id)) { return std::make_unique(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); if (spirv_constant) { return parser_impl_.MakeConstantExpression(id); } const auto* inst = def_use_mgr_->GetDef(id); if (inst == nullptr) { Fail() << "ID " << id << " does not have a defining SPIR-V instruction"; return nullptr; } switch (inst->opcode()) { default: break; } Fail() << "unhandled expression for ID " << id << "\n" << inst->PrettyPrint(); return nullptr; } bool FunctionEmitter::EmitFunctionBodyStatements() { // TODO(dneto): For now, emit only regular statements in the entry block. // We'll use assignments as markers in the tests, to be able to tell where // code is placed in control flow. First prove that we can emit assignments. return EmitStatementsInBasicBlock(*function_.entry()); } bool FunctionEmitter::EmitStatementsInBasicBlock( const spvtools::opt::BasicBlock& bb) { const auto* terminator = bb.terminator(); const auto* merge = bb.GetMergeInst(); // Might be nullptr // Emit regular statements. for (auto& inst : bb) { if (&inst == terminator || &inst == merge || inst.opcode() == SpvOpLabel || inst.opcode() == SpvOpVariable) { continue; } if (!EmitStatement(inst)) { return false; } } // TODO(dneto): Handle the terminator return true; } bool FunctionEmitter::EmitConstDefinition( const spvtools::opt::Instruction& inst, std::unique_ptr 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(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) { // 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()) { case SpvOpStore: { // TODO(dneto): Order of evaluation? auto lhs = MakeExpression(inst.GetSingleWordInOperand(0)); auto rhs = MakeExpression(inst.GetSingleWordInOperand(1)); ast_body_.emplace_back(std::make_unique( std::move(lhs), std::move(rhs))); return success(); } case SpvOpLoad: // Memory accesses must be issued in SPIR-V program order. // So represent a load by a new const definition. return EmitConstDefinition( inst, MakeExpression(inst.GetSingleWordInOperand(0))); case SpvOpFunctionCall: // TODO(dneto): Fill this out. Make this pass, for existing tests return success(); default: break; } return Fail() << "unhandled instruction with opcode " << inst.opcode(); } std::unique_ptr 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(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 reader } // namespace tint