--- /dev/null
+/*
+ * Copyright 2022 Alyssa Rosenzweig
+ * Copyright 2021 Collabora, Ltd.
+ * Copyright 2014 Valve Corporation
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "compiler.h"
+#include "agx_builder.h"
+
+#define XXH_INLINE_ALL
+#include "xxhash.h"
+
+/*
+ * This pass handles CSE'ing repeated expressions created in the process of
+ * translating from NIR. Also, currently this is intra-block only, to make it
+ * work over multiple block we'd need to bring forward dominance calculation.
+ */
+
+static inline uint32_t
+HASH(uint32_t hash, unsigned data)
+{
+ return XXH32(&data, sizeof(data), hash);
+}
+
+static uint32_t
+hash_index(uint32_t hash, agx_index index)
+{
+ assert(!index.kill && "CSE is run early");
+ assert(!index.cache && "CSE is run early");
+ assert(!index.discard && "CSE is run early");
+
+ hash = HASH(hash, index.value);
+ hash = HASH(hash, index.abs);
+ hash = HASH(hash, index.neg);
+ hash = HASH(hash, index.size);
+ hash = HASH(hash, index.type);
+ return hash;
+}
+
+/* Hash an ALU instruction. */
+static uint32_t
+hash_instr(const void *data)
+{
+ const agx_instr *I = data;
+ uint32_t hash = 0;
+
+ hash = HASH(hash, I->op);
+ hash = HASH(hash, I->nr_dests);
+ hash = HASH(hash, I->nr_srcs);
+
+ /* Explcitly skip destinations, except for size and type */
+ agx_foreach_dest(I, d) {
+ hash = HASH(hash, I->dest[d].type);
+ hash = HASH(hash, I->dest[d].size);
+ }
+
+ agx_foreach_src(I, s) {
+ hash = hash_index(hash, I->src[s]);
+ }
+
+ /* Explicitly skip last, scoreboard, nest */
+
+ hash = HASH(hash, I->imm);
+ hash = HASH(hash, I->perspective);
+ hash = HASH(hash, I->invert_cond);
+ hash = HASH(hash, I->dim);
+ hash = HASH(hash, I->offset);
+ hash = HASH(hash, I->shadow);
+ hash = HASH(hash, I->shift);
+ hash = HASH(hash, I->saturate);
+ hash = HASH(hash, I->mask);
+
+ return hash;
+}
+
+static bool
+instrs_equal(const void *_i1, const void *_i2)
+{
+ const agx_instr *i1 = _i1, *i2 = _i2;
+
+ if (i1->op != i2->op) return false;
+ if (i1->nr_srcs != i2->nr_srcs) return false;
+ if (i1->nr_dests != i2->nr_dests) return false;
+
+ /* Explicitly skip everything but size and type */
+ agx_foreach_dest(i1, d) {
+ if (i1->dest[d].type != i2->dest[d].type) return false;
+ if (i1->dest[d].size != i2->dest[d].size) return false;
+ }
+
+ agx_foreach_src(i1, s) {
+ agx_index s1 = i1->src[s], s2 = i2->src[s];
+
+ if (memcmp(&s1, &s2, sizeof(s1)) != 0)
+ return false;
+ }
+
+ if (i1->imm != i2->imm) return false;
+ if (i1->perspective != i2->perspective) return false;
+ if (i1->invert_cond != i2->invert_cond) return false;
+ if (i1->dim != i2->dim) return false;
+ if (i1->offset != i2->offset) return false;
+ if (i1->shadow != i2->shadow) return false;
+ if (i1->shift != i2->shift) return false;
+ if (i1->saturate != i2->saturate) return false;
+ if (i1->mask != i2->mask) return false;
+
+ return true;
+}
+
+/* Determines what instructions the above routines have to handle */
+static bool
+instr_can_cse(const agx_instr *I)
+{
+ return agx_opcodes_info[I->op].can_eliminate &&
+ agx_opcodes_info[I->op].can_reorder;
+}
+
+void
+agx_opt_cse(agx_context *ctx)
+{
+ struct set *instr_set = _mesa_set_create(NULL, hash_instr, instrs_equal);
+
+ agx_foreach_block(ctx, block) {
+ agx_index *replacement = calloc(sizeof(agx_index), ctx->alloc);
+ _mesa_set_clear(instr_set, NULL);
+
+ agx_foreach_instr_in_block(block, instr) {
+ /* Rewrite as we go so we converge locally in 1 iteration */
+ agx_foreach_ssa_src(instr, s) {
+ agx_index repl = replacement[instr->src[s].value];
+ if (!agx_is_null(repl))
+ agx_replace_src(instr, s, repl);
+ }
+
+ if (!instr_can_cse(instr))
+ continue;
+
+ bool found;
+ struct set_entry *entry =
+ _mesa_set_search_or_add(instr_set, instr, &found);
+ if (found) {
+ const agx_instr *match = entry->key;
+
+ agx_foreach_dest(instr, d) {
+ replacement[instr->dest[d].value] = match->dest[d];
+ }
+ }
+ }
+
+ free(replacement);
+ }
+
+ _mesa_set_destroy(instr_set, NULL);
+}