[Mesa-dev] [PATCH 09/13] SQUASH: i965/fs: Rework fs_visitor::lower_load_payload
Francisco Jerez
currojerez at riseup.net
Fri Apr 3 13:18:54 PDT 2015
Jason Ekstrand <jason at jlekstrand.net> writes:
> On Fri, Apr 3, 2015 at 8:37 AM, Francisco Jerez <currojerez at riseup.net> wrote:
>> Jason Ekstrand <jason at jlekstrand.net> writes:
>>
>>> On Fri, Apr 3, 2015 at 7:28 AM, Francisco Jerez <currojerez at riseup.net> wrote:
>>>> Jason Ekstrand <jason at jlekstrand.net> writes:
>>>>
>>>>> On Thu, Apr 2, 2015 at 3:01 AM, Francisco Jerez <currojerez at riseup.net> wrote:
>>>>>> Jason Ekstrand <jason at jlekstrand.net> writes:
>>>>>>
>>>>>>> Instead of the complicated and broken-by-design pile of heuristics we had
>>>>>>> before, we now have a straightforward lowering:
>>>>>>>
>>>>>>> 1) All header sources are copied directly using force_writemask_all and,
>>>>>>> since they are guaranteed to be a single register, there are no
>>>>>>> force_sechalf issues.
>>>>>>>
>>>>>>> 2) All non-header sources are copied using the exact same force_sechalf
>>>>>>> and saturate modifiers as the LOAD_PAYLOAD operation itself.
>>>>>>>
>>>>>>> 3) In order to accommodate older gens that need interleaved colors,
>>>>>>> lower_load_payload detects when the destination is a COMPR4 register
>>>>>>> and automatically interleaves the non-header sources. The
>>>>>>> lower_load_payload pass does the right thing here regardless of whether
>>>>>>> or not the hardware actually supports COMPR4.
>>>>>>
>>>>>> I had a quick glance at the series and it seems to be going in the right
>>>>>> direction.
>>>>>>
>>>>>> One thing I honestly don't like is the ad-hoc and IMHO premature
>>>>>> treatment of payload headers, it still feels like the LOAD_PAYLOAD
>>>>>> instruction has more complex semantics than necessary and the benefit is
>>>>>> unclear to me.
>>>>>>
>>>>>> I suppose that your motivation was to avoid setting force_writemask_all
>>>>>> in LOAD_PAYLOAD instructions with header. The optimizer should be able
>>>>>> to cope with those flags and get rid of them from the resulting moves
>>>>>> where they are redundant, and if it's not able to it looks like
>>>>>> something that should be fixed anyway. The explicit handling of headers
>>>>>> is responsible for much of the churn in this series and is likely to
>>>>>> complicate users of LOAD_PAYLOAD and optimization passes that have to
>>>>>> manipulate them.
>>>>>
>>>>> Avoiding force_writemask_all is only half of the motivation and the
>>>>> small half at that. A header source, more properly defined, is a
>>>>> single physical register that, conceptually, applies to all channels.
>>>>> Effectively, a header source (I should have stated this clearly) has
>>>>> two properties:
>>>>>
>>>>> 1) It has force_writemask_all set
>>>>> 2) It is exactly one physical hardware register.
>>>>>
>>>>> This second property is the more important of the two. Most of the
>>>>> disaster of the previous LOAD_PAYLOAD implementation was that we did a
>>>>> pile of guesswork and had a ill-conceved "effective width" think in
>>>>> order to figure out how big the register actually was. Making the
>>>>> user specify which sources are header sources eliminates that
>>>>> guesswork. It also has the nice side-effect that we can do the right
>>>>> force_writemask_all and we can properly handle COMPR4 for the the
>>>>> user.
>
> Ok, Allow me to be a bit more explicit as to what all we need to keep track of:
>
> 1) How big is the source register for real. Even immediates can end
> up being two registers in the copy.
> 2) Do we want force_writemask_all?
> 3) If not, do we want force_sechalf?
> 4) On g45 and gen5, we want to use COMPR4 for interlaced movs
> 5) When lowering, we want to use 16-wide moves when possible in SIMD16
>
> With the patch series I sent, all of this is explicit except for
> COMPR4 which is, admittedly, kind of magic. "Which of these sources
> are headers?" is a reasonable question for the caller to answer. It
> knows explicitly and it would take the LOAD_PAYLOAD helper some work
> to guess it correctly. Another option would be to guess that based on
> exec sizes but then the caller has to know not to pass in the wrong
> register type or the guess will be wrong. I like explicit.
>
I'm not advocating LOAD_PAYLOAD to perform any guesswork, I'm advocating
it to be more stupid -- Let it do as much as it can sensibly do with the
information it already has, and no more.
>>>> Yeah, true, but this seems like the least orthogonal and most annoying
>>>> to use solution for this problem, it forces the caller to provide
>>>> redundant information, it takes into account the saturate flag on some
>>>> arguments and not others, it shuffles sources with respect to the
>>>> specified order when COMPR4 is set, but only for the first four
>>>> non-header sources. I think any of the following solutions would be
>>>> better-behaved than the current approach:
>>>
>>> I don't know that saying which sources are headers is really
>>> redundant. It's explicit which is what we want. Yes, the COMPR4
>>> thing is a bit magical but we have to do COMPR4 in lower_load_payload
>>> so we have to have some way of doing it and this method puts the
>>> interleving code in one place instead of two.
>>>
>> Well, at least with the previous approach LOAD_PAYLOAD had consistent
>> (if broken) semantics across its arguments, and regardless of COMPR4
>> being used or not, which IMHO is preferable to the modest code saving.
>
> To be clear, I don't really like the way I did COMPR4 either. I just
> couldn't come up with anything better.
>
I don't see how the new handling of COMPR4 is more "explicit". It
magically changes how the specified registers are laid out in the
payload and an optimization pass dealing with LOAD_PAYLOAD must be aware
of that. Your previous approach even though not beautiful was
transparent in the sense that the rest of the compiler didn't have to
care about LOAD_PAYLOAD using COMPR4 or not, because it was nothing more
than an implementation detail with no effect on the semantics of the
instruction.
>>>> 1/ Use the source width to determine the size of each copy. This would
>>>> imply that the source width carries semantic information and hence
>>>> would have to be left alone by e.g. copy propagation.
>>>
>>> That's what do now and it's terrible. The "effective width" field was
>>> basically a width that gets kept.
>>>
>> Couldn't you just prevent copy propagation from propagating a source of
>> different width into a LOAD_PAYLOAD? You would still be able to get rid
>> of the metadata guess and effective_width, but you may have to re-run
>> copy propagate after lower_load_payload() for the case you missed any
>> optimization oportunities.
>
> Yes, we could prevent copy propagation from messing up exec sizes.
> That solves 1 but 2-5 still require terrible heuristics. Setting
> force_writemask_all sort-of solves 2 and 3.
Splitting the LOAD_PAYLOAD monster into two separate instructions
(proposal 3) solves this too, without setting force_writemask_all.
> COMPR4 would have to have a hueristic again which is too complicated.
I wouldn't call that a heuristic, the rule is completely deterministic:
If 'src[i+4] == offset(src[i], 1)' enable COMPR4 and skip emitting
src[i+4] explicitly.
> The 16-wide stuff is still a problem.
>
How so? With solution 1 you emit a 16-wide copy iff the source is
16-wide. With solution 3 you emit a 16-wide copy iff exec_size is 16.
With solution 2 you emit a 16-wide copy if exec_size is 16 or, as an
additional optimization without effect on the semantics of the
instruction, if exec_size is 8, force_writemask_all is set and
'src[i+1] == offset(src[i], 1)'.
>>>> 2/ Use the instruction exec size and flags to determine the properties
>>>> of *all* copies. This means that if a header is present the exec
>>>> size would necessarily have to be 8 and the halves of a 16-wide
>>>> register would have to be specified separately, which sounds annoying
>>>> at first but in practice wouldn't necessarily be because it could be
>>>> handled by the LOAD_PAYLOAD() helper based on the argument widths
>>>> without running into problems with optimization passes changing the
>>>> meaning of the instruction. The semantics of the instruction itself
>>>> would be as stupid as possible, but the implementation could still
>>>> trivially recognise 16-wide and COMPR4 copies using the exact same
>>>> mechanism you are using now.
>>>
>>> Yes, that might work. I'll try and take a swing at it today. It will
>>> *hopefully* have less code churn than the solution in this series
>>> because the magic will still happen, just in a different place. Of
>>> course, this solution also requires that we lower everything with
>>> force_writemask_all and *hopefully* we can get rid of those and set
>>> force_sechalf appropriately in optimization passes.
>
> Thinking about this some more, I'm not really a fan. As with your
> first suggestion, we can force_writemask_all and the heuristic for
> COMPR4 is pretty simple. However, it still has the problem of
> properly handling SIMD16.
>
I think I've answered to that already in the last paragraph.
>>>> 3/ Split LOAD_PAYLOAD into two separate instructions, each of them dead
>>>> simple, say COLLECT and LOAD_HEADER. "COLLECT dst, src0, ..., srcn"
>>>> would be equivalent to the LOAD_PAYLOAD instruction described in 2,
>>>> but it would only be used to load full-width non-header sources of
>>>> the payload, so you would avoid the need to split 16-wide registers
>>>> in half. "LOAD_HEADER dst, header, payload" would handle the
>>>> asymmetric requirements of prepending a header, like using 8-wide
>>>> instead of exec_size-wide copies and setting force_writemask_all.
>>>> You could use mlen to specify the size of payload as is usual for
>>>> instructions taking a payload source.
>>>
>>> I thought about doing something like that but it seems more convoluted
>>> than would be at all worth it.
>>>
>> It's not so obvious to me that 3 would necessarily be more difficult
>> than getting 2 right, but yeah it would definitely be more effort than 1.
>
> The problem with this solution is that headers can be different sizes.
> Texturing instructions use 1-reg headers but fb writes a header that's
> between 0 and 4 registers. Also, we don't want to have to look at the
> size of a vgrf to figure out what the instruction does so we have to
> have some way of encoding the number of registers read in both
> arguments into the instruction. And why is overloading mlen better
> than renaming and overloading has_header?
>
I'm not against renaming header_present to header_size in principle, I
suggested using mlen because you'd likely need a way to specify both the
size of the header and the size of the payload. There are alternatives
like using immediate sources but I don't have a very strong preference.
> Swinging back around to a more general discussion...
>
> 1) Any of the solutions currently on the table will take care of this.
> I'm not a big fan of making copy-prop be LOAD_PAYLOAD-aware, but my
> solutions (which I'll call 0) and 2 and 3 handle it ok.
>
> 2+3) My solution gives us this explicitly for free. The other option,
> which you keep suggesting, is to force_writemask_all the universe.
> This works, but uses more than needed which, at the moment, is ok.
> However, if we want to clean it up, optimization passes will need to
> be aware of it and it's not clear to me how we would handle it.
>
It's simple: an instruction with force_writemask_all set reading from a
register assigned without it is equivalent to the same instruction
without force_writemask_all. Also an instruction with
force_writemask_all set writing to a register which is never used as
send-from-GRF source or by other instructions with the flag set is
equivalent to the same instruction without force_writemask_all. But at
this point this all seems like a premature optimization to me.
> 4) My solution is, admittedly, a hack but it's a predictable hack.
> Also, my hack does work for all of the messages we care about and I
> don't think we'll be adding new messages for gen <= 5 so I'm not
> terribly concerned about it. Having a heuristic at lowering time
> would be nicer because the user can simply say what registers they
> want where and lowering does the right thing. However, if we have to
> pick between COMPR4 being nice and other things being nice, I'd rather
> leave COMPR4 a hack.
>
I don't see why that's a choice we have to make. You could just leave
the old-style handling of COMPR4 alone.
> 5) This one is tricky. CSE can generate payload copies that don't get
> eaten up by register coalesce. I don't remember exactly how this
> happens but I do know that I've seen it in the wild. When this
> happens, you can end up with a copy payload that copies an odd number
> of registers. If you use the simple heuristic of just use SIMD16
> moves until you can't and then use a SIMD8, you end up moving things
> that aren't pairs as pairs.
Yeah, of course, with the current approach and my proposal 1, CSE would
have to preserve the widths of the sources of the original LOAD_PAYLOAD
(or use force_writemask_all), otherwise you end up reading stuff across
channels.
> Sure, if you set force_writemask_all, the code is correct. However,
> it's possible that register coalesce could have cleaned up part of the
> payload but now it can't because the moves are off-center. This
> problem is *why* I wrote the nasty pile of heuristics that we have
> now. None of your suggestions offer a solution to this
>
Actually this seems less a problem with 2 or 3 because the copies are
homogeneous (i.e. they are exec_size-wide). With 2 you'd just split up
the temporary in 8-wide sources and assume that the LOAD_PAYLOAD will be
lowered to the optimal sequence of SIMD16/SIMD8 copies. With 3 you
could even use a LOAD_HEADER with an empty header. In both cases
register coalesce would have to learn how to treat those as multiple
register copies.
> where as having the user specify the header size gives it to you for
> free.
If you consider "for free" checking if the instruction is a LOAD_PAYLOAD
and in that case mimicking its header/payload structure in the new
LOAD_PAYLOAD, sure. But it doesn't seem conceptually different to the
solution you could have applied now.
> All in all, I'm now even more convinced that we want the user of
> LOAD_PAYLOAD to just tell us what sources are headers and what aren't.
> I think it's also conceptually cleaner because the header sources are
> different from the rest of the payload.
> --Jason
>
> P.S. For whatever it's worth, it would also allow us to split
> LOAD_PAYLOAD instructions from SIMD16 to SIMD8 correctly by copying
> the headers directly and splitting the non-headers in half.
I doubt that's something we want to do at the IR level.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 212 bytes
Desc: not available
URL: <http://lists.freedesktop.org/archives/mesa-dev/attachments/20150403/8788e616/attachment-0001.sig>
More information about the mesa-dev
mailing list