[PATCH vkd3d 4/6] vkd3d-shader/hlsl: Perform a copy propagation pass.
Giovanni Mascellani
gmascellani at codeweavers.com
Thu Nov 11 04:13:45 CST 2021
Signed-off-by: Giovanni Mascellani <gmascellani at codeweavers.com>
---
libs/vkd3d-shader/hlsl_codegen.c | 248 ++++++++++++++++++++++++++++++-
1 file changed, 247 insertions(+), 1 deletion(-)
diff --git a/libs/vkd3d-shader/hlsl_codegen.c b/libs/vkd3d-shader/hlsl_codegen.c
index a0154e3b..4b9e3113 100644
--- a/libs/vkd3d-shader/hlsl_codegen.c
+++ b/libs/vkd3d-shader/hlsl_codegen.c
@@ -237,6 +237,247 @@ static void replace_node(struct hlsl_ir_node *old, struct hlsl_ir_node *new)
hlsl_free_instr(old);
}
+/* The copy propagation pass scans the code trying to reconstruct, for
+ * each load, which is the store that last wrote to that
+ * variable. When this happens, the load can be replaced with the node
+ * from which the variable was stored.
+ *
+ * In order to do that, the pass keeps a map of all the variables it
+ * has already seen; for each variable, a pointer to a node and a
+ * component index is kept for each of the registers that belong to
+ * the variable. This means that, at that point of the scan, that
+ * register was last stored to from that component of that node. The
+ * pointer can be NULL if information for that register could not be
+ * gathered statically (either because a non-constant offset is used,
+ * or because control flow forces us to drop information).
+ *
+ * When scanning through a store, data for the stored-to variable is
+ * updated. When scanning through a load, it is checked if all the
+ * registers involved in the load come from a single node. In such
+ * case, the store can be replaced with a swizzle based on that
+ * node. */
+
+struct copy_propagation_value
+{
+ struct hlsl_ir_node *node;
+ unsigned int component;
+};
+
+struct copy_propagation_variable
+{
+ struct rb_entry entry;
+ struct hlsl_ir_var *var;
+ struct copy_propagation_value *values;
+};
+
+struct copy_propagation_state
+{
+ struct rb_tree variables;
+};
+
+static int copy_propagation_variable_compare(const void *key, const struct rb_entry *entry)
+{
+ struct copy_propagation_variable *variable = RB_ENTRY_VALUE(entry, struct copy_propagation_variable, entry);
+ uintptr_t key_int = (uintptr_t)key, entry_int = (uintptr_t)variable->var;
+
+ if (key_int < entry_int)
+ return -1;
+ else if (key_int > entry_int)
+ return 1;
+ else
+ return 0;
+}
+
+static void copy_propagation_variable_destroy(struct rb_entry *entry, void *context)
+{
+ struct copy_propagation_variable *variable = RB_ENTRY_VALUE(entry, struct copy_propagation_variable, entry);
+
+ vkd3d_free(variable);
+}
+
+static struct copy_propagation_variable *copy_propagation_get_variable(struct hlsl_ctx *ctx,
+ struct copy_propagation_state *state, struct hlsl_ir_var *var, bool create)
+{
+ struct rb_entry *entry = rb_get(&state->variables, var);
+ struct copy_propagation_variable *variable;
+ int res;
+
+ if (entry)
+ return RB_ENTRY_VALUE(entry, struct copy_propagation_variable, entry);
+
+ if (!create)
+ return NULL;
+
+ variable = hlsl_alloc(ctx, sizeof(*variable));
+ if (!variable)
+ return NULL;
+
+ variable->var = var;
+ variable->values = hlsl_alloc(ctx, sizeof(*variable->values) * var->data_type->reg_size);
+ if (!variable->values)
+ {
+ vkd3d_free(variable);
+ return NULL;
+ }
+
+ res = rb_put(&state->variables, var, &variable->entry);
+ assert(!res);
+
+ return variable;
+}
+
+static void copy_propagation_invalidate_whole_variable(struct copy_propagation_variable *variable)
+{
+ TRACE("invalidate variable %s\n", variable->var->name);
+ memset(variable->values, 0, sizeof(*variable->values) * variable->var->data_type->reg_size);
+}
+
+static void copy_propagation_set_value(struct copy_propagation_variable *variable, unsigned int offset,
+ unsigned char writemask, struct hlsl_ir_node *node)
+{
+ unsigned int i;
+
+ for (i = 0; i < 4; ++i)
+ {
+ if (writemask & (1u << i))
+ {
+ TRACE("variable %s[%d] is written by %p[%d]\n", variable->var->name, offset + i, node, i);
+ variable->values[offset + i].node = node;
+ variable->values[offset + i].component = i;
+ }
+ }
+}
+
+/* Check if locations [offset, offset+count) in variable were all
+ * written from the same node. If so return the node the corresponding
+ * swizzle, otherwise return NULL (because in that case copy
+ * propagation is impossible). */
+static struct hlsl_ir_node *copy_propagation_reconstruct_node(struct copy_propagation_variable *variable,
+ unsigned int offset, unsigned int count, unsigned int *swizzle)
+{
+ struct hlsl_ir_node *node = NULL;
+ unsigned int i;
+
+ assert(offset + count <= variable->var->data_type->reg_size);
+
+ *swizzle = 0;
+
+ for (i = 0; i < count; ++i)
+ {
+ if (!node)
+ node = variable->values[offset + i].node;
+ else if (node != variable->values[offset + i].node)
+ return NULL;
+ *swizzle |= variable->values[offset + i].component << (2 * i);
+ }
+
+ return node;
+}
+
+static bool copy_propagation_analyze_load(struct hlsl_ctx *ctx, struct hlsl_ir_load *load,
+ struct copy_propagation_state *state)
+{
+ struct hlsl_ir_node *node = &load->node, *new_node;
+ struct copy_propagation_variable *variable;
+ struct hlsl_type *type = node->data_type;
+ unsigned int offset, swizzle;
+ struct hlsl_deref *src = &load->src;
+ struct hlsl_ir_var *var = src->var;
+ struct hlsl_ir_swizzle *swizzle_node;
+
+ if (type->type != HLSL_CLASS_SCALAR && type->type != HLSL_CLASS_VECTOR)
+ return false;
+
+ if (!hlsl_offset_from_deref(src, &offset))
+ return false;
+
+ variable = copy_propagation_get_variable(ctx, state, var, false);
+ if (!variable)
+ return false;
+
+ new_node = copy_propagation_reconstruct_node(variable, offset, type->dimx, &swizzle);
+
+ TRACE("load from %s[%d-%d] reconstructed to %p[%u %u %u %u]\n", var->name, offset, offset + type->dimx,
+ new_node, swizzle % 4, (swizzle >> 2) % 4, (swizzle >> 4) % 4, (swizzle >> 6) % 4);
+
+ if (!new_node)
+ return false;
+
+ if (!(swizzle_node = hlsl_new_swizzle(ctx, swizzle, type->dimx, new_node, &node->loc)))
+ return false;
+ list_add_before(&node->entry, &swizzle_node->node.entry);
+
+ replace_node(node, &swizzle_node->node);
+
+ return true;
+}
+
+static void copy_propagation_record_store(struct hlsl_ctx *ctx, struct hlsl_ir_store *store,
+ struct copy_propagation_state *state)
+{
+ struct copy_propagation_variable *variable;
+ struct hlsl_deref *lhs = &store->lhs;
+ struct hlsl_ir_var *var = lhs->var;
+ unsigned int offset;
+
+ variable = copy_propagation_get_variable(ctx, state, var, true);
+ if (!variable)
+ return;
+
+ if (hlsl_offset_from_deref(lhs, &offset))
+ copy_propagation_set_value(variable, offset, store->writemask, store->rhs.node);
+ else
+ copy_propagation_invalidate_whole_variable(variable);
+}
+
+static bool copy_propagation_transform_block(struct hlsl_ctx *ctx, struct hlsl_block *block,
+ struct copy_propagation_state *state)
+{
+ struct hlsl_ir_node *instr, *next;
+ bool progress = false;
+
+ LIST_FOR_EACH_ENTRY_SAFE(instr, next, &block->instrs, struct hlsl_ir_node, entry)
+ {
+ switch (instr->type)
+ {
+ case HLSL_IR_LOAD:
+ progress |= copy_propagation_analyze_load(ctx, hlsl_ir_load(instr), state);
+ break;
+
+ case HLSL_IR_STORE:
+ copy_propagation_record_store(ctx, hlsl_ir_store(instr), state);
+ break;
+
+ case HLSL_IR_IF:
+ FIXME("Copy propagation doesn't support conditionals yet, leaving.\n");
+ return progress;
+
+ case HLSL_IR_LOOP:
+ FIXME("Copy propagation doesn't support loops yet, leaving.\n");
+ return progress;
+
+ default:
+ break;
+ }
+ }
+
+ return progress;
+}
+
+static bool copy_propagation_execute(struct hlsl_ctx *ctx, struct hlsl_block *block)
+{
+ struct copy_propagation_state state;
+ bool progress;
+
+ rb_init(&state.variables, copy_propagation_variable_compare);
+
+ progress = copy_propagation_transform_block(ctx, block, &state);
+
+ rb_destroy(&state.variables, copy_propagation_variable_destroy, NULL);
+
+ return progress;
+}
+
static bool is_vec1(const struct hlsl_type *type)
{
return (type->type == HLSL_CLASS_SCALAR) || (type->type == HLSL_CLASS_VECTOR && type->dimx == 1);
@@ -1390,7 +1631,12 @@ int hlsl_emit_dxbc(struct hlsl_ctx *ctx, struct hlsl_ir_function_decl *entry_fun
progress |= transform_ir(ctx, split_struct_copies, body, NULL);
}
while (progress);
- while (transform_ir(ctx, fold_constants, body, NULL));
+ do
+ {
+ progress = transform_ir(ctx, fold_constants, body, NULL);
+ progress |= copy_propagation_execute(ctx, body);
+ }
+ while (progress);
transform_ir(ctx, remove_trivial_swizzles, body, NULL);
if (ctx->profile->major_version < 4)
--
2.33.1
More information about the wine-devel
mailing list