gc icon indicating copy to clipboard operation
gc copied to clipboard

Should there be an "escape hatch" for vtable structural subtyping?

Open aardappel opened this issue 6 years ago • 2 comments

The "median" target for this proposal appears to be Java-like languages (though others can certainly be represented) which will heavily make use of vtable calls. In that light, it seems odd that we're willing to put up with adding code like:

(block $fail (result (ref $D)) (set_local $this (cast_down (ref $Cthis) (ref $D) $fail (get_local $Cthis))))

to most methods, which is both a speed and code bloat issue.

I'm probably missing something, but maybe someone can clarify why we shouldn't special-case this super common use case.

I'd imagine we can have an escape hatch to structural subtyping that goes something like this:

  • Mark certain structs as being a vtable.
  • When checking structural subtyping between types A and B, allow B to be a subtype of A even if it has a vtable member whose function types have a B where an A is expected.
  • Disallow regular load/store on vtable structs, instead provide a new call operation on the parent of the vtable that enforces that parent as first argument.

aardappel avatar Dec 01 '17 18:12 aardappel

There is a wealth of options for enriching the type system in a principled manner to avoid casts in certain situations. They all tend to conflict with simplicity, of course. We will need implementation experience to see what is bearable and what isn't -- such casts can be much cheaper than you'd expect and may already be dominated by the cost of a call. That said, a special type system hack for the benefit of Java-like languages -- and language bias in general -- is something we'd rather want to avoid.

rossberg avatar Dec 04 '17 10:12 rossberg

I think we should make virtual calls in Kotlin-like languages efficient at runtime and in terms if code size.

We would get a lot of value having a simple type-check rule and an instruction call_vtable that can be easily desugared into existing struct.gets and ref_call.

Proposed shortcut can be completely ignored without any cost by languages that don't need this feature.

My main concern with current proposal is the size of generated code, a quality we absolutely want to have when targeting web clients.

Inside a function that overrides virtual method we would need 4 extra instructions and extra local:

(local $new-this ...) ;; if 'this' is used more than once
(set_local $new-this 
  (ref.cast ... ...
    (get_local $old-this)
    (get_global $rtt)))

If we would like to keep zero-overhead direct calls we'd put this cast into a bridge method. It would require an extra func and extra call. This would make virtual calls even slower.

Virtual methods become bloated, but call sites look even worse. In order to translate a virtual call:

(<object-instance-expression>).foo(<arguments...>)

in some cases we would need 5 extra instructions and a temp local

(local $tmp)
...
(set_local $tmp                     ;; Store value in temp local 
   (<object-instance-expression>))  ;; if expression is big or has side-effects
(call_ref
    (get_local $tmp)
    (<arguments...>)
    (struct.get $vt <foo-slot> 
        (struct.get $class 0
            (get_local $tmp)))

compared to proposed call_vtable:

(call_vtable $class <foo-slot>
   (<object-instance-expression>)
   (<arguments...>))

skuzmich avatar Sep 16 '19 21:09 skuzmich

We have discussed improved method dispatch as a potential high-priority post-MVP feature, but we won't be adding it to the MVP, so closing this.

tlively avatar Nov 01 '22 18:11 tlively