Thinking about callee-checked call tags vs. caller-checked casting from funcref to typed function references via ref.cast, both seem valuable for optimizing different cases. The former can efficiently capture situations like interface dispatch, as presented. The latter can allow a type check to hoisted so it is performed once, followed by multiple typed (unchecked) calls.
It seems like one can support both by specifying:
- call tags are unified with the GC proposal's rtts, factoring them out of the GC proposal if necessary
- functions can be given explicit tags, which also seems to be a common theme between this proposal and gc/#99
- there is a new way to define a function (with type
funcref) as an "overload set" of N other functions (i.e., dispatch_func from #1346)
funcrefs can be downcast via ref.cast
- for the overload-set case, the list of functions is scanned and the cast succeeds if any match the given
rtt
- there is a new variant of
call_indirect for passing an rtt (call_funcref from #1346)
- for the overload-set case, the call succeeds if it matches any of the N functions'
rtts
- for the normal case, the call succeeds if it matches the functions'
rtt
- the existing
call_indirect $t behaves as if using the new call_indirect variant, passing rtt.canon $t, which means it also works for overload sets
I think it would be possible to implement this feature with purely caller-side checking (reducing the two call_indirect variants into a ref.cast followed by call_ref), but I think it would be somewhat more efficient for the implementation to implement the two call_indirects as a callee-side check. In any case, to support ref.cast on an overload-set funcref, the implementation would need to have the list of rtts efficiently reachable from the funcref, which seems doable.
Anyhow, this is just a sketch of an idea for how to achieve both of these goals. Maybe I'm neglecting some details.
Thinking about callee-checked call tags vs. caller-checked casting from
funcrefto typed function references viaref.cast, both seem valuable for optimizing different cases. The former can efficiently capture situations like interface dispatch, as presented. The latter can allow a type check to hoisted so it is performed once, followed by multiple typed (unchecked) calls.It seems like one can support both by specifying:
funcref) as an "overload set" of N other functions (i.e.,dispatch_funcfrom #1346)funcrefs can be downcast viaref.castrttcall_indirectfor passing anrtt(call_funcreffrom #1346)rttsrttcall_indirect $tbehaves as if using the newcall_indirectvariant, passingrtt.canon $t, which means it also works for overload setsI think it would be possible to implement this feature with purely caller-side checking (reducing the two
call_indirectvariants into aref.castfollowed bycall_ref), but I think it would be somewhat more efficient for the implementation to implement the twocall_indirects as a callee-side check. In any case, to supportref.caston an overload-setfuncref, the implementation would need to have the list ofrtts efficiently reachable from thefuncref, which seems doable.Anyhow, this is just a sketch of an idea for how to achieve both of these goals. Maybe I'm neglecting some details.