+
+static void
+nir_deref_instr_fixup_child_types(nir_deref_instr *parent)
+{
+ nir_foreach_use(use, &parent->dest.ssa) {
+ if (use->parent_instr->type != nir_instr_type_deref)
+ continue;
+
+ nir_deref_instr *child = nir_instr_as_deref(use->parent_instr);
+ switch (child->deref_type) {
+ case nir_deref_type_var:
+ unreachable("nir_deref_type_var cannot be a child");
+
+ case nir_deref_type_array:
+ case nir_deref_type_array_wildcard:
+ child->type = glsl_get_array_element(parent->type);
+ break;
+
+ case nir_deref_type_ptr_as_array:
+ child->type = parent->type;
+ break;
+
+ case nir_deref_type_struct:
+ child->type = glsl_get_struct_field(parent->type,
+ child->strct.index);
+ break;
+
+ case nir_deref_type_cast:
+ /* We stop the recursion here */
+ continue;
+ }
+
+ /* Recurse into children */
+ nir_deref_instr_fixup_child_types(child);
+ }
+}
+
+static bool
+is_trivial_array_deref_cast(nir_deref_instr *cast)
+{
+ assert(is_trivial_deref_cast(cast));
+
+ nir_deref_instr *parent = nir_src_as_deref(cast->parent);
+
+ if (parent->deref_type == nir_deref_type_array) {
+ return cast->cast.ptr_stride ==
+ glsl_get_explicit_stride(nir_deref_instr_parent(parent)->type);
+ } else if (parent->deref_type == nir_deref_type_ptr_as_array) {
+ return cast->cast.ptr_stride ==
+ nir_deref_instr_ptr_as_array_stride(parent);
+ } else {
+ return false;
+ }
+}
+
+static bool
+is_deref_ptr_as_array(nir_instr *instr)
+{
+ return instr->type == nir_instr_type_deref &&
+ nir_instr_as_deref(instr)->deref_type == nir_deref_type_ptr_as_array;
+}
+
+/**
+ * Remove casts that just wrap other casts.
+ */
+static bool
+opt_remove_cast_cast(nir_deref_instr *cast)
+{
+ nir_deref_instr *first_cast = cast;
+
+ while (true) {
+ nir_deref_instr *parent = nir_deref_instr_parent(first_cast);
+ if (parent == NULL || parent->deref_type != nir_deref_type_cast)
+ break;
+ first_cast = parent;
+ }
+ if (cast == first_cast)
+ return false;
+
+ nir_instr_rewrite_src(&cast->instr, &cast->parent,
+ nir_src_for_ssa(first_cast->parent.ssa));
+ return true;
+}
+
+static bool
+opt_remove_sampler_cast(nir_deref_instr *cast)
+{
+ assert(cast->deref_type == nir_deref_type_cast);
+ nir_deref_instr *parent = nir_src_as_deref(cast->parent);
+ if (parent == NULL)
+ return false;
+
+ /* Strip both types down to their non-array type and bail if there are any
+ * discrepancies in array lengths.
+ */
+ const struct glsl_type *parent_type = parent->type;
+ const struct glsl_type *cast_type = cast->type;
+ while (glsl_type_is_array(parent_type) && glsl_type_is_array(cast_type)) {
+ if (glsl_get_length(parent_type) != glsl_get_length(cast_type))
+ return false;
+ parent_type = glsl_get_array_element(parent_type);
+ cast_type = glsl_get_array_element(cast_type);
+ }
+
+ if (glsl_type_is_array(parent_type) || glsl_type_is_array(cast_type))
+ return false;
+
+ if (!glsl_type_is_sampler(parent_type) ||
+ cast_type != glsl_bare_sampler_type())
+ return false;
+
+ /* We're a cast from a more detailed sampler type to a bare sampler */
+ nir_ssa_def_rewrite_uses(&cast->dest.ssa,
+ nir_src_for_ssa(&parent->dest.ssa));
+ nir_instr_remove(&cast->instr);
+
+ /* Recursively crawl the deref tree and clean up types */
+ nir_deref_instr_fixup_child_types(parent);
+
+ return true;
+}
+
+/**
+ * Is this casting a struct to a contained struct.
+ * struct a { struct b field0 };
+ * ssa_5 is structa;
+ * deref_cast (structb *)ssa_5 (function_temp structb);
+ * converts to
+ * deref_struct &ssa_5->field0 (function_temp structb);
+ * This allows subsequent copy propagation to work.
+ */
+static bool
+opt_replace_struct_wrapper_cast(nir_builder *b, nir_deref_instr *cast)
+{
+ nir_deref_instr *parent = nir_src_as_deref(cast->parent);
+ if (!parent)
+ return false;
+
+ if (!glsl_type_is_struct(parent->type))
+ return false;
+
+ if (glsl_get_struct_field_offset(parent->type, 0) != 0)
+ return false;
+
+ if (cast->type != glsl_get_struct_field(parent->type, 0))
+ return false;
+
+ nir_deref_instr *replace = nir_build_deref_struct(b, parent, 0);
+ nir_ssa_def_rewrite_uses(&cast->dest.ssa, nir_src_for_ssa(&replace->dest.ssa));
+ nir_deref_instr_remove_if_unused(cast);
+ return true;
+}
+
+static bool
+opt_deref_cast(nir_builder *b, nir_deref_instr *cast)
+{
+ bool progress;
+
+ if (opt_replace_struct_wrapper_cast(b, cast))
+ return true;
+
+ if (opt_remove_sampler_cast(cast))
+ return true;
+
+ progress = opt_remove_cast_cast(cast);
+ if (!is_trivial_deref_cast(cast))
+ return progress;
+
+ bool trivial_array_cast = is_trivial_array_deref_cast(cast);
+
+ assert(cast->dest.is_ssa);
+ assert(cast->parent.is_ssa);
+
+ nir_foreach_use_safe(use_src, &cast->dest.ssa) {
+ /* If this isn't a trivial array cast, we can't propagate into
+ * ptr_as_array derefs.
+ */
+ if (is_deref_ptr_as_array(use_src->parent_instr) &&
+ !trivial_array_cast)
+ continue;
+
+ nir_instr_rewrite_src(use_src->parent_instr, use_src, cast->parent);
+ progress = true;
+ }
+
+ /* If uses would be a bit crazy */
+ assert(list_is_empty(&cast->dest.ssa.if_uses));
+
+ if (nir_deref_instr_remove_if_unused(cast))
+ progress = true;
+
+ return progress;
+}
+
+static bool
+opt_deref_ptr_as_array(nir_builder *b, nir_deref_instr *deref)
+{
+ assert(deref->deref_type == nir_deref_type_ptr_as_array);
+
+ nir_deref_instr *parent = nir_deref_instr_parent(deref);
+
+ if (nir_src_is_const(deref->arr.index) &&
+ nir_src_as_int(deref->arr.index) == 0) {
+ /* If it's a ptr_as_array deref with an index of 0, it does nothing
+ * and we can just replace its uses with its parent.
+ *
+ * The source of a ptr_as_array deref always has a deref_type of
+ * nir_deref_type_array or nir_deref_type_cast. If it's a cast, it
+ * may be trivial and we may be able to get rid of that too. Any
+ * trivial cast of trivial cast cases should be handled already by
+ * opt_deref_cast() above.
+ */
+ if (parent->deref_type == nir_deref_type_cast &&
+ is_trivial_deref_cast(parent))
+ parent = nir_deref_instr_parent(parent);
+ nir_ssa_def_rewrite_uses(&deref->dest.ssa,
+ nir_src_for_ssa(&parent->dest.ssa));
+ nir_instr_remove(&deref->instr);
+ return true;
+ }
+
+ if (parent->deref_type != nir_deref_type_array &&
+ parent->deref_type != nir_deref_type_ptr_as_array)
+ return false;
+
+ assert(parent->parent.is_ssa);
+ assert(parent->arr.index.is_ssa);
+ assert(deref->arr.index.is_ssa);
+
+ nir_ssa_def *new_idx = nir_iadd(b, parent->arr.index.ssa,
+ deref->arr.index.ssa);
+
+ deref->deref_type = parent->deref_type;
+ nir_instr_rewrite_src(&deref->instr, &deref->parent, parent->parent);
+ nir_instr_rewrite_src(&deref->instr, &deref->arr.index,
+ nir_src_for_ssa(new_idx));
+ return true;
+}
+
+bool
+nir_opt_deref_impl(nir_function_impl *impl)
+{
+ bool progress = false;
+
+ nir_builder b;
+ nir_builder_init(&b, impl);
+
+ nir_foreach_block(block, impl) {
+ nir_foreach_instr_safe(instr, block) {
+ if (instr->type != nir_instr_type_deref)
+ continue;
+
+ b.cursor = nir_before_instr(instr);
+
+ nir_deref_instr *deref = nir_instr_as_deref(instr);
+ switch (deref->deref_type) {
+ case nir_deref_type_ptr_as_array:
+ if (opt_deref_ptr_as_array(&b, deref))
+ progress = true;
+ break;
+
+ case nir_deref_type_cast:
+ if (opt_deref_cast(&b, deref))
+ progress = true;
+ break;
+
+ default:
+ /* Do nothing */
+ break;
+ }
+ }
+ }
+
+ if (progress) {
+ nir_metadata_preserve(impl, nir_metadata_block_index |
+ nir_metadata_dominance);
+ } else {
+ nir_metadata_preserve(impl, nir_metadata_all);
+ }
+
+ return progress;
+}
+
+bool
+nir_opt_deref(nir_shader *shader)
+{
+ bool progress = false;
+
+ nir_foreach_function(func, shader) {
+ if (func->impl && nir_opt_deref_impl(func->impl))
+ progress = true;
+ }
+
+ return progress;
+}