diff options
author | Ian Romanick <ian.d.romanick@intel.com> | 2017-09-27 18:42:32 -0700 |
---|---|---|
committer | Ian Romanick <ian.d.romanick@intel.com> | 2018-03-29 14:16:12 -0700 |
commit | 33553271efcb7adf37cbd192e262d98ab5697b47 (patch) | |
tree | d4f6d3e933a3f62029b0f55f30bb45cbe37287f8 | |
parent | d236d0d6f973e49c30ebabbfdcf1dc8b7254a187 (diff) |
glsl/spirv: Emit loads and stores
v2: The result type of an access chain is a pointer-to- the type of
the final thing dereferenced.
v3: Be sure not to overlap emitting the SpvOpAccessChain of an array
acces and the (potentialy) SpvOpAccessChain of the array index.
There's a bit of a hack here (noted with FIXME).
v4: Fix 'uint = int' and 'int = uint' stores. These need an OpBitcast
so that the types match exactly.
v5: Use SpvOpCompositeInsert instead of SpvOpVectorShuffle when the
write mask is scalar.
-rw-r--r-- | src/compiler/Makefile.glsl.am | 1 | ||||
-rw-r--r-- | src/compiler/glsl/spirv_visitor.cpp | 339 | ||||
-rw-r--r-- | src/compiler/glsl/spirv_visitor.h | 3 | ||||
-rw-r--r-- | src/compiler/glsl/tests/emit_spirv_access_chain_test.cpp | 808 | ||||
-rw-r--r-- | src/compiler/glsl/tests/meson.build | 9 | ||||
-rw-r--r-- | src/compiler/spirv/spirv_builder.h | 29 |
6 files changed, 1175 insertions, 14 deletions
diff --git a/src/compiler/Makefile.glsl.am b/src/compiler/Makefile.glsl.am index 2c2a3b7fa57..467b8191207 100644 --- a/src/compiler/Makefile.glsl.am +++ b/src/compiler/Makefile.glsl.am @@ -92,6 +92,7 @@ glsl_tests_general_ir_test_LDADD = \ glsl_tests_emit_spirv_test_SOURCES = \ glsl/tests/emit_spirv_test.h \ + glsl/tests/emit_spirv_access_chain_test.cpp \ glsl/tests/emit_spirv_constants_test.cpp \ glsl/tests/emit_spirv_entry_point_test.cpp \ glsl/tests/emit_spirv_functions_test.cpp \ diff --git a/src/compiler/glsl/spirv_visitor.cpp b/src/compiler/glsl/spirv_visitor.cpp index c6f50c8247f..fe4612b1789 100644 --- a/src/compiler/glsl/spirv_visitor.cpp +++ b/src/compiler/glsl/spirv_visitor.cpp @@ -25,6 +25,166 @@ #include "spirv/GLSL.std.450.h" #include "spirv/spirv_builder.h" +/** + * Generate the pointer operand used for SpvOpLoad or SpvOpStore + * + * Descends an \c ir_dereference squence to generate the pointer operand to be + * used for a \c SpvOpLoad or \c SpvOpStore instruction. If the deference is + * an array or record type, a \c SpvOpAccessChain will be emitted, and the + * result ID of the access chain will be returned. Otherwise the ID of the + * variable is returned. + * + * \note + * This function assumes that any types encountered during the dereference + * sequence will have already been emitted. No types are emitted by this + * function. + * + * \param deref "Root" of the \c ir_dereference tree. + * + * \param result_type_id ID of the type of the whole dereference tree. This + * is used during the recursion. If the first node encountered is an + * \c ir_dereference_array or \c ir_dereference_record, the type will be + * passed down the recursion. When the \c ir_dereference_variable is + * encounterd, seeing a non-zero value indicates that a \c SpvOpAccessChain + * should be started. As the recursion unwinds the other indexes of the + * access chain are emitted. + * + * \returns + * The ID of the \c SpvOpLoad or \c SpvOpStore operand. If the object being + * referenced is not an \c ir_variable (e.g., for an \c ir_dereference_array + * of an \c ir_constant array), zero is returned. + */ +unsigned +spirv_visitor::operand_pointer(const ir_dereference *deref, + const glsl_type *const result_type) +{ + unsigned id = 0; + + /* If this dereference determines the type of the whole dereference + * expression, it's type will be the result type of the SpvOpAccessChain. + * In SPIR-V, this is the "last index," but in GLSL IR it's the first + * encountered ir_dereference. + */ + const glsl_type *const type = result_type == NULL + ? deref->type : result_type; + + assert(type != NULL); + + switch (deref->ir_type) { + case ir_type_dereference_array: { + const ir_dereference_array *const array_deref = + deref->as_dereference_array(); + + const ir_dereference *const sub_deref = + array_deref->array->as_dereference(); + + /* If we never reach an ir_variable, bail. */ + if (sub_deref == NULL) + return 0; + + /* This must be done here. If the array index also, for example, needs + * to emit SpvOpLoad, it will start another SpvOpAccessChain. When + * begin_SpvOpAccessChain is called (again), the spirv_builder code will + * fail an assertion. + * + * However, this access chain can still fail. In the call, below, to + * operand_pointer, we may encounter something that isn't a variable. + * That will cause us to abort, but the array index instructions will + * still be written. When the array is processed again, the index + * instructions wil be written again. Save the current instruction + * pointer. If we bail, restore the old instruction pointer. + */ + const unsigned rollback = prog->functions.getpos(); + const unsigned index_id = get_result(array_deref->array_index); + + const unsigned var_id = operand_pointer(sub_deref, type); + if (var_id == 0) { + prog->functions.setpos(rollback); + return 0; + } + + /* The next index in the access chain is the calculated array index. */ + emit_SpvData_uint32(&prog->functions, index_id); + + id = var_id; + break; + } + + case ir_type_dereference_record: { + const ir_dereference_record *const record_deref = + deref->as_dereference_record(); + + const ir_dereference *const sub_deref = + record_deref->record->as_dereference(); + + /* If we never reach an ir_variable, bail. */ + if (sub_deref == NULL) + return 0; + + const unsigned var_id = operand_pointer(sub_deref, type); + if (var_id == 0) + return 0; + + /* The next index in the access chain is the member index of the field + * in the record. All of the index values are SpvIds, so the member + * index has to be wrapped in an ir_constant that is emitted as a + * SpvOpConstant. + */ + void *const mem_ctx = ralloc_parent(record_deref); + const ir_constant *const index = + new(mem_ctx) ir_constant(record_deref->field_idx); + + result_id = prog->emit_constant(index); + + emit_SpvData_uint32(&prog->functions, result_id); + + id = var_id; + break; + } + + case ir_type_dereference_variable: { + const ir_dereference_variable *const var_deref = + deref->as_dereference_variable(); + + const ir_variable *const var = var_deref->var; + + const unsigned var_id = prog->known_items.get_id(var); + + assert(var_id != 0); + + if (result_type != NULL) { + const unsigned pointer_type = + prog->emit_pointer_to_type(result_type, + glsl_to_spirv::storage_class(var)); + + assert(pointer_type != 0); + + id = prog->known_items.get_next_id(); + + begin_SpvOpAccessChain(&prog->functions, + pointer_type, + id, + var_id); + } else { + id = var_id; + } + + break; + } + + default: + unreachable("Not actually a dereference."); + } + + /* If result_type is NULL, then this must be the end of the dereference + * sequence, so the SpvOpAccessChain must be terminated. + */ + if (result_type == NULL && deref->ir_type != ir_type_dereference_variable) + end_SpvOpAccessChain(&prog->functions); + + return id; +} + void spirv_visitor::visit(ir_variable *) { @@ -62,33 +222,192 @@ spirv_visitor::visit(ir_swizzle *) } void -spirv_visitor::visit(ir_dereference_variable *) +spirv_visitor::visit(ir_dereference_variable *ir) { - success = false; + result_id = prog->known_items.get_next_id(); + + emit_SpvOpLoad(&prog->functions, + prog->known_items.get_id(ir->type), + result_id, + prog->known_items.get_id(ir->var), + SpvMemoryAccessMaskNone, + NULL); } void -spirv_visitor::visit(ir_dereference_array *) +spirv_visitor::visit(ir_dereference_array *ir) { - success = false; + const unsigned rhs_id = operand_pointer(ir); + + result_id = prog->known_items.get_next_id(); + + emit_SpvOpLoad(&prog->functions, + prog->known_items.get_id(ir->type), + result_id, + rhs_id, + SpvMemoryAccessMaskNone, NULL); } void -spirv_visitor::visit(ir_dereference_record *) +spirv_visitor::visit(ir_dereference_record *ir) { - success = false; + const unsigned rhs_id = operand_pointer(ir); + + result_id = prog->known_items.get_next_id(); + + emit_SpvOpLoad(&prog->functions, + prog->known_items.get_id(ir->type), + result_id, + rhs_id, + SpvMemoryAccessMaskNone, NULL); +} + +static bool +is_same_size_integer_type_conversion(const ir_rvalue *ir) +{ + const ir_expression *const expr = ir->as_expression(); + + if (expr == NULL) + return false; + + switch (expr->operation) { + case ir_unop_i2u: + case ir_unop_u2i: + case ir_unop_u642i64: + case ir_unop_i642u64: + return true; + + default: + return false; + } } void -spirv_visitor::visit(ir_assignment *) +spirv_visitor::visit(ir_assignment *ir) { - success = false; + /* No conditional assignments yet. */ + if (ir->condition != NULL) { + success = false; + return; + } + + /* Save a copy of this because result_id may be modified during the call to + * operand_pointer below. This can happen if there is an ir_expression or + * ir_dereference in an array index, for example. + */ + unsigned rhs_id = get_result(ir->rhs); + + if (is_same_size_integer_type_conversion(ir->rhs)) { + /* The source and destination of SpvOpStore must have the same type. In + * most of SPIR-V int and uint can be used interchangably, but that is + * not the case here. + */ + result_id = prog->known_items.get_next_id(); + + emit_SpvOpBitcast(&prog->functions, + prog->emit_type(ir->rhs->type), + result_id, + rhs_id); + + rhs_id = result_id; + } + + const unsigned num_components = ir->lhs->type->vector_elements; + const unsigned lhs_id = operand_pointer(ir->lhs); + + /* Masked writes are implemented by using a SpvOpShuffle to combine the + * original LHS and the RHS into a vector with the same size as the full + * LHS. This new vector is then writen to the entirty of the LHS. + * Something like + * + * vec4 x; + * x.yw = some_vec2; + * + * becomes + * + * x = shuffle(some_vec2, x, + * 2, // x.x + * 0, // some_vec2.x + * 4, // x.z + * 1); // some_vec2.y + */ + if (ir->lhs->type->is_vector() && + ir->write_mask != (1U << num_components) - 1) { + const unsigned lhs_load = prog->known_items.get_next_id(); + const unsigned lhs_type = prog->known_items.get_id(ir->lhs->type); + + emit_SpvOpLoad(&prog->functions, + lhs_type, + lhs_load, + lhs_id, + SpvMemoryAccessMaskNone, NULL); + + const unsigned shuffle_id = prog->known_items.get_next_id(); + + /* SpvOpVectorShuffle cannot have scalar operands, so it cannot be used + * when the RHS is a scalar. Use SpvOpCompositeInsert instead. This is + * also more compact. + */ + if (ir->rhs->type->is_scalar()) { + begin_SpvOpCompositeInsert(&prog->functions, + lhs_type, + shuffle_id, + rhs_id, + lhs_load); + + const int component = ffs(ir->write_mask); + + assert(component >= 1 && component <= 4); + + emit_SpvData_uint32(&prog->functions, component - 1); + + end_SpvOpCompositeInsert(&prog->functions); + } else { + const unsigned rhs_components = ir->rhs->type->vector_elements; + + begin_SpvOpVectorShuffle(&prog->functions, + lhs_type, + shuffle_id, + rhs_id, + lhs_load); + + unsigned next_rhs_element = 0; + for (unsigned i = 0; i < num_components; ++i) { + /* Depending on whether or not the component is to be written, + * select either an element from the source or the destination. + */ + if (ir->write_mask & (1U << i)) { + /* Consume elements of the real RHS in order. */ + emit_SpvData_uint32(&prog->functions, next_rhs_element++); + } else { + /* Match the ith element of the output with the ith element of + * the LHS. + */ + emit_SpvData_uint32(&prog->functions, rhs_components + i); + } + } + + /* If the IR is valid, we will have consumed all of the RHS + * components. + */ + assert(next_rhs_element == rhs_components); + + end_SpvOpVectorShuffle(&prog->functions); + } + + rhs_id = shuffle_id; + } + + emit_SpvOpStore(&prog->functions, + lhs_id, + rhs_id, + SpvMemoryAccessMaskNone, NULL); } void -spirv_visitor::visit(ir_constant *) +spirv_visitor::visit(ir_constant *ir) { - success = false; + result_id = prog->emit_constant(ir); } void diff --git a/src/compiler/glsl/spirv_visitor.h b/src/compiler/glsl/spirv_visitor.h index fa93f42ecf6..04fe8e8f08a 100644 --- a/src/compiler/glsl/spirv_visitor.h +++ b/src/compiler/glsl/spirv_visitor.h @@ -68,6 +68,9 @@ public: bool run(struct exec_list *instructions); + unsigned operand_pointer(const ir_dereference *deref, + const glsl_type *const result_type = NULL); + private: /** SPIR-V program collection that is being generated. */ glsl_to_spirv::_mesa_spirv_program *prog; diff --git a/src/compiler/glsl/tests/emit_spirv_access_chain_test.cpp b/src/compiler/glsl/tests/emit_spirv_access_chain_test.cpp new file mode 100644 index 00000000000..3524af9c976 --- /dev/null +++ b/src/compiler/glsl/tests/emit_spirv_access_chain_test.cpp @@ -0,0 +1,808 @@ +/* + * Copyright © 2017 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include <gtest/gtest.h> +#include "main/compiler.h" +#include "main/mtypes.h" +#include "main/macros.h" +#include "ir.h" +#include "ir_builder.h" +#include "util/hash_table.h" +#include "spirv/spirv.h" +#include "spirv/GLSL.std.450.h" +#include "spirv/spirv_capabilities.h" +#include "spirv/spirv_builder.h" +#include "emit_spirv_test.h" +#include "spirv_visitor.h" + +using namespace ir_builder; + +/** + * \file emit_spirv_access_chain_test.cpp + * Validate spirv_visitor::operand_pointer + */ + +class validate_emit_access_chain : public validate_emit_spirv { +public: + void SetUp(); + void TearDown(); + + ir_dereference_array *deref_array(const operand &op, int index) + { + return new(mem_ctx) ir_dereference_array(op.val, body.constant(index)); + } + + ir_dereference_record *deref_record(const operand &op, const char *field) + { + return new(mem_ctx) ir_dereference_record(op.val, field); + } + + ir_constant *make_constant(const glsl_type *type, unsigned base = 0) + { + ir_constant_data d; + + switch (type->base_type) { + case GLSL_TYPE_UINT: + case GLSL_TYPE_INT: + for (unsigned i = 0; i < type->components(); ++i) + d.u[i] = i + base; + + return new(mem_ctx) ir_constant(type, &d); + + case GLSL_TYPE_FLOAT: + for (unsigned i = 0; i < type->components(); ++i) + d.f[i] = i + base; + + return new(mem_ctx) ir_constant(type, &d); + + case GLSL_TYPE_ARRAY: { + exec_list init; + + for (unsigned i = 0; i < type->length; ++i) + init.push_tail(make_constant(type->fields.array, base + i)); + + return new(mem_ctx) ir_constant(type, &init); + } + + case GLSL_TYPE_STRUCT: { + exec_list init; + + for (unsigned i = 0; i < type->length; ++i) { + init.push_tail(make_constant(type->fields.structure[i].type, + base + i)); + } + + return new(mem_ctx) ir_constant(type, &init); + } + + default: + assert(!"Other types aren't supported."); + return NULL; + } + } + + exec_list instructions; + ir_factory body; + spirv_visitor *v; + gl_shader_stage stage; + const glsl_type *S1_type; + const glsl_type *S2_type; + const glsl_type *S3_type; +}; + + +void +validate_emit_access_chain::SetUp() +{ + validate_emit_spirv::SetUp(); + instructions.make_empty(); + + /* The parent TearDown destroys the memory context, and SetUp creates a new + * one. As a result, we have to bind the new mem_ctx to the ir_factory + * everytime this SetUp is called. + */ + body.mem_ctx = mem_ctx; + + stage = MESA_SHADER_VERTEX; + v = new spirv_visitor(spv, stage); + + const glsl_struct_field f1[] = { + { glsl_type::int_type, "i1" }, + { glsl_type::int_type, "i2" } + }; + + S1_type = glsl_type::get_record_instance(f1, ARRAY_SIZE(f1), "S1"); + + const glsl_struct_field f2[] = { + { S1_type, "s1" }, + { glsl_type::get_array_instance(glsl_type::float_type, 3), "f" } + }; + + S2_type = glsl_type::get_record_instance(f2, ARRAY_SIZE(f2), "S2"); + + const glsl_struct_field f3[] = { + { glsl_type::float_type, "f" }, + { S2_type, "s2" }, + { glsl_type::mat2_type, "m" } + }; + + S3_type = glsl_type::get_record_instance(f3, ARRAY_SIZE(f3), "S3"); +} + +void +validate_emit_access_chain::TearDown() +{ + delete v; + validate_emit_spirv::TearDown(); +} + +TEST_F(validate_emit_access_chain, deref_variable) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(glsl_type::float_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const unsigned var_id = spv->emit_variable(uni, stage); + + EXPECT_NE(0, var_id); + + const unsigned id = v->operand_pointer(deref(uni).val); + + EXPECT_EQ(var_id, id); + EXPECT_EQ(0, spv->functions.size_in_bytes()); +} + +TEST_F(validate_emit_access_chain, deref_array_variable) +{ + const glsl_type *const array3_float = + glsl_type::get_array_instance(glsl_type::float_type, 3); + + ir_variable *const uni = + new(mem_ctx) ir_variable(array3_float, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const int index = 2; + + const ir_dereference_array *const d = deref_array(uni, index); + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d); + const unsigned type_id = + spv->known_items.get_id(d->type, SpvStorageClassUniformConstant); + const unsigned index_id = spv->known_items.get_id(d->array_index->as_constant()); + + EXPECT_EQ(SpvStorageClassUniformConstant, + glsl_to_spirv::storage_class(uni)); + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, index_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00050041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + index_id + ); +} + +TEST_F(validate_emit_access_chain, deref_array_constant) +{ + const glsl_type *const array3_float = + glsl_type::get_array_instance(glsl_type::float_type, 3); + + ir_constant *const cnst = make_constant(array3_float); + + const int index = 2; + + const ir_dereference_array *const d = deref_array(cnst, index); + const unsigned cnst_id = spv->emit_constant(cnst); + const unsigned result_id = v->operand_pointer(d); + const unsigned type_id = spv->known_items.get_id(d->type); + + EXPECT_NE(0, cnst_id); + EXPECT_EQ(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(cnst_id, result_id); + + EXPECT_EQ(0, spv->functions.size_in_bytes()); +} + +TEST_F(validate_emit_access_chain, deref_record_variable) +{ + ir_variable *const uni = new(mem_ctx) ir_variable(S1_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const ir_dereference_record *const d = deref_record(uni, "i2"); + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d); + const unsigned type_id = + spv->known_items.get_id(d->type, SpvStorageClassUniformConstant); + const unsigned index_id = spv->known_items.get_id(body.constant(d->field_idx)); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, index_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00050041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + index_id + ); +} + +TEST_F(validate_emit_access_chain, deref_record_constant) +{ + ir_constant *const cnst = make_constant(S1_type); + + const ir_dereference_record *const d = deref_record(cnst, "i2"); + const unsigned var_id = spv->emit_constant(cnst); + const unsigned result_id = v->operand_pointer(d); + const unsigned type_id = spv->known_items.get_id(d->type); + + EXPECT_NE(0, var_id); + EXPECT_EQ(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(var_id, result_id); + + EXPECT_EQ(0, spv->functions.size_in_bytes()); +} + +TEST_F(validate_emit_access_chain, deref_array_array_variable) +{ + const glsl_type *const array3_float = + glsl_type::get_array_instance(glsl_type::float_type, 3); + + const glsl_type *const array5_array3_float = + glsl_type::get_array_instance(array3_float, 5); + + ir_variable *const uni = + new(mem_ctx) ir_variable(array5_array3_float, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const int inner = 2; + const int outer = 3; + + ir_dereference_array *const d_out = deref_array(uni, outer); + const ir_dereference_array *const d_in = deref_array(d_out, inner); + + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d_in); + const unsigned type_id = + spv->known_items.get_id(d_in->type, SpvStorageClassUniformConstant); + const unsigned inner_id = spv->known_items.get_id(d_in->array_index->as_constant()); + const unsigned outer_id = spv->known_items.get_id(d_out->array_index->as_constant()); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, inner_id); + EXPECT_NE(0, outer_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00060041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + outer_id, + inner_id + ); +} + +TEST_F(validate_emit_access_chain, deref_array_record_variable) +{ + const glsl_type *const array3_S = + glsl_type::get_array_instance(S1_type, 3); + + ir_variable *const uni = + new(mem_ctx) ir_variable(array3_S, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const int outer = 1; + + ir_dereference_array *const d_out = deref_array(uni, outer); + const ir_dereference_record *const d_in = deref_record(d_out, "i2"); + + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d_in); + const unsigned type_id = + spv->known_items.get_id(d_in->type, SpvStorageClassUniformConstant); + const unsigned inner_id = spv->known_items.get_id(body.constant(d_in->field_idx)); + const unsigned outer_id = spv->known_items.get_id(d_out->array_index->as_constant()); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, inner_id); + EXPECT_NE(0, outer_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00060041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + outer_id, + inner_id + ); +} + +TEST_F(validate_emit_access_chain, deref_record_record_variable) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(S2_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + ir_dereference_record *const d_out = deref_record(uni, "s1"); + const ir_dereference_record *const d_in = deref_record(d_out, "i2"); + + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d_in); + const unsigned type_id = + spv->known_items.get_id(d_in->type, SpvStorageClassUniformConstant); + const unsigned inner_id = spv->known_items.get_id(body.constant(d_in->field_idx)); + const unsigned outer_id = spv->known_items.get_id(body.constant(d_out->field_idx)); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, inner_id); + EXPECT_NE(0, outer_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00060041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + outer_id, + inner_id + ); +} + +TEST_F(validate_emit_access_chain, deref_record_array_variable) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(S2_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const unsigned inner = 1; + + ir_dereference_record *const d_out = deref_record(uni, "f"); + const ir_dereference_array *const d_in = deref_array(d_out, inner); + + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d_in); + const unsigned type_id = + spv->known_items.get_id(d_in->type, SpvStorageClassUniformConstant); + const unsigned inner_id = spv->known_items.get_id(d_in->array_index->as_constant()); + const unsigned outer_id = spv->known_items.get_id(body.constant(d_out->field_idx)); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, inner_id); + EXPECT_NE(0, outer_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00060041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + outer_id, + inner_id + ); +} + +TEST_F(validate_emit_access_chain, deref_record_matrix_variable) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(S3_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + const unsigned inner = 1; + + ir_dereference_record *const d_out = deref_record(uni, "m"); + const ir_dereference_array *const d_in = deref_array(d_out, inner); + + const unsigned var_id = spv->emit_variable(uni, stage); + const unsigned result_id = v->operand_pointer(d_in); + const unsigned type_id = + spv->known_items.get_id(d_in->type, SpvStorageClassUniformConstant); + const unsigned inner_id = spv->known_items.get_id(d_in->array_index->as_constant()); + const unsigned outer_id = spv->known_items.get_id(body.constant(d_out->field_idx)); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, result_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, inner_id); + EXPECT_NE(0, outer_id); + EXPECT_NE(var_id, result_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x00060041, /* SpvOpAccessChain */ + type_id, + result_id, + var_id, + outer_id, + inner_id + ); +} + +TEST_F(validate_emit_access_chain, constant_store_to_global) +{ + ir_variable *const var = + new(mem_ctx) ir_variable(glsl_type::vec4_type, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + + ir_assignment *const ir = assign(var, make_constant(var->type)); + + ir->accept(v); + + EXPECT_TRUE(get_success(v)); + + const unsigned type_id = spv->known_items.get_id(ir->rhs->type); + const unsigned rhs_id = spv->known_items.get_id(ir->rhs->as_constant()); + + EXPECT_NE(0, var_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, rhs_id); + EXPECT_NE(var_id, rhs_id); + + EXPECT_INSTRUCTIONS(spv->functions, + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + rhs_id, /* Source */ + /* Memory access - omitted */ + ); +} + +TEST_F(validate_emit_access_chain, load_uniform_store_to_global) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(glsl_type::vec4_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + ir_variable *const var = + new(mem_ctx) ir_variable(uni->type, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + const unsigned uni_id = spv->emit_variable(uni, stage); + + ir_assignment *const ir = assign(var, uni); + + ir->accept(v); + + EXPECT_TRUE(get_success(v)); + + const unsigned type_id = spv->known_items.get_id(ir->rhs->type); + + EXPECT_NE(0, var_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, uni_id); + EXPECT_NE(var_id, uni_id); + + VALIDATE_INSTRUCTIONS(spv->functions, + 0x0004003d, /* SpvOpLoad */ + type_id, /* Result type */ + uint32_t(-1), /* Result */ + uni_id, /* Pointer */ + /* Memory access - omitted */ + + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + uint32_t(-1) /* Source */ + /* Memory access - omitted */ + ); +} + +TEST_F(validate_emit_access_chain, load_uniform_array_store_to_global) +{ + const glsl_type *const array3_vec4 = + glsl_type::get_array_instance(glsl_type::vec4_type, 3); + + ir_variable *const uni = + new(mem_ctx) ir_variable(array3_vec4, "uni", ir_var_uniform); + + uni->data.location = 9; + + ir_variable *const var = + new(mem_ctx) ir_variable(array3_vec4->fields.array, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + const unsigned uni_id = spv->emit_variable(uni, stage); + + ir_assignment *const ir = assign(var, deref_array(uni, 2)); + + ir->accept(v); + + EXPECT_TRUE(get_success(v)); + + const unsigned type_id = spv->known_items.get_id(ir->rhs->type); + const unsigned pointer_type = + spv->known_items.get_id(ir->rhs->type, SpvStorageClassUniformConstant); + const unsigned index_id = spv->known_items.get_id(body.constant(int(2))); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, uni_id); + EXPECT_NE(0, index_id); + EXPECT_NE(var_id, uni_id); + + VALIDATE_INSTRUCTIONS(spv->functions, + 0x00050041, /* SpvOpAccessChain */ + pointer_type, /* Result type */ + uint32_t(-1), /* Result */ + uni_id, /* Base */ + index_id, /* Index */ + + 0x0004003d, /* SpvOpLoad */ + type_id, /* Result type */ + uint32_t(-2), /* Result */ + uint32_t(-1), /* Pointer */ + /* Memory access - omitted */ + + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + uint32_t(-2) /* Source */ + /* Memory access - omitted */ + ); +} + +TEST_F(validate_emit_access_chain, load_uniform_record_store_to_global) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(S1_type, "uni", ir_var_uniform); + + uni->data.location = 9; + + ir_variable *const var = + new(mem_ctx) ir_variable(glsl_type::int_type, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + const unsigned uni_id = spv->emit_variable(uni, stage); + + ir_assignment *const ir = assign(var, deref_record(uni, "i2")); + + ir->accept(v); + + EXPECT_TRUE(get_success(v)); + + const unsigned type_id = spv->known_items.get_id(ir->rhs->type); + const unsigned pointer_type = + spv->known_items.get_id(ir->rhs->type, SpvStorageClassUniformConstant); + const unsigned index_id = spv->known_items.get_id(body.constant(int(1))); + + EXPECT_NE(0, type_id); + EXPECT_NE(0, var_id); + EXPECT_NE(0, type_id); + EXPECT_NE(0, uni_id); + EXPECT_NE(0, index_id); + EXPECT_NE(var_id, uni_id); + + VALIDATE_INSTRUCTIONS(spv->functions, + 0x00050041, /* SpvOpAccessChain */ + pointer_type, /* Result Type */ + uint32_t(-1), /* Result */ + uni_id, /* Base */ + index_id, /* Index 1 */ + + 0x0004003d, /* SpvOpLoad */ + type_id, /* Result type */ + uint32_t(-2), /* Result */ + uint32_t(-1), /* Pointer */ + /* Memory access - omitted */ + + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + uint32_t(-2) /* Source */ + /* Memory access - omitted */ + ); +} + +TEST_F(validate_emit_access_chain, load_uniform_store_to_global_partial) +{ + ir_variable *const uni = + new(mem_ctx) ir_variable(glsl_type::vec2_type, + "uni", + ir_var_uniform); + + uni->data.location = 9; + + ir_variable *const var = + new(mem_ctx) ir_variable(glsl_type::vec4_type, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + const unsigned uni_id = spv->emit_variable(uni, stage); + + ir_assignment *const ir = assign(var, uni, WRITEMASK_Y | WRITEMASK_W); + + ir->accept(v); + + EXPECT_TRUE(get_success(v)); + + const unsigned vec2_id = spv->known_items.get_id(ir->rhs->type); + const unsigned vec4_id = spv->known_items.get_id(var->type); + + EXPECT_NE(0, var_id); + EXPECT_NE(0, vec2_id); + EXPECT_NE(0, vec4_id); + EXPECT_NE(0, uni_id); + EXPECT_NE(var_id, uni_id); + + VALIDATE_INSTRUCTIONS(spv->functions, + 0x0004003d, /* SpvOpLoad */ + vec2_id, /* Result type */ + uint32_t(-1), /* Result */ + uni_id, /* Pointer */ + /* Memory access - omitted */ + + 0x0004003d, /* SpvOpLoad */ + vec4_id, /* Result type */ + uint32_t(-2), /* Result */ + var_id, /* Pointer */ + /* Memory access - omitted */ + + 0x0009004f, /* SpvOpShuffle */ + vec4_id, /* Result type */ + uint32_t(-3), /* Result */ + uint32_t(-1), /* Vector 1 */ + uint32_t(-2), /* Vector 2 */ + 0x00000002, /* Component 0 */ + 0x00000000, /* Component 1 */ + 0x00000004, /* Component 2 */ + 0x00000001, /* Component 3 */ + + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + uint32_t(-3) /* Source */ + /* Memory access - omitted */ + ); +} + +TEST_F(validate_emit_access_chain, load_uniform_array_uniform_index_store_to_global) +{ + const glsl_type *const array3_vec4 = + glsl_type::get_array_instance(glsl_type::vec4_type, 3); + + ir_variable *const index = + new(mem_ctx) ir_variable(glsl_type::int_type, "index", ir_var_uniform); + + index->data.location = 9; + + ir_variable *const array = + new(mem_ctx) ir_variable(array3_vec4, "array", ir_var_uniform); + + array->data.location = 10; + + ir_variable *const var = + new(mem_ctx) ir_variable(array3_vec4->fields.array, + "v", + ir_var_auto); + + const unsigned var_id = spv->emit_variable(var, stage); + const unsigned array_id = spv->emit_variable(array, stage); + const unsigned index_id = spv->emit_variable(index, stage); + + ir_assignment *const ir = assign(var, + new(mem_ctx) ir_dereference_array(array, + deref(index).val)); + + const unsigned result_id = v->get_result(ir); + + EXPECT_TRUE(get_success(v)); + EXPECT_NE(0, result_id); + + const unsigned pointer_type = + spv->known_items.get_id(ir->rhs->type, SpvStorageClassUniformConstant); + const unsigned int_id = spv->known_items.get_id(index->type); + const unsigned vec4_id = spv->known_items.get_id(var->type); + + EXPECT_NE(0, var_id); + EXPECT_NE(0, int_id); + EXPECT_NE(0, vec4_id); + EXPECT_NE(0, spv->known_items.get_id(array3_vec4)); + EXPECT_NE(0, array_id); + EXPECT_NE(0, index_id); + EXPECT_NE(var_id, array_id); + EXPECT_NE(var_id, index_id); + EXPECT_NE(array_id, index_id); + + + VALIDATE_INSTRUCTIONS(spv->functions, + 0x0004003d, /* SpvOpLoad */ + int_id, /* Result type */ + uint32_t(-1), /* Result */ + index_id, /* Pointer */ + /* Memory access - omitted */ + + 0x00050041, /* SpvOpAccessChain */ + pointer_type, /* Result Type */ + uint32_t(-2), /* Result */ + array_id, /* Base */ + uint32_t(-1), /* Index 1 */ + + 0x0004003d, /* SpvOpLoad */ + vec4_id, /* Result type */ + uint32_t(-3), /* Result */ + uint32_t(-2), /* Pointer */ + /* Memory access - omitted */ + + 0x0003003e, /* SpvOpStore */ + var_id, /* Target */ + uint32_t(-3) /* Source */ + /* Memory access - omitted */ + ); +} diff --git a/src/compiler/glsl/tests/meson.build b/src/compiler/glsl/tests/meson.build index bb71f3efa53..017b1b04757 100644 --- a/src/compiler/glsl/tests/meson.build +++ b/src/compiler/glsl/tests/meson.build @@ -45,10 +45,11 @@ test( 'emit-spirv_test', executable( 'emit-spirv_test', - ['emit_spirv_test.h', 'emit_spirv_constants_test.cpp', - 'emit_spirv_header_test.cpp', 'emit_spirv_entry_point_test.cpp', - 'emit_spirv_functions_test.cpp', 'emit_spirv_types_test.cpp', - 'emit_spirv_variables_test.cpp', 'validate_spirv.cpp'], + ['emit_spirv_test.h', 'emit_spirv_access_chain_test.cpp', + 'emit_spirv_constants_test.cpp', 'emit_spirv_header_test.cpp', + 'emit_spirv_entry_point_test.cpp', 'emit_spirv_functions_test.cpp', + 'emit_spirv_types_test.cpp', 'emit_spirv_variables_test.cpp', + 'validate_spirv.cpp'], cpp_args : [cpp_vis_args, cpp_msvc_compat_args], include_directories : [inc_common, inc_compiler, inc_glsl, inc_spirv], link_with : [libglsl, libglsl_standalone, libglsl_util], diff --git a/src/compiler/spirv/spirv_builder.h b/src/compiler/spirv/spirv_builder.h index f82f00f2793..9522b7f4ee5 100644 --- a/src/compiler/spirv/spirv_builder.h +++ b/src/compiler/spirv/spirv_builder.h @@ -167,6 +167,35 @@ public: } /** + * Get the current position in the output stream. + * + * This can later be used as a parameter to \c setpos. + */ + unsigned getpos() const + { + return instruction; + } + + /** + * Set a new position in the output stream. + * + * The new position \b must be before the current position. This generally + * used to discard instructions that were written. + * + * \note + * This function does not reset the start / finish tracking, so it should + * not be called between \c start_instruction and \c finish_instruction. + */ + void setpos(unsigned new_pos) + { + assert(new_pos <= instruction); + + memset(&memory[new_pos], 0, (instruction - new_pos) * sizeof(uint32_t)); + + instruction = new_pos; + } + + /** * Set of capabilities associated with this program. * * The \c spirv_capability_set may be shared with other programs that will |