blob: 623ac51d7289fe7b37a5f5a1365fef0860293f36 [file] [log] [blame]
// 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/parser_impl.h"
#include <cassert>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "source/opt/basic_block.h"
#include "source/opt/build_module.h"
#include "source/opt/constants.h"
#include "source/opt/decoration_manager.h"
#include "source/opt/function.h"
#include "source/opt/instruction.h"
#include "source/opt/module.h"
#include "source/opt/type_manager.h"
#include "source/opt/types.h"
#include "spirv-tools/libspirv.hpp"
#include "src/ast/as_expression.h"
#include "src/ast/binary_expression.h"
#include "src/ast/bool_literal.h"
#include "src/ast/builtin_decoration.h"
#include "src/ast/decorated_variable.h"
#include "src/ast/float_literal.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/sint_literal.h"
#include "src/ast/struct.h"
#include "src/ast/struct_decoration.h"
#include "src/ast/struct_member.h"
#include "src/ast/struct_member_decoration.h"
#include "src/ast/struct_member_offset_decoration.h"
#include "src/ast/type/alias_type.h"
#include "src/ast/type/array_type.h"
#include "src/ast/type/bool_type.h"
#include "src/ast/type/f32_type.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/matrix_type.h"
#include "src/ast/type/pointer_type.h"
#include "src/ast/type/struct_type.h"
#include "src/ast/type/type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/vector_type.h"
#include "src/ast/type/void_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/uint_literal.h"
#include "src/ast/unary_op_expression.h"
#include "src/ast/variable.h"
#include "src/ast/variable_decl_statement.h"
#include "src/ast/variable_decoration.h"
#include "src/reader/spirv/function.h"
#include "src/type_manager.h"
namespace tint {
namespace reader {
namespace spirv {
namespace {
const spv_target_env kTargetEnv = SPV_ENV_WEBGPU_0;
// A FunctionTraverser is used to compute an ordering of functions in the
// module such that callees precede callers.
class FunctionTraverser {
public:
explicit FunctionTraverser(const spvtools::opt::Module& module)
: module_(module) {}
// @returns the functions in the modules such that callees precede callers.
std::vector<const spvtools::opt::Function*> TopologicallyOrderedFunctions() {
visited_.clear();
ordered_.clear();
id_to_func_.clear();
for (const auto& f : module_) {
id_to_func_[f.result_id()] = &f;
}
for (const auto& f : module_) {
Visit(f);
}
return ordered_;
}
private:
void Visit(const spvtools::opt::Function& f) {
if (visited_.count(&f)) {
return;
}
visited_.insert(&f);
for (const auto& bb : f) {
for (const auto& inst : bb) {
if (inst.opcode() != SpvOpFunctionCall) {
continue;
}
const auto* callee = id_to_func_[inst.GetSingleWordInOperand(0)];
if (callee) {
Visit(*callee);
}
}
}
ordered_.push_back(&f);
}
const spvtools::opt::Module& module_;
std::unordered_set<const spvtools::opt::Function*> visited_;
std::unordered_map<uint32_t, const spvtools::opt::Function*> id_to_func_;
std::vector<const spvtools::opt::Function*> ordered_;
};
// Returns true if the opcode operates as if its operands are signed integral.
bool AssumesSignedOperands(SpvOp opcode) {
switch (opcode) {
case SpvOpSNegate:
case SpvOpSDiv:
case SpvOpSRem:
case SpvOpSMod:
case SpvOpSLessThan:
case SpvOpSLessThanEqual:
case SpvOpSGreaterThan:
case SpvOpSGreaterThanEqual:
return true;
default:
break;
}
return false;
}
// Returns true if the opcode operates as if its operands are unsigned integral.
bool AssumesUnsignedOperands(SpvOp opcode) {
switch (opcode) {
case SpvOpUDiv:
case SpvOpUMod:
case SpvOpULessThan:
case SpvOpULessThanEqual:
case SpvOpUGreaterThan:
case SpvOpUGreaterThanEqual:
return true;
default:
break;
}
return false;
}
// Returns true if the operation is binary, and the WGSL operation requires
// the signedness of the result to match the signedness of the first operand.
bool AssumesResultSignednessMatchesBinaryFirstOperand(SpvOp opcode) {
switch (opcode) {
case SpvOpSDiv:
case SpvOpSMod:
case SpvOpSRem:
return true;
default:
break;
}
return false;
}
} // namespace
TypedExpression::TypedExpression() : type(nullptr), expr(nullptr) {}
TypedExpression::TypedExpression(ast::type::Type* t,
std::unique_ptr<ast::Expression> e)
: type(t), expr(std::move(e)) {}
TypedExpression::TypedExpression(TypedExpression&& other)
: type(other.type), expr(std::move(other.expr)) {}
TypedExpression::~TypedExpression() {}
void TypedExpression::reset(TypedExpression&& other) {
type = other.type;
expr = std::move(other.expr);
}
ParserImpl::ParserImpl(Context* ctx, const std::vector<uint32_t>& spv_binary)
: Reader(ctx),
spv_binary_(spv_binary),
fail_stream_(&success_, &errors_),
namer_(fail_stream_),
enum_converter_(fail_stream_),
tools_context_(kTargetEnv),
tools_(kTargetEnv) {
// Create a message consumer to propagate error messages from SPIRV-Tools
// out as our own failures.
message_consumer_ = [this](spv_message_level_t level, const char* /*source*/,
const spv_position_t& position,
const char* message) {
switch (level) {
// Ignore info and warning message.
case SPV_MSG_WARNING:
case SPV_MSG_INFO:
break;
// Otherwise, propagate the error.
default:
// For binary validation errors, we only have the instruction
// number. It's not text, so there is no column number.
this->Fail() << "line:" << position.index << ": " << message;
}
};
}
ParserImpl::~ParserImpl() = default;
bool ParserImpl::Parse() {
// Set up use of SPIRV-Tools utilities.
spvtools::SpirvTools spv_tools(kTargetEnv);
// Error messages from SPIRV-Tools are forwarded as failures, including
// setting |success_| to false.
spv_tools.SetMessageConsumer(message_consumer_);
if (!success_) {
return false;
}
// Only consider valid modules. On failure, the message consumer
// will set the error status.
if (!spv_tools.Validate(spv_binary_)) {
return false;
}
if (!BuildInternalModule()) {
return false;
}
if (!ParseInternalModule()) {
return false;
}
return success_;
}
ast::Module ParserImpl::module() {
// TODO(dneto): Should we clear out spv_binary_ here, to reduce
// memory usage?
return std::move(ast_module_);
}
ast::type::Type* ParserImpl::ConvertType(uint32_t type_id) {
if (!success_) {
return nullptr;
}
if (type_mgr_ == nullptr) {
Fail() << "ConvertType called when the internal module has not been built";
return nullptr;
}
auto where = id_to_type_.find(type_id);
if (where != id_to_type_.end()) {
return where->second;
}
auto* spirv_type = type_mgr_->GetType(type_id);
if (spirv_type == nullptr) {
Fail() << "ID is not a SPIR-V type: " << type_id;
return nullptr;
}
auto save = [this, type_id](ast::type::Type* type) {
if (type != nullptr) {
id_to_type_[type_id] = type;
}
return type;
};
switch (spirv_type->kind()) {
case spvtools::opt::analysis::Type::kVoid:
return save(ctx_.type_mgr().Get(std::make_unique<ast::type::VoidType>()));
case spvtools::opt::analysis::Type::kBool:
return save(ctx_.type_mgr().Get(std::make_unique<ast::type::BoolType>()));
case spvtools::opt::analysis::Type::kInteger:
return save(ConvertType(spirv_type->AsInteger()));
case spvtools::opt::analysis::Type::kFloat:
return save(ConvertType(spirv_type->AsFloat()));
case spvtools::opt::analysis::Type::kVector:
return save(ConvertType(spirv_type->AsVector()));
case spvtools::opt::analysis::Type::kMatrix:
return save(ConvertType(spirv_type->AsMatrix()));
case spvtools::opt::analysis::Type::kRuntimeArray:
return save(ConvertType(spirv_type->AsRuntimeArray()));
case spvtools::opt::analysis::Type::kArray:
return save(ConvertType(spirv_type->AsArray()));
case spvtools::opt::analysis::Type::kStruct:
return save(ConvertType(spirv_type->AsStruct()));
case spvtools::opt::analysis::Type::kPointer:
return save(ConvertType(spirv_type->AsPointer()));
case spvtools::opt::analysis::Type::kFunction:
// Tint doesn't have a Function type.
// We need to convert the result type and parameter types.
// But the SPIR-V defines those before defining the function
// type. No further work is required here.
return nullptr;
default:
break;
}
Fail() << "unknown SPIR-V type: " << type_id;
return nullptr;
}
DecorationList ParserImpl::GetDecorationsFor(uint32_t id) const {
DecorationList result;
const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
for (const auto* inst : decorations) {
if (inst->opcode() != SpvOpDecorate) {
continue;
}
// Example: OpDecorate %struct_id Block
// Example: OpDecorate %array_ty ArrayStride 16
std::vector<uint32_t> inst_as_words;
inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
Decoration d(inst_as_words.begin() + 2, inst_as_words.end());
result.push_back(d);
}
return result;
}
DecorationList ParserImpl::GetDecorationsForMember(
uint32_t id,
uint32_t member_index) const {
DecorationList result;
const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
for (const auto* inst : decorations) {
if ((inst->opcode() != SpvOpMemberDecorate) ||
(inst->GetSingleWordInOperand(1) != member_index)) {
continue;
}
// Example: OpMemberDecorate %struct_id 2 Offset 24
std::vector<uint32_t> inst_as_words;
inst->ToBinaryWithoutAttachedDebugInsts(&inst_as_words);
Decoration d(inst_as_words.begin() + 3, inst_as_words.end());
result.push_back(d);
}
return result;
}
std::unique_ptr<ast::StructMemberDecoration>
ParserImpl::ConvertMemberDecoration(const Decoration& decoration) {
if (decoration.empty()) {
Fail() << "malformed SPIR-V decoration: it's empty";
return nullptr;
}
switch (decoration[0]) {
case SpvDecorationOffset:
if (decoration.size() != 2) {
Fail()
<< "malformed Offset decoration: expected 1 literal operand, has "
<< decoration.size() - 1;
return nullptr;
}
return std::make_unique<ast::StructMemberOffsetDecoration>(decoration[1]);
default:
// TODO(dneto): Support the remaining member decorations.
break;
}
Fail() << "unhandled member decoration: " << decoration[0];
return nullptr;
}
bool ParserImpl::BuildInternalModule() {
if (!success_) {
return false;
}
tools_.SetMessageConsumer(message_consumer_);
const spv_context& context = tools_context_.CContext();
ir_context_ = spvtools::BuildModule(context->target_env, context->consumer,
spv_binary_.data(), spv_binary_.size());
if (!ir_context_) {
return Fail() << "internal error: couldn't build the internal "
"representation of the module";
}
module_ = ir_context_->module();
def_use_mgr_ = ir_context_->get_def_use_mgr();
constant_mgr_ = ir_context_->get_constant_mgr();
type_mgr_ = ir_context_->get_type_mgr();
deco_mgr_ = ir_context_->get_decoration_mgr();
return success_;
}
void ParserImpl::ResetInternalModule() {
ir_context_.reset(nullptr);
module_ = nullptr;
def_use_mgr_ = nullptr;
constant_mgr_ = nullptr;
type_mgr_ = nullptr;
deco_mgr_ = nullptr;
import_map_.clear();
glsl_std_450_imports_.clear();
}
bool ParserImpl::ParseInternalModule() {
if (!success_) {
return false;
}
if (!ParseInternalModuleExceptFunctions()) {
return false;
}
if (!EmitFunctions()) {
return false;
}
return success_;
}
bool ParserImpl::ParseInternalModuleExceptFunctions() {
if (!success_) {
return false;
}
if (!RegisterExtendedInstructionImports()) {
return false;
}
if (!RegisterUserAndStructMemberNames()) {
return false;
}
if (!EmitEntryPoints()) {
return false;
}
if (!RegisterTypes()) {
return false;
}
if (!EmitAliasTypes()) {
return false;
}
if (!EmitModuleScopeVariables()) {
return false;
}
return success_;
}
bool ParserImpl::RegisterExtendedInstructionImports() {
for (const spvtools::opt::Instruction& import : module_->ext_inst_imports()) {
std::string name(
reinterpret_cast<const char*>(import.GetInOperand(0).words.data()));
// TODO(dneto): Handle other extended instruction sets when needed.
if (name == "GLSL.std.450") {
// Only create the AST import once, so we can use import name 'std::glsl'.
// This is a canonicalization.
if (glsl_std_450_imports_.empty()) {
auto ast_import =
std::make_unique<tint::ast::Import>(name, "std::glsl");
import_map_[import.result_id()] = ast_import.get();
ast_module_.AddImport(std::move(ast_import));
}
glsl_std_450_imports_.insert(import.result_id());
} else {
return Fail() << "Unrecognized extended instruction set: " << name;
}
}
return true;
}
bool ParserImpl::RegisterUserAndStructMemberNames() {
if (!success_) {
return false;
}
// Register entry point names. An entry point name is the point of contact
// between the API and the shader. It has the highest priority for
// preservation, so register it first.
for (const spvtools::opt::Instruction& entry_point :
module_->entry_points()) {
const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
const std::string name = entry_point.GetInOperand(2).AsString();
namer_.SuggestSanitizedName(function_id, name);
}
// Register names from OpName and OpMemberName
for (const auto& inst : module_->debugs2()) {
switch (inst.opcode()) {
case SpvOpName:
namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0),
inst.GetInOperand(1).AsString());
break;
case SpvOpMemberName:
namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
inst.GetSingleWordInOperand(1),
inst.GetInOperand(2).AsString());
break;
default:
break;
}
}
// Fill in struct member names, and disambiguate them.
for (const auto* type_inst : module_->GetTypes()) {
if (type_inst->opcode() == SpvOpTypeStruct) {
namer_.ResolveMemberNamesForStruct(type_inst->result_id(),
type_inst->NumInOperands());
}
}
return true;
}
bool ParserImpl::EmitEntryPoints() {
for (const spvtools::opt::Instruction& entry_point :
module_->entry_points()) {
const auto stage = SpvExecutionModel(entry_point.GetSingleWordInOperand(0));
const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
const std::string name = namer_.GetName(function_id);
ast_module_.AddEntryPoint(std::make_unique<ast::EntryPoint>(
enum_converter_.ToPipelineStage(stage), "", name));
}
// The enum conversion could have failed, so return the existing status value.
return success_;
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Integer* int_ty) {
if (int_ty->width() == 32) {
auto* signed_ty =
ctx_.type_mgr().Get(std::make_unique<ast::type::I32Type>());
auto* unsigned_ty =
ctx_.type_mgr().Get(std::make_unique<ast::type::U32Type>());
signed_type_for_[unsigned_ty] = signed_ty;
unsigned_type_for_[signed_ty] = unsigned_ty;
return int_ty->IsSigned() ? signed_ty : unsigned_ty;
}
Fail() << "unhandled integer width: " << int_ty->width();
return nullptr;
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Float* float_ty) {
if (float_ty->width() == 32) {
return ctx_.type_mgr().Get(std::make_unique<ast::type::F32Type>());
}
Fail() << "unhandled float width: " << float_ty->width();
return nullptr;
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Vector* vec_ty) {
const auto num_elem = vec_ty->element_count();
auto* ast_elem_ty = ConvertType(type_mgr_->GetId(vec_ty->element_type()));
if (ast_elem_ty == nullptr) {
return nullptr;
}
auto* this_ty = ctx_.type_mgr().Get(
std::make_unique<ast::type::VectorType>(ast_elem_ty, num_elem));
// Generate the opposite-signedness vector type, if this type is integral.
if (unsigned_type_for_.count(ast_elem_ty)) {
auto* other_ty =
ctx_.type_mgr().Get(std::make_unique<ast::type::VectorType>(
unsigned_type_for_[ast_elem_ty], num_elem));
signed_type_for_[other_ty] = this_ty;
unsigned_type_for_[this_ty] = other_ty;
} else if (signed_type_for_.count(ast_elem_ty)) {
auto* other_ty =
ctx_.type_mgr().Get(std::make_unique<ast::type::VectorType>(
signed_type_for_[ast_elem_ty], num_elem));
unsigned_type_for_[other_ty] = this_ty;
signed_type_for_[this_ty] = other_ty;
}
return this_ty;
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Matrix* mat_ty) {
const auto* vec_ty = mat_ty->element_type()->AsVector();
const auto* scalar_ty = vec_ty->element_type();
const auto num_rows = vec_ty->element_count();
const auto num_columns = mat_ty->element_count();
auto* ast_scalar_ty = ConvertType(type_mgr_->GetId(scalar_ty));
if (ast_scalar_ty == nullptr) {
return nullptr;
}
return ctx_.type_mgr().Get(std::make_unique<ast::type::MatrixType>(
ast_scalar_ty, num_rows, num_columns));
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::RuntimeArray* rtarr_ty) {
// TODO(dneto): Handle ArrayStride. Blocked by crbug.com/tint/30
auto* ast_elem_ty = ConvertType(type_mgr_->GetId(rtarr_ty->element_type()));
if (ast_elem_ty == nullptr) {
return nullptr;
}
return ctx_.type_mgr().Get(
std::make_unique<ast::type::ArrayType>(ast_elem_ty));
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Array* arr_ty) {
// TODO(dneto): Handle ArrayStride. Blocked by crbug.com/tint/30
auto* ast_elem_ty = ConvertType(type_mgr_->GetId(arr_ty->element_type()));
if (ast_elem_ty == nullptr) {
return nullptr;
}
const auto& length_info = arr_ty->length_info();
if (length_info.words.empty()) {
// The internal representation is invalid. The discriminant vector
// is mal-formed.
Fail() << "internal error: Array length info is invalid";
return nullptr;
}
if (length_info.words[0] !=
spvtools::opt::analysis::Array::LengthInfo::kConstant) {
Fail() << "Array type " << type_mgr_->GetId(arr_ty)
<< " length is a specialization constant";
return nullptr;
}
const auto* constant = constant_mgr_->FindDeclaredConstant(length_info.id);
if (constant == nullptr) {
Fail() << "Array type " << type_mgr_->GetId(arr_ty) << " length ID "
<< length_info.id << " does not name an OpConstant";
return nullptr;
}
const uint64_t num_elem = constant->GetZeroExtendedValue();
// For now, limit to only 32bits.
if (num_elem > std::numeric_limits<uint32_t>::max()) {
Fail() << "Array type " << type_mgr_->GetId(arr_ty)
<< " has too many elements (more than can fit in 32 bits): "
<< num_elem;
return nullptr;
}
return ctx_.type_mgr().Get(std::make_unique<ast::type::ArrayType>(
ast_elem_ty, static_cast<uint32_t>(num_elem)));
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Struct* struct_ty) {
const auto type_id = type_mgr_->GetId(struct_ty);
// Compute the struct decoration.
auto struct_decorations = this->GetDecorationsFor(type_id);
auto ast_struct_decoration = ast::StructDecoration::kNone;
if (struct_decorations.size() == 1 &&
struct_decorations[0][0] == SpvDecorationBlock) {
ast_struct_decoration = ast::StructDecoration::kBlock;
} else if (struct_decorations.size() > 1) {
Fail() << "can't handle a struct with more than one decoration: struct "
<< type_id << " has " << struct_decorations.size();
return nullptr;
}
// Compute members
ast::StructMemberList ast_members;
const auto members = struct_ty->element_types();
for (uint32_t member_index = 0; member_index < members.size();
++member_index) {
auto* ast_member_ty = ConvertType(type_mgr_->GetId(members[member_index]));
if (ast_member_ty == nullptr) {
// Already emitted diagnostics.
return nullptr;
}
ast::StructMemberDecorationList ast_member_decorations;
for (auto& deco : GetDecorationsForMember(type_id, member_index)) {
auto ast_member_decoration = ConvertMemberDecoration(deco);
if (ast_member_decoration == nullptr) {
// Already emitted diagnostics.
return nullptr;
}
ast_member_decorations.push_back(std::move(ast_member_decoration));
}
const auto member_name = namer_.GetMemberName(type_id, member_index);
auto ast_struct_member = std::make_unique<ast::StructMember>(
member_name, ast_member_ty, std::move(ast_member_decorations));
ast_members.push_back(std::move(ast_struct_member));
}
// Now make the struct.
auto ast_struct = std::make_unique<ast::Struct>(ast_struct_decoration,
std::move(ast_members));
// The struct type will be assigned a name during EmitAliasTypes.
auto ast_struct_type =
std::make_unique<ast::type::StructType>(std::move(ast_struct));
// Set the struct name before registering it.
namer_.SuggestSanitizedName(type_id, "S");
ast_struct_type->set_name(namer_.GetName(type_id));
return ctx_.type_mgr().Get(std::move(ast_struct_type));
}
ast::type::Type* ParserImpl::ConvertType(
const spvtools::opt::analysis::Pointer* ptr_ty) {
auto* ast_elem_ty = ConvertType(type_mgr_->GetId(ptr_ty->pointee_type()));
if (ast_elem_ty == nullptr) {
Fail() << "SPIR-V pointer type with ID " << type_mgr_->GetId(ptr_ty)
<< " has invalid pointee type "
<< type_mgr_->GetId(ptr_ty->pointee_type());
return nullptr;
}
auto ast_storage_class =
enum_converter_.ToStorageClass(ptr_ty->storage_class());
if (ast_storage_class == ast::StorageClass::kNone) {
Fail() << "SPIR-V pointer type with ID " << type_mgr_->GetId(ptr_ty)
<< " has invalid storage class "
<< static_cast<uint32_t>(ptr_ty->storage_class());
return nullptr;
}
return ctx_.type_mgr().Get(
std::make_unique<ast::type::PointerType>(ast_elem_ty, ast_storage_class));
}
bool ParserImpl::RegisterTypes() {
if (!success_) {
return false;
}
for (auto& type_or_const : module_->types_values()) {
const auto* type = type_mgr_->GetType(type_or_const.result_id());
if (type == nullptr) {
continue;
}
ConvertType(type_or_const.result_id());
}
return success_;
}
bool ParserImpl::EmitAliasTypes() {
if (!success_) {
return false;
}
// The algorithm here emits type definitions in the order presented in
// the SPIR-V module. This is valid because:
//
// - There are no back-references. OpTypeForwarddPointer is not supported
// by the WebGPU shader programming model.
// - Arrays are always sized by an OpConstant of scalar integral type.
// WGSL currently doesn't have specialization constants.
// crbug.com/32 tracks implementation in case they are added.
for (auto& type_or_const : module_->types_values()) {
const auto type_id = type_or_const.result_id();
// We only care about struct, arrays, and runtime arrays.
switch (type_or_const.opcode()) {
case SpvOpTypeStruct:
// The struct already got a name when the type was first registered.
break;
case SpvOpTypeRuntimeArray:
// Runtime arrays are always decorated with ArrayStride so always get a
// type alias.
namer_.SuggestSanitizedName(type_id, "RTArr");
break;
case SpvOpTypeArray:
// Only make a type aliase for arrays with decorations.
if (GetDecorationsFor(type_id).empty()) {
continue;
}
namer_.SuggestSanitizedName(type_id, "Arr");
break;
default:
// Ignore constants, and any other types.
continue;
}
auto* ast_underlying_type = id_to_type_[type_id];
if (ast_underlying_type == nullptr) {
Fail() << "internal error: no type registered for SPIR-V ID: " << type_id;
return false;
}
const auto name = namer_.GetName(type_id);
auto* ast_type = ctx_.type_mgr().Get(
std::make_unique<ast::type::AliasType>(name, ast_underlying_type));
ast_module_.AddAliasType(ast_type->AsAlias());
}
return success_;
}
bool ParserImpl::EmitModuleScopeVariables() {
if (!success_) {
return false;
}
for (const auto& type_or_value : module_->types_values()) {
if (type_or_value.opcode() != SpvOpVariable) {
continue;
}
const auto& var = type_or_value;
const auto spirv_storage_class = var.GetSingleWordInOperand(0);
auto ast_storage_class = enum_converter_.ToStorageClass(
static_cast<SpvStorageClass>(spirv_storage_class));
if (!success_) {
return false;
}
auto* ast_type = id_to_type_[var.type_id()];
if (ast_type == nullptr) {
return Fail() << "internal error: failed to register Tint AST type for "
"SPIR-V type with ID: "
<< var.type_id();
}
if (!ast_type->IsPointer()) {
return Fail() << "variable with ID " << var.result_id()
<< " has non-pointer type " << var.type_id();
}
auto* ast_store_type = ast_type->AsPointer()->type();
auto ast_var =
MakeVariable(var.result_id(), ast_storage_class, ast_store_type);
if (var.NumInOperands() > 1) {
// SPIR-V initializers are always constants.
// (OpenCL also allows the ID of an OpVariable, but we don't handle that
// here.)
ast_var->set_constructor(
MakeConstantExpression(var.GetSingleWordInOperand(1)).expr);
}
// TODO(dneto): initializers (a.k.a. constructor expression)
ast_module_.AddGlobalVariable(std::move(ast_var));
}
return success_;
}
std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
ast::StorageClass sc,
ast::type::Type* type) {
if (type == nullptr) {
Fail() << "internal error: can't make ast::Variable for null type";
return nullptr;
}
auto ast_var = std::make_unique<ast::Variable>(namer_.Name(id), sc, type);
ast::VariableDecorationList ast_decorations;
for (auto& deco : GetDecorationsFor(id)) {
if (deco.empty()) {
Fail() << "malformed decoration on ID " << id << ": it is empty";
return nullptr;
}
if (deco[0] == SpvDecorationBuiltIn) {
if (deco.size() == 1) {
Fail() << "malformed BuiltIn decoration on ID " << id
<< ": has no operand";
return nullptr;
}
auto ast_builtin =
enum_converter_.ToBuiltin(static_cast<SpvBuiltIn>(deco[1]));
if (ast_builtin == ast::Builtin::kNone) {
return nullptr;
}
ast_decorations.emplace_back(
std::make_unique<ast::BuiltinDecoration>(ast_builtin));
}
}
if (!ast_decorations.empty()) {
auto decorated_var =
std::make_unique<ast::DecoratedVariable>(std::move(ast_var));
decorated_var->set_decorations(std::move(ast_decorations));
ast_var = std::move(decorated_var);
}
return ast_var;
}
TypedExpression ParserImpl::MakeConstantExpression(uint32_t id) {
if (!success_) {
return {};
}
const auto* inst = def_use_mgr_->GetDef(id);
if (inst == nullptr) {
Fail() << "ID " << id << " is not a registered instruction";
return {};
}
auto* ast_type = ConvertType(inst->type_id());
if (ast_type == nullptr) {
return {};
}
// TODO(dneto): Handle spec constants too?
const auto* spirv_const = constant_mgr_->FindDeclaredConstant(id);
if (spirv_const == nullptr) {
Fail() << "ID " << id << " is not a constant";
return {};
}
// TODO(dneto): Note: NullConstant for int, uint, float map to a regular 0.
// So canonicalization should map that way too.
// Currently "null<type>" is missing from the WGSL parser.
// See https://bugs.chromium.org/p/tint/issues/detail?id=34
if (ast_type->IsU32()) {
return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::UintLiteral>(
ast_type, spirv_const->GetU32()))};
}
if (ast_type->IsI32()) {
return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::SintLiteral>(
ast_type, spirv_const->GetS32()))};
}
if (ast_type->IsF32()) {
return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::FloatLiteral>(
ast_type, spirv_const->GetFloat()))};
}
if (ast_type->IsBool()) {
const bool value = spirv_const->AsNullConstant()
? false
: spirv_const->AsBoolConstant()->value();
return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::BoolLiteral>(ast_type, value))};
}
auto* spirv_composite_const = spirv_const->AsCompositeConstant();
if (spirv_composite_const != nullptr) {
// Handle vector, matrix, array, and struct
// TODO(dneto): Handle the spirv_composite_const->IsZero() case specially.
// See https://github.com/gpuweb/gpuweb/issues/685
// Generate a composite from explicit components.
ast::ExpressionList ast_components;
for (const auto* component : spirv_composite_const->GetComponents()) {
auto* def = constant_mgr_->GetDefiningInstruction(component);
if (def == nullptr) {
Fail() << "internal error: SPIR-V constant doesn't have defining "
"instruction";
return {};
}
auto ast_component = MakeConstantExpression(def->result_id());
if (!success_) {
// We've already emitted a diagnostic.
return {};
}
ast_components.emplace_back(std::move(ast_component.expr));
}
return {ast_type, std::make_unique<ast::TypeConstructorExpression>(
ast_type, std::move(ast_components))};
}
auto* spirv_null_const = spirv_const->AsNullConstant();
if (spirv_null_const != nullptr) {
return {ast_type, MakeNullValue(ast_type)};
}
Fail() << "Unhandled constant type " << inst->type_id() << " for value ID "
<< id;
return {};
}
std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
ast::type::Type* type) {
// TODO(dneto): Use the no-operands constructor syntax when it becomes
// available in Tint.
// https://github.com/gpuweb/gpuweb/issues/685
// https://bugs.chromium.org/p/tint/issues/detail?id=34
if (!type) {
Fail() << "trying to create null value for a null type";
return nullptr;
}
if (type->IsBool()) {
return std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::BoolLiteral>(type, false));
}
if (type->IsU32()) {
return std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::UintLiteral>(type, 0u));
}
if (type->IsI32()) {
return std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::SintLiteral>(type, 0));
}
if (type->IsF32()) {
return std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::FloatLiteral>(type, 0.0f));
}
if (type->IsVector()) {
const auto* vec_ty = type->AsVector();
ast::ExpressionList ast_components;
for (size_t i = 0; i < vec_ty->size(); ++i) {
ast_components.emplace_back(MakeNullValue(vec_ty->type()));
}
return std::make_unique<ast::TypeConstructorExpression>(
type, std::move(ast_components));
}
if (type->IsMatrix()) {
const auto* mat_ty = type->AsMatrix();
// Matrix components are columns
auto* column_ty =
ctx_.type_mgr().Get(std::make_unique<ast::type::VectorType>(
mat_ty->type(), mat_ty->rows()));
ast::ExpressionList ast_components;
for (size_t i = 0; i < mat_ty->columns(); ++i) {
ast_components.emplace_back(MakeNullValue(column_ty));
}
return std::make_unique<ast::TypeConstructorExpression>(
type, std::move(ast_components));
}
if (type->IsArray()) {
auto* arr_ty = type->AsArray();
ast::ExpressionList ast_components;
for (size_t i = 0; i < arr_ty->size(); ++i) {
ast_components.emplace_back(MakeNullValue(arr_ty->type()));
}
return std::make_unique<ast::TypeConstructorExpression>(
type, std::move(ast_components));
}
if (type->IsStruct()) {
auto* struct_ty = type->AsStruct();
ast::ExpressionList ast_components;
for (auto& member : struct_ty->impl()->members()) {
ast_components.emplace_back(MakeNullValue(member->type()));
}
return std::make_unique<ast::TypeConstructorExpression>(
type, std::move(ast_components));
}
Fail() << "can't make null value for type: " << type->type_name();
return nullptr;
}
TypedExpression ParserImpl::RectifyOperandSignedness(SpvOp op,
TypedExpression&& expr) {
const bool requires_signed = AssumesSignedOperands(op);
const bool requires_unsigned = AssumesUnsignedOperands(op);
if (!requires_signed && !requires_unsigned) {
// No conversion is required, assuming our tables are complete.
return std::move(expr);
}
if (!expr.expr) {
Fail() << "internal error: RectifyOperandSignedness given a null expr\n";
return {};
}
auto* type = expr.type;
if (!type) {
Fail() << "internal error: unmapped type for: " << expr.expr->str() << "\n";
return {};
}
if (requires_unsigned) {
auto* unsigned_ty = unsigned_type_for_[type];
if (unsigned_ty != nullptr) {
// Conversion is required.
return {unsigned_ty, std::make_unique<ast::AsExpression>(
unsigned_ty, std::move(expr.expr))};
}
} else if (requires_signed) {
auto* signed_ty = signed_type_for_[type];
if (signed_ty != nullptr) {
// Conversion is required.
return {signed_ty, std::make_unique<ast::AsExpression>(
signed_ty, std::move(expr.expr))};
}
}
// We should not reach here.
return std::move(expr);
}
ast::type::Type* ParserImpl::ForcedResultType(
SpvOp op,
ast::type::Type* first_operand_type) {
const bool binary_match_first_operand =
AssumesResultSignednessMatchesBinaryFirstOperand(op);
const bool unary_match_operand = (op == SpvOpSNegate) || (op == SpvOpNot);
if (binary_match_first_operand || unary_match_operand) {
return first_operand_type;
}
return nullptr;
}
bool ParserImpl::EmitFunctions() {
if (!success_) {
return false;
}
for (const auto* f :
FunctionTraverser(*module_).TopologicallyOrderedFunctions()) {
if (!success_) {
return false;
}
FunctionEmitter emitter(this, *f);
success_ = emitter.Emit();
}
return success_;
}
} // namespace spirv
} // namespace reader
} // namespace tint