-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static classes, enabling compile-time elimination of objects #308
Comments
Using This proposal does not preclude uses of the class which requires instantiation, like: dynamic c = C();
print(identityHash(c));
c.foo(); For ahead-of-time compilation, this would mean that you need the instance method anyway, and if you also have a static function desugaring, you double the code size (or at least introduce code in the instance methods that forward to the static functions). To avoid that, you might want to require that object creation expressions for the class only occurs in non-leaking positions too. That should ensure that nobody ever stores a reference to the class and accesses instance members through that reference later. As for mutable variables, a desugaring at the VM level should be able to make a static function act directly on stack slots, and reuse the same stack slots for a number of consecutive member accesses (for cascades), and those slots could be mutable. Nitpicks:
|
An interesting question to me for this proposal is can it handle the following real-world need for enabling the compile-time elimination of objects? Flutter uses Take the call graph below If the scheme described above is not quite up to the job, what would make it work? The proposal would need to handle simultaneously 'unwrapping' in the receiver, argument and result positions of a single call where there are multiple 'static class' types. e.g. e.g. |
Right, and it conflicts with the C# notion of static classes. The core property here is actually that it is a class that does not internally use the identity of the receiver (in particular, So maybe
That's right, but it would be trivially easy to make such usages an error, as you mention later. I just thought that it would be a useful trade-off to allow non-leaking instance creations to use the optimization (of eliminating the object), and letting all other usages have the standard semantics. This means that you will get an actual wrapper for free whenever that is required, and you'll get the optimization otherwise.
What I'm saying is that these expressions are non-leaking, so in particular
Right, that one can be generalized. Thanks! |
To some extent, yes. The code for those two classes seems to rely on passing instances of type Can
|
Could abstract classes which could have constant constructors including |
In response to #306, this issue proposes the introduction of static classes. This is a class modifier
static
and a compile-time constraint on each classC
which has that modifier, ensuring that certain instances ofC
can be eliminated altogether, without changing the observable behavior of the program.The computations associated with the creation of an instance
o
ofC
and running one or more instance methods witho
as the receiver will be desugared by the compiler into invocations of statically resolved functions (e.g., top-level functions), thus lowering the pressure on the memory management system and enabling various other optimizations.As seen from a developer's point of view, we can mark a class
C
asstatic
in order to indicate the intention that compilers should be able to apply that optimization, and the classC
will then have a compile-time error unless it satisfies the requirements for allowing the optimization.As an alternative, the optimization could be applied to each class which satisfies the constraints, silently. However, developers would then have more trouble detecting when a seemingly benign change to a class suddenly prevents that class from admitting this optimization. So we consider the explicit approach better: Developers will announce their intention to define a class whose instances can be eliminated in specific situations by including the modifier
static
, and future violations of the associated constraint will then be flagged explicitly as a compile-time error.A couple of examples of classes where this concept could be useful are given in #306, but the main motivation for having the concept are the following:
It should be possible to provide a guarantee that an invocation of a static extension method (cf. Static Extension Methods #41) corresponds to an invocation of a top-level function, and in particular that no objects are allocated as part of the method invocation itself.
It should be possible to provide a guarantee that no wrapper object is created when an object is accessed under a specific extension type (cf. Static Extension Types #42), except in the case where that object is "leaked", e.g., by being passed as an argument to a function invocation whose type is a superinterface of that extension type.
In other words, the optimization which is the core point of having this proposal is crucial for extension methods and extension types, but it may be helpful in a lot of other situations in which case it is useful to be able to specify this property separately.
Syntax
The grammar is adjusted as follows in order to support this feature:
The only difference is that it is possible to add
static
as a modifier of the class or mixin.Static Analysis
The static classes are the following: The class
Object
is a static class. When the declaration of a classC
has the modifierstatic
,C
is a static class. A mixin applicationS with M
is a static class ifS
is a static class andM
is a static mixin or class.It is a compile-time error if a class
C
is static and its superclass is not static.The classes implemented by a static class or mixin may or may not be static, no special constraints are imposed for that.
It is a compile-time error if a static class has one or more mutable instance variables.
Consider an expression
e
. We say thate
occurs in a non-leaking position if it occurs in one of the following ways:e ('||' <logicalAndExpression>)+
e ('&&' <equalityExpression>)+
e <equalityOperator> <relationalExpression>
e <typeTest>
e <typeCast>
e <relationalOperator> <bitwiseOrExpression>
e ('|' <bitwiseXorExpression>)+
e ('^' <bitwiseAndExpression>)+
e ('&' <shiftExpression>)+
e (<shiftOperator> <additiveExpression>)+
e (<additiveOperator> <multiplicativeExpression>)+
e (<multiplicativeOperator> <unaryExpression>)+
<prefixOperator> e
e <selector>+
e <assignableSelectorPart>+ <assignmentOperator> <expression>
e <cascadeSection>+
, when it occurs as an<expressionStatement>
It is a compile-time error if the reserved word
this
occurs in an expression in the body of a static class, unless it occurs in a non-leaking position.This allows for
this
to occur in all the syntactic contexts where the semantics is a method invocation onthis
, plus possibly some additional steps that are language defined and known to not store a reference tothis
anywhere (including in variables of any kind, or as a parameter in a function invocation). With the cascade it is crucial that the value of the expression as a whole is discarded, which is ensured by the requirement that the cascade must be an expression statement.In short, the implementation of a static class can call methods on
this
, but it cannot leakthis
.Note that a constructor in a static class may have an initializing formal, e.g.,
C(this.x)
, but the occurrence ofthis
in such a formal parameter is not an expression, so there are no special constraints on that. Similarly, an element in the initializer list of a constructor in a static class may usethis
as inthis.x = 42
in static classes, just like other classes.Dynamic Semantics
No special rules apply for the dynamic semantics of instances of a static class.
However, consider an instance creation expression
e
which invokes a generative constructor to create an instanceo
of a classC
. In the case whereC
is static ande
occurs in a non-leaking position it is guaranteed that no reference too
is ever stored, except that the stack frame for each instance method invocation ofC
will store a binding ofthis
—but the code in the implementation of that instance method has the same constraints, so they will also not leak the value ofthis
.Because of this guarantee, it is permissible for a compiler to generate code whereby the values of the instance variables of
o
(which must be final becauseC
is static) can be copied freely and stored in activation records on the run-time stack. This means that there is no need to allocate an actual object in the heap, it can effectively be transformed into separate variables, one for each instance variable ofC
,which may be passed as actual arguments to static functions and copied freely to as many stack frames as needed, and stored as local variables, one for each instance variable of
o
.Discussion
Consider the following situation:
The class needs to exist as usual if it is used for any purpose which does not admit elimination of the instance. So the initialization of
c
causes allocation of an instance ofC
, and it has state and methods just like it would have had ifC
were not a static class.However, the cascade
C(3, 4)..foo()..bar(5)
can be optimized because it is a non-leaking occurrence of an instance creation that invokes a generative constructor. Similarly for the declaration and initialization ofi
. This could be achieved by a desugaring translation along the following lines:It should be noted that this optimization is only applicable in the situation where the exact type of the instance-which-will-be-eliminated is known, and in particular it can be determined statically which implementation each instance method has, and which getters correspond to a native storage read operation (there is no mutable state, so if there are any setters then they are just regular instance methods, with the usual constraints).
The desugared code may look like there is not much gained by this optimization, but it should be noted that invocations of instance methods from other instance methods would otherwise be hard to optimize (when
baz
callsfoo
it is not known that there is no overriding implementation offoo
, but in the desugared code it is known that the exact type of the "receiver which isn't there" isC
, so we can callfoo
statically, and might inline it). It should also be noted that the parameters of the desugared methods may be allocated in registers whereas the instance variables of an instance ofC
would have to be stored in the heap.The text was updated successfully, but these errors were encountered: