mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:49:00 +00:00
[dartdevc] Appending and checking nullability caches earlier for performance.
Because we cache on instances of type objects, all "type"-ish constructs in DDC must be pre-initialized if we want to avoid any hasOwnProperty checks (which are slow). 1) We now consult legacy/nullable caches earlier when wrapping types, as the downstream logic can be much slower than a lookup. 2) We cache on nullble and legacy wrappers themselves as well as just the underlying object (formerly we cached on just the latter) for faster lookups. 3) We attach local caches upfront, maintaining monomorphicity during hot code paths. This improves regressions for opt-out worst-case tests from (120 us -> 2100 us) to (120 us -> 500 us) and weak mode tests from (120 us -> 1100 us) to (120 us -> 330 us). Change-Id: I98a26064404e746162ca3feb22b0993c3b39e63c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/138330 Commit-Queue: Mark Zhou <markzipan@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
parent
8c7a443ddc
commit
db63e89fcf
5 changed files with 66 additions and 19 deletions
|
@ -588,6 +588,12 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
|||
|
||||
var finishGenericTypeTest = _emitClassTypeTests(c, className, body);
|
||||
|
||||
// Attach caches on all canonicalized types not in our runtime.
|
||||
// Types in the runtime will have caches attached in their constructors.
|
||||
if (!isSdkInternalRuntime(_currentLibrary)) {
|
||||
body.add(runtimeStatement('addTypeCaches(#)', [className]));
|
||||
}
|
||||
|
||||
_emitVirtualFieldSymbols(c, body);
|
||||
_emitClassSignature(c, className, body);
|
||||
_initExtensionSymbols(c);
|
||||
|
|
|
@ -556,6 +556,11 @@ addTypeTests(ctor, isClass) {
|
|||
cast);
|
||||
}
|
||||
|
||||
/// Pre-initializes types with empty type caches.
|
||||
///
|
||||
/// Required for the null-safe SDK. Stubbed here.
|
||||
addTypeCaches(type) => null;
|
||||
|
||||
// TODO(jmesserly): should we do this for all interfaces?
|
||||
|
||||
/// The well known symbol for testing `is Future`
|
||||
|
|
|
@ -161,6 +161,7 @@ normalizeFutureOr(typeConstructor, setBaseClass) {
|
|||
// this method. This ensures that the we can test a type value returned here
|
||||
// as a FutureOr because it is equal to 'async.FutureOr` (in the JS).
|
||||
JS('!', '#[#] = #', genericType, _originalDeclaration, normalize);
|
||||
JS('!', '#(#)', addTypeCaches, genericType);
|
||||
return genericType;
|
||||
}
|
||||
|
||||
|
@ -216,6 +217,7 @@ generic(typeConstructor, setBaseClass) => JS('', '''(() => {
|
|||
return value;
|
||||
}
|
||||
makeGenericType[$_genericTypeCtor] = $typeConstructor;
|
||||
$addTypeCaches(makeGenericType);
|
||||
return makeGenericType;
|
||||
})()''');
|
||||
|
||||
|
@ -568,6 +570,16 @@ addTypeTests(ctor, isClass) {
|
|||
cast);
|
||||
}
|
||||
|
||||
/// Pre-initializes types with empty type caches.
|
||||
///
|
||||
/// Allows us to perform faster lookups on local caches without having to
|
||||
/// filter out the prototype chain. Also allows types to remain relatively
|
||||
/// monomorphic, which results in faster execution in V8.
|
||||
addTypeCaches(type) {
|
||||
JS('', '#[#] = void 0', type, _cachedLegacy);
|
||||
JS('', '#[#] = void 0', type, _cachedNullable);
|
||||
}
|
||||
|
||||
// TODO(jmesserly): should we do this for all interfaces?
|
||||
|
||||
/// The well known symbol for testing `is Future`
|
||||
|
|
|
@ -170,7 +170,7 @@ void setStartAsyncSynchronously([bool value = true]) {
|
|||
///
|
||||
/// This is used by [hotRestart] to ensure we don't leak types from previous
|
||||
/// libraries.
|
||||
/// Results made against Null are cached in _nullComparisonMap and must be
|
||||
/// Results made against Null are cached in _nullComparisonSet and must be
|
||||
/// cleared separately.
|
||||
@notNull
|
||||
final List<Object> _cacheMaps = JS('!', '[]');
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
/// This library defines the representation of runtime types.
|
||||
part of dart._runtime;
|
||||
|
||||
@notNull
|
||||
bool _strictSubtypeChecks = false;
|
||||
|
||||
/// Sets the mode of the runtime subtype checks.
|
||||
|
@ -74,6 +75,11 @@ class DartType implements Type {
|
|||
|
||||
@JSExportName('_check')
|
||||
check_T(object) => cast(object, this, true);
|
||||
|
||||
DartType() {
|
||||
// Every instance of a DartType requires a set of type caches.
|
||||
JS('', '#(this)', addTypeCaches);
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicType extends DartType {
|
||||
|
@ -273,26 +279,34 @@ final _cachedLegacy = JS('', 'Symbol("cachedLegacy")');
|
|||
/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
|
||||
@notNull
|
||||
Object nullable(type) {
|
||||
// Check if a nullable version of this type has already been created.
|
||||
var cached = JS<Object>('', '#[#]', type, _cachedNullable);
|
||||
if (JS<bool>('!', '# !== void 0', cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Cache a canonical nullable version of this type on this type.
|
||||
Object cachedType = _computeNullable(type);
|
||||
JS('', '#[#] = #', type, _cachedNullable, cachedType);
|
||||
return cachedType;
|
||||
}
|
||||
|
||||
Object _computeNullable(type) {
|
||||
// *? normalizes to ?.
|
||||
if (_isLegacy(type)) {
|
||||
return nullable(JS<Object>('', '#.type', type));
|
||||
}
|
||||
if (_isNullable(type) ||
|
||||
_isTop(type) ||
|
||||
_isNullType(type) ||
|
||||
// Normalize FutureOr<T?>? --> FutureOr<T?>
|
||||
// All other runtime FutureOr normalization is in `normalizeFutureOr()`.
|
||||
(_isFutureOr(type)) &&
|
||||
_isNullable(JS<Object>('!', '#[0]', getGenericArgs(type)))) {
|
||||
((_isFutureOr(type)) &&
|
||||
_isNullable(JS<Object>('!', '#[0]', getGenericArgs(type))))) {
|
||||
return type;
|
||||
}
|
||||
if (type == never_) return unwrapType(Null);
|
||||
if (_isLegacy(type)) type = JS<Object>('', '#.type', type);
|
||||
|
||||
// Check if a nullable version of this type has already been created.
|
||||
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedNullable)) {
|
||||
return JS<NullableType>('!', '#[#]', type, _cachedNullable);
|
||||
}
|
||||
// Cache a canonical nullable version of this type on this type.
|
||||
var cachedType = NullableType(JS<Type>('!', '#', type));
|
||||
JS('', '#[#] = #', type, _cachedNullable, cachedType);
|
||||
return cachedType;
|
||||
return NullableType(type);
|
||||
}
|
||||
|
||||
/// Returns a legacy (star, *) version of [type].
|
||||
|
@ -302,19 +316,29 @@ Object nullable(type) {
|
|||
/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
|
||||
@notNull
|
||||
Object legacy(type) {
|
||||
if (_isLegacy(type) || _isNullable(type) || _isTop(type) || _isNullType(type))
|
||||
return type;
|
||||
|
||||
// Check if a legacy version of this type has already been created.
|
||||
if (JS<bool>('!', '#.hasOwnProperty(#)', type, _cachedLegacy)) {
|
||||
return JS<LegacyType>('!', '#[#]', type, _cachedLegacy);
|
||||
var cached = JS<Object>('', '#[#]', type, _cachedLegacy);
|
||||
if (JS<bool>('!', '# !== void 0', cached)) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Cache a canonical legacy version of this type on this type.
|
||||
var cachedType = LegacyType(JS<Type>('!', '#', type));
|
||||
Object cachedType = _computeLegacy(type);
|
||||
JS('', '#[#] = #', type, _cachedLegacy, cachedType);
|
||||
return cachedType;
|
||||
}
|
||||
|
||||
Object _computeLegacy(type) {
|
||||
// Note: ?* normalizes to ?, so we cache type? at type?[_cachedLegacy].
|
||||
if (_isLegacy(type) ||
|
||||
_isNullable(type) ||
|
||||
_isTop(type) ||
|
||||
_isNullType(type)) {
|
||||
return type;
|
||||
}
|
||||
return LegacyType(JS<Type>('!', '#', type));
|
||||
}
|
||||
|
||||
/// A wrapper to identify a nullable (question, ?) type of the form [type]?.
|
||||
class NullableType extends DartType {
|
||||
final Type type;
|
||||
|
|
Loading…
Reference in a new issue