-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Provide CFE functionality whether is/as checks can omit checking the type arguments #54998
Comments
That's a very neat optimization that I believe is quite possible, so my answer to both questions would be "yes". I also think it shouldn't take much time, and I'd be happy to implement it. A related question is how useful such a bit on @johnniwinther WDYT? |
That seems like a valuable flag to have on @mkustermann Would it be useful for your use-case to also mark checks against record types as "only structural" (for instance |
See simple version in cl/353900 (as part of a bugfix in dart2wasm compiler)
That may be helpful yes, as we can have fast ways to check whether an object has a certain shape.
Definitely useful for wasm, vm. Very likely also for dart2js /cc @rakudrama |
The change in [0] was somewhat finishing RTT checks but didn't fix that part correctly. A later fix to that code in [1] also didn't fix the underlying issue. [0] https://dart-review.googlesource.com/c/sdk/+/249641/22/pkg/dart2wasm/lib/types.dart#471 [1] https://dart-review.googlesource.com/c/sdk/+/292881 (which got relanded) Closes #54994 Issue #54998 Change-Id: I507070514e98cb66a57f4f7f08906a32993265d5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/353900 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com>
@mkustermann I'm looking into this optimization now, and I have multiple design alternatives for the flags on The type-kind specific flags are more or less self explanatory: they indicate what kind of shape that should be checked. We have the following possibilities for non-simple and not-erased types in Kernel: interface types, record types, function types, and FutureOr types. The flags that make distinction between the operand-dependent and operand-independent checks signal whether the optimizing flag computation took the static type of the operand expression into account. For example, in the check Please let me know which of the alternatives would work the best for you. Variant 0: We do want the type-specific flags, and we do want to distinguish between the operand-dependent and operand-independent shape checks We add the following flags to
The following is an example of those flags being set. class A<X> {}
class B<Y extends num> extends A<Y> {}
test(A<int> a, (int,) r, Function(num) f, FutureOr<int> fo) {
a as B<int>; // isOperandDependentInterfaceTypeShapeCheckSufficient = true
a as B<num>; // isOperandIndependentInterfaceTypeShapeCheckSufficient = true
r as (num,); // isOperandDependentRecordTypeShapeCheckSufficient = true
r as (Object?,); // isOperandIndependentRecordTypeShapeCheckSufficient = true
f as Function(int); // isOperandDependentFunctionTypeShapeCheckSufficient = true
f as Function(Never); // isOperandIndependentFunctionTypeShapeCheckSufficient = true
fo as FutureOr<num>; // isOperandDependentFutureOrTypeShapeCheckSufficient = true
fo as FutureOr<dynamic>; // isOperandIndependentFutureOrTypeShapeCheckSufficient = true
} Variant 1: We do want the type-specific flags, but we don’t want to distinguish between the operand-dependent and operand-independent shape checks We add the following flags to
The following is an example of those flags being set. class A<X> {}
class B<Y extends num> extends A<Y> {}
test(A<int> a, (int,) r, Function(num) f, FutureOr<int> fo) {
a as B<int>; // isInterfaceTypeShapeCheckSufficient = true
a as B<num>; // isInterfaceTypeShapeCheckSufficient = true
r as (num,); // isRecordTypeShapeCheckSufficient = true
r as (Object?,); // isRecordTypeShapeCheckSufficient = true
f as Function(int); // isFunctionTypeShapeCheckSufficient = true
f as Function(Never); // isFunctionTypeShapeCheckSufficient = true
fo as FutureOr<num>; // isFutureOrTypeShapeCheckSufficient = true
fo as FutureOr<dynamic>; // isFutureOrTypeShapeCheckSufficient = true
} Variant 2: We don’t want the type-specific flags, but we do want to distinguish between the operand-dependent and operand-independent shape checks We add the following flags to
The following is an example of those flags being set. class A<X> {}
class B<Y extends num> extends A<Y> {}
test(A<int> a, (int,) r, Function(num) f, FutureOr<int> fo) {
a as B<int>; // isOperandDependentShapeCheckSufficient = true
a as B<num>; // isOperandIndependentShapeCheckSufficient = true
r as (num,); // isOperandDependentShapeCheckSufficient = true
r as (Object?,); // isOperandIndependentShapeCheckSufficient = true
f as Function(int); // isOperandDependentShapeCheckSufficient = true
f as Function(Never); // isOperandIndependentShapeCheckSufficient = true
fo as FutureOr<num>; // isOperandDependentShapeCheckSufficient = true
fo as FutureOr<dynamic>; // isOperandIndependentShapeCheckSufficient = true
} Variant 3: We don’t want the type-specific flags, and we don’t want to distinguish between the operand-dependent and operand-independent shape checks We add the following flags to
class A<X> {}
class B<Y extends num> extends A<Y> {}
test(A<int> a, (int,) r, Function(num) f, FutureOr<int> fo) {
a as B<int>; // isShapeCheckSufficient = true
a as B<num>; // isShapeCheckSufficient = true
r as (num,); // isShapeCheckSufficient = true
r as (Object?,); // isShapeCheckSufficient = true
f as Function(int); // isShapeCheckSufficient = true
f as Function(Never); // isShapeCheckSufficient = true
fo as FutureOr<num>; // isShapeCheckSufficient = true
fo as FutureOr<dynamic>; // isShapeCheckSufficient = true
} |
Thanks! I see it may not handle some of the type combinations, for example, the one in the example below won't be optimized even though it could be, but it gives a good idea. class A<X> {}
class B<Y> extends A<Y> {}
test(A<Function(num)> a) => a is B<Function(num)>; |
For backends it's somewhat irrelevant why a check can be simplified. It only matters that the check can be simplified. (So I think it's not important to distinguish whether the optimization can be made because it took the static type into consideration or not) The kind of simplification depends on the
I think it makes sense to separate the two, as they are different concepts. Guess this would then be Variant 1? One additional thing that may(?) make sense to add a flag for: T? value;
value is T;
value as T; => e.g. a |
Thanks! I'll go with this one then. A related clarification: would it be better to have the set of four flags or an enum instead, where one of the values will signal that no optimizations can be made?
Good point about nullability check! I'll make sure to include that. |
I am skeptical that this will be much use to dart2js. I propose an alternative. dart2js already detects the nullable-removal pattern Another way to express the 'shape' optimization would be to replace the type expression with one instantiated to bounds, or the structural equivalent. Often the only reason we write In many cases these maximal types are already optimized ( Annotating
dart2js and the VM both use some kind of compiled type testing stubs. This allows A preferable scheme is the following
|
I really like this! The maximal equivalent types cover a wider range of checks, and generating such helper types can be done with an extension of the algorithm I already had in mind for computing the bits. I think it’s a good idea to store the maximal equivalent types in I can see the appeal of the shape-check bits too: even if the maximal type is present, sometimes it can be helpful to know that only the shape of such type needs checking. Consider the following example. class A<X> {}
class B<X, Y> extends A<X> {}
test(A<int> a) {
// Maximal equivalent type: B<dynamic, String>.
// isInterfaceTypeShapeCheckSufficient = false.
a as B<num, String>;
// Maximal equivalent type: B<dynamic, dynamic>.
// isInterfaceTypeShapeCheckSufficient = true.
a as B<num, dynamic>;
}
I see value in providing both, together with the shape-check bits or a shape-check enum value. @mkustermann, @rakudrama WDYT? |
My main request is to have the functionality shared in The bits on the AST nodes would be a convenience - and wouldn't incur additional memory overhead as those nodes already an In terms of precision: Only performing shape checks vs full type check covers many important cases, but it would of course be nice if it's more general. Rewriting the existing type of the is/as check may loose information that backends may rely on in their type propagator. Maybe some backends have an advanced intersection type that can infer from
Can only speak for the VM: The flag actually tells what to do:
So maybe we can start out with having the algorithm in |
Yep, that would be an inconvenience. But I suggest to store the maximal equivalent type alongside the target check type, not to replace one with the other.
Yep, we can certainly do that. |
Part of #54998 Change-Id: Ib60e8fbce0ca3bef9b12b6a1950b1943ab86bd99 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354861 Commit-Queue: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
…mization The CFE added this functionality (in a more general form) in [0] so we can remove our implementation in dart2wasm. [0] https://dart-review.googlesource.com/c/sdk/+/354861 Issue #54998 Change-Id: I4f3159fc1697d09aa1af68cc333e4b377b71f497 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/357561 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Part of #54998 Change-Id: Ib33391d56208e66dba0396da712ca05d21faece0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358453 Commit-Queue: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
Sorry for the silent on the issue! Some time ago I implemented the boolean predicate @mkustermann asked, and it can be updated into an algorithm that produces the maximal equivalent types for the type checks too. @rakudrama is there still interest in such API? |
@chloestefantsova Yes, a function to compute a maximal equivalent type would still be used. |
For backends it's usually much simpler & faster to check whether an object is an instance of a class than to check whether it's an instance of an interface type with arguments.
Concretely, we have
<obj> is/as <type>
expressions where<type>
is anInterfaceType
but with non-defaults-to-bounds type arguments. Doing those checks is expensive.Now there's a subset of cases where we actually have a static guarantee that the type arguments will match and therefore don't have to be checked, reducing the check to a simple
obj instanceof Class
check.Here's a few examples where type argument checks are not needed:
The algorithm to determine whether type arguments don't have to be checked for a
<obj> is X<A0, ... An>
with<obj>
having static typeY<B0, ..., Bm>
could be something like this:X
, i.e.X<X::T, X::T2, X::T3>
X
toY
Y<Expr-1(X::T1..Tn), ..., Expr-m(X::T1..Tn)>
against the static type of operand (Y<B0, ..., Bm>
)X::T1
...X::Tn
Operand(X::Ti)
for allX::Ti
X<Operand(X::Ti), ..., Operand(X::Tn)>
is a subtype ofX<A0, ..., An>
obj
is anX
.So the concrete request I have is
package:kernel/*.dart
AsExpression
andIsExpression
with a bit indicating whether the type arguments don't need to be checked/cc @johnniwinther @chloestefantsova?
The text was updated successfully, but these errors were encountered: