[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:
Mark Zhou 2020-03-05 16:30:54 +00:00 committed by commit-bot@chromium.org
parent 8c7a443ddc
commit db63e89fcf
5 changed files with 66 additions and 19 deletions

View file

@ -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);

View file

@ -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`

View file

@ -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`

View file

@ -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('!', '[]');

View file

@ -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;