On Fri, Apr 17, 2020 at 6:57 PM Zebediah Figura zfigura@codeweavers.com wrote:
On 4/17/20 2:03 AM, Matteo Bruni wrote:
On Wed, Apr 15, 2020 at 2:41 AM Zebediah Figura z.figura12@gmail.com wrote:
From: Zebediah Figura zfigura@codeweavers.com
Signed-off-by: Zebediah Figura z.figura12@gmail.com
aaa625217, and the approach it implies, works, but on further reflection it's still not very pretty. For example, the following line of HLSL:
var.a.b = 2.0;
produces the following IR:
2: 2.0 3: deref(var) 4: @3.b 5: @4.c = @2
This essentially works, provided that the codegen layer knows how to unwind a deref chain, but it's pretty janky. Node 4 implies we're reading from node 3, and node 3 implies we're reading from var, neither of which is actually happening. The RA pass can't easily be smart enough to recognize that 4 doesn't need to read from 3, which is a problem if we introduce a "maybe uninitialized" warning.
Moreover, if we introduce DCE, we can't DCE out node 3 because of node 4, and while we could DCE out node 4 in terms of removing it from the list, we can't actually destroy the node, since node 5 is using it. Having nodes not in the list is also janky, I feel.
With this patch we instead get the following IR:
2: 2.0 3: deref(var) 4: @3.b 5: @4.c 6: deref(var).b.c = @2
We still get redundant reads, but at least this time we can easily DCE them out. Needing less sanity checks in RA is also nice.
dlls/d3dcompiler_43/d3dcompiler_private.h | 21 +++++- dlls/d3dcompiler_43/hlsl.y | 28 +++++--- dlls/d3dcompiler_43/utils.c | 83 ++++++++++++++++------- 3 files changed, 97 insertions(+), 35 deletions(-)
Hi Zeb,
after reading it again and actually getting into "review mode", I have some more thoughts... Sorry for not thinking of this earlier. See below.
diff --git a/dlls/d3dcompiler_43/d3dcompiler_private.h b/dlls/d3dcompiler_43/d3dcompiler_private.h index 1f1d8e26637..8f81271383b 100644 --- a/dlls/d3dcompiler_43/d3dcompiler_private.h +++ b/dlls/d3dcompiler_43/d3dcompiler_private.h @@ -873,10 +873,29 @@ struct hlsl_ir_deref struct hlsl_deref src; };
+struct hlsl_lvalue +{
- enum hlsl_ir_deref_type type;
- union
- {
struct hlsl_ir_var *var;
struct
{
struct hlsl_lvalue *array;
struct hlsl_ir_node *index;
} array;
struct
{
struct hlsl_lvalue *record;
struct hlsl_struct_field *field;
} record;
- } v;
+};
- struct hlsl_ir_assignment { struct hlsl_ir_node node;
- struct hlsl_deref lhs;
- struct hlsl_lvalue lhs; struct hlsl_ir_node *rhs; unsigned char writemask; };
diff --git a/dlls/d3dcompiler_43/hlsl.y b/dlls/d3dcompiler_43/hlsl.y index d3ef18bb6e9..5366181f356 100644 --- a/dlls/d3dcompiler_43/hlsl.y +++ b/dlls/d3dcompiler_43/hlsl.y @@ -2642,16 +2642,16 @@ static unsigned int index_instructions(struct list *instrs, unsigned int index) }
/* Walk the chain of derefs and retrieve the actual variable we care about. */ -static struct hlsl_ir_var *hlsl_var_from_deref(const struct hlsl_deref *deref) +static struct hlsl_ir_var *hlsl_var_from_lvalue(const struct hlsl_lvalue *deref) { switch (deref->type) { case HLSL_IR_DEREF_VAR: return deref->v.var; case HLSL_IR_DEREF_ARRAY:
return hlsl_var_from_deref(&deref_from_node(deref->v.array.array)->src);
return hlsl_var_from_lvalue(deref->v.array.array); case HLSL_IR_DEREF_RECORD:
return hlsl_var_from_deref(&deref_from_node(deref->v.record.record)->src);
return hlsl_var_from_lvalue(deref->v.record.record); } assert(0); return NULL;
@@ -2674,7 +2674,7 @@ static void compute_liveness_recurse(struct list *instrs, unsigned int loop_firs case HLSL_IR_ASSIGNMENT: { struct hlsl_ir_assignment *assignment = assignment_from_node(instr);
var = hlsl_var_from_deref(&assignment->lhs);
var = hlsl_var_from_lvalue(&assignment->lhs); if (!var->first_write) var->first_write = loop_first ? min(instr->index, loop_first) : instr->index; assignment->rhs->last_read = instr->index;
@@ -2693,10 +2693,22 @@ static void compute_liveness_recurse(struct list *instrs, unsigned int loop_firs case HLSL_IR_DEREF: { struct hlsl_ir_deref *deref = deref_from_node(instr);
var = hlsl_var_from_deref(&deref->src);
var->last_read = loop_last ? max(instr->index, loop_last) : instr->index;
if (deref->src.type == HLSL_IR_DEREF_ARRAY)
deref->src.v.array.index->last_read = instr->index;
switch (deref->src.type)
{
case HLSL_IR_DEREF_VAR:
deref->src.v.var->last_read = loop_last ? max(instr->index, loop_last) : instr->index;
break;
case HLSL_IR_DEREF_ARRAY:
deref->src.v.array.array->last_read = instr->index;
deref->src.v.array.index->last_read = instr->index;
break;
case HLSL_IR_DEREF_RECORD:
deref->src.v.record.record->last_read = instr->index;
break;
} break; } case HLSL_IR_EXPR:
diff --git a/dlls/d3dcompiler_43/utils.c b/dlls/d3dcompiler_43/utils.c index d24341329d3..519f50612e8 100644 --- a/dlls/d3dcompiler_43/utils.c +++ b/dlls/d3dcompiler_43/utils.c @@ -1478,27 +1478,41 @@ static unsigned int invert_swizzle(unsigned int *swizzle, unsigned int writemask return new_writemask; }
-static BOOL validate_lhs_deref(const struct hlsl_ir_node *lhs) +static BOOL get_assignment_lhs(struct hlsl_lvalue *dst, struct hlsl_ir_node *node) { struct hlsl_ir_deref *deref;
- if (lhs->type != HLSL_IR_DEREF)
- if (node->type != HLSL_IR_DEREF) {
hlsl_report_message(lhs->loc, HLSL_LEVEL_ERROR, "invalid lvalue");
hlsl_report_message(node->loc, HLSL_LEVEL_ERROR, "invalid lvalue"); return FALSE; }
- deref = deref_from_node(lhs);
- deref = deref_from_node(node);
- dst->type = deref->src.type;
- if (deref->src.type == HLSL_IR_DEREF_VAR)
return TRUE;
- if (deref->src.type == HLSL_IR_DEREF_ARRAY)
return validate_lhs_deref(deref->src.v.array.array);
- if (deref->src.type == HLSL_IR_DEREF_RECORD)
return validate_lhs_deref(deref->src.v.record.record);
- switch (deref->src.type)
- {
case HLSL_IR_DEREF_VAR:
dst->v.var = deref->src.v.var;
return TRUE;
- assert(0);
- return FALSE;
case HLSL_IR_DEREF_ARRAY:
dst->v.array.index = deref->src.v.array.index;
if (!(dst->v.array.array = d3dcompiler_alloc(sizeof(*dst->v.array.array))))
return FALSE;
return get_assignment_lhs(dst->v.array.array, deref->src.v.array.array);
case HLSL_IR_DEREF_RECORD:
dst->v.record.field = deref->src.v.record.field;
if (!(dst->v.record.record = d3dcompiler_alloc(sizeof(*dst->v.record.field))))
return FALSE;
return get_assignment_lhs(dst->v.record.record, deref->src.v.record.record);
default:
assert(0);
return FALSE;
- } }
I'm somewhat annoyed by this, it feels like we could directly create lvalue nodes while parsing... Except there doesn't seem to be any simple way. So I'm going to try and keep my initial knee-jerk reaction at bay.
On the other hand, I think it would be nicer to enforce a flat IR structure. Instead of having an arbitrarily complex lvalue inside the assignment instruction, maybe it should have the same structure of a deref, where each "level" is a separate instruction. This is probably why I was thinking of a flag rather than a separate node type: when constructing the assignment you would traverse the lhs derefs and turn them into lvalues. Although having a separate node type and changing that works as well (and it's probably nicer with liveness computation and other passes).
Does that sound reasonable? Am I missing anything?
Well, note that one problem is we kind of have to (recursively) dup the lvalue anyway, so that complex assignments work.
I see your point. I guess a possibility opened by the flat derefs is that you could share intermediate derefs, although that might well make things more complicated for no benefit.
I think I understand why flattening is desirable in general, but it's not clear to me how that would apply to lvalues (or even to rvalue deref chains, honestly), since at codegen time we essentially resolve arbitrarily complex deref chains into one register (well, except for non-constant array indexing...)
True, but the same can be said in principle for any other instruction. Also if you always "go flat" you have only one way to follow sources, instead of having to mix tree traversing inside a single instruction with instruction traversing. Relatedly, if we start doing more invasive IR transformations, having simple, "atomic" instructions might make things easier. E.g. with structure splitting, I think you'd end up with replacing one instruction (as in one instruction list entry) without changing the rest of the IR, while in the other case you have to replace a piece of a larger instruction which might be buried arbitrarily deep in the tree. Admittedly this is all very abstract and I might not be seeing a sensible overall picture.
It's also not obvious to me that's prettier to flatten it, given on the other hand we're also now inserting nodes into the IR list that would presumably not participate in RA or generate code.
Right, but that's a bit like doing an optimization right while parsing. I guess we want lvalues and rvalues / derefs to either be both flat or both tree-like. Let's focus on rvalues for a moment. Imagine you have a complicated deref chain, eventually picking a single float out of something larger and complex. If you have a very naive compiler you can imagine that each step of the deref is implemented as a copy of whatever the result of the deref step is into a temporary variable. This might mean e.g. temporarily copying a full matrix before getting at the individual component. Incredibly inefficient, but simple and consistent. Obviously this isn't very interesting and can be simplified right away... except when it can't, like the non-constant array indexing case you mention. Or copying a whole input structure to an output variable. For lvalues you can imagine the specular thing, where the naive compiler stores results into a temporary variable and then copies that into the actual destination.
It seems to me that it would be nicer to have a simple and flat IR structure where each entry can do very little, or even nothing, in practice, and handle simplifications and IR restructuring over a number of passes rather than the opposite. Somewhat related, you can already get instructions that eventually do nothing of consequence (e.g. dead code, swizzling into the exact same thing, ...) and I don't think that has to be necessarily that different from the compiler's point of view.
Maybe I'm misunderstanding what you're trying to propose here, though?
I don't think you are. I might be missing something more or less obvious to you though.