blob: 33061eab2d890e681c2d185f0fdedc4e5d1d519a [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/tint/writer/spirv/builder.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "spirv/unified1/GLSL.std.450.h"
#include "src/tint/ast/call_statement.h"
#include "src/tint/ast/fallthrough_statement.h"
#include "src/tint/ast/id_attribute.h"
#include "src/tint/ast/internal_attribute.h"
#include "src/tint/ast/traverse_expressions.h"
#include "src/tint/sem/array.h"
#include "src/tint/sem/atomic.h"
#include "src/tint/sem/builtin.h"
#include "src/tint/sem/call.h"
#include "src/tint/sem/constant.h"
#include "src/tint/sem/depth_multisampled_texture.h"
#include "src/tint/sem/depth_texture.h"
#include "src/tint/sem/function.h"
#include "src/tint/sem/materialize.h"
#include "src/tint/sem/member_accessor_expression.h"
#include "src/tint/sem/module.h"
#include "src/tint/sem/multisampled_texture.h"
#include "src/tint/sem/reference.h"
#include "src/tint/sem/sampled_texture.h"
#include "src/tint/sem/statement.h"
#include "src/tint/sem/struct.h"
#include "src/tint/sem/type_constructor.h"
#include "src/tint/sem/type_conversion.h"
#include "src/tint/sem/variable.h"
#include "src/tint/sem/vector.h"
#include "src/tint/transform/add_spirv_block_attribute.h"
#include "src/tint/utils/defer.h"
#include "src/tint/utils/map.h"
#include "src/tint/writer/append_vector.h"
namespace tint::writer::spirv {
namespace {
using BuiltinType = sem::BuiltinType;
const char kGLSLstd450[] = "GLSL.std.450";
uint32_t size_of(const InstructionList& instructions) {
uint32_t size = 0;
for (const auto& inst : instructions) {
size += inst.word_length();
}
return size;
}
uint32_t pipeline_stage_to_execution_model(ast::PipelineStage stage) {
SpvExecutionModel model = SpvExecutionModelVertex;
switch (stage) {
case ast::PipelineStage::kFragment:
model = SpvExecutionModelFragment;
break;
case ast::PipelineStage::kVertex:
model = SpvExecutionModelVertex;
break;
case ast::PipelineStage::kCompute:
model = SpvExecutionModelGLCompute;
break;
case ast::PipelineStage::kNone:
model = SpvExecutionModelMax;
break;
}
return model;
}
bool LastIsFallthrough(const ast::BlockStatement* stmts) {
return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
}
/// Returns the matrix type that is `type` or that is wrapped by
/// one or more levels of an arrays inside of `type`.
/// @param type the given type, which must not be null
/// @returns the nested matrix type, or nullptr if none
const sem::Matrix* GetNestedMatrixType(const sem::Type* type) {
while (auto* arr = type->As<sem::Array>()) {
type = arr->ElemType();
}
return type->As<sem::Matrix>();
}
uint32_t builtin_to_glsl_method(const sem::Builtin* builtin) {
switch (builtin->Type()) {
case BuiltinType::kAcos:
return GLSLstd450Acos;
case BuiltinType::kAsin:
return GLSLstd450Asin;
case BuiltinType::kAtan:
return GLSLstd450Atan;
case BuiltinType::kAtan2:
return GLSLstd450Atan2;
case BuiltinType::kCeil:
return GLSLstd450Ceil;
case BuiltinType::kClamp:
if (builtin->ReturnType()->is_float_scalar_or_vector()) {
return GLSLstd450NClamp;
} else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
return GLSLstd450UClamp;
} else {
return GLSLstd450SClamp;
}
case BuiltinType::kCos:
return GLSLstd450Cos;
case BuiltinType::kCosh:
return GLSLstd450Cosh;
case BuiltinType::kCross:
return GLSLstd450Cross;
case BuiltinType::kDegrees:
return GLSLstd450Degrees;
case BuiltinType::kDeterminant:
return GLSLstd450Determinant;
case BuiltinType::kDistance:
return GLSLstd450Distance;
case BuiltinType::kExp:
return GLSLstd450Exp;
case BuiltinType::kExp2:
return GLSLstd450Exp2;
case BuiltinType::kFaceForward:
return GLSLstd450FaceForward;
case BuiltinType::kFloor:
return GLSLstd450Floor;
case BuiltinType::kFma:
return GLSLstd450Fma;
case BuiltinType::kFract:
return GLSLstd450Fract;
case BuiltinType::kFrexp:
return GLSLstd450FrexpStruct;
case BuiltinType::kInverseSqrt:
return GLSLstd450InverseSqrt;
case BuiltinType::kLdexp:
return GLSLstd450Ldexp;
case BuiltinType::kLength:
return GLSLstd450Length;
case BuiltinType::kLog:
return GLSLstd450Log;
case BuiltinType::kLog2:
return GLSLstd450Log2;
case BuiltinType::kMax:
if (builtin->ReturnType()->is_float_scalar_or_vector()) {
return GLSLstd450NMax;
} else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
return GLSLstd450UMax;
} else {
return GLSLstd450SMax;
}
case BuiltinType::kMin:
if (builtin->ReturnType()->is_float_scalar_or_vector()) {
return GLSLstd450NMin;
} else if (builtin->ReturnType()->is_unsigned_scalar_or_vector()) {
return GLSLstd450UMin;
} else {
return GLSLstd450SMin;
}
case BuiltinType::kMix:
return GLSLstd450FMix;
case BuiltinType::kModf:
return GLSLstd450ModfStruct;
case BuiltinType::kNormalize:
return GLSLstd450Normalize;
case BuiltinType::kPack4x8snorm:
return GLSLstd450PackSnorm4x8;
case BuiltinType::kPack4x8unorm:
return GLSLstd450PackUnorm4x8;
case BuiltinType::kPack2x16snorm:
return GLSLstd450PackSnorm2x16;
case BuiltinType::kPack2x16unorm:
return GLSLstd450PackUnorm2x16;
case BuiltinType::kPack2x16float:
return GLSLstd450PackHalf2x16;
case BuiltinType::kPow:
return GLSLstd450Pow;
case BuiltinType::kRadians:
return GLSLstd450Radians;
case BuiltinType::kReflect:
return GLSLstd450Reflect;
case BuiltinType::kRefract:
return GLSLstd450Refract;
case BuiltinType::kRound:
return GLSLstd450RoundEven;
case BuiltinType::kSign:
return GLSLstd450FSign;
case BuiltinType::kSin:
return GLSLstd450Sin;
case BuiltinType::kSinh:
return GLSLstd450Sinh;
case BuiltinType::kSmoothstep:
return GLSLstd450SmoothStep;
case BuiltinType::kSqrt:
return GLSLstd450Sqrt;
case BuiltinType::kStep:
return GLSLstd450Step;
case BuiltinType::kTan:
return GLSLstd450Tan;
case BuiltinType::kTanh:
return GLSLstd450Tanh;
case BuiltinType::kTrunc:
return GLSLstd450Trunc;
case BuiltinType::kUnpack4x8snorm:
return GLSLstd450UnpackSnorm4x8;
case BuiltinType::kUnpack4x8unorm:
return GLSLstd450UnpackUnorm4x8;
case BuiltinType::kUnpack2x16snorm:
return GLSLstd450UnpackSnorm2x16;
case BuiltinType::kUnpack2x16unorm:
return GLSLstd450UnpackUnorm2x16;
case BuiltinType::kUnpack2x16float:
return GLSLstd450UnpackHalf2x16;
default:
break;
}
return 0;
}
/// @return the vector element type if ty is a vector, otherwise return ty.
const sem::Type* ElementTypeOf(const sem::Type* ty) {
if (auto* v = ty->As<sem::Vector>()) {
return v->type();
}
return ty;
}
} // namespace
Builder::AccessorInfo::AccessorInfo() : source_id(0), source_type(nullptr) {}
Builder::AccessorInfo::~AccessorInfo() {}
Builder::Builder(const Program* program, bool zero_initialize_workgroup_memory)
: builder_(ProgramBuilder::Wrap(program)),
scope_stack_{Scope{}},
zero_initialize_workgroup_memory_(zero_initialize_workgroup_memory) {}
Builder::~Builder() = default;
bool Builder::Build() {
push_capability(SpvCapabilityShader);
push_memory_model(spv::Op::OpMemoryModel,
{U32Operand(SpvAddressingModelLogical), U32Operand(SpvMemoryModelGLSL450)});
for (auto ext : builder_.Sem().Module()->Extensions()) {
GenerateExtension(ext);
}
for (auto* var : builder_.AST().GlobalVariables()) {
if (!GenerateGlobalVariable(var)) {
return false;
}
}
auto* mod = builder_.Sem().Module();
for (auto* decl : mod->DependencyOrderedDeclarations()) {
if (auto* func = decl->As<ast::Function>()) {
if (!GenerateFunction(func)) {
return false;
}
}
}
return true;
}
void Builder::RegisterVariable(const sem::Variable* var, uint32_t id) {
var_to_id_.emplace(var, id);
id_to_var_.emplace(id, var);
}
uint32_t Builder::LookupVariableID(const sem::Variable* var) {
auto it = var_to_id_.find(var);
if (it == var_to_id_.end()) {
error_ = "unable to find ID for variable: " +
builder_.Symbols().NameFor(var->Declaration()->symbol);
return 0;
}
return it->second;
}
void Builder::PushScope() {
// Push a new scope, by copying the top-most stack
scope_stack_.push_back(scope_stack_.back());
}
void Builder::PopScope() {
scope_stack_.pop_back();
}
Operand Builder::result_op() {
return Operand(next_id());
}
uint32_t Builder::total_size() const {
// The 5 covers the magic, version, generator, id bound and reserved.
uint32_t size = 5;
size += size_of(capabilities_);
size += size_of(extensions_);
size += size_of(ext_imports_);
size += size_of(memory_model_);
size += size_of(entry_points_);
size += size_of(execution_modes_);
size += size_of(debug_);
size += size_of(annotations_);
size += size_of(types_);
for (const auto& func : functions_) {
size += func.word_length();
}
return size;
}
void Builder::iterate(std::function<void(const Instruction&)> cb) const {
for (const auto& inst : capabilities_) {
cb(inst);
}
for (const auto& inst : extensions_) {
cb(inst);
}
for (const auto& inst : ext_imports_) {
cb(inst);
}
for (const auto& inst : memory_model_) {
cb(inst);
}
for (const auto& inst : entry_points_) {
cb(inst);
}
for (const auto& inst : execution_modes_) {
cb(inst);
}
for (const auto& inst : debug_) {
cb(inst);
}
for (const auto& inst : annotations_) {
cb(inst);
}
for (const auto& inst : types_) {
cb(inst);
}
for (const auto& func : functions_) {
func.iterate(cb);
}
}
void Builder::push_capability(uint32_t cap) {
if (capability_set_.count(cap) == 0) {
capability_set_.insert(cap);
capabilities_.push_back(Instruction{spv::Op::OpCapability, {Operand(cap)}});
}
}
void Builder::push_extension(const char* extension) {
extensions_.push_back(Instruction{spv::Op::OpExtension, {Operand(extension)}});
}
bool Builder::GenerateExtension(ast::Extension extension) {
/*
For each supported extension, push corresponding capability into the builder.
For example:
if (kind == ast::Extension::Kind::kF16) {
push_capability(SpvCapabilityFloat16);
push_capability(SpvCapabilityUniformAndStorageBuffer16BitAccess);
push_capability(SpvCapabilityStorageBuffer16BitAccess);
push_capability(SpvCapabilityStorageInputOutput16);
}
*/
switch (extension) {
case ast::Extension::kChromiumExperimentalDP4a:
push_extension("SPV_KHR_integer_dot_product");
push_capability(SpvCapabilityDotProductKHR);
push_capability(SpvCapabilityDotProductInput4x8BitPackedKHR);
break;
default:
return false;
}
return true;
}
bool Builder::GenerateLabel(uint32_t id) {
if (!push_function_inst(spv::Op::OpLabel, {Operand(id)})) {
return false;
}
current_label_id_ = id;
return true;
}
bool Builder::GenerateAssignStatement(const ast::AssignmentStatement* assign) {
if (assign->lhs->Is<ast::PhonyExpression>()) {
if (builder_.Sem().Get(assign->rhs)->ConstantValue()) {
// RHS of phony assignment is constant.
// Constants can't have side-effects, so just drop this.
return true;
}
auto rhs_id = GenerateExpression(assign->rhs);
if (rhs_id == 0) {
return false;
}
return true;
} else {
auto lhs_id = GenerateExpression(assign->lhs);
if (lhs_id == 0) {
return false;
}
auto rhs_id = GenerateExpressionWithLoadIfNeeded(assign->rhs);
if (rhs_id == 0) {
return false;
}
return GenerateStore(lhs_id, rhs_id);
}
}
bool Builder::GenerateBreakStatement(const ast::BreakStatement*) {
if (merge_stack_.empty()) {
error_ = "Attempted to break without a merge block";
return false;
}
if (!push_function_inst(spv::Op::OpBranch, {Operand(merge_stack_.back())})) {
return false;
}
return true;
}
bool Builder::GenerateContinueStatement(const ast::ContinueStatement*) {
if (continue_stack_.empty()) {
error_ = "Attempted to continue without a continue block";
return false;
}
if (!push_function_inst(spv::Op::OpBranch, {Operand(continue_stack_.back())})) {
return false;
}
return true;
}
// TODO(dsinclair): This is generating an OpKill but the semantics of kill
// haven't been defined for WGSL yet. So, this may need to change.
// https://github.com/gpuweb/gpuweb/issues/676
bool Builder::GenerateDiscardStatement(const ast::DiscardStatement*) {
if (!push_function_inst(spv::Op::OpKill, {})) {
return false;
}
return true;
}
bool Builder::GenerateEntryPoint(const ast::Function* func, uint32_t id) {
auto stage = pipeline_stage_to_execution_model(func->PipelineStage());
if (stage == SpvExecutionModelMax) {
error_ = "Unknown pipeline stage provided";
return false;
}
OperandList operands = {Operand(stage), Operand(id),
Operand(builder_.Symbols().NameFor(func->symbol))};
auto* func_sem = builder_.Sem().Get(func);
for (const auto* var : func_sem->TransitivelyReferencedGlobals()) {
// For SPIR-V 1.3 we only output Input/output variables. If we update to
// SPIR-V 1.4 or later this should be all variables.
if (var->StorageClass() != ast::StorageClass::kInput &&
var->StorageClass() != ast::StorageClass::kOutput) {
continue;
}
uint32_t var_id = LookupVariableID(var);
if (var_id == 0) {
error_ = "unable to find ID for global variable: " +
builder_.Symbols().NameFor(var->Declaration()->symbol);
return false;
}
operands.push_back(Operand(var_id));
}
push_entry_point(spv::Op::OpEntryPoint, operands);
return true;
}
bool Builder::GenerateExecutionModes(const ast::Function* func, uint32_t id) {
auto* func_sem = builder_.Sem().Get(func);
// WGSL fragment shader origin is upper left
if (func->PipelineStage() == ast::PipelineStage::kFragment) {
push_execution_mode(spv::Op::OpExecutionMode,
{Operand(id), U32Operand(SpvExecutionModeOriginUpperLeft)});
} else if (func->PipelineStage() == ast::PipelineStage::kCompute) {
auto& wgsize = func_sem->WorkgroupSize();
// Check if the workgroup_size uses pipeline-overridable constants.
if (wgsize[0].overridable_const || wgsize[1].overridable_const ||
wgsize[2].overridable_const) {
if (has_overridable_workgroup_size_) {
// Only one stage can have a pipeline-overridable workgroup size.
// TODO(crbug.com/tint/810): Use LocalSizeId to handle this scenario.
TINT_ICE(Writer, builder_.Diagnostics())
<< "multiple stages using pipeline-overridable workgroup sizes";
}
has_overridable_workgroup_size_ = true;
auto* vec3_u32 = builder_.create<sem::Vector>(builder_.create<sem::U32>(), 3u);
uint32_t vec3_u32_type_id = GenerateTypeIfNeeded(vec3_u32);
if (vec3_u32_type_id == 0) {
return 0;
}
OperandList wgsize_ops;
auto wgsize_result = result_op();
wgsize_ops.push_back(Operand(vec3_u32_type_id));
wgsize_ops.push_back(wgsize_result);
// Generate OpConstant instructions for each dimension.
for (int i = 0; i < 3; i++) {
auto constant = ScalarConstant::U32(wgsize[i].value);
if (wgsize[i].overridable_const) {
// Make the constant specializable.
auto* sem_const =
builder_.Sem().Get<sem::GlobalVariable>(wgsize[i].overridable_const);
if (!sem_const->IsOverridable()) {
TINT_ICE(Writer, builder_.Diagnostics())
<< "expected a pipeline-overridable constant";
}
constant.is_spec_op = true;
constant.constant_id = sem_const->ConstantId();
}
auto result = GenerateConstantIfNeeded(constant);
wgsize_ops.push_back(Operand(result));
}
// Generate the WorkgroupSize builtin.
push_type(spv::Op::OpSpecConstantComposite, wgsize_ops);
push_annot(spv::Op::OpDecorate, {wgsize_result, U32Operand(SpvDecorationBuiltIn),
U32Operand(SpvBuiltInWorkgroupSize)});
} else {
// Not overridable, so just use OpExecutionMode LocalSize.
uint32_t x = wgsize[0].value;
uint32_t y = wgsize[1].value;
uint32_t z = wgsize[2].value;
push_execution_mode(spv::Op::OpExecutionMode,
{Operand(id), U32Operand(SpvExecutionModeLocalSize), Operand(x),
Operand(y), Operand(z)});
}
}
for (auto builtin : func_sem->TransitivelyReferencedBuiltinVariables()) {
if (builtin.second->builtin == ast::Builtin::kFragDepth) {
push_execution_mode(spv::Op::OpExecutionMode,
{Operand(id), U32Operand(SpvExecutionModeDepthReplacing)});
}
}
return true;
}
uint32_t Builder::GenerateExpression(const ast::Expression* expr) {
if (auto* sem = builder_.Sem().Get(expr)) {
if (auto constant = sem->ConstantValue()) {
return GenerateConstantIfNeeded(constant);
}
}
return Switch(
expr, //
[&](const ast::IndexAccessorExpression* a) { return GenerateAccessorExpression(a); },
[&](const ast::BinaryExpression* b) { return GenerateBinaryExpression(b); },
[&](const ast::BitcastExpression* b) { return GenerateBitcastExpression(b); },
[&](const ast::CallExpression* c) { return GenerateCallExpression(c); },
[&](const ast::IdentifierExpression* i) { return GenerateIdentifierExpression(i); },
[&](const ast::LiteralExpression* l) { return GenerateLiteralIfNeeded(nullptr, l); },
[&](const ast::MemberAccessorExpression* m) { return GenerateAccessorExpression(m); },
[&](const ast::UnaryOpExpression* u) { return GenerateUnaryOpExpression(u); },
[&](Default) {
error_ = "unknown expression type: " + std::string(expr->TypeInfo().name);
return 0;
});
}
bool Builder::GenerateFunction(const ast::Function* func_ast) {
auto* func = builder_.Sem().Get(func_ast);
uint32_t func_type_id = GenerateFunctionTypeIfNeeded(func);
if (func_type_id == 0) {
return false;
}
auto func_op = result_op();
auto func_id = std::get<uint32_t>(func_op);
push_debug(spv::Op::OpName,
{Operand(func_id), Operand(builder_.Symbols().NameFor(func_ast->symbol))});
auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
if (ret_id == 0) {
return false;
}
PushScope();
TINT_DEFER(PopScope());
auto definition_inst = Instruction{
spv::Op::OpFunction,
{Operand(ret_id), func_op, U32Operand(SpvFunctionControlMaskNone), Operand(func_type_id)}};
InstructionList params;
for (auto* param : func->Parameters()) {
auto param_op = result_op();
auto param_id = std::get<uint32_t>(param_op);
auto param_type_id = GenerateTypeIfNeeded(param->Type());
if (param_type_id == 0) {
return false;
}
push_debug(
spv::Op::OpName,
{Operand(param_id), Operand(builder_.Symbols().NameFor(param->Declaration()->symbol))});
params.push_back(
Instruction{spv::Op::OpFunctionParameter, {Operand(param_type_id), param_op}});
RegisterVariable(param, param_id);
}
push_function(Function{definition_inst, result_op(), std::move(params)});
for (auto* stmt : func_ast->body->statements) {
if (!GenerateStatement(stmt)) {
return false;
}
}
if (InsideBasicBlock()) {
if (func->ReturnType()->Is<sem::Void>()) {
push_function_inst(spv::Op::OpReturn, {});
} else {
auto zero = GenerateConstantNullIfNeeded(func->ReturnType());
push_function_inst(spv::Op::OpReturnValue, {Operand(zero)});
}
}
if (func_ast->IsEntryPoint()) {
if (!GenerateEntryPoint(func_ast, func_id)) {
return false;
}
if (!GenerateExecutionModes(func_ast, func_id)) {
return false;
}
}
func_symbol_to_id_[func_ast->symbol] = func_id;
return true;
}
uint32_t Builder::GenerateFunctionTypeIfNeeded(const sem::Function* func) {
return utils::GetOrCreate(func_sig_to_id_, func->Signature(), [&]() -> uint32_t {
auto func_op = result_op();
auto func_type_id = std::get<uint32_t>(func_op);
auto ret_id = GenerateTypeIfNeeded(func->ReturnType());
if (ret_id == 0) {
return 0;
}
OperandList ops = {func_op, Operand(ret_id)};
for (auto* param : func->Parameters()) {
auto param_type_id = GenerateTypeIfNeeded(param->Type());
if (param_type_id == 0) {
return 0;
}
ops.push_back(Operand(param_type_id));
}
push_type(spv::Op::OpTypeFunction, std::move(ops));
return func_type_id;
});
}
bool Builder::GenerateFunctionVariable(const ast::Variable* var) {
uint32_t init_id = 0;
if (var->constructor) {
init_id = GenerateExpressionWithLoadIfNeeded(var->constructor);
if (init_id == 0) {
return false;
}
}
auto* sem = builder_.Sem().Get(var);
if (var->is_const) {
if (!var->constructor) {
error_ = "missing constructor for constant";
return false;
}
RegisterVariable(sem, init_id);
return true;
}
auto result = result_op();
auto var_id = std::get<uint32_t>(result);
auto sc = ast::StorageClass::kFunction;
auto* type = sem->Type();
auto type_id = GenerateTypeIfNeeded(type);
if (type_id == 0) {
return false;
}
push_debug(spv::Op::OpName,
{Operand(var_id), Operand(builder_.Symbols().NameFor(var->symbol))});
// TODO(dsinclair) We could detect if the constructor is fully const and emit
// an initializer value for the variable instead of doing the OpLoad.
auto null_id = GenerateConstantNullIfNeeded(type->UnwrapRef());
if (null_id == 0) {
return 0;
}
push_function_var(
{Operand(type_id), result, U32Operand(ConvertStorageClass(sc)), Operand(null_id)});
if (var->constructor) {
if (!GenerateStore(var_id, init_id)) {
return false;
}
}
RegisterVariable(sem, var_id);
return true;
}
bool Builder::GenerateStore(uint32_t to, uint32_t from) {
return push_function_inst(spv::Op::OpStore, {Operand(to), Operand(from)});
}
bool Builder::GenerateGlobalVariable(const ast::Variable* var) {
auto* sem = builder_.Sem().Get(var);
auto* type = sem->Type()->UnwrapRef();
uint32_t init_id = 0;
if (var->constructor) {
if (!var->is_overridable) {
auto* ctor = builder_.Sem().Get(var->constructor);
if (auto constant = ctor->ConstantValue()) {
init_id = GenerateConstantIfNeeded(std::move(constant));
}
}
if (init_id == 0) {
init_id = GenerateConstructorExpression(var, var->constructor);
}
if (init_id == 0) {
return false;
}
}
if (var->is_const) {
if (!var->constructor) {
// Constants must have an initializer unless they are overridable.
if (!var->is_overridable) {
error_ = "missing constructor for constant";
return false;
}
// SPIR-V requires specialization constants to have initializers.
init_id = Switch(
type, //
[&](const sem::F32*) {
ast::FloatLiteralExpression l(ProgramID{}, Source{}, 0,
ast::FloatLiteralExpression::Suffix::kF);
return GenerateLiteralIfNeeded(var, &l);
},
[&](const sem::U32*) {
ast::IntLiteralExpression l(ProgramID{}, Source{}, 0,
ast::IntLiteralExpression::Suffix::kU);
return GenerateLiteralIfNeeded(var, &l);
},
[&](const sem::I32*) {
ast::IntLiteralExpression l(ProgramID{}, Source{}, 0,
ast::IntLiteralExpression::Suffix::kI);
return GenerateLiteralIfNeeded(var, &l);
},
[&](const sem::Bool*) {
ast::BoolLiteralExpression l(ProgramID{}, Source{}, false);
return GenerateLiteralIfNeeded(var, &l);
},
[&](Default) {
error_ = "invalid type for pipeline constant ID, must be scalar";
return 0;
});
if (init_id == 0) {
return 0;
}
}
push_debug(spv::Op::OpName,
{Operand(init_id), Operand(builder_.Symbols().NameFor(var->symbol))});
RegisterVariable(sem, init_id);
return true;
}
auto result = result_op();
auto var_id = std::get<uint32_t>(result);
auto sc = sem->StorageClass() == ast::StorageClass::kNone ? ast::StorageClass::kPrivate
: sem->StorageClass();
auto type_id = GenerateTypeIfNeeded(sem->Type());
if (type_id == 0) {
return false;
}
push_debug(spv::Op::OpName,
{Operand(var_id), Operand(builder_.Symbols().NameFor(var->symbol))});
OperandList ops = {Operand(type_id), result, U32Operand(ConvertStorageClass(sc))};
if (var->constructor) {
ops.push_back(Operand(init_id));
} else {
auto* st = type->As<sem::StorageTexture>();
if (st || type->Is<sem::Struct>()) {
// type is a sem::Struct or a sem::StorageTexture
auto access = st ? st->access() : sem->Access();
switch (access) {
case ast::Access::kWrite:
push_annot(spv::Op::OpDecorate,
{Operand(var_id), U32Operand(SpvDecorationNonReadable)});
break;
case ast::Access::kRead:
push_annot(spv::Op::OpDecorate,
{Operand(var_id), U32Operand(SpvDecorationNonWritable)});
break;
case ast::Access::kUndefined:
case ast::Access::kReadWrite:
break;
}
}
if (!type->Is<sem::Sampler>()) {
// If we don't have a constructor and we're an Output or Private
// variable, then WGSL requires that we zero-initialize.
// If we're a Workgroup variable, and the
// VK_KHR_zero_initialize_workgroup_memory extension is enabled, we should
// also zero-initialize.
if (sem->StorageClass() == ast::StorageClass::kPrivate ||
sem->StorageClass() == ast::StorageClass::kOutput ||
(zero_initialize_workgroup_memory_ &&
sem->StorageClass() == ast::StorageClass::kWorkgroup)) {
init_id = GenerateConstantNullIfNeeded(type);
if (init_id == 0) {
return 0;
}
ops.push_back(Operand(init_id));
}
}
}
push_type(spv::Op::OpVariable, std::move(ops));
for (auto* attr : var->attributes) {
bool ok = Switch(
attr,
[&](const ast::BuiltinAttribute* builtin) {
push_annot(spv::Op::OpDecorate,
{Operand(var_id), U32Operand(SpvDecorationBuiltIn),
U32Operand(ConvertBuiltin(builtin->builtin, sem->StorageClass()))});
return true;
},
[&](const ast::LocationAttribute* location) {
push_annot(spv::Op::OpDecorate, {Operand(var_id), U32Operand(SpvDecorationLocation),
Operand(location->value)});
return true;
},
[&](const ast::InterpolateAttribute* interpolate) {
AddInterpolationDecorations(var_id, interpolate->type, interpolate->sampling);
return true;
},
[&](const ast::InvariantAttribute*) {
push_annot(spv::Op::OpDecorate,
{Operand(var_id), U32Operand(SpvDecorationInvariant)});
return true;
},
[&](const ast::BindingAttribute* binding) {
push_annot(spv::Op::OpDecorate, {Operand(var_id), U32Operand(SpvDecorationBinding),
Operand(binding->value)});
return true;
},
[&](const ast::GroupAttribute* group) {
push_annot(spv::Op::OpDecorate,
{Operand(var_id), U32Operand(SpvDecorationDescriptorSet),
Operand(group->value)});
return true;
},
[&](const ast::IdAttribute*) {
return true; // Spec constants are handled elsewhere
},
[&](const ast::InternalAttribute*) {
return true; // ignored
},
[&](Default) {
error_ = "unknown attribute";
return false;
});
if (!ok) {
return false;
}
}
RegisterVariable(sem, var_id);
return true;
}
bool Builder::GenerateIndexAccessor(const ast::IndexAccessorExpression* expr, AccessorInfo* info) {
auto idx_id = GenerateExpressionWithLoadIfNeeded(expr->index);
if (idx_id == 0) {
return 0;
}
// If the source is a reference, we access chain into it.
// In the future, pointers may support access-chaining.
// See https://github.com/gpuweb/gpuweb/pull/1580
if (info->source_type->Is<sem::Reference>()) {
info->access_chain_indices.push_back(idx_id);
info->source_type = TypeOf(expr);
return true;
}
auto result_type_id = GenerateTypeIfNeeded(TypeOf(expr));
if (result_type_id == 0) {
return false;
}
// We don't have a pointer, so we can just directly extract the value.
auto extract = result_op();
auto extract_id = std::get<uint32_t>(extract);
// If the index is compile-time constant, we use OpCompositeExtract.
auto* idx = builder_.Sem().Get(expr->index);
if (auto idx_constval = idx->ConstantValue()) {
if (!push_function_inst(spv::Op::OpCompositeExtract,
{
Operand(result_type_id),
extract,
Operand(info->source_id),
Operand(idx_constval.Element<uint32_t>(0)),
})) {
return false;
}
info->source_id = extract_id;
info->source_type = TypeOf(expr);
return true;
}
// If the source is a vector, we use OpVectorExtractDynamic.
if (info->source_type->Is<sem::Vector>()) {
if (!push_function_inst(
spv::Op::OpVectorExtractDynamic,
{Operand(result_type_id), extract, Operand(info->source_id), Operand(idx_id)})) {
return false;
}
info->source_id = extract_id;
info->source_type = TypeOf(expr);
return true;
}
TINT_ICE(Writer, builder_.Diagnostics()) << "unsupported index accessor expression";
return false;
}
bool Builder::GenerateMemberAccessor(const ast::MemberAccessorExpression* expr,
AccessorInfo* info) {
auto* expr_sem = builder_.Sem().Get(expr);
auto* expr_type = expr_sem->Type();
if (auto* access = expr_sem->As<sem::StructMemberAccess>()) {
uint32_t idx = access->Member()->Index();
if (info->source_type->Is<sem::Reference>()) {
auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(idx));
if (idx_id == 0) {
return 0;
}
info->access_chain_indices.push_back(idx_id);
info->source_type = expr_type;
} else {
auto result_type_id = GenerateTypeIfNeeded(expr_type);
if (result_type_id == 0) {
return false;
}
auto extract = result_op();
auto extract_id = std::get<uint32_t>(extract);
if (!push_function_inst(
spv::Op::OpCompositeExtract,
{Operand(result_type_id), extract, Operand(info->source_id), Operand(idx)})) {
return false;
}
info->source_id = extract_id;
info->source_type = expr_type;
}
return true;
}
if (auto* swizzle = expr_sem->As<sem::Swizzle>()) {
// Single element swizzle is either an access chain or a composite extract
auto& indices = swizzle->Indices();
if (indices.size() == 1) {
if (info->source_type->Is<sem::Reference>()) {
auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(indices[0]));
if (idx_id == 0) {
return 0;
}
info->access_chain_indices.push_back(idx_id);
} else {
auto result_type_id = GenerateTypeIfNeeded(expr_type);
if (result_type_id == 0) {
return 0;
}
auto extract = result_op();
auto extract_id = std::get<uint32_t>(extract);
if (!push_function_inst(spv::Op::OpCompositeExtract,
{Operand(result_type_id), extract, Operand(info->source_id),
Operand(indices[0])})) {
return false;
}
info->source_id = extract_id;
info->source_type = expr_type;
}
return true;
}
// Store the type away as it may change if we run the access chain
auto* incoming_type = info->source_type;
// Multi-item extract is a VectorShuffle. We have to emit any existing
// access chain data, then load the access chain and shuffle that.
if (!info->access_chain_indices.empty()) {
auto result_type_id = GenerateTypeIfNeeded(info->source_type);
if (result_type_id == 0) {
return 0;
}
auto extract = result_op();
auto extract_id = std::get<uint32_t>(extract);
OperandList ops = {Operand(result_type_id), extract, Operand(info->source_id)};
for (auto id : info->access_chain_indices) {
ops.push_back(Operand(id));
}
if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
return false;
}
info->source_id = GenerateLoadIfNeeded(expr_type, extract_id);
info->source_type = expr_type->UnwrapRef();
info->access_chain_indices.clear();
}
auto result_type_id = GenerateTypeIfNeeded(expr_type);
if (result_type_id == 0) {
return false;
}
auto vec_id = GenerateLoadIfNeeded(incoming_type, info->source_id);
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
OperandList ops = {Operand(result_type_id), result, Operand(vec_id), Operand(vec_id)};
for (auto idx : indices) {
ops.push_back(Operand(idx));
}
if (!push_function_inst(spv::Op::OpVectorShuffle, ops)) {
return false;
}
info->source_id = result_id;
info->source_type = expr_type;
return true;
}
TINT_ICE(Writer, builder_.Diagnostics())
<< "unhandled member index type: " << expr_sem->TypeInfo().name;
return false;
}
uint32_t Builder::GenerateAccessorExpression(const ast::Expression* expr) {
if (!expr->IsAnyOf<ast::IndexAccessorExpression, ast::MemberAccessorExpression>()) {
TINT_ICE(Writer, builder_.Diagnostics()) << "expression is not an accessor";
return 0;
}
// Gather a list of all the member and index accessors that are in this chain.
// The list is built in reverse order as that's the order we need to access
// the chain.
std::vector<const ast::Expression*> accessors;
const ast::Expression* source = expr;
while (true) {
if (auto* array = source->As<ast::IndexAccessorExpression>()) {
accessors.insert(accessors.begin(), source);
source = array->object;
} else if (auto* member = source->As<ast::MemberAccessorExpression>()) {
accessors.insert(accessors.begin(), source);
source = member->structure;
} else {
break;
}
}
AccessorInfo info;
info.source_id = GenerateExpression(source);
if (info.source_id == 0) {
return 0;
}
info.source_type = TypeOf(source);
// Note: Dynamic index on array and matrix values (lets) should have been
// promoted to storage with the VarForDynamicIndex transform.
for (auto* accessor : accessors) {
bool ok = Switch(
accessor,
[&](const ast::IndexAccessorExpression* array) {
return GenerateIndexAccessor(array, &info);
},
[&](const ast::MemberAccessorExpression* member) {
return GenerateMemberAccessor(member, &info);
},
[&](Default) {
error_ = "invalid accessor in list: " + std::string(accessor->TypeInfo().name);
return false;
});
if (!ok) {
return false;
}
}
if (!info.access_chain_indices.empty()) {
auto* type = TypeOf(expr);
auto result_type_id = GenerateTypeIfNeeded(type);
if (result_type_id == 0) {
return 0;
}
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
OperandList ops = {Operand(result_type_id), result, Operand(info.source_id)};
for (auto id : info.access_chain_indices) {
ops.push_back(Operand(id));
}
if (!push_function_inst(spv::Op::OpAccessChain, ops)) {
return false;
}
info.source_id = result_id;
}
return info.source_id;
}
uint32_t Builder::GenerateIdentifierExpression(const ast::IdentifierExpression* expr) {
auto* sem = builder_.Sem().Get(expr);
if (auto* user = sem->As<sem::VariableUser>()) {
return LookupVariableID(user->Variable());
}
error_ = "identifier '" + builder_.Symbols().NameFor(expr->symbol) +
"' does not resolve to a variable";
return 0;
}
uint32_t Builder::GenerateExpressionWithLoadIfNeeded(const sem::Expression* expr) {
// The semantic node directly knows both the AST node and the resolved type.
if (const auto id = GenerateExpression(expr->Declaration())) {
return GenerateLoadIfNeeded(expr->Type(), id);
}
return 0;
}
uint32_t Builder::GenerateExpressionWithLoadIfNeeded(const ast::Expression* expr) {
if (const auto id = GenerateExpression(expr)) {
// Perform a lookup to get the resolved type.
return GenerateLoadIfNeeded(TypeOf(expr), id);
}
return 0;
}
uint32_t Builder::GenerateLoadIfNeeded(const sem::Type* type, uint32_t id) {
if (auto* ref = type->As<sem::Reference>()) {
type = ref->StoreType();
} else {
return id;
}
auto type_id = GenerateTypeIfNeeded(type);
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
if (!push_function_inst(spv::Op::OpLoad, {Operand(type_id), result, Operand(id)})) {
return 0;
}
return result_id;
}
uint32_t Builder::GenerateUnaryOpExpression(const ast::UnaryOpExpression* expr) {
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
spv::Op op = spv::Op::OpNop;
switch (expr->op) {
case ast::UnaryOp::kComplement:
op = spv::Op::OpNot;
break;
case ast::UnaryOp::kNegation:
if (TypeOf(expr)->is_float_scalar_or_vector()) {
op = spv::Op::OpFNegate;
} else {
op = spv::Op::OpSNegate;
}
break;
case ast::UnaryOp::kNot:
op = spv::Op::OpLogicalNot;
break;
case ast::UnaryOp::kAddressOf:
case ast::UnaryOp::kIndirection:
// Address-of converts a reference to a pointer, and dereference converts
// a pointer to a reference. These are the same thing in SPIR-V, so this
// is a no-op.
return GenerateExpression(expr->expr);
}
auto val_id = GenerateExpressionWithLoadIfNeeded(expr->expr);
if (val_id == 0) {
return 0;
}
auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
if (type_id == 0) {
return 0;
}
if (!push_function_inst(op, {Operand(type_id), result, Operand(val_id)})) {
return false;
}
return result_id;
}
uint32_t Builder::GetGLSLstd450Import() {
auto where = import_name_to_id_.find(kGLSLstd450);
if (where != import_name_to_id_.end()) {
return where->second;
}
// It doesn't exist yet. Generate it.
auto result = result_op();
auto id = std::get<uint32_t>(result);
push_ext_import(spv::Op::OpExtInstImport, {result, Operand(kGLSLstd450)});
// Remember it for later.
import_name_to_id_[kGLSLstd450] = id;
return id;
}
uint32_t Builder::GenerateConstructorExpression(const ast::Variable* var,
const ast::Expression* expr) {
if (auto* literal = expr->As<ast::LiteralExpression>()) {
return GenerateLiteralIfNeeded(var, literal);
}
if (auto* call = builder_.Sem().Get<sem::Call>(expr)) {
if (call->Target()->IsAnyOf<sem::TypeConstructor, sem::TypeConversion>()) {
return GenerateTypeConstructorOrConversion(call, var);
}
}
error_ = "unknown constructor expression";
return 0;
}
bool Builder::IsConstructorConst(const ast::Expression* expr) {
bool is_const = true;
ast::TraverseExpressions(expr, builder_.Diagnostics(), [&](const ast::Expression* e) {
if (e->Is<ast::LiteralExpression>()) {
return ast::TraverseAction::Descend;
}
if (auto* ce = e->As<ast::CallExpression>()) {
auto* sem = builder_.Sem().Get(ce);
if (sem->Is<sem::Materialize>()) {
// Materialize can only occur on compile time expressions, so this sub-tree must be
// constant.
return ast::TraverseAction::Skip;
}
auto* call = sem->As<sem::Call>();
if (call->Target()->Is<sem::TypeConstructor>()) {
return ast::TraverseAction::Descend;
}
}
is_const = false;
return ast::TraverseAction::Stop;
});
return is_const;
}
uint32_t Builder::GenerateTypeConstructorOrConversion(const sem::Call* call,
const ast::Variable* var) {
auto& args = call->Arguments();
auto* global_var = builder_.Sem().Get<sem::GlobalVariable>(var);
auto* result_type = call->Type();
// Generate the zero initializer if there are no values provided.
if (args.empty()) {
if (global_var && global_var->IsOverridable()) {
auto constant_id = global_var->ConstantId();
if (result_type->Is<sem::I32>()) {
return GenerateConstantIfNeeded(ScalarConstant::I32(0).AsSpecOp(constant_id));
}
if (result_type->Is<sem::U32>()) {
return GenerateConstantIfNeeded(ScalarConstant::U32(0).AsSpecOp(constant_id));
}
if (result_type->Is<sem::F32>()) {
return GenerateConstantIfNeeded(ScalarConstant::F32(0).AsSpecOp(constant_id));
}
if (result_type->Is<sem::Bool>()) {
return GenerateConstantIfNeeded(ScalarConstant::Bool(false).AsSpecOp(constant_id));
}
}
return GenerateConstantNullIfNeeded(result_type->UnwrapRef());
}
result_type = result_type->UnwrapRef();
bool constructor_is_const = IsConstructorConst(call->Declaration());
if (has_error()) {
return 0;
}
bool can_cast_or_copy = result_type->is_scalar();
if (auto* res_vec = result_type->As<sem::Vector>()) {
if (res_vec->type()->is_scalar()) {
auto* value_type = args[0]->Type()->UnwrapRef();
if (auto* val_vec = value_type->As<sem::Vector>()) {
if (val_vec->type()->is_scalar()) {
can_cast_or_copy = res_vec->Width() == val_vec->Width();
}
}
}
}
if (can_cast_or_copy) {
return GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(), global_var);
}
auto type_id = GenerateTypeIfNeeded(result_type);
if (type_id == 0) {
return 0;
}
bool result_is_constant_composite = constructor_is_const;
bool result_is_spec_composite = false;
if (auto* vec = result_type->As<sem::Vector>()) {
result_type = vec->type();
}
OperandList ops;
static constexpr size_t kOpsResultIdx = 1;
static constexpr size_t kOpsFirstValueIdx = 2;
ops.reserve(8);
ops.push_back(Operand(type_id));
ops.push_back(Operand(0u)); // Placeholder for the result ID
for (auto* e : args) {
uint32_t id = 0;
id = GenerateExpressionWithLoadIfNeeded(e);
if (id == 0) {
return 0;
}
auto* value_type = e->Type()->UnwrapRef();
// If the result and value types are the same we can just use the object.
// If the result is not a vector then we should have validated that the
// value type is a correctly sized vector so we can just use it directly.
if (result_type == value_type || result_type->Is<sem::Matrix>() ||
result_type->Is<sem::Array>() || result_type->Is<sem::Struct>()) {
ops.push_back(Operand(id));
continue;
}
// Both scalars, but not the same type so we need to generate a conversion
// of the value.
if (value_type->is_scalar() && result_type->is_scalar()) {
id = GenerateCastOrCopyOrPassthrough(result_type, args[0]->Declaration(), global_var);
ops.push_back(Operand(id));
continue;
}
// When handling vectors as the values there a few cases to take into
// consideration:
// 1. Module scoped vec3<f32>(vec2<f32>(1, 2), 3) -> OpSpecConstantOp
// 2. Function scoped vec3<f32>(vec2<f32>(1, 2), 3) -> OpCompositeExtract
// 3. Either array<vec3<f32>, 1>(vec3<f32>(1, 2, 3)) -> use the ID.
// -> handled above
//
// For cases 1 and 2, if the type is different we also may need to insert
// a type cast.
if (auto* vec = value_type->As<sem::Vector>()) {
auto* vec_type = vec->type();
auto value_type_id = GenerateTypeIfNeeded(vec_type);
if (value_type_id == 0) {
return 0;
}
for (uint32_t i = 0; i < vec->Width(); ++i) {
auto extract = result_op();
auto extract_id = std::get<uint32_t>(extract);
if (!global_var) {
// A non-global initializer. Case 2.
if (!push_function_inst(
spv::Op::OpCompositeExtract,
{Operand(value_type_id), extract, Operand(id), Operand(i)})) {
return false;
}
// We no longer have a constant composite, but have to do a
// composite construction as these calls are inside a function.
result_is_constant_composite = false;
} else {
// A global initializer, must use OpSpecConstantOp. Case 1.
auto idx_id = GenerateConstantIfNeeded(ScalarConstant::U32(i));
if (idx_id == 0) {
return 0;
}
push_type(spv::Op::OpSpecConstantOp,
{Operand(value_type_id), extract, U32Operand(SpvOpCompositeExtract),
Operand(id), Operand(idx_id)});
result_is_spec_composite = true;
}
ops.push_back(Operand(extract_id));
}
} else {
error_ = "Unhandled type cast value type";
return 0;
}
}
// For a single-value vector initializer, splat the initializer value.
auto* const init_result_type = call->Type()->UnwrapRef();
if (args.size() == 1 && init_result_type->is_scalar_vector() &&
args[0]->Type()->UnwrapRef()->is_scalar()) {
size_t vec_size = init_result_type->As<sem::Vector>()->Width();
for (size_t i = 0; i < (vec_size - 1); ++i) {
ops.push_back(ops[kOpsFirstValueIdx]);
}
}
auto& stack = (result_is_spec_composite || result_is_constant_composite)
? scope_stack_[0] // Global scope
: scope_stack_.back(); // Lexical scope
return utils::GetOrCreate(stack.type_ctor_to_id_, OperandListKey{ops}, [&]() -> uint32_t {
auto result = result_op();
ops[kOpsResultIdx] = result;
if (result_is_spec_composite) {
push_type(spv::Op::OpSpecConstantComposite, ops);
} else if (result_is_constant_composite) {
push_type(spv::Op::OpConstantComposite, ops);
} else {
if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
return 0;
}
}
return std::get<uint32_t>(result);
});
}
uint32_t Builder::GenerateCastOrCopyOrPassthrough(const sem::Type* to_type,
const ast::Expression* from_expr,
bool is_global_init) {
// This should not happen as we rely on constant folding to obviate
// casts/conversions for module-scope variables
if (is_global_init) {
TINT_ICE(Writer, builder_.Diagnostics())
<< "Module-level conversions are not supported. Conversions should "
"have already been constant-folded by the FoldConstants transform.";
return 0;
}
auto elem_type_of = [](const sem::Type* t) -> const sem::Type* {
if (t->is_scalar()) {
return t;
}
if (auto* v = t->As<sem::Vector>()) {
return v->type();
}
return nullptr;
};
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
auto result_type_id = GenerateTypeIfNeeded(to_type);
if (result_type_id == 0) {
return 0;
}
auto val_id = GenerateExpressionWithLoadIfNeeded(from_expr);
if (val_id == 0) {
return 0;
}
auto* from_type = TypeOf(from_expr)->UnwrapRef();
spv::Op op = spv::Op::OpNop;
if ((from_type->Is<sem::I32>() && to_type->Is<sem::F32>()) ||
(from_type->is_signed_integer_vector() && to_type->is_float_vector())) {
op = spv::Op::OpConvertSToF;
} else if ((from_type->Is<sem::U32>() && to_type->Is<sem::F32>()) ||
(from_type->is_unsigned_integer_vector() && to_type->is_float_vector())) {
op = spv::Op::OpConvertUToF;
} else if ((from_type->Is<sem::F32>() && to_type->Is<sem::I32>()) ||
(from_type->is_float_vector() && to_type->is_signed_integer_vector())) {
op = spv::Op::OpConvertFToS;
} else if ((from_type->Is<sem::F32>() && to_type->Is<sem::U32>()) ||
(from_type->is_float_vector() && to_type->is_unsigned_integer_vector())) {
op = spv::Op::OpConvertFToU;
} else if ((from_type->Is<sem::Bool>() && to_type->Is<sem::Bool>()) ||
(from_type->Is<sem::U32>() && to_type->Is<sem::U32>()) ||
(from_type->Is<sem::I32>() && to_type->Is<sem::I32>()) ||
(from_type->Is<sem::F32>() && to_type->Is<sem::F32>()) ||
(from_type->Is<sem::Vector>() && (from_type == to_type))) {
return val_id;
} else if ((from_type->Is<sem::I32>() && to_type->Is<sem::U32>()) ||
(from_type->Is<sem::U32>() && to_type->Is<sem::I32>()) ||
(from_type->is_signed_integer_vector() && to_type->is_unsigned_integer_vector()) ||
(from_type->is_unsigned_integer_vector() &&
to_type->is_integer_scalar_or_vector())) {
op = spv::Op::OpBitcast;
} else if ((from_type->is_numeric_scalar() && to_type->Is<sem::Bool>()) ||
(from_type->is_numeric_vector() && to_type->is_bool_vector())) {
// Convert scalar (vector) to bool (vector)
// Return the result of comparing from_expr with zero
uint32_t zero = GenerateConstantNullIfNeeded(from_type);
const auto* from_elem_type = elem_type_of(from_type);
op = from_elem_type->is_integer_scalar() ? spv::Op::OpINotEqual : spv::Op::OpFUnordNotEqual;
if (!push_function_inst(op, {Operand(result_type_id), Operand(result_id), Operand(val_id),
Operand(zero)})) {
return 0;
}
return result_id;
} else if (from_type->is_bool_scalar_or_vector() && to_type->is_numeric_scalar_or_vector()) {
// Convert bool scalar/vector to numeric scalar/vector.
// Use the bool to select between 1 (if true) and 0 (if false).
const auto* to_elem_type = elem_type_of(to_type);
uint32_t one_id;
uint32_t zero_id;
if (to_elem_type->Is<sem::F32>()) {
zero_id = GenerateConstantIfNeeded(ScalarConstant::F32(0));
one_id = GenerateConstantIfNeeded(ScalarConstant::F32(1));
} else if (to_elem_type->Is<sem::U32>()) {
zero_id = GenerateConstantIfNeeded(ScalarConstant::U32(0));
one_id = GenerateConstantIfNeeded(ScalarConstant::U32(1));
} else if (to_elem_type->Is<sem::I32>()) {
zero_id = GenerateConstantIfNeeded(ScalarConstant::I32(0));
one_id = GenerateConstantIfNeeded(ScalarConstant::I32(1));
} else {
error_ = "invalid destination type for bool conversion";
return false;
}
if (auto* to_vec = to_type->As<sem::Vector>()) {
// Splat the scalars into vectors.
zero_id = GenerateConstantVectorSplatIfNeeded(to_vec, zero_id);
one_id = GenerateConstantVectorSplatIfNeeded(to_vec, one_id);
}
if (!one_id || !zero_id) {
return false;
}
op = spv::Op::OpSelect;
if (!push_function_inst(op, {Operand(result_type_id), Operand(result_id), Operand(val_id),
Operand(one_id), Operand(zero_id)})) {
return 0;
}
return result_id;
} else {
TINT_ICE(Writer, builder_.Diagnostics()) << "Invalid from_type";
}
if (op == spv::Op::OpNop) {
error_ = "unable to determine conversion type for cast, from: " +
from_type->FriendlyName(builder_.Symbols()) +
" to: " + to_type->FriendlyName(builder_.Symbols());
return 0;
}
if (!push_function_inst(op, {Operand(result_type_id), result, Operand(val_id)})) {
return 0;
}
return result_id;
}
uint32_t Builder::GenerateLiteralIfNeeded(const ast::Variable* var,
const ast::LiteralExpression* lit) {
ScalarConstant constant;
auto* global = builder_.Sem().Get<sem::GlobalVariable>(var);
if (global && global->IsOverridable()) {
constant.is_spec_op = true;
constant.constant_id = global->ConstantId();
}
Switch(
lit,
[&](const ast::BoolLiteralExpression* l) {
constant.kind = ScalarConstant::Kind::kBool;
constant.value.b = l->value;
},
[&](const ast::IntLiteralExpression* i) {
switch (i->suffix) {
case ast::IntLiteralExpression::Suffix::kNone:
case ast::IntLiteralExpression::Suffix::kI:
constant.kind = ScalarConstant::Kind::kI32;
constant.value.i32 = static_cast<int32_t>(i->value);
return;
case ast::IntLiteralExpression::Suffix::kU:
constant.kind = ScalarConstant::Kind::kU32;
constant.value.u32 = static_cast<uint32_t>(i->value);
return;
}
},
[&](const ast::FloatLiteralExpression* f) {
switch (f->suffix) {
case ast::FloatLiteralExpression::Suffix::kNone:
case ast::FloatLiteralExpression::Suffix::kF:
constant.kind = ScalarConstant::Kind::kF32;
constant.value.f32 = static_cast<float>(f->value);
return;
case ast::FloatLiteralExpression::Suffix::kH:
error_ = "Type f16 is not completely implemented yet";
}
},
[&](Default) { error_ = "unknown literal type"; });
if (!error_.empty()) {
return false;
}
return GenerateConstantIfNeeded(constant);
}
uint32_t Builder::GenerateConstantIfNeeded(const sem::Constant& constant) {
if (constant.AllZero()) {
return GenerateConstantNullIfNeeded(constant.Type());
}
static constexpr size_t kOpsResultIdx = 1; // operand index of the result
auto& global_scope = scope_stack_[0];
auto gen_bool = [&](size_t element_idx) {
bool val = constant.Element<AInt>(element_idx);
return GenerateConstantIfNeeded(ScalarConstant::Bool(val));
};
auto gen_f32 = [&](size_t element_idx) {
auto val = f32(constant.Element<AFloat>(element_idx));
return GenerateConstantIfNeeded(ScalarConstant::F32(val.value));
};
auto gen_i32 = [&](size_t element_idx) {
auto val = i32(constant.Element<AInt>(element_idx));
return GenerateConstantIfNeeded(ScalarConstant::I32(val.value));
};
auto gen_u32 = [&](size_t element_idx) {
auto val = u32(constant.Element<AInt>(element_idx));
return GenerateConstantIfNeeded(ScalarConstant::U32(val.value));
};
auto gen_els = [&](std::vector<Operand>& ids, size_t start, size_t end, auto gen_el) {
for (size_t i = start; i < end; i++) {
auto id = gen_el(i);
if (!id) {
return false;
}
ids.emplace_back(id);
}
return true;
};
auto gen_vector = [&](const sem::Vector* ty, size_t start, size_t end) -> uint32_t {
auto type_id = GenerateTypeIfNeeded(ty);
if (!type_id) {
return 0;
}
std::vector<Operand> ops;
ops.reserve(end - start + 2);
ops.emplace_back(type_id);
ops.push_back(Operand(0u)); // Placeholder for the result ID
auto ok = Switch(
constant.ElementType(), //
[&](const sem::Bool*) { return gen_els(ops, start, end, gen_bool); }, //
[&](const sem::F32*) { return gen_els(ops, start, end, gen_f32); }, //
[&](const sem::I32*) { return gen_els(ops, start, end, gen_i32); }, //
[&](const sem::U32*) { return gen_els(ops, start, end, gen_u32); }, //
[&](Default) {
error_ = "unhandled constant element type: " + builder_.FriendlyName(ty);
return false;
});
if (!ok) {
return 0;
}
return utils::GetOrCreate(global_scope.type_ctor_to_id_, OperandListKey{ops},
[&]() -> uint32_t {
auto result = result_op();
ops[kOpsResultIdx] = result;
push_type(spv::Op::OpConstantComposite, std::move(ops));
return std::get<uint32_t>(result);
});
};
auto gen_matrix = [&](const sem::Matrix* m) -> uint32_t {
auto mat_type_id = GenerateTypeIfNeeded(m);
if (!mat_type_id) {
return 0;
}
std::vector<Operand> ops;
ops.reserve(m->columns() + 2);
ops.emplace_back(mat_type_id);
ops.push_back(Operand(0u)); // Placeholder for the result ID
for (size_t column_idx = 0; column_idx < m->columns(); column_idx++) {
size_t start = m->rows() * column_idx;
size_t end = m->rows() * (column_idx + 1);
auto column_id = gen_vector(m->ColumnType(), start, end);
if (!column_id) {
return 0;
}
ops.emplace_back(column_id);
}
return utils::GetOrCreate(global_scope.type_ctor_to_id_, OperandListKey{ops},
[&]() -> uint32_t {
auto result = result_op();
ops[kOpsResultIdx] = result;
push_type(spv::Op::OpConstantComposite, std::move(ops));
return std::get<uint32_t>(result);
});
};
return Switch(
constant.Type(), //
[&](const sem::Bool*) { return gen_bool(0); }, //
[&](const sem::F32*) { return gen_f32(0); }, //
[&](const sem::I32*) { return gen_i32(0); }, //
[&](const sem::U32*) { return gen_u32(0); }, //
[&](const sem::Vector* v) { return gen_vector(v, 0, constant.ElementCount()); }, //
[&](const sem::Matrix* m) { return gen_matrix(m); }, //
[&](Default) {
error_ = "unhandled constant type: " + builder_.FriendlyName(constant.Type());
return false;
});
}
uint32_t Builder::GenerateConstantIfNeeded(const ScalarConstant& constant) {
auto it = const_to_id_.find(constant);
if (it != const_to_id_.end()) {
return it->second;
}
uint32_t type_id = 0;
switch (constant.kind) {
case ScalarConstant::Kind::kU32: {
type_id = GenerateTypeIfNeeded(builder_.create<sem::U32>());
break;
}
case ScalarConstant::Kind::kI32: {
type_id = GenerateTypeIfNeeded(builder_.create<sem::I32>());
break;
}
case ScalarConstant::Kind::kF32: {
type_id = GenerateTypeIfNeeded(builder_.create<sem::F32>());
break;
}
case ScalarConstant::Kind::kBool: {
type_id = GenerateTypeIfNeeded(builder_.create<sem::Bool>());
break;
}
}
if (type_id == 0) {
return 0;
}
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
if (constant.is_spec_op) {
push_annot(spv::Op::OpDecorate, {Operand(result_id), U32Operand(SpvDecorationSpecId),
Operand(constant.constant_id)});
}
switch (constant.kind) {
case ScalarConstant::Kind::kU32: {
push_type(constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
{Operand(type_id), result, Operand(constant.value.u32)});
break;
}
case ScalarConstant::Kind::kI32: {
push_type(constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
{Operand(type_id), result, U32Operand(constant.value.i32)});
break;
}
case ScalarConstant::Kind::kF32: {
push_type(constant.is_spec_op ? spv::Op::OpSpecConstant : spv::Op::OpConstant,
{Operand(type_id), result, Operand(constant.value.f32)});
break;
}
case ScalarConstant::Kind::kBool: {
if (constant.value.b) {
push_type(
constant.is_spec_op ? spv::Op::OpSpecConstantTrue : spv::Op::OpConstantTrue,
{Operand(type_id), result});
} else {
push_type(
constant.is_spec_op ? spv::Op::OpSpecConstantFalse : spv::Op::OpConstantFalse,
{Operand(type_id), result});
}
break;
}
}
const_to_id_[constant] = result_id;
return result_id;
}
uint32_t Builder::GenerateConstantNullIfNeeded(const sem::Type* type) {
auto type_id = GenerateTypeIfNeeded(type);
if (type_id == 0) {
return 0;
}
return utils::GetOrCreate(const_null_to_id_, type, [&] {
auto result = result_op();
push_type(spv::Op::OpConstantNull, {Operand(type_id), result});
return std::get<uint32_t>(result);
});
}
uint32_t Builder::GenerateConstantVectorSplatIfNeeded(const sem::Vector* type, uint32_t value_id) {
auto type_id = GenerateTypeIfNeeded(type);
if (type_id == 0 || value_id == 0) {
return 0;
}
uint64_t key = (static_cast<uint64_t>(type->Width()) << 32) + value_id;
return utils::GetOrCreate(const_splat_to_id_, key, [&] {
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
OperandList ops;
ops.push_back(Operand(type_id));
ops.push_back(result);
for (uint32_t i = 0; i < type->Width(); i++) {
ops.push_back(Operand(value_id));
}
push_type(spv::Op::OpConstantComposite, ops);
const_splat_to_id_[key] = result_id;
return result_id;
});
}
uint32_t Builder::GenerateShortCircuitBinaryExpression(const ast::BinaryExpression* expr) {
auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
if (lhs_id == 0) {
return false;
}
// Get the ID of the basic block where control flow will diverge. It's the
// last basic block generated for the left-hand-side of the operator.
auto original_label_id = current_label_id_;
auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
if (type_id == 0) {
return 0;
}
auto merge_block = result_op();
auto merge_block_id = std::get<uint32_t>(merge_block);
auto block = result_op();
auto block_id = std::get<uint32_t>(block);
auto true_block_id = block_id;
auto false_block_id = merge_block_id;
// For a logical or we want to only check the RHS if the LHS is failed.
if (expr->IsLogicalOr()) {
std::swap(true_block_id, false_block_id);
}
if (!push_function_inst(spv::Op::OpSelectionMerge,
{Operand(merge_block_id), U32Operand(SpvSelectionControlMaskNone)})) {
return 0;
}
if (!push_function_inst(spv::Op::OpBranchConditional,
{Operand(lhs_id), Operand(true_block_id), Operand(false_block_id)})) {
return 0;
}
// Output block to check the RHS
if (!GenerateLabel(block_id)) {
return 0;
}
auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
if (rhs_id == 0) {
return 0;
}
// Get the block ID of the last basic block generated for the right-hand-side
// expression. That block will be an immediate predecessor to the merge block.
auto rhs_block_id = current_label_id_;
if (!push_function_inst(spv::Op::OpBranch, {Operand(merge_block_id)})) {
return 0;
}
// Output the merge block
if (!GenerateLabel(merge_block_id)) {
return 0;
}
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
if (!push_function_inst(spv::Op::OpPhi,
{Operand(type_id), result, Operand(lhs_id), Operand(original_label_id),
Operand(rhs_id), Operand(rhs_block_id)})) {
return 0;
}
return result_id;
}
uint32_t Builder::GenerateSplat(uint32_t scalar_id, const sem::Type* vec_type) {
// Create a new vector to splat scalar into
auto splat_vector = result_op();
auto* splat_vector_type = builder_.create<sem::Pointer>(vec_type, ast::StorageClass::kFunction,
ast::Access::kReadWrite);
push_function_var({Operand(GenerateTypeIfNeeded(splat_vector_type)), splat_vector,
U32Operand(ConvertStorageClass(ast::StorageClass::kFunction)),
Operand(GenerateConstantNullIfNeeded(vec_type))});
// Splat scalar into vector
auto splat_result = result_op();
OperandList ops;
ops.push_back(Operand(GenerateTypeIfNeeded(vec_type)));
ops.push_back(splat_result);
for (size_t i = 0; i < vec_type->As<sem::Vector>()->Width(); ++i) {
ops.push_back(Operand(scalar_id));
}
if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
return 0;
}
return std::get<uint32_t>(splat_result);
}
uint32_t Builder::GenerateMatrixAddOrSub(uint32_t lhs_id,
uint32_t rhs_id,
const sem::Matrix* type,
spv::Op op) {
// Example addition of two matrices:
// %31 = OpLoad %mat3v4float %m34
// %32 = OpLoad %mat3v4float %m34
// %33 = OpCompositeExtract %v4float %31 0
// %34 = OpCompositeExtract %v4float %32 0
// %35 = OpFAdd %v4float %33 %34
// %36 = OpCompositeExtract %v4float %31 1
// %37 = OpCompositeExtract %v4float %32 1
// %38 = OpFAdd %v4float %36 %37
// %39 = OpCompositeExtract %v4float %31 2
// %40 = OpCompositeExtract %v4float %32 2
// %41 = OpFAdd %v4float %39 %40
// %42 = OpCompositeConstruct %mat3v4float %35 %38 %41
auto* column_type = builder_.create<sem::Vector>(type->type(), type->rows());
auto column_type_id = GenerateTypeIfNeeded(column_type);
OperandList ops;
for (uint32_t i = 0; i < type->columns(); ++i) {
// Extract column `i` from lhs mat
auto lhs_column_id = result_op();
if (!push_function_inst(
spv::Op::OpCompositeExtract,
{Operand(column_type_id), lhs_column_id, Operand(lhs_id), Operand(i)})) {
return 0;
}
// Extract column `i` from rhs mat
auto rhs_column_id = result_op();
if (!push_function_inst(
spv::Op::OpCompositeExtract,
{Operand(column_type_id), rhs_column_id, Operand(rhs_id), Operand(i)})) {
return 0;
}
// Add or subtract the two columns
auto result = result_op();
if (!push_function_inst(op,
{Operand(column_type_id), result, lhs_column_id, rhs_column_id})) {
return 0;
}
ops.push_back(result);
}
// Create the result matrix from the added/subtracted column vectors
auto result_mat_id = result_op();
ops.insert(ops.begin(), result_mat_id);
ops.insert(ops.begin(), Operand(GenerateTypeIfNeeded(type)));
if (!push_function_inst(spv::Op::OpCompositeConstruct, ops)) {
return 0;
}
return std::get<uint32_t>(result_mat_id);
}
uint32_t Builder::GenerateBinaryExpression(const ast::BinaryExpression* expr) {
// There is special logic for short circuiting operators.
if (expr->IsLogicalAnd() || expr->IsLogicalOr()) {
return GenerateShortCircuitBinaryExpression(expr);
}
auto lhs_id = GenerateExpressionWithLoadIfNeeded(expr->lhs);
if (lhs_id == 0) {
return 0;
}
auto rhs_id = GenerateExpressionWithLoadIfNeeded(expr->rhs);
if (rhs_id == 0) {
return 0;
}
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
auto type_id = GenerateTypeIfNeeded(TypeOf(expr));
if (type_id == 0) {
return 0;
}
// Handle int and float and the vectors of those types. Other types
// should have been rejected by validation.
auto* lhs_type = TypeOf(expr->lhs)->UnwrapRef();
auto* rhs_type = TypeOf(expr->rhs)->UnwrapRef();
// Handle matrix-matrix addition and subtraction
if ((expr->IsAdd() || expr->IsSubtract()) && lhs_type->is_float_matrix() &&
rhs_type->is_float_matrix()) {
auto* lhs_mat = lhs_type->As<sem::Matrix>();
auto* rhs_mat = rhs_type->As<sem::Matrix>();
// This should already have been validated by resolver
if (lhs_mat->rows() != rhs_mat->rows() || lhs_mat->columns() != rhs_mat->columns()) {
error_ = "matrices must have same dimensionality for add or subtract";
return 0;
}
return GenerateMatrixAddOrSub(lhs_id, rhs_id, lhs_mat,
expr->IsAdd() ? spv::Op::OpFAdd : spv::Op::OpFSub);
}
// For vector-scalar arithmetic operations, splat scalar into a vector. We
// skip this for multiply as we can use OpVectorTimesScalar.
const bool is_float_scalar_vector_multiply =
expr->IsMultiply() && ((lhs_type->is_float_scalar() && rhs_type->is_float_vector()) ||
(lhs_type->is_float_vector() && rhs_type->is_float_scalar()));
if (expr->IsArithmetic() && !is_float_scalar_vector_multiply) {
if (lhs_type->Is<sem::Vector>() && rhs_type->is_numeric_scalar()) {
uint32_t splat_vector_id = GenerateSplat(rhs_id, lhs_type);
if (splat_vector_id == 0) {
return 0;
}
rhs_id = splat_vector_id;
rhs_type = lhs_type;
} else if (lhs_type->is_numeric_scalar() && rhs_type->Is<sem::Vector>()) {
uint32_t splat_vector_id = GenerateSplat(lhs_id, rhs_type);
if (splat_vector_id == 0) {
return 0;
}
lhs_id = splat_vector_id;
lhs_type = rhs_type;
}
}
bool lhs_is_float_or_vec = lhs_type->is_float_scalar_or_vector();
bool lhs_is_bool_or_vec = lhs_type->is_bool_scalar_or_vector();
bool lhs_is_integer_or_vec = lhs_type->is_integer_scalar_or_vector();
bool lhs_is_unsigned = lhs_type->is_unsigned_scalar_or_vector();
spv::Op op = spv::Op::OpNop;
if (expr->IsAnd()) {
if (lhs_is_integer_or_vec) {
op = spv::Op::OpBitwiseAnd;
} else if (lhs_is_bool_or_vec) {
op = spv::Op::OpLogicalAnd;
} else {
error_ = "invalid and expression";
return 0;
}
} else if (expr->IsAdd()) {
op = lhs_is_float_or_vec ? spv::Op::OpFAdd : spv::Op::OpIAdd;
} else if (expr->IsDivide()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFDiv;
} else if (lhs_is_unsigned) {
op = spv::Op::OpUDiv;
} else {
op = spv::Op::OpSDiv;
}
} else if (expr->IsEqual()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdEqual;
} else if (lhs_is_bool_or_vec) {
op = spv::Op::OpLogicalEqual;
} else if (lhs_is_integer_or_vec) {
op = spv::Op::OpIEqual;
} else {
error_ = "invalid equal expression";
return 0;
}
} else if (expr->IsGreaterThan()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdGreaterThan;
} else if (lhs_is_unsigned) {
op = spv::Op::OpUGreaterThan;
} else {
op = spv::Op::OpSGreaterThan;
}
} else if (expr->IsGreaterThanEqual()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdGreaterThanEqual;
} else if (lhs_is_unsigned) {
op = spv::Op::OpUGreaterThanEqual;
} else {
op = spv::Op::OpSGreaterThanEqual;
}
} else if (expr->IsLessThan()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdLessThan;
} else if (lhs_is_unsigned) {
op = spv::Op::OpULessThan;
} else {
op = spv::Op::OpSLessThan;
}
} else if (expr->IsLessThanEqual()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdLessThanEqual;
} else if (lhs_is_unsigned) {
op = spv::Op::OpULessThanEqual;
} else {
op = spv::Op::OpSLessThanEqual;
}
} else if (expr->IsModulo()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFRem;
} else if (lhs_is_unsigned) {
op = spv::Op::OpUMod;
} else {
op = spv::Op::OpSMod;
}
} else if (expr->IsMultiply()) {
if (lhs_type->is_integer_scalar_or_vector()) {
// If the left hand side is an integer then this _has_ to be OpIMul as
// there there is no other integer multiplication.
op = spv::Op::OpIMul;
} else if (lhs_type->is_float_scalar() && rhs_type->is_float_scalar()) {
// Float scalars multiply with OpFMul
op = spv::Op::OpFMul;
} else if (lhs_type->is_float_vector() && rhs_type->is_float_vector()) {
// Float vectors must be validated to be the same size and then use OpFMul
op = spv::Op::OpFMul;
} else if (lhs_type->is_float_scalar() && rhs_type->is_float_vector()) {
// Scalar * Vector we need to flip lhs and rhs types
// because OpVectorTimesScalar expects <vector>, <scalar>
std::swap(lhs_id, rhs_id);
op = spv::Op::OpVectorTimesScalar;
} else if (lhs_type->is_float_vector() && rhs_type->is_float_scalar()) {
// float vector * scalar
op = spv::Op::OpVectorTimesScalar;
} else if (lhs_type->is_float_scalar() && rhs_type->is_float_matrix()) {
// Scalar * Matrix we need to flip lhs and rhs types because
// OpMatrixTimesScalar expects <matrix>, <scalar>
std::swap(lhs_id, rhs_id);
op = spv::Op::OpMatrixTimesScalar;
} else if (lhs_type->is_float_matrix() && rhs_type->is_float_scalar()) {
// float matrix * scalar
op = spv::Op::OpMatrixTimesScalar;
} else if (lhs_type->is_float_vector() && rhs_type->is_float_matrix()) {
// float vector * matrix
op = spv::Op::OpVectorTimesMatrix;
} else if (lhs_type->is_float_matrix() && rhs_type->is_float_vector()) {
// float matrix * vector
op = spv::Op::OpMatrixTimesVector;
} else if (lhs_type->is_float_matrix() && rhs_type->is_float_matrix()) {
// float matrix * matrix
op = spv::Op::OpMatrixTimesMatrix;
} else {
error_ = "invalid multiply expression";
return 0;
}
} else if (expr->IsNotEqual()) {
if (lhs_is_float_or_vec) {
op = spv::Op::OpFOrdNotEqual;
} else if (lhs_is_bool_or_vec) {
op = spv::Op::OpLogicalNotEqual;
} else if (lhs_is_integer_or_vec) {
op = spv::Op::OpINotEqual;
} else {
error_ = "invalid not-equal expression";
return 0;
}
} else if (expr->IsOr()) {
if (lhs_is_integer_or_vec) {
op = spv::Op::OpBitwiseOr;
} else if (lhs_is_bool_or_vec) {
op = spv::Op::OpLogicalOr;
} else {
error_ = "invalid and expression";
return 0;
}
} else if (expr->IsShiftLeft()) {
op = spv::Op::OpShiftLeftLogical;
} else if (expr->IsShiftRight() && lhs_type->is_signed_scalar_or_vector()) {
// A shift right with a signed LHS is an arithmetic shift.
op = spv::Op::OpShiftRightArithmetic;
} else if (expr->IsShiftRight()) {
op = spv::Op::OpShiftRightLogical;
} else if (expr->IsSubtract()) {
op = lhs_is_float_or_vec ? spv::Op::OpFSub : spv::Op::OpISub;
} else if (expr->IsXor()) {
op = spv::Op::OpBitwiseXor;
} else {
error_ = "unknown binary expression";
return 0;
}
if (!push_function_inst(op, {Operand(type_id), result, Operand(lhs_id), Operand(rhs_id)})) {
return 0;
}
return result_id;
}
bool Builder::GenerateBlockStatement(const ast::BlockStatement* stmt) {
PushScope();
TINT_DEFER(PopScope());
return GenerateBlockStatementWithoutScoping(stmt);
}
bool Builder::GenerateBlockStatementWithoutScoping(const ast::BlockStatement* stmt) {
for (auto* block_stmt : stmt->statements) {
if (!GenerateStatement(block_stmt)) {
return false;
}
}
return true;
}
uint32_t Builder::GenerateCallExpression(const ast::CallExpression* expr) {
auto* call = builder_.Sem().Get<sem::Call>(expr);
auto* target = call->Target();
return Switch(
target, [&](const sem::Function* func) { return GenerateFunctionCall(call, func); },
[&](const sem::Builtin* builtin) { return GenerateBuiltinCall(call, builtin); },
[&](const sem::TypeConversion*) {
return GenerateTypeConstructorOrConversion(call, nullptr);
},
[&](const sem::TypeConstructor*) {
return GenerateTypeConstructorOrConversion(call, nullptr);
},
[&](Default) {
TINT_ICE(Writer, builder_.Diagnostics())
<< "unhandled call target: " << target->TypeInfo().name;
return 0;
});
}
uint32_t Builder::GenerateFunctionCall(const sem::Call* call, const sem::Function*) {
auto* expr = call->Declaration();
auto* ident = expr->target.name;
auto type_id = GenerateTypeIfNeeded(call->Type());
if (type_id == 0) {
return 0;
}
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
OperandList ops = {Operand(type_id), result};
auto func_id = func_symbol_to_id_[ident->symbol];
if (func_id == 0) {
error_ = "unable to find called function: " + builder_.Symbols().NameFor(ident->symbol);
return 0;
}
ops.push_back(Operand(func_id));
for (auto* arg : expr->args) {
auto id = GenerateExpressionWithLoadIfNeeded(arg);
if (id == 0) {
return 0;
}
ops.push_back(Operand(id));
}
if (!push_function_inst(spv::Op::OpFunctionCall, std::move(ops))) {
return 0;
}
return result_id;
}
uint32_t Builder::GenerateBuiltinCall(const sem::Call* call, const sem::Builtin* builtin) {
auto result = result_op();
auto result_id = std::get<uint32_t>(result);
auto result_type_id = GenerateTypeIfNeeded(builtin->ReturnType());
if (result_type_id == 0) {
return 0;
}
if (builtin->IsFineDerivative() || builtin->IsCoarseDerivative()) {
push_capability(SpvCapabilityDerivativeControl);
}
if (builtin->IsImageQuery()) {
push_capability(SpvCapabilityImageQuery);
}
if (builtin->IsTexture()) {
if (!GenerateTextureBuiltin(call, builtin, Operand(result_type_id), result)) {
return 0;
}
return result_id;
}
if (builtin->IsBarrier()) {
if (!GenerateControlBarrierBuiltin(builtin)) {
return 0;
}
return result_id;
}
if (builtin->IsAtomic()) {
if (!GenerateAtomicBuiltin(call, builtin, Operand(result_type_id), result)) {
return 0;
}
return result_id;
}
// Generates the SPIR-V ID for the expression for the indexed call argument,
// and loads it if necessary. Returns 0 on error.
auto get_arg_as_value_id = [&](size_t i, bool generate_load = true) -> uint32_t {
auto* arg = call->Arguments()[i];
auto* param = builtin->Parameters()[i];
auto val_id = GenerateExpression(arg->Declaration());
if (val_id == 0) {
return 0;
}
if (generate_load && !param->Type()->Is<sem::Pointer>()) {
val_id = GenerateLoadIfNeeded(arg->Type(), val_id);
}
return val_id;
};
OperandList params = {Operand(result_type_id), result};
spv::Op op = spv::Op::OpNop;
// Pushes the arguments for a GlslStd450 extended instruction, and sets op
// to OpExtInst.
auto glsl_std450 = [&](uint32_t inst_id) {
auto set_id = GetGLSLstd450Import();
params.push_back(Operand(set_id));
params.push_back(Operand(inst_id));