[Mesa-dev] [PATCH v2 2/3] mesa/st: glsl_to_tgsi Implement a new lifetime tracker for temporaries

Gert Wollny gw.fossdev at gmail.com
Sun Jun 18 12:41:11 UTC 2017


Hello Nicolai, 


Am Sonntag, den 18.06.2017, 12:05 +0200 schrieb Nicolai Hähnle:
>   
> >   if HAVE_XLIB_GLX
> >   SUBDIRS += drivers/x11
> > @@ -101,7 +101,7 @@ AM_CFLAGS = \
> >   	$(VISIBILITY_CFLAGS) \
> >   	$(MSVC2013_COMPAT_CFLAGS)
> >   AM_CXXFLAGS = \
> > -	$(LLVM_CFLAGS) \
> > +        $(LLVM_CXXFLAGS) \
> 
> I kind of suspect that this might be a no-no. On the one hand it
> makes sense, because it makes the use of CXXFLAGS consistent, but
> it's a pretty significant build system change.
I understand that, but c++11 is just such an improvement. 

> 
> At least separate it out into its own patch.
Okay, 


> 
> 
> 
> >   	$(VISIBILITY_CXXFLAGS) \
> >   	$(MSVC2013_COMPAT_CXXFLAGS)
> >   
> > diff --git a/src/mesa/Makefile.sources b/src/mesa/Makefile.sources
> > index 21f9167bda..a68e9d2afe 100644
> > --- a/src/mesa/Makefile.sources
> > +++ b/src/mesa/Makefile.sources
> > @@ -509,6 +509,8 @@ STATETRACKER_FILES = \
> >   	state_tracker/st_glsl_to_tgsi.h \
> >   	state_tracker/st_glsl_to_tgsi_private.cpp \
> >   	state_tracker/st_glsl_to_tgsi_private.h \
> > +        state_tracker/st_glsl_to_tgsi_temprename.cpp \
> > +	state_tracker/st_glsl_to_tgsi_temprename.h \
> 
> Inconsistent use of whitespace.
> 
> Then the whole patch needs to be re-arranged, i.e. the 
> st_glsl_to_tgsi_private stuff, 
This was a mistake when I was re-basing the patch. 

> and I agree with Emil that the tests  
> should be separated out into their own patch.
I'm currently working on this. 

> 
> BTW, you can and should use
> 
> git rebase -x "cd $builddir && make" $basecommit
> 
> to verify that you don't break the build in an intermediate patch.
Nice trick, thanks. 


> > +typedef int scope_idx;
> 
> Please remove this, it's an unnecessary and distracting abstraction
> that  doesn't gain you anything.
Actually, with the refactoring it did the last two days it helped a
lot. 


> I consider the scopes back-reference here and in temp_access to be
> bad  style. 
I already got rid of them. 

> Think less object- and more data-oriented, then you won't need them.
> Most of those helper functions should be members of 
> tgsi_temp_lifetime -- and the getters/setters can be removed
> entirely.  YAGNI.
I think with the new design that uses pointers into a pre-allocated
array the methods must stay where they are. 


> To first approximation, you should never use mutable. Those methods 
> should not be const to begin with.
This is something else I actually didn't like and corrected already.  

> > +scope_idx
> > +tgsi_temp_lifetime::make_scope(e_scope_type type, int id, int lvl,
> > +                               int s_begin)const
> > +{
> > +   int idx = scopes.size();
> > +   scopes.push_back(prog_scope(type, idx, id, lvl, s_begin,
> > scopes));
> > +   return idx;
> > +}
> 
> AFAICS this overload is only used once. Please remove it.
Okay. 

> Use C-style comments. 
Okay, 

> Also, this should probably have an assert, or can this really happen?
I don't think it can happen, an assert it is.


> +      case TGSI_OPCODE_IF:
> > +      case TGSI_OPCODE_UIF:{
> > +         if (inst->src[0].file == PROGRAM_TEMPORARY) {
> > +               acc[inst->src[0].index].append(line, acc_read,
> > current);
> > +         }
> > +         scope_idx scope = make_scope(current, sct_if, if_id,
> > nesting_lvl, line);
> 
> It's probably a good idea to have scopes start at line + 1.
> Otherwise, 
> it may be tempting to think that the read access of the IF condition 
> happens inside the IF scope, at least based on the line numbers.

I don't think that's a problem. At least I can't think of a test case
that would trigger a problem with that. 


> 
> > +      case TGSI_OPCODE_DEFAULT: {
> > +         auto scope_type = (inst->op == TGSI_OPCODE_CASE) ?
> > +                              sct_switch_case :
> > sct_switch_default;
> > +         if ( scopes[current].type() == sct_switch ) {
> 
> No spaces inside parenthesis (also elsewhere).
Okay. 

> > +            scope_stack.push(current);
> > +            current = make_scope(current, scope_type,
> > scopes[current].id(),
> > +                                 nesting_lvl, line);
> > +         }else{
> 
> Spaces around else (also elsewhere).
Okay. 

> 
> No comment between if- and else-block.
Okay. 

> 
> 
> > +            scopes[current].set_continue(current, line);
> 
> You do need to distinguish between break and continue (at least for 
> accuracy), because break allows you to skip over a piece of code 
> indefinitely while continue doesn't. I.e.:

I risk to differ, in the worst case a CONT can act like it would be a
BRK, and because I have to handle the worst case it doesn't make sense
to distinguish between the two. 


> 
>     BGNLOOP
>        ...
>           BRK/CONT
>        ...
>        MOV TEMP[i], ...
>        ...
>     ENDLOOP
> 
> If it's a CONT, then i is written unconditionally inside the loop. 
While one would normally expect this, it is not guarantied. Think of a
shader like this: 

... 
varying int a; 
varying float b; 

int f(int a, float b) {...}
for (int i = 0; i< n; ++i ) {
   if (a && f(i, b)) 
      continue; 
   ...
   x = 
}

It may not be the best style. but it is possible.


> +
> > +         for (unsigned j = 0; j < inst->tex_offset_num_offset;
> > j++) {
> > +            if (inst->tex_offsets[j].file == PROGRAM_TEMPORARY) {
> > +               acc[inst->tex_offsets[j].index].append(line,
> > acc_read, current);
> > +            }
> > +         }
> 
> You need to handle the reads before the writes. After all, you might
> have a
> 
>     ADD TEMP[i], TEMP[i], ...
> 
> which may have to register as an undefined read.
I will add a test case, and correct the code accordingly. 


> 
> 
> > +
> > +      } // end default
> > +      } // end switch (op)
> 
> No end-of-scope comments.
okay, 

> > 
> > +scope_idx
> > +prog_scope::get_parent_loop() const
> 
> Based on what it does, this should be renamed to something like 
> get_innermost_loop.
Okay. 

> 
> > +void temp_access::append(int line, e_acc_type acc, scope_idx idx)
> > +{
> > +   last_write = line;
> 
> Looks like last_write should be called last_access.

Initially I thought the same, but a last read is also (most of the
time) the last access, and it is handled differently then a write past
the last read, so i think that last_write catches it better. 


> > +   if (acc == acc_read) {
> > +      last_read = line;
> > +      last_read_scope = idx;
> > +      if (undefined_read > line) {
> > +         undefined_read = line;
> > +         undefined_read_scope = idx;
> > +      }
> 
> The way you're using it this is effectively just first_read, not 
> undefined_read.

Indeed. 

> 
> 
> > +   } else {
> > +      if (first_write == -1) {
> > +         first_write = line;
> > +         first_write_scope = idx;
> > +
> > +         // we write in an if-branch
> > +         auto fw_ifthen_scope = scopes[idx].in_ifelse();
> > +         if ((fw_ifthen_scope >= 0) &&
> > scopes[fw_ifthen_scope].in_loop()) {
> > +            // value not always written, in loops we must keep it
> > +            keep_for_full_loop = true;
> > +         } else {
> > +            // same thing for switch-case
> > +            auto fw_switch_scope = scopes[idx].in_switchcase();
> > +            if (fw_switch_scope >= 0 &&
> > scopes[fw_switch_scope].in_loop()) {
> > +               keep_for_full_loop = true;
> > +            }
> > +         }
> 
> Simplify this by using an in_conditional() instead of in_ifelse + 
> in_switchcase().
I was thinking about this, but I also thought that when I want to track
all code path later then I will have to distinguish the two again, but,
on the other hand, adding a method that combines the two is a no-
brainer. 


> 
> > +
> > +   /* Only written to, just make sure that renaming
> > +    * doesn't reuse this register too early (corner
> > +    * case is the one opcode with two destinations) */
> > +   if (last_read_scope < 0) {
> > +      return make_pair(first_write, first_write + 1);
> 
> What if there are multiple writes to the temporary?
Good catch! Next text case :) 


> > +   /* propagate lifetime also if there was a continue/break
> > +    * in a loop and the write was after it (so it constitutes
> > +    * a conditional write */
> 
> It's only conditional if there was a break. Continue doesn't make it 
> conditional.
I think I made my argument. 

> +   /* propagate lifetimes before moving to upper scopes */
> > +   if ((scopes[first_write_scope].type() == sct_loop) &&
> > +       (keep_for_full_loop || (undefined_read < first_write))) {
> > +      first_write = scopes[first_write_scope].begin();
> > +      int lr = scopes[first_write_scope].end();
> > +      if (last_read < lr)
> > +         last_read = lr;
> > +   }
> 
> What about:
> 
>     BGNLOOP
>        ...
>        BGNLOOP
>           IF ...
>              read TEMP[i]
>           ENDIF
>           write TEMP[i]
>        ENDLOOP
>        ...
>     ENDLOOP
> 
> In this case, you don't set keep_for_full_loop, yet the lifetime
> must extend for the entire _outer_ loop.
Another test case that needs to be added :) 

> 
> I see two related problems in the code:
> 
> 1. In this case, keep_for_full_loop needs to be set to true (because 
> overwriting first_write in the code above means that the related
> check below won't fire).

> 
> 2. target_level is set to the inner loop even though it really needs
> to be set to the outer loop.
Indeed. 

Thanks a lot, 
Gert 


More information about the mesa-dev mailing list