This page describes how type testing stubs (TTS) are implemented.
<obj> as <desination-type> checks in Dart code (also generated implicitly in CFE) compile in the Dart VM's IL to the
A type testing stub is a piece of generated machine code, specialized for a type. The
RawAbstractType has pointer to the entry of this machine code in
type_test_stub_entry_point_ (as well as to the corresponding
RawCode, but that is actually not required for the operation of the stub)
The specialized machine code is self-contained and doesn't need/have an object pool. The stub will not setup a frame: In the fast case it will perform the type check and return.
The TTS has a special calling convention which accepts:
<obj> to perform the type test against
<destination-type> to perform the test against
<destination-type> is not instantiated)
RawSubtypeTestCache if the TTS was unable to handle the type test).
This calling convention is the same as for the SubtypeTestCache-based implementation.
We distuinguish between different type tests (based on the
Types of the form
<obj> as Foo<T1, ..., Tn> where
Tn are all top-types are performed by loading the class id of
<obj> and checking whether it is contained in the set of all class-id ranges of classes directly or transitively extending/implementing
In AOT mode in particular this is very fast because we perform a depth-first preorder numbering of classes, which means we have a single range for
as Foo if
Foo is not implemented. Otherwise we might have multiple ranges.
This test is exhaustive.
Types of the form
<obj> as Foo<T1, ..., Tn> where at least one
Ti is not a top type are performed by loading the class id of
<obj> and checking whether it lies inside the subclass ranges. Since we check for direct/indirect subclasses and not implementors (this test is non-exhaustive) we know at which offset the type argument vector is.
We then load the instance type arguments vector
<obj>.<tav> and perform a type check for each
Ti. Notice these “inner type checks” are different from the original check: Instead of checking
<some-obj> as <type> we now have to check whether
<some-type> is <other-type>. This asymmetry is also the reason why a TTS cannot call other TTS.
Ti we perform now a check of the form
<obj>.tav[i] is ` as follows:
Tx is an instantiated type of the form
Bar<H1, ..., Hn> where all
Hn are top-types we perform the same ClassId-based range check as above in the simple case (this test is exhaustive)
Tx is a type parameter we will load its value (i.e. instantiate it) via the instantiator/function type argument vector and compare
<obj>.tav[i] == <Ti>.value (this test is non-exhaustive)
If the TTS performed the type test and it succeeded, it will return immediately (fast case). If the type test failed or the TTS was non-exhaustive it will do a tail-call to a
SlowTypeTestStub which will examine the test to be done and call the much slower SubtypeTestCache based implementation. It will, based on the STC's return value either return successfully or go to runtime to either lazily create/update a SubtypeTestCache or throw an exception.
The implementation of
AssertAssignable will perform an in-line ClassId-based range check (based on code size heuristics). If such an inline check fails or the check would be too big, we will do an actual call.
Currently this call always uses TTS in AOT mode (and in JIT mode if the destination type is either a type parameter or an instantiated interface type). To perform the call it will ensure the arguments to the TTS are in the right registers.
<destination-type> is a type parameter we will load its value and call its TTS instead.
In JIT mode we build the TTS lazily on first involcation (the TTS of types will be initialized to the
LazySpecializeTypeTest stub). If later on more classes get loaded and the TTS fast path starts failing we re-build the TTS.
After a hot-reload we reset all types to the lazy specialize TTS.
In AOT mode we try to guess for which types we might need a TTS. For every
AssertAssignable we remember its
<destination-type>. If it was a type parameter
T we also try to see (in a limited way) what can flow into
T and mark such types for needing a TTS.