c833c1227c
Now that we have support for generating checked & unchecked entrypoints,
we can make dispatch tables also target unchecked entry points.
This is beneficial especially in cases where there's dispatches on
`this` that require covariant checks but we don't know the target method (i.e. we cannot devirtualize it because the method we
dispatch to may beoverriden).
We keep the existing selectors that we have, but a selector will now
have
* one row if none of the implementations of the selector need to
perform type checks
=> `SelectorInfo` has a `SelectorTargets _normal`
* two rows if any of the implementations of the selector need t
perform
a type check
=> `SelectorInfo` has a `SelectorTargets _checked`
=> `SelectorInfo` has a `SelectorTargets _unchecked`
Once an unchecked entrypoint is also used in the dispatch table (only
if there's any unchecked calls to that selector) then binaryen can no
longer optimize the signature of the function. It means we may have
perform e.g. downcasts / boxing in the unchecked entry where we
wouldn't do before (because we only had static calls to unchecked entry
before this PR).
So we're going to force-inline calls to unchecked entrypoints. This
avoids sometimes down casts and boxing. It also seems to actually
shrink the binary size.
Change-Id: I3ba4980c42886cc883fb610533f5fac9cce39b65
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/407740
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
165 lines
5.4 KiB
Dart
165 lines
5.4 KiB
Dart
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:kernel/ast.dart';
|
|
|
|
// Extend references with flags to more easily identify getters and setters.
|
|
|
|
extension GetterSetterReference on Reference {
|
|
bool get isImplicitGetter {
|
|
Member member = asMember;
|
|
return member is Field && member.getterReference == this;
|
|
}
|
|
|
|
bool get isImplicitSetter {
|
|
Member member = asMember;
|
|
if (member is Field) {
|
|
if (member.setterReference == this) return true;
|
|
if (member.isInstanceMember) {
|
|
return _isUncheckedEntrySetterReference ||
|
|
_isCheckedEntrySetterReference;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool get isGetter {
|
|
Member member = asMember;
|
|
return (member is Procedure && member.isGetter) || isImplicitGetter;
|
|
}
|
|
|
|
bool get isSetter {
|
|
Member member = asMember;
|
|
return (member is Procedure && member.isSetter) || isImplicitSetter;
|
|
}
|
|
}
|
|
|
|
// Extend procedures with a tearOffReference that refers to the tear-off
|
|
// implementation for that procedure. This enables a Reference to refer to any
|
|
// implementation relating to a member, including its tear-off, which it can't
|
|
// do in plain kernel.
|
|
|
|
// Use Expandos to avoid keeping the procedure alive.
|
|
final Expando<Reference> _tearOffReference = Expando();
|
|
final Expando<Reference> _typeCheckerReference = Expando();
|
|
final Expando<Reference> _checkedEntryReferences = Expando();
|
|
final Expando<Reference> _uncheckedEntryReferences = Expando();
|
|
final Expando<Reference> _bodyReferences = Expando();
|
|
final Expando<Reference> _initializerReference = Expando();
|
|
final Expando<Reference> _constructorBodyReference = Expando();
|
|
|
|
extension CustomReference on Member {
|
|
Reference get tearOffReference =>
|
|
_tearOffReference[this] ??= Reference()..node = this;
|
|
|
|
Reference get typeCheckerReference =>
|
|
_typeCheckerReference[this] ??= Reference()..node = this;
|
|
|
|
Reference get checkedEntryReference {
|
|
assert(_memberCanHaveMultipleEntryPoints(this));
|
|
return _checkedEntryReferences[this] ??= Reference()..node = this;
|
|
}
|
|
|
|
Reference get uncheckedEntryReference {
|
|
assert(_memberCanHaveMultipleEntryPoints(this));
|
|
return _uncheckedEntryReferences[this] ??= Reference()..node = this;
|
|
}
|
|
|
|
Reference get bodyReference {
|
|
assert(_memberCanHaveMultipleEntryPoints(this));
|
|
return _bodyReferences[this] ??= Reference()..node = this;
|
|
}
|
|
|
|
Reference get initializerReference =>
|
|
_initializerReference[this] ??= Reference()..node = this;
|
|
|
|
Reference get constructorBodyReference =>
|
|
_constructorBodyReference[this] ??= Reference()..node = this;
|
|
}
|
|
|
|
extension IsCustomReference on Reference {
|
|
bool get isTearOffReference => _tearOffReference[asMember] == this;
|
|
|
|
bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this;
|
|
|
|
bool get isCheckedEntryReference => _checkedEntryReferences[asMember] == this;
|
|
|
|
bool get _isCheckedEntrySetterReference =>
|
|
(asMember is Field) && isCheckedEntryReference;
|
|
|
|
bool get isUncheckedEntryReference =>
|
|
_uncheckedEntryReferences[asMember] == this;
|
|
|
|
bool get _isUncheckedEntrySetterReference =>
|
|
(asMember is Field) && isUncheckedEntryReference;
|
|
|
|
bool get isBodyReference => _bodyReferences[asMember] == this;
|
|
|
|
bool get isInitializerReference => _initializerReference[asMember] == this;
|
|
|
|
bool get isConstructorBodyReference =>
|
|
_constructorBodyReference[asMember] == this;
|
|
|
|
EntryPoint get entryKind {
|
|
if (isUncheckedEntryReference) {
|
|
return EntryPoint.unchecked;
|
|
}
|
|
if (isCheckedEntryReference) {
|
|
return EntryPoint.checked;
|
|
}
|
|
if (isBodyReference) {
|
|
return EntryPoint.body;
|
|
}
|
|
return EntryPoint.normal;
|
|
}
|
|
}
|
|
|
|
extension ReferenceAs on Member {
|
|
Reference referenceAs({required bool getter, required bool setter}) {
|
|
assert(!getter || !setter); // members cannot be both setter and getter
|
|
Member member = this;
|
|
return member is Field
|
|
? setter
|
|
? member.setterReference!
|
|
: member.getterReference
|
|
: getter && member is Procedure && member.kind == ProcedureKind.Method
|
|
? member.tearOffReference
|
|
: member.reference;
|
|
}
|
|
}
|
|
|
|
// Sanity check that [member] may have multiple entry points.
|
|
bool _memberCanHaveMultipleEntryPoints(Member member) {
|
|
if (member is Field && member.hasSetter ||
|
|
member is Procedure && member.isSetter) {
|
|
// The unchecked entry will bypass type checks on the setter values.
|
|
return true;
|
|
}
|
|
if (member is Procedure &&
|
|
const [ProcedureKind.Method, ProcedureKind.Operator]
|
|
.contains(member.kind) &&
|
|
(member.function.positionalParameters.isNotEmpty ||
|
|
member.function.namedParameters.isNotEmpty ||
|
|
member.function.typeParameters.isNotEmpty)) {
|
|
// The unchecked entry will bypass type checks on the method.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
enum EntryPoint {
|
|
// A single procedure doing type argument checks, optional argument handling
|
|
// and the body.
|
|
normal,
|
|
// A entry function doing type argument checks, optional argument handling but
|
|
// delegates to the actual [body] function.
|
|
checked,
|
|
// A entry function doing optional argument handling but
|
|
// delegates to the actual [body] function.
|
|
unchecked,
|
|
// The body of a function excluding type checks and optional argument
|
|
// handling.
|
|
body,
|
|
}
|