summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Romanick <ian.d.romanick@intel.com>2017-09-27 18:42:32 -0700
committerIan Romanick <ian.d.romanick@intel.com>2018-03-29 14:16:12 -0700
commit33553271efcb7adf37cbd192e262d98ab5697b47 (patch)
treed4f6d3e933a3f62029b0f55f30bb45cbe37287f8
parentd236d0d6f973e49c30ebabbfdcf1dc8b7254a187 (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.am1
-rw-r--r--src/compiler/glsl/spirv_visitor.cpp339
-rw-r--r--src/compiler/glsl/spirv_visitor.h3
-rw-r--r--src/compiler/glsl/tests/emit_spirv_access_chain_test.cpp808
-rw-r--r--src/compiler/glsl/tests/meson.build9
-rw-r--r--src/compiler/spirv/spirv_builder.h29
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