<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Mon, Apr 9, 2018 at 5:35 AM, Rob Clark <span dir="ltr"><<a href="mailto:robdclark@gmail.com" target="_blank">robdclark@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On Mon, Apr 9, 2018 at 1:35 AM, Jason Ekstrand <<a href="mailto:jason@jlekstrand.net">jason@jlekstrand.net</a>> wrote:<br>
</span><div><div class="h5">> Rather lively discussion we've got going here...<br>
><br>
> On Sun, Apr 8, 2018 at 3:23 PM, Rob Clark <<a href="mailto:robdclark@gmail.com">robdclark@gmail.com</a>> wrote:<br>
>><br>
>> On Sun, Apr 8, 2018 at 5:54 PM, Bas Nieuwenhuizen<br>
>> <<a href="mailto:bas@basnieuwenhuizen.nl">bas@basnieuwenhuizen.nl</a>> wrote:<br>
>> > On Sun, Apr 8, 2018 at 11:40 PM, Rob Clark <<a href="mailto:robdclark@gmail.com">robdclark@gmail.com</a>> wrote:<br>
>> >> On Sun, Apr 8, 2018 at 5:20 PM, Bas Nieuwenhuizen<br>
>> >> <<a href="mailto:bas@basnieuwenhuizen.nl">bas@basnieuwenhuizen.nl</a>> wrote:<br>
>> >>>>>>>>>>> +<br>
>> >>>>>>>>>>> + /** The mode of the underlying variable */<br>
>> >>>>>>>>>>> + nir_variable_mode mode;<br>
>> >>>>>>>>>><br>
>> >>>>>>>>>> In fact, it seems like deref->mode is unused outside of<br>
>> >>>>>>>>>> nir_print and<br>
>> >>>>>>>>>> nir_validate.. for logical addressing we can get the mode from<br>
>> >>>>>>>>>> the<br>
>> >>>>>>>>>> deref_var->var at the start of the chain, and deref->mode has<br>
>> >>>>>>>>>> no<br>
>> >>>>>>>>>> meaning for physical addressing (where the mode comes from the<br>
>> >>>>>>>>>> pointer).<br>
>> >>>>>>>>>><br>
>> >>>>>>>>>> So maybe just drop deref->mode?<br>
>> >>>>>>>>><br>
>> >>>>>>>>> Isn't it still useful with logical addressing in case a var is<br>
>> >>>>>>>>> not<br>
>> >>>>>>>>> immediately available? (think VK_KHR_variable_pointers)<br>
><br>
><br>
> Yes, the idea here is to basically be the NIR equivalent of storage classes.<br>
> For cases where you can chase it all the way back to the variable, this is<br>
> pointless. For cases where you can't, this can be very useful.<br>
><br>
>><br>
>> >>>>>>>> not sure, maybe this should just also use fat-pointers like<br>
>> >>>>>>>> physical<br>
>> >>>>>>>> addressing does??<br>
><br>
><br>
> I *think* (without knowing the future with perfect accuracy) that I'd like<br>
> us to start moving away from fat pointers in SPIR-V -> NIR. They've been<br>
> working ok thus far but we already have had complaints from the radv guys<br>
> that they don't want fat pointers for SLM and I'm not sure there that great<br>
> of a fit for other things. The big reason is that it's actually fairly<br>
> painful to change from one fat pointer scheme to another if whatever the<br>
> spirv_to_nir authors picked doesn't really fit your hardware.<br>
><br>
> I'm not 100% sure what to do but the idea I've got at the moment is<br>
> something like this:<br>
><br>
> 1) For every mode (or storage class), the driver provides to spirv_to_nir a<br>
> glsl_type which is the type it wants the pointer represented as.<br>
> 2) Add a deref_to_pointer intrinsic to convert the deref to that type and<br>
> use casts to convert the fat pointer to a deref again<br>
><br>
> Why the deref_to_pointer intrinsic? Because right now some annoying details<br>
> about nir_intrinsic_info force us to have all drefs have a single component.<br>
> If we didn't, then nir_intrinsic_store_var would have two variable-size<br>
> sources which aren't the same size. We could modify the rules and have a<br>
> concept of a "deref source" and to nir_intrinsic_info and say that a deref<br>
> source can be any size. That would also work and may actually be a better<br>
> plan. I'm open to other suggestions as well.<br>
><br>
> The key point, however, is (1) where we just let the driver choose the<br>
> storage type of pointers and then they can have whatever lowering pass they<br>
> want. If that's a "standard" fat-pointers pass in nir_lower_io, so be it.<br>
> If they want to translate directly into LLVM derefs, they can do that too, I<br>
> suppose.<br>
><br>
>> >>>>>>>>> Also I could see this being useful in physical addressing too to<br>
>> >>>>>>>>> avoid<br>
>> >>>>>>>>> all passes working with derefs needing to do the constant<br>
>> >>>>>>>>> folding?<br>
><br>
><br>
> Constant folding is cheap, so I'm not too worried about that. The bigger<br>
> thing that actual derefs gain us is the ability of the compiler to reason<br>
> about derefs. Suppose, for instance, that you had the following:<br>
><br>
> struct S {<br>
> float a;<br>
> float b;<br>
> };<br>
><br>
> location(set = 0, binding = 0) Block {<br>
> S arr[10];<br>
> } foo;<br>
><br>
> void<br>
> my_func(int i)<br>
> {<br>
> foo.arr[i].a = make_a_value();<br>
> foo.arr[i].b = make_another_value();<br>
> use_value(foo.arr[i].a);<br>
> }<br>
><br>
> If everything is lowered to fat pointers, the compiler can't very easily<br>
> tell that the second line of my_func() didn't stomp foo.arr[i].a and so it<br>
> has to re-load the value in the third line. With derefs, we can easily see<br>
> that the second line didn't stomp it and just re-use the re-use the result<br>
> of the make_a_value() call. Yes, you may be able to tell the difference<br>
> between foo.arr[i].a and foo.arr[i].b with some fancy analysis and<br>
> arithmetic tracking but it'd be very painful to do. I've given this<br>
> substantial thought for almost two years now and haven't actually come up<br>
> with a good way to do it without the information provided by derefs.<br>
><br>
> The biggest thing that I think we will gain from deref instructions isn't<br>
> fancy syntax sugar and better nir_printability of pointers. It also isn't<br>
> the removal of explicit of fat pointers in spirv_to_nir (thought that is<br>
> also nice). The biggest thing I'm going for is improving NIR's ability to<br>
> optimize UBO, SSBO, and SLM access. We're pretty good today for local<br>
> variables (though there are a couple of known places for improvements) but<br>
> we're utterly rubbish for anything that leaves the current shader<br>
> invocation. If we want competent compute performance (which we do!) we need<br>
> to solve that problem.<br>
><br>
>><br>
>> >>>>>>>> The problem is that you don't necessarily know the type at<br>
>> >>>>>>>> compile<br>
>> >>>>>>>> time (and in the case where you do, you need to do constant<br>
>> >>>>>>>> folding to<br>
>> >>>>>>>> figure it out)<br>
>> >>>>>>><br>
>> >>>>>>> So I have two considerations here<br>
>> >>>>>>><br>
>> >>>>>>> 1) for vulkan you always know the mode, even when you don't know<br>
>> >>>>>>> the var.<br>
>> >>>>>>> 2) In CL the mode can still get annotated in the source program<br>
>> >>>>>>> (CL C<br>
>> >>>>>>> non-generic pointers) in cases in which we cannot reasonably<br>
>> >>>>>>> figure it<br>
>> >>>>>>> out with just constant folding. In those cases the mode is extra<br>
>> >>>>>>> information that you really lose.<br>
>> >>>>>><br>
>> >>>>>> so, even in cl 1.x, you could do things like 'somefxn(foo ?<br>
>> >>>>>> global_ptr<br>
>> >>>>>> : local_ptr)'.. depending on how much we inline all the things,<br>
>> >>>>>> that<br>
>> >>>>>> might not get CF'd away.<br>
>> >>><br>
>> >>> How does this even work btw? somefxn has a definition, and the<br>
>> >>> definition specifies a mode for the argument right? (which is<br>
>> >>> implicitly __private if the app does not specify anything?)<br>
>> >><br>
>> >> iirc, the cl spec has an example something along these lines..<br>
>> >><br>
>> >> it doesn't require *physical* storage for anything where you don't<br>
>> >> know what the ptr type is, however.. so fat ptrs in ssa space works<br>
>> >> out<br>
>> >><br>
>> >>>>><br>
>> >>>>> But something like<br>
>> >>>>> __constant int *ptr_value = ...;<br>
>> >>>>> store ptr in complex data structure.<br>
>> >>>>> __constant int* ptr2 = load from complex data structure.<br>
>> >>>>><br>
>> >>>>> Without explicitly annotating ptr2 it is unlikely that constant<br>
>> >>>>> folding would find that ptr2 is pointing to __constant address<br>
>> >>>>> space.<br>
>> >>>>> Hence removing the modes loses valuable information that you cannot<br>
>> >>>>> get back by constant folding. However, if you have a pointer with<br>
>> >>>>> unknown mode, we could have a special mode (or mode_all?) and you<br>
>> >>>>> can<br>
>> >>>>> use the uvec2 representation in that case?<br>
><br>
><br>
> I've thought about using a mode of 0 for this or maybe letting you OR all<br>
> the possible modes together since nir_variable_mode is, after all, a<br>
> bitfield. I don't have any huge preference at the moment.<br>
><br>
>><br>
>> >>>> hmm, I'm not really getting how deref->mode could magically have<br>
>> >>>> information that fatptr.y doesn't have.. if the mode is known, vtn<br>
>> >>>> could stash it in fatptr.y and everyone is happy? If vtn doesn't<br>
>> >>>> know<br>
>> >>>> this, then I don't see how deref->mode helps..<br>
>> >>><br>
>> >>> You mean insert it into the fatptr every time deref_cast is called?<br>
>> >>><br>
>> >>> Wouldn't that blow up the IR size significantly for very little<br>
>> >>> benefit?<br>
>> >><br>
>> >> in an easy to clean up way, so meh?<br>
>> ><br>
>> > We can't clean it up if we want to keep the information. Also nir is<br>
>> > pretty slow to compile already, so I'd like not to add a significant<br>
>> > number of instruction for very little benefit.<br>
><br>
><br>
> Really? I mean, I can believe it, but do you have any actual numbers to<br>
> back this up? It's considerably faster than some other IRs we have in mesa<br>
> though they are known to be pretty big pigs if we're honest. I'm very open<br>
> to any suggestions on how to improve compile times. If NIR is fundamentally<br>
> a pig, we should fix that.<br>
><br>
> I don't think compile time should be the deciding factor in whether or not<br>
> we use fat pointers as I doubt it will have a huge effect. However, how<br>
> much work it takes to get at information is a reasonable argument.<br>
><br>
>><br>
>> I guess I'm failing to see what information you'd loose.. or how there<br>
>> would be a problem..<br>
>><br>
>> I'm not strictly against having nir_var_unknown as a possible value<br>
>> for deref->mode, but I'm not really seeing how that helps anything,<br>
>> other than adding extra complexity to the IR..<br>
>><br>
>><br>
>> >>>>>><br>
>> >>>>>> I think I'm leaning towards using fat ptrs for the vk case, since I<br>
>> >>>>>> guess that is a case where you could always expect<br>
>> >>>>>> nir_src_as_const_value() to work, to get the variable mode. If for<br>
>> >>>>>> no<br>
>> >>>>>> other reason than I guess these deref's, if the var is not known,<br>
>> >>>>>> start w/ deref_cast, and it would be ugly for deref_cast to have to<br>
>> >>>>>> work differently for compute vs vk. But maybe Jason already has<br>
>> >>>>>> some<br>
>> >>>>>> thoughts about it?<br>
><br>
><br>
> I don't see why they would be different. Vulkan and CL are starting to<br>
> converge. In vulkan, you already can't always chase the pointer back to the<br>
> variable VK_KHR_variable_pointers is used. I don't think that CL is as<br>
> special as you think it is. The only thing special about CL is the fact<br>
> that global and local are in the same address space. Beyond that, all the<br>
> problems we have to solve are fundamentally the same.<br>
><br>
>><br>
>> >>>>> I'd like to avoid fat pointers alltogether on AMD since we would not<br>
>> >>>>> use it even for CL. a generic pointer is just a uint64_t for us,<br>
>> >>>>> with<br>
>> >>>>> no bitfield in there for the address space.<br>
><br>
><br>
> I tend to agree with this. (See above discussion).<br>
><br>
>><br>
>> >>>>> I think we may need to think a bit more about representation<br>
>> >>>>> however,<br>
>> >>>>> as e.g. for AMD a pointer is typically 64-bits (but we can do e.g.<br>
>> >>>>> 32-bits for known workgroup pointers), the current deref<br>
>> >>>>> instructions<br>
>> >>>>> return 32-bit, and you want something like a uvec2 as pointer<br>
>> >>>>> representation?<br>
>> >>>><br>
>> >>>> afaiu, newer AMD (and NV) hw can remap shared/private into a single<br>
>> >>>> global address space.. But I guess that is an easy subset of the<br>
>> >>>> harder case where drivers need to use different instructions.. so a<br>
>> >>>> pretty simple lowering pass run before lower_io could remap things<br>
>> >>>> that use fatptrs into something that ignores fatptr.y. Then opt<br>
>> >>>> passes make fatptr.y go away. So both AMD and hw that doesn't have a<br>
>> >>>> flat address space are happy.<br>
>> >>><br>
>> >>> But then you run into other issues, like how are you going to stuff a<br>
>> >>> 64-bit fatptr.x + a ?-bit fatptr.y into a 64-bit value for Physical64<br>
>> >>> addressing? Also this means we have to track to the sources back to<br>
>> >>> the cast/var any time we want to do anything at all with any deref<br>
>> >>> which seems less efficient to me than just stuffing the deref in<br>
>> >>> there.<br>
>> >><br>
>> >> so fat ptrs only have to exist in ssa space, not be stored to<br>
>> >> something with a physically defined size..<br>
>> ><br>
>> > how does storing __generic pointers work then? those still need the<br>
>> > fat bit for your hw right?<br>
>><br>
>> for hw that can't map everything into a single flat address space, you<br>
>> *can't* store a fat pointer.. oh well, it doesn't mean that that hw<br>
>> can't implement lesser ocl versions (since this is defn not required<br>
>> for ocl 1.x and I don't *think* it is required for 2.x, but I haven't<br>
>> checked the spec.. I guess if intel supports 2.x then it isn't<br>
>> required..)<br>
><br>
><br>
> You can still carve up the address space and put local memory at some<br>
> absurdly high address and do<br>
><br>
> if (ptr > 0xffffffffffff0000)<br>
> store_local(ptr & 0xffff)<br>
> else<br>
> store_global(ptr)<br>
><br>
<br>
</div></div>That might be an option if we need to physically store a fat pointer<br>
(but I'm not convinced we need to). But as a general solution it is a<br>
horrible idea. You either loose information (since when was TMI a<br>
problem for compilers?) or you get to teach constant folding that even<br>
though it doesn't know the value of 'a' it does know the value of 'a &<br>
0xffffffffffff0000', which is insanity.<br>
<br>
And simply not optimizing away the global/local/private turns every<br>
load/store into something that looks like:<br>
<br>
<a href="http://cmps.f.lt" rel="noreferrer" target="_blank">cmps.f.lt</a> p0.x, c4.x, r0.z<br>
; delay 6 instructions so branch can see the value in p0.x<br>
(rpt5)nop<br>
br !p0.x, #3<br>
ldg.u32 r0.w, r0.y, 1<br>
jump #11<br>
(jp)<a href="http://cmps.f.lt" rel="noreferrer" target="_blank">cmps.f.lt</a> p0.x, c4.y, r0.z<br>
(rpt5)nop ; delay 6 instruction slots<br>
br !p0.x, #8<br>
ldl.u32 r0.w, r0.y, 1<br>
jump #3<br>
; private is really just global with a driver provided buffer<br>
; and compiler computed offset<br>
(jp)add.s r0.y, c10.x ; add base address of buffer<br>
(rpt3)nop ; alu results not immediately avail and no<br>
other instr to fill the empty slots<br>
add.s r0.y, ... offset calculated from local_id<br>
(rpt5)nop ; 6 slots for result from alu avail to mem<br>
instructions<br>
ldg.u32 r0.w, r0.y, 1<br>
(jp)(sy)... and now we have a result..<br>
<br>
<br>
which is simply not an option! And the result is actually worse since<br>
we end up with 64b pointer math lowered to 32b instructions!<br>
<br>
tbh, I *really* don't see the problem with fat pointers, but if<br>
someone else doesn't want deref_cast to use fat pointers, then I must<br>
insist on a different intrinsic which does, and a compiler option for<br>
vtn to choose which to emit.<br><div class="HOEnZb"><div class="h5"></div></div></blockquote><div><br></div><div>I'm sorry. That comment was NOT intended as a suggestion for universal alternative to fat pointers. It was merely to demonstrate how an architecture like Intel's which uses different messages for local vs. global could implement physical 64-bit (uint64_t) generic pointers. Someone (looks like Rob from the indentation) made a comment about difficulties implementing OpenCL 2.x and I was just trying to show that it could be done. Of course, stuffing the extra information in a vector component is easier.<br><br>This was a side comment to what I thought was a side comment in the discussion.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">
>><br>
>> >><br>
>> >> As far as tracking things to the head of the chain of deref<br>
>> >> instructions, that is a matter of a simple helper or two. Not like<br>
>> >> the chain of deref's is going to be 1000's of instructions..<br>
><br>
><br>
> Yes and no (mostly no) once you start throwing in phis, selects, and loading<br>
> pointers from variables. The first two you can explain away by just saying,<br>
> "follow the phi/select sources" but the moment you load a pointer out of a<br>
> variable, you have to do something. Sure, you could just drop .y and stomp<br>
> it to the known storage class, but that hardly seems ideal.<br>
><br>
>><br>
>> >>> Also, what would the something which ignores fatptr.y be? I'd assume<br>
>> >>> that would be the normal deref based stuff, but requiring fatptr<br>
>> >>> contradicts that?<br>
>> >><br>
>> >> if you have a flat address space, maybe a pass (or option for<br>
>> >> lower_io) to just convert everything to load/store_global (since<br>
>> >> essentially what these GPUs are doing is remapping shared/private into<br>
>> >> the global address space)<br>
>> ><br>
>> > but I'd like to only do that if we really don't know the mode<br>
>> > statically since it is somewhat slower/less flexible. (also radv is<br>
>> > unlikely to use nir_lower_io for a lot of this stuff since we can<br>
>> > lower derefs into LLVM GEPs directly)<br>
>><br>
>> I mean, we control all the src code, we can add more options to lower_io.<br>
>><br>
>> ><br>
>> > Hence if we want the cases where we know the mode statically we need<br>
>> > to not lower the fatptr, but then we have the whole fatptr mess.<br>
>> ><br>
>><br>
>> well, fatptr mess is unavoidable.. I mostly just fail to see how a<br>
>> more general solution (ie. storing the variable mode in ssa) is worse<br>
>> than having to deal with both cases (in ssa or in instr). The in ssa<br>
>> case is something that is easy to recover since anything that does<br>
>> pointer math has to split out the .y component and fuse it back in<br>
>> after the pointer math. (And if you have a flat address space, I<br>
>> guess I'm failing to see why you care about the mode in the first<br>
>> place.)<br>
><br>
><br>
> Yes, fat pointers are inevitable for some hardware at some stage in the<br>
> compile. That does not, however, mean that we want fat pointers straight<br>
> out of SPIR-V.<br>
</div></div></blockquote></div><br></div></div>