- Companion
- object
Type members
Classlikes
Utility definition used for better error-reporting of argument errors
Utility definition used for better error-reporting of argument errors
Interpreter configuration
Interpreter configuration
The (abstract) interpreter can be seen as a push-down automaton that transits between the configurations where the stack is the implicit call stack of the meta-language.
It's important that the configuration is finite for the analysis to terminate.
For soundness, we need to compute fixed point of the cache, which maps configuration to evaluation result.
Thanks to heap monotonicity, heap is not part of the configuration. Which also avoid computing fix-point on the cache, as the cache is immutable.
The environment for method parameters
The environment for method parameters
For performance and usability, we restrict parameters to be either Cold
or Hot
.
Despite that we have environment for evaluating expressions in secondary
constructors, we don't need to put environment as the cache key. The
reason is that constructor parameters are determined by the value of
this
--- it suffices to make the value of this
as part of the cache
key.
This crucially depends on the fact that in the initialization process there can be exactly one call to a specific constructor for a given receiver. However, once we relax the design to allow non-hot values to methods and functions, we have to put the environment as part of the cache key. The reason is that given the same receiver, a method or function may be called with different arguments -- they are not decided by the receiver anymore.
Abstract heap stores abstract objects
Abstract heap stores abstract objects
As in the OOPSLA paper, the abstract heap is monotonistic.
The abstract object which stores value about its fields and immediate outers.
The abstract object which stores value about its fields and immediate outers.
Semantically it suffices to store the outer for klass
. We cache other outers
for performance reasons.
Note: Object is NOT a value.
A value which represents a set of addresses
A value which represents a set of addresses
It comes from if
expressions.
A reference to the object under initialization pointed by this
A reference to the object under initialization pointed by this
Abstract values
Abstract values
Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
Cold
┌──────► ▲ ◄──┐ ◄────┐
│ │ │ │
│ │ │ │
ThisRef(C) │ │ │ ▲ │ │ │ │ Warm(D) Fun RefSet │ ▲ ▲ ▲ │ │ │ │ Warm(C) │ │ │ ▲ │ │ │ │ │ │ │ └─────────┴──────┴───────┘ Hot
The most important ordering is the following:
Hot ⊑ Warm(C) ⊑ ThisRef(C) ⊑ Cold
The diagram above does not reflect relationship between RefSet
and other values. RefSet
represents a set of values which could
be ThisRef
, Warm
or Fun
. The following ordering applies for
RefSet:
R_a ⊑ R_b if R_a ⊆ R_b
V ⊑ R if V ∈ R
Types
Cache used to terminate the analysis
Cache used to terminate the analysis
A finitary configuration is not enough for the analysis to terminate. We need to use cache to let the interpreter "know" that it can terminate.
For performance reasons we use curried key.
Note: It's tempting to use location of trees as key. That should be avoided as a template may have the same location as its single statement body. Macros may also create incorrect locations.
Value members
Concrete methods
Handles the evaluation of different expressions
Handles the evaluation of different expressions
Note: Recursive call should go to eval
instead of cases
.
Check that path in path-dependent types are initialized
Check that path in path-dependent types are initialized
This is intended to avoid type soundness issues in Dotty.
Evaluate an expression with the given value for this
in a given class klass
Evaluate an expression with the given value for this
in a given class klass
Note that klass
might be a super class of the object referred by thisV
.
The parameter klass
is needed for this
resolution. Consider the following code:
class A { A.this class B extends A { A.this } }
As can be seen above, the meaning of the expression A.this
depends on where
it is located.
This method only handles cache logic and delegates the work to cases
.
Initialize part of an abstract object in klass
of the inheritance chain
Initialize part of an abstract object in klass
of the inheritance chain
Compute the outer value that correspond to tref.prefix
Compute the outer value that correspond to tref.prefix
Extensions
Extensions
Ensure the corresponding object exists in the heap
Ensure the corresponding object exists in the heap
Whether the object is fully assigned
Whether the object is fully assigned
It means all fields and outers are set. For performance, we don't check outers here, because Scala semantics ensure that they are always set before any user code in the constructor.
Note that isFullyFilled = true
does not mean we can use the
object freely, as its fields or outers may still reach uninitialized
objects.
Try early promotion of warm objects
Try early promotion of warm objects
Promotion is expensive and should only be performed for small classes.
-
for each concrete method
m
of the warm object: call the method and promote the result -
for each concrete field
f
of the warm object: promote the field value
If the object contains nested classes as members, the checker simply reports a warning to avoid expensive checks.
TODO: we need to revisit whether this is needed once we make the system more flexible in other dimentions: e.g. leak to methods or constructors, or use ownership for creating cold data structures.