| // 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/writer/spirv/builder.h" |
| |
| #include <sstream> |
| #include <utility> |
| |
| #include "spirv/unified1/spirv.h" |
| #include "src/ast/assignment_statement.h" |
| #include "src/ast/binary_expression.h" |
| #include "src/ast/binding_decoration.h" |
| #include "src/ast/bool_literal.h" |
| #include "src/ast/builtin_decoration.h" |
| #include "src/ast/constructor_expression.h" |
| #include "src/ast/decorated_variable.h" |
| #include "src/ast/float_literal.h" |
| #include "src/ast/identifier_expression.h" |
| #include "src/ast/int_literal.h" |
| #include "src/ast/location_decoration.h" |
| #include "src/ast/return_statement.h" |
| #include "src/ast/scalar_constructor_expression.h" |
| #include "src/ast/set_decoration.h" |
| #include "src/ast/struct.h" |
| #include "src/ast/struct_member.h" |
| #include "src/ast/struct_member_offset_decoration.h" |
| #include "src/ast/type/array_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/u32_type.h" |
| #include "src/ast/type/vector_type.h" |
| #include "src/ast/type_constructor_expression.h" |
| #include "src/ast/uint_literal.h" |
| #include "src/ast/variable_decl_statement.h" |
| |
| namespace tint { |
| namespace writer { |
| namespace spirv { |
| namespace { |
| |
| uint32_t size_of(const std::vector<Instruction>& 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; |
| } |
| |
| } // namespace |
| |
| Builder::Builder() : scope_stack_({}) {} |
| |
| Builder::~Builder() = default; |
| |
| bool Builder::Build(const ast::Module& m) { |
| push_preamble(spv::Op::OpCapability, {Operand::Int(SpvCapabilityShader)}); |
| push_preamble(spv::Op::OpCapability, |
| {Operand::Int(SpvCapabilityVulkanMemoryModel)}); |
| |
| for (const auto& imp : m.imports()) { |
| GenerateImport(imp.get()); |
| } |
| |
| push_preamble(spv::Op::OpMemoryModel, |
| {Operand::Int(SpvAddressingModelLogical), |
| Operand::Int(SpvMemoryModelVulkanKHR)}); |
| |
| for (const auto& var : m.global_variables()) { |
| if (!GenerateGlobalVariable(var.get())) { |
| return false; |
| } |
| } |
| |
| for (const auto& func : m.functions()) { |
| if (!GenerateFunction(func.get())) { |
| return false; |
| } |
| } |
| |
| for (const auto& ep : m.entry_points()) { |
| if (!GenerateEntryPoint(ep.get())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| Operand Builder::result_op() { |
| return Operand::Int(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(preamble_); |
| 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 : preamble_) { |
| 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); |
| } |
| } |
| |
| bool Builder::GenerateAssignStatement(ast::AssignmentStatement* assign) { |
| auto lhs_id = GenerateExpression(assign->lhs()); |
| if (lhs_id == 0) { |
| return false; |
| } |
| auto rhs_id = GenerateExpression(assign->rhs()); |
| if (rhs_id == 0) { |
| return false; |
| } |
| |
| GenerateStore(lhs_id, rhs_id); |
| return true; |
| } |
| |
| bool Builder::GenerateEntryPoint(ast::EntryPoint* ep) { |
| auto name = ep->name(); |
| if (name.empty()) { |
| name = ep->function_name(); |
| } |
| |
| auto id = id_for_func_name(ep->function_name()); |
| if (id == 0) { |
| error_ = "unable to find ID for function: " + ep->function_name(); |
| return false; |
| } |
| |
| auto stage = pipeline_stage_to_execution_model(ep->stage()); |
| if (stage == SpvExecutionModelMax) { |
| error_ = "Unknown pipeline stage provided"; |
| return false; |
| } |
| |
| push_preamble(spv::Op::OpEntryPoint, |
| {Operand::Int(stage), Operand::Int(id), Operand::String(name)}); |
| |
| return true; |
| } |
| |
| uint32_t Builder::GenerateExpression(ast::Expression* expr) { |
| if (expr->IsBinary()) { |
| return GenerateBinaryExpression(expr->AsBinary()); |
| } |
| if (expr->IsConstructor()) { |
| return GenerateConstructorExpression(expr->AsConstructor(), false); |
| } |
| if (expr->IsIdentifier()) { |
| return GenerateIdentifierExpression(expr->AsIdentifier()); |
| } |
| |
| error_ = "unknown expression type"; |
| return 0; |
| } |
| |
| bool Builder::GenerateFunction(ast::Function* func) { |
| uint32_t func_type_id = GenerateFunctionTypeIfNeeded(func); |
| if (func_type_id == 0) { |
| return false; |
| } |
| |
| auto func_op = result_op(); |
| auto func_id = func_op.to_i(); |
| |
| push_debug(spv::Op::OpName, |
| {Operand::Int(func_id), Operand::String(func->name())}); |
| |
| auto ret_id = GenerateTypeIfNeeded(func->return_type()); |
| if (ret_id == 0) { |
| return false; |
| } |
| |
| // TODO(dsinclair): Handle parameters |
| |
| auto definition_inst = Instruction{ |
| spv::Op::OpFunction, |
| {Operand::Int(ret_id), func_op, Operand::Int(SpvFunctionControlMaskNone), |
| Operand::Int(func_type_id)}}; |
| std::vector<Instruction> params; |
| push_function(Function{definition_inst, result_op(), std::move(params)}); |
| |
| scope_stack_.push_scope(); |
| |
| for (const auto& stmt : func->body()) { |
| if (!GenerateStatement(stmt.get())) { |
| return false; |
| } |
| } |
| |
| scope_stack_.pop_scope(); |
| |
| func_name_to_id_[func->name()] = func_id; |
| return true; |
| } |
| |
| uint32_t Builder::GenerateFunctionTypeIfNeeded(ast::Function* func) { |
| auto val = type_name_to_id_.find(func->type_name()); |
| if (val != type_name_to_id_.end()) { |
| return val->second; |
| } |
| |
| auto func_op = result_op(); |
| auto func_type_id = func_op.to_i(); |
| |
| auto ret_id = GenerateTypeIfNeeded(func->return_type()); |
| if (ret_id == 0) { |
| return 0; |
| } |
| |
| // TODO(dsinclair): Handle parameters |
| push_type(spv::Op::OpTypeFunction, {func_op, Operand::Int(ret_id)}); |
| |
| type_name_to_id_[func->type_name()] = func_type_id; |
| return func_type_id; |
| } |
| |
| bool Builder::GenerateFunctionVariable(ast::Variable* var) { |
| uint32_t init_id = 0; |
| if (var->has_constructor()) { |
| init_id = GenerateExpression(var->constructor()); |
| if (init_id == 0) { |
| return false; |
| } |
| } |
| |
| if (var->is_const()) { |
| if (!var->has_constructor()) { |
| error_ = "missing constructor for constant"; |
| return false; |
| } |
| scope_stack_.set(var->name(), init_id); |
| return true; |
| } |
| |
| auto result = result_op(); |
| auto var_id = result.to_i(); |
| auto sc = ast::StorageClass::kFunction; |
| ast::type::PointerType pt(var->type(), sc); |
| auto type_id = GenerateTypeIfNeeded(&pt); |
| if (type_id == 0) { |
| return false; |
| } |
| |
| push_debug(spv::Op::OpName, |
| {Operand::Int(var_id), Operand::String(var->name())}); |
| |
| // TODO(dsinclair) We could detect if the constructor is fully const and emit |
| // an initializer value for the variable instead of doing the OpLoad. |
| |
| push_function_var( |
| {Operand::Int(type_id), result, Operand::Int(ConvertStorageClass(sc))}); |
| if (var->has_constructor()) { |
| GenerateStore(var_id, init_id); |
| } |
| |
| scope_stack_.set(var->name(), var_id); |
| |
| return true; |
| } |
| |
| void Builder::GenerateStore(uint32_t to, uint32_t from) { |
| push_function_inst(spv::Op::OpStore, {Operand::Int(to), Operand::Int(from)}); |
| } |
| |
| bool Builder::GenerateGlobalVariable(ast::Variable* var) { |
| uint32_t init_id = 0; |
| if (var->has_constructor()) { |
| if (!var->constructor()->IsConstructor()) { |
| error_ = "scalar constructor expected"; |
| return false; |
| } |
| |
| init_id = GenerateConstructorExpression(var->constructor()->AsConstructor(), |
| true); |
| if (init_id == 0) { |
| return false; |
| } |
| } |
| |
| if (var->is_const()) { |
| if (!var->has_constructor()) { |
| error_ = "missing constructor for constant"; |
| return false; |
| } |
| scope_stack_.set_global(var->name(), init_id); |
| return true; |
| } |
| |
| auto result = result_op(); |
| auto var_id = result.to_i(); |
| |
| auto sc = var->storage_class() == ast::StorageClass::kNone |
| ? ast::StorageClass::kPrivate |
| : var->storage_class(); |
| |
| ast::type::PointerType pt(var->type(), sc); |
| auto type_id = GenerateTypeIfNeeded(&pt); |
| if (type_id == 0) { |
| return false; |
| } |
| |
| push_debug(spv::Op::OpName, |
| {Operand::Int(var_id), Operand::String(var->name())}); |
| |
| std::vector<Operand> ops = {Operand::Int(type_id), result, |
| Operand::Int(ConvertStorageClass(sc))}; |
| if (var->has_constructor()) { |
| ops.push_back(Operand::Int(init_id)); |
| } |
| |
| push_type(spv::Op::OpVariable, std::move(ops)); |
| |
| if (var->IsDecorated()) { |
| for (const auto& deco : var->AsDecorated()->decorations()) { |
| if (deco->IsBuiltin()) { |
| push_debug(spv::Op::OpDecorate, |
| {Operand::Int(var_id), Operand::Int(SpvDecorationBuiltIn), |
| Operand::Int(ConvertBuiltin(deco->AsBuiltin()->value()))}); |
| } else if (deco->IsLocation()) { |
| push_debug(spv::Op::OpDecorate, |
| {Operand::Int(var_id), Operand::Int(SpvDecorationLocation), |
| Operand::Int(deco->AsLocation()->value())}); |
| } else if (deco->IsBinding()) { |
| push_debug(spv::Op::OpDecorate, |
| {Operand::Int(var_id), Operand::Int(SpvDecorationBinding), |
| Operand::Int(deco->AsBinding()->value())}); |
| } else if (deco->IsSet()) { |
| push_debug( |
| spv::Op::OpDecorate, |
| {Operand::Int(var_id), Operand::Int(SpvDecorationDescriptorSet), |
| Operand::Int(deco->AsSet()->value())}); |
| } else { |
| error_ = "unknown decoration"; |
| return false; |
| } |
| } |
| } |
| scope_stack_.set_global(var->name(), var_id); |
| return true; |
| } |
| |
| uint32_t Builder::GenerateIdentifierExpression( |
| ast::IdentifierExpression* expr) { |
| // TODO(dsinclair): handle names with namespaces in them ... |
| |
| uint32_t val = 0; |
| if (!scope_stack_.get(expr->name()[0], &val)) { |
| error_ = "unable to find name for identifier: " + expr->name()[0]; |
| return 0; |
| } |
| |
| return val; |
| } |
| |
| void Builder::GenerateImport(ast::Import* imp) { |
| auto result = result_op(); |
| auto id = result.to_i(); |
| |
| push_preamble(spv::Op::OpExtInstImport, |
| {result, Operand::String(imp->path())}); |
| |
| import_name_to_id_[imp->name()] = id; |
| } |
| |
| uint32_t Builder::GenerateConstructorExpression( |
| ast::ConstructorExpression* expr, |
| bool is_global_init) { |
| if (expr->IsScalarConstructor()) { |
| return GenerateLiteralIfNeeded(expr->AsScalarConstructor()->literal()); |
| } |
| if (expr->IsTypeConstructor()) { |
| auto init = expr->AsTypeConstructor(); |
| auto type_id = GenerateTypeIfNeeded(init->type()); |
| if (type_id == 0) { |
| return 0; |
| } |
| |
| std::ostringstream out; |
| out << "__const"; |
| |
| std::vector<Operand> ops; |
| bool constructor_is_const = true; |
| for (const auto& e : init->values()) { |
| if (!e->IsConstructor()) { |
| if (is_global_init) { |
| error_ = "constructor must be a constant expression"; |
| return 0; |
| } |
| constructor_is_const = false; |
| } |
| auto id = |
| GenerateConstructorExpression(e->AsConstructor(), is_global_init); |
| if (id == 0) { |
| return 0; |
| } |
| |
| out << "_" << id; |
| ops.push_back(Operand::Int(id)); |
| } |
| |
| auto str = out.str(); |
| auto val = const_to_id_.find(str); |
| if (val != const_to_id_.end()) { |
| return val->second; |
| } |
| |
| auto result = result_op(); |
| ops.insert(ops.begin(), result); |
| ops.insert(ops.begin(), Operand::Int(type_id)); |
| |
| const_to_id_[str] = result.to_i(); |
| |
| if (constructor_is_const) { |
| push_type(spv::Op::OpConstantComposite, ops); |
| } else { |
| push_function_inst(spv::Op::OpCompositeConstruct, ops); |
| } |
| return result.to_i(); |
| } |
| |
| error_ = "unknown constructor expression"; |
| return 0; |
| } |
| |
| uint32_t Builder::GenerateLiteralIfNeeded(ast::Literal* lit) { |
| auto type_id = GenerateTypeIfNeeded(lit->type()); |
| if (type_id == 0) { |
| return 0; |
| } |
| auto name = lit->name(); |
| auto val = const_to_id_.find(name); |
| if (val != const_to_id_.end()) { |
| return val->second; |
| } |
| |
| auto result = result_op(); |
| auto result_id = result.to_i(); |
| |
| if (lit->IsBool()) { |
| if (lit->AsBool()->IsTrue()) { |
| push_type(spv::Op::OpConstantTrue, {Operand::Int(type_id), result}); |
| } else { |
| push_type(spv::Op::OpConstantFalse, {Operand::Int(type_id), result}); |
| } |
| } else if (lit->IsInt()) { |
| push_type(spv::Op::OpConstant, {Operand::Int(type_id), result, |
| Operand::Int(lit->AsInt()->value())}); |
| } else if (lit->IsUint()) { |
| push_type(spv::Op::OpConstant, {Operand::Int(type_id), result, |
| Operand::Int(lit->AsUint()->value())}); |
| } else if (lit->IsFloat()) { |
| push_type(spv::Op::OpConstant, {Operand::Int(type_id), result, |
| Operand::Float(lit->AsFloat()->value())}); |
| } else { |
| error_ = "unknown literal type"; |
| return 0; |
| } |
| |
| const_to_id_[name] = result_id; |
| return result_id; |
| } |
| |
| uint32_t Builder::GenerateBinaryExpression(ast::BinaryExpression* expr) { |
| if (expr->IsAdd()) { |
| auto lhs_id = GenerateExpression(expr->lhs()); |
| if (lhs_id == 0) { |
| return 0; |
| } |
| auto rhs_id = GenerateExpression(expr->rhs()); |
| if (rhs_id == 0) { |
| return 0; |
| } |
| |
| auto result = result_op(); |
| auto result_id = result.to_i(); |
| |
| auto expr_type = expr->result_type(); |
| auto type_id = GenerateTypeIfNeeded(expr_type); |
| if (type_id == 0) { |
| return 0; |
| } |
| |
| // This handles int and float and the vectors of those types. Other types |
| // should have been rejected by validation. |
| spv::Op op = spv::Op::OpIAdd; |
| if (expr_type->IsF32() || |
| (expr_type->IsVector() && expr_type->AsVector()->type()->IsF32())) { |
| op = spv::Op::OpFAdd; |
| } |
| push_function_inst(op, {Operand::Int(type_id), result, Operand::Int(lhs_id), |
| Operand::Int(rhs_id)}); |
| |
| return result_id; |
| } |
| return 0; |
| } |
| |
| bool Builder::GenerateReturnStatement(ast::ReturnStatement* stmt) { |
| if (stmt->has_value()) { |
| auto val_id = GenerateExpression(stmt->value()); |
| if (val_id == 0) { |
| return false; |
| } |
| push_function_inst(spv::Op::OpReturnValue, {Operand::Int(val_id)}); |
| } else { |
| push_function_inst(spv::Op::OpReturn, {}); |
| } |
| |
| return true; |
| } |
| |
| bool Builder::GenerateStatement(ast::Statement* stmt) { |
| if (stmt->IsAssign()) { |
| return GenerateAssignStatement(stmt->AsAssign()); |
| } |
| if (stmt->IsReturn()) { |
| return GenerateReturnStatement(stmt->AsReturn()); |
| } |
| if (stmt->IsVariableDecl()) { |
| return GenerateVariableDeclStatement(stmt->AsVariableDecl()); |
| } |
| |
| error_ = "Unknown statement"; |
| return false; |
| } |
| |
| bool Builder::GenerateVariableDeclStatement(ast::VariableDeclStatement* stmt) { |
| return GenerateFunctionVariable(stmt->variable()); |
| } |
| |
| uint32_t Builder::GenerateTypeIfNeeded(ast::type::Type* type) { |
| if (type == nullptr) { |
| error_ = "attempting to generate type from null type"; |
| return 0; |
| } |
| |
| if (type->IsAlias()) { |
| return GenerateTypeIfNeeded(type->AsAlias()->type()); |
| } |
| |
| auto val = type_name_to_id_.find(type->type_name()); |
| if (val != type_name_to_id_.end()) { |
| return val->second; |
| } |
| |
| auto result = result_op(); |
| auto id = result.to_i(); |
| |
| if (type->IsArray()) { |
| if (!GenerateArrayType(type->AsArray(), result)) { |
| return 0; |
| } |
| } else if (type->IsBool()) { |
| push_type(spv::Op::OpTypeBool, {result}); |
| } else if (type->IsF32()) { |
| push_type(spv::Op::OpTypeFloat, {result, Operand::Int(32)}); |
| } else if (type->IsI32()) { |
| push_type(spv::Op::OpTypeInt, {result, Operand::Int(32), Operand::Int(1)}); |
| } else if (type->IsMatrix()) { |
| if (!GenerateMatrixType(type->AsMatrix(), result)) { |
| return 0; |
| } |
| } else if (type->IsPointer()) { |
| if (!GeneratePointerType(type->AsPointer(), result)) { |
| return 0; |
| } |
| } else if (type->IsStruct()) { |
| if (!GenerateStructType(type->AsStruct(), result)) { |
| return 0; |
| } |
| } else if (type->IsU32()) { |
| push_type(spv::Op::OpTypeInt, {result, Operand::Int(32), Operand::Int(0)}); |
| } else if (type->IsVector()) { |
| if (!GenerateVectorType(type->AsVector(), result)) { |
| return 0; |
| } |
| } else if (type->IsVoid()) { |
| push_type(spv::Op::OpTypeVoid, {result}); |
| } else { |
| error_ = "unable to convert type: " + type->type_name(); |
| return 0; |
| } |
| |
| type_name_to_id_[type->type_name()] = id; |
| return id; |
| } |
| |
| bool Builder::GenerateArrayType(ast::type::ArrayType* ary, |
| const Operand& result) { |
| auto elem_type = GenerateTypeIfNeeded(ary->type()); |
| if (elem_type == 0) { |
| return false; |
| } |
| |
| if (ary->IsRuntimeArray()) { |
| push_type(spv::Op::OpTypeRuntimeArray, {result, Operand::Int(elem_type)}); |
| } else { |
| ast::type::U32Type u32; |
| ast::IntLiteral ary_size(&u32, ary->size()); |
| auto len_id = GenerateLiteralIfNeeded(&ary_size); |
| if (len_id == 0) { |
| return false; |
| } |
| |
| push_type(spv::Op::OpTypeArray, |
| {result, Operand::Int(elem_type), Operand::Int(len_id)}); |
| } |
| return true; |
| } |
| |
| bool Builder::GenerateMatrixType(ast::type::MatrixType* mat, |
| const Operand& result) { |
| ast::type::VectorType col_type(mat->type(), mat->rows()); |
| auto col_type_id = GenerateTypeIfNeeded(&col_type); |
| if (has_error()) { |
| return false; |
| } |
| |
| push_type(spv::Op::OpTypeMatrix, |
| {result, Operand::Int(col_type_id), Operand::Int(mat->columns())}); |
| return true; |
| } |
| |
| bool Builder::GeneratePointerType(ast::type::PointerType* ptr, |
| const Operand& result) { |
| auto pointee_id = GenerateTypeIfNeeded(ptr->type()); |
| if (pointee_id == 0) { |
| return false; |
| } |
| |
| auto stg_class = ConvertStorageClass(ptr->storage_class()); |
| if (stg_class == SpvStorageClassMax) { |
| error_ = "invalid storage class for pointer"; |
| return false; |
| } |
| |
| push_type(spv::Op::OpTypePointer, |
| {result, Operand::Int(stg_class), Operand::Int(pointee_id)}); |
| |
| return true; |
| } |
| |
| bool Builder::GenerateStructType(ast::type::StructType* struct_type, |
| const Operand& result) { |
| auto struct_id = result.to_i(); |
| auto impl = struct_type->impl(); |
| |
| if (!struct_type->name().empty()) { |
| push_debug(spv::Op::OpName, |
| {Operand::Int(struct_id), Operand::String(struct_type->name())}); |
| } |
| |
| std::vector<Operand> ops; |
| ops.push_back(result); |
| |
| if (impl->decoration() == ast::StructDecoration::kBlock) { |
| push_annot(spv::Op::OpDecorate, |
| {Operand::Int(struct_id), Operand::Int(SpvDecorationBlock)}); |
| } else { |
| if (impl->decoration() != ast::StructDecoration::kNone) { |
| error_ = "unknown struct decoration"; |
| return false; |
| } |
| } |
| |
| auto& members = impl->members(); |
| for (uint32_t i = 0; i < members.size(); ++i) { |
| auto mem_id = GenerateStructMember(struct_id, i, members[i].get()); |
| if (mem_id == 0) { |
| return false; |
| } |
| |
| ops.push_back(Operand::Int(mem_id)); |
| } |
| |
| push_type(spv::Op::OpTypeStruct, std::move(ops)); |
| return true; |
| } |
| |
| uint32_t Builder::GenerateStructMember(uint32_t struct_id, |
| uint32_t idx, |
| ast::StructMember* member) { |
| push_debug(spv::Op::OpMemberName, {Operand::Int(struct_id), Operand::Int(idx), |
| Operand::String(member->name())}); |
| |
| for (const auto& deco : member->decorations()) { |
| if (deco->IsOffset()) { |
| push_annot(spv::Op::OpMemberDecorate, |
| {Operand::Int(struct_id), Operand::Int(idx), |
| Operand::Int(SpvDecorationOffset), |
| Operand::Int(deco->AsOffset()->offset())}); |
| } else { |
| error_ = "unknown struct member decoration"; |
| return 0; |
| } |
| } |
| |
| return GenerateTypeIfNeeded(member->type()); |
| } |
| |
| bool Builder::GenerateVectorType(ast::type::VectorType* vec, |
| const Operand& result) { |
| auto type_id = GenerateTypeIfNeeded(vec->type()); |
| if (has_error()) { |
| return false; |
| } |
| |
| push_type(spv::Op::OpTypeVector, |
| {result, Operand::Int(type_id), Operand::Int(vec->size())}); |
| return true; |
| } |
| |
| SpvStorageClass Builder::ConvertStorageClass(ast::StorageClass klass) const { |
| switch (klass) { |
| case ast::StorageClass::kInput: |
| return SpvStorageClassInput; |
| case ast::StorageClass::kOutput: |
| return SpvStorageClassOutput; |
| case ast::StorageClass::kUniform: |
| return SpvStorageClassUniform; |
| case ast::StorageClass::kWorkgroup: |
| return SpvStorageClassWorkgroup; |
| case ast::StorageClass::kUniformConstant: |
| return SpvStorageClassUniformConstant; |
| case ast::StorageClass::kStorageBuffer: |
| return SpvStorageClassStorageBuffer; |
| case ast::StorageClass::kImage: |
| return SpvStorageClassImage; |
| case ast::StorageClass::kPushConstant: |
| return SpvStorageClassPushConstant; |
| case ast::StorageClass::kPrivate: |
| return SpvStorageClassPrivate; |
| case ast::StorageClass::kFunction: |
| return SpvStorageClassFunction; |
| case ast::StorageClass::kNone: |
| break; |
| } |
| return SpvStorageClassMax; |
| } |
| |
| SpvBuiltIn Builder::ConvertBuiltin(ast::Builtin builtin) const { |
| switch (builtin) { |
| case ast::Builtin::kPosition: |
| return SpvBuiltInPosition; |
| case ast::Builtin::kVertexIdx: |
| return SpvBuiltInVertexIndex; |
| case ast::Builtin::kInstanceIdx: |
| return SpvBuiltInInstanceIndex; |
| case ast::Builtin::kFrontFacing: |
| return SpvBuiltInFrontFacing; |
| case ast::Builtin::kFragCoord: |
| return SpvBuiltInFragCoord; |
| case ast::Builtin::kFragDepth: |
| return SpvBuiltInFragDepth; |
| case ast::Builtin::kNumWorkgroups: |
| return SpvBuiltInNumWorkgroups; |
| case ast::Builtin::kWorkgroupSize: |
| return SpvBuiltInWorkgroupSize; |
| case ast::Builtin::kLocalInvocationId: |
| return SpvBuiltInLocalInvocationId; |
| case ast::Builtin::kLocalInvocationIdx: |
| return SpvBuiltInLocalInvocationIndex; |
| case ast::Builtin::kGlobalInvocationId: |
| return SpvBuiltInGlobalInvocationId; |
| case ast::Builtin::kNone: |
| break; |
| } |
| return SpvBuiltInMax; |
| } |
| |
| } // namespace spirv |
| } // namespace writer |
| } // namespace tint |