[RFC] Better support for typed pointers in an opaque pointer world

Thank you so much for sharing this RFC. I think the problem you’re addressing is sufficiently close to a challenge we have in supporting WebAssembly GC types that it would be worth combining forces. I’d spotted previously that there were some related work in DXIL and SPIR-V, but until this RFC it wasn’t clear quite how great the overlap is. I’ll say a little about our requirements below and give a few comments on some of the suggestions in this thread, but it would be great to meet with you all to discuss further - either at the next GPU working group meeting (mid July?) or at an earlier special-purpose meeting.

Future WebAssembly specifications introduce a rich set of “GC” types, e.g. structs and arrays with a memory representation that is opaque to LLVM (typically these would represent values stored on a GC managed heap, e.g. within a host Javascript environment). There are all sorts of semantic restrictions on these values stemming from the fact they can’t be load/stored to standard linear memory, though I think that doesn’t impact this discussion too much. Just like you do, we have a requirement that these precise types are maintained all through from the frontend, through LLVM IR and in our case through instruction selection and the MC layer so that globals, locals, function signatures etc are all emitted with the correct type. In our case, operations on values of these types will occur exclusively through intrinsics.

I’ve been exploring options in a very similar direction to introducing a new “opaque type” construct. Here are some notes on my thinking and experimentation so far:

  • Similar to @nikic’s concerns, I’m not sure that looking to encode the Wasm type system in LLVM IR is future proof or desirable. That’s not a very strongly held view though, so I’m open to revisiting. My version of opaque("opencl.sampler_t") looks more like refty typeid(1234) where there is an assumption that there is module-level metadata containing a target-specific representation of these opaque types that can be used at code emission (and would also need to be used if merging LLVM modules).
    • The only reliance on metadata would be for this type table at the module level. There’s precedence for module-level metadata not being dropped, so this seems much more reasonable than e.g. attaching metadata to function arguments and hoping for the best.
    • In our case you could almost get away with just always passing an i32 with the typeid to all the intrinsics that operate on these types. But that would leave a gap in the handling of types of globals and function arguments / return types.
  • Just for purposes of prototyping something more rapidly, I’ve started out with an address space hack. Assume all AS above 255 are non-integral and use pointers to those address spaces to represent values with a certain typeid. There are some fiddly issues around access these type IDs in the backend due to some assumptions in the lowering infrastructure in general and the Wasm backend specifically (e.g. that you can always freely represent a Wasm type as a MachineValueType, and sadly there’s no real scope for having a MVT with a typeid attribute). So my focus has been there rather than thinking more broadly about IR-level representation.
    • If this reservation of ASes could be made target-specific rather than my hack, it might even be an alternative worth considering vs introducing a new type.
  • In our case, having something like refty typeid(1234) / opaque("opencl.sampler_t") would mean there’s no need for changes to elementtype, as we’d pass that new type by value and so it would never be obscured by opaque pointers.
  • Given our heavy use of intrinsics, the fact these aren’t parameterisable by type. We’d need the frontend to introduce casts so e.g. the return value of @llvm.wasm.struct.get is converted to the specific reftype that field is known to hold. If of by-value structs were used, this would presumably need a alloca+store+load, or changing bitcast to allow it to be used on aggregate types, or introducing a new instruction. Plus of course we’d need intrinsics that accept any struct type.

I’ve only given a narrow view of Wasm GC / reftypes here, so hopefully I’ve managed to summarise relevant parts rather than introducing confusion with incomplete explanations. Do speak up if that’s not the case!