[PATCH 2/5] d3dcompiler: Introduce a separate structure for lvalue derefs.

Matteo Bruni matteo.mystral at gmail.com
Fri Apr 17 17:43:54 CDT 2020


On Fri, Apr 17, 2020 at 6:57 PM Zebediah Figura <zfigura at 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 at gmail.com> wrote:
> >>
> >> From: Zebediah Figura <zfigura at codeweavers.com>
> >>
> >> Signed-off-by: Zebediah Figura <z.figura12 at 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.



More information about the wine-devel mailing list