mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-05 06:03:34 +00:00
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>
361 lines
11 KiB
C++
361 lines
11 KiB
C++
// 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 <utility>
|
|
|
|
#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<ast::Function>(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<ast::VariableDeclStatement>(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<ast::Expression> FunctionEmitter::MakeExpression(uint32_t id) {
|
|
if (failed()) {
|
|
return nullptr;
|
|
}
|
|
if (identifier_values_.count(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);
|
|
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::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) {
|
|
// 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<ast::AssignmentStatement>(
|
|
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<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 reader
|
|
} // namespace tint
|