Skip to content

Commit ba90daf

Browse files
MarkzipanCommit Queue
authored andcommitted
Reland "[ddc] Overhauling tearoff equality and identity."
This new update adds fixes + tests for cross-module super mixins. Super getters can refer to mixins compiled in other modules, so we need to expose all mixin names to make them accessible from their enclosing library. This is a reland of commit e4c4d0f Original change's description: > [ddc] Overhauling tearoff equality and identity. > > Hot reload requires DDC to update how its tearoffs are represented. Tearoffs obey the following conventions: > * Instance tearoffs are never identical > * Tearoffs with the same object target and name have the same hash code (even if they resolve to different functions across hot reloads) > * Two separate tearoffs of the same member are equal > > To support this, tearoff equality must not depend on the bound object and method but a composite of the bound object, torn off member name, and the exact class/object from which the member was torn off. > > Notable changes: > * Methods' immediately bound targets are emitted with member signatures. This is required to determine the bound targets for instance and dynamic tearoffs. Bound targets are identified by `libraryUri:class` strings. > * `applyMixin` passes in a 'true' bound target. This is because mixin applications' members are considered children of their 'on' class (not the mixed in class) wrt equality/hashCode. > * `bind` is modified to pass in its 'true' bound object to support mixins' super getters. > * `tearoff` and `staticTearoff` are modified to accept a bound target string (only required for static tearoffs, as they are bound at tearoff-creation-time). > * Static tearoffs avoid using their bound object for hashcode and equality, as these libraries may be wrapped in proxy objects. > * Tearoff equality and hashCode are updated to consider bound object, bound name, and its bound method's immediate target. > * 'noSuchMethod' and 'toString' methods are always accessed through their extension property during signature lookups. > > > Change-Id: Ica5501b6860c605db50aa945bafb6802a7317511 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406723 > Reviewed-by: Nate Biggs <[email protected]> > Reviewed-by: Nicholas Shahan <[email protected]> > Commit-Queue: Mark Zhou <[email protected]> Change-Id: I645992030f9106c158426412387ddecafc78e3f4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415102 Reviewed-by: Nate Biggs <[email protected]> Commit-Queue: Mark Zhou <[email protected]> Reviewed-by: Nicholas Shahan <[email protected]>
1 parent fd3eaa6 commit ba90daf

File tree

16 files changed

+608
-175
lines changed

16 files changed

+608
-175
lines changed

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 180 additions & 110 deletions
Large diffs are not rendered by default.

pkg/dev_compiler/lib/src/kernel/compiler_new.dart

Lines changed: 123 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,20 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
232232
Map<Class, js_ast.Identifier> get classIdentifiers =>
233233
_symbolData.classIdentifiers;
234234

235+
/// Maps every mixin application to a unique identifier.
236+
///
237+
/// A mixin application is represented as a (mixin, class) pair, where
238+
/// 'mixin' is being mixed into 'class'. Anonymous mixins are already
239+
/// unique per mixin application and so pass themselves in as both 'mixin'
240+
/// and 'class'.
241+
///
242+
/// This mapping is used when generating super property getters in mixins.
243+
final Map<(Class, Class), js_ast.Identifier> _mixinCache = {};
244+
245+
/// Records a reference to a mixin application's passed in superclass.
246+
/// (see [_emitMixinStatement]).
247+
final Map<Class, js_ast.Identifier> _mixinSuperclassCache = {};
248+
235249
/// Maps each class `Member` node compiled in the module to the name used for
236250
/// the member in JavaScript.
237251
///
@@ -1173,6 +1187,13 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
11731187
// a simpler representation within instance members of the class.
11741188
_currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
11751189

1190+
// Store identifiers for a mixin application's passed in superclass.
1191+
// (see [_emitMixinStatement]).
1192+
if (c.isMixinDeclaration && !c.isMixinClass) {
1193+
_mixinSuperclassCache.putIfAbsent(
1194+
c, () => _emitScopedId(getLocalClassName(c.superclass!)));
1195+
}
1196+
11761197
// Mixins are unrolled in _defineClass.
11771198
if (!c.isAnonymousMixin) {
11781199
// If this class is annotated with `@JS`, then we only need to emit the
@@ -1463,7 +1484,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
14631484
var instanceProperties = properties.where((m) => !m.isStatic).toList();
14641485

14651486
body.addAll(_emitClassStatement(c, className, heritage, staticProperties));
1466-
var superclassId = _emitScopedId(getLocalClassName(c.superclass!));
1487+
var superclassId = _mixinSuperclassCache[c]!;
14671488
var classId = className is js_ast.Identifier
14681489
? className
14691490
: _emitScopedId(getLocalClassName(c));
@@ -1590,9 +1611,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
15901611
var mixinClass = m.isAnonymousMixin ? m.mixedInClass! : m;
15911612
var mixinType =
15921613
_hierarchy.getClassAsInstanceOf(c, mixinClass)!.asInterfaceType;
1593-
var mixinName =
1594-
'${getLocalClassName(superclass)}_${getLocalClassName(mixinClass)}';
1595-
var mixinId = _emitScopedId('$mixinName\$');
1614+
var mixinId = _emitMixinId(m, m.isAnonymousMixin ? m : c);
15961615
// Collect all forwarding stub members from anonymous mixins classes.
15971616
// These can contain covariant parameter checks that need to be applied.
15981617
var savedClassProperties = _classProperties;
@@ -1630,24 +1649,36 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
16301649
_currentTypeEnvironment = savedTypeEnvironment;
16311650
_classProperties = savedClassProperties;
16321651

1633-
// Bind the mixin class to a name to workaround a V8 bug with es6 classes
1634-
// and anonymous function names.
1635-
// TODO(leafp:) Eliminate this once the bug is fixed:
1636-
// https://bugs.chromium.org/p/v8/issues/detail?id=7069
1637-
body.add(js.statement('const # = #', [
1652+
// Mixins need to be exposed in this library in case they are
1653+
// referenced in a super getter.
1654+
// TODO(markzipan): We originally bound mixin classes to a temporary as a
1655+
// workaround for a now-resolved Chrome issue. However, a side effect of
1656+
// this operation is that mixin IDs are renamed by the local visitor. We
1657+
// can remove this hoisting after we give mixins unique names.
1658+
body.add(js.statement('let # = #', [
16381659
mixinId,
1639-
js_ast.ClassExpression(
1640-
_emitScopedId(mixinName), null, forwardingMethodStubs)
1660+
_runtimeCall('declareClass(#, #, #)', [
1661+
_emitLibraryName(_currentLibrary!),
1662+
js.string(mixinId.name),
1663+
js_ast.ClassExpression(
1664+
_emitScopedId('${mixinId.name}\$'),
1665+
null,
1666+
forwardingMethodStubs,
1667+
)
1668+
])
16411669
]));
1670+
16421671
_classExtendsLinks.add(_runtimeStatement(
16431672
'classExtends(#, #)', [mixinId, embedderResolvedBaseClass]));
16441673
emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
16451674
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
1675+
var mixinTargetLabel = js.string(fullyResolvedMixinClassLabel(m));
16461676
// The SDK is never hot reloaded, so we can avoid the overhead of
16471677
// resolving their classes through the embedder.
1648-
_mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #)', [
1678+
_mixinApplicationLinks.add(_runtimeStatement('applyMixin(#, #, #)', [
16491679
mixinId,
1650-
emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk)
1680+
emitClassRef(mixinType, resolvedFromEmbedder: !_isBuildingSdk),
1681+
mixinTargetLabel
16511682
]));
16521683
baseClass = mixinId;
16531684
embedderResolvedBaseClass = mixinId;
@@ -1810,6 +1841,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
18101841
var staticMethods = <js_ast.Property>[];
18111842
var instanceMethods = <js_ast.Property>[];
18121843
var instanceMethodsDefaultTypeArgs = <js_ast.Property>[];
1844+
var methodsImmediateTarget = <js_ast.Property>[];
18131845
var staticGetters = <js_ast.Property>[];
18141846
var instanceGetters = <js_ast.Property>[];
18151847
var staticSetters = <js_ast.Property>[];
@@ -1863,9 +1895,16 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
18631895
var needsSignature = memberOverride == null ||
18641896
reifiedType != _memberRuntimeType(memberOverride, c);
18651897

1898+
var memberName = _declareMemberName(member,
1899+
useExtension: _isObjectMethodTearoff(member.name.text));
1900+
if (!member.isAccessor) {
1901+
var immediateTarget = js.string(fullyResolvedTargetLabel(member));
1902+
methodsImmediateTarget
1903+
.add(js_ast.Property(memberName, immediateTarget));
1904+
}
1905+
18661906
if (needsSignature) {
18671907
js_ast.Expression type;
1868-
var memberName = _declareMemberName(member);
18691908
if (member.isAccessor) {
18701909
// These signatures are used for dynamic access and to inform the
18711910
// debugger. The `arrayRti` accessor is only used by the dart:_rti
@@ -1918,6 +1957,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
19181957

19191958
emitSignature('Method', instanceMethods);
19201959
emitSignature('MethodsDefaultTypeArg', instanceMethodsDefaultTypeArgs);
1960+
emitSignature('MethodsImmediateTarget', methodsImmediateTarget);
19211961
// TODO(40273) Skip for all statics when the debugger consumes signature
19221962
// information from symbol files.
19231963
emitSignature('StaticMethod', staticMethods);
@@ -3136,12 +3176,14 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
31363176
///
31373177
/// Unlike call sites, we always have an element available, so we can use it
31383178
/// directly rather than computing the relevant options for [_emitMemberName].
3139-
js_ast.Expression _declareMemberName(Member m, {bool? useExtension}) {
3179+
js_ast.Expression _declareMemberName(Member m, {bool useExtension = false}) {
31403180
var c = m.enclosingClass;
3141-
return _emitMemberName(m.name.text,
3181+
var name = m.name.text;
3182+
var actualUseExtension =
3183+
useExtension || (c != null && _extensionTypes.isNativeClass(c));
3184+
return _emitMemberName(name,
31423185
isStatic: m is Field ? m.isStatic : (m as Procedure).isStatic,
3143-
useExtension:
3144-
useExtension ?? c != null && _extensionTypes.isNativeClass(c),
3186+
useExtension: actualUseExtension,
31453187
member: m);
31463188
}
31473189

@@ -5431,7 +5473,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
54315473
}
54325474
var jsMemberName = _emitMemberName(memberName, member: member);
54335475
if (_reifyTearoff(member)) {
5434-
return _runtimeCall('tearoff(#, #)', [jsReceiver, jsMemberName]);
5476+
return _runtimeCall('tearoff(#, null, #)', [jsReceiver, jsMemberName]);
54355477
}
54365478
var jsPropertyAccess = js_ast.PropertyAccess(jsReceiver, jsMemberName);
54375479
return isJsMember(member)
@@ -5572,11 +5614,41 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
55725614
return _emitSuperPropertyGet(node.interfaceTarget);
55735615
}
55745616

5617+
/// Emits a reference to a distinct mixin application, represented by
5618+
/// a [mixedInClass] being mixed into [baseClass].
5619+
///
5620+
/// Anonymous mixins should pass themselves as [baseClass] since they are
5621+
/// already uniquely generated per distinct mixin application
5622+
js_ast.Identifier _emitMixinId(Class mixedInClass, Class baseClass) {
5623+
var mixinName = mixedInClass.name;
5624+
if (!mixedInClass.isAnonymousMixin) {
5625+
mixinName += '#${baseClass.name}';
5626+
}
5627+
return _mixinCache
5628+
.putIfAbsent((mixedInClass, baseClass), () => _emitScopedId(mixinName));
5629+
}
5630+
55755631
js_ast.Expression _emitSuperPropertyGet(Member target) {
55765632
if (_reifyTearoff(target)) {
55775633
if (_superAllowed) {
55785634
var jsTarget = _emitSuperTarget(target);
5579-
return _runtimeCall('bind(this, #, #)', [jsTarget.selector, jsTarget]);
5635+
var jsName = _declareMemberName(target);
5636+
var enclosingClass = target.enclosingClass!;
5637+
js_ast.Expression? supertypeReference =
5638+
_mixinSuperclassCache[_currentClass!];
5639+
if (supertypeReference == null) {
5640+
if (enclosingClass.isAnonymousMixin) {
5641+
var mixinId = _emitMixinId(enclosingClass, enclosingClass);
5642+
var enclosingLibrary = _emitLibraryName(getLibrary(enclosingClass));
5643+
supertypeReference = js_ast.PropertyAccess(
5644+
enclosingLibrary, js.string(mixinId.name));
5645+
} else {
5646+
supertypeReference =
5647+
_emitTopLevelNameNoExternalInterop(enclosingClass);
5648+
}
5649+
}
5650+
return _runtimeCall(
5651+
'bind(this, #, #, #)', [supertypeReference, jsName, jsTarget]);
55805652
} else {
55815653
return _emitSuperTearoff(target);
55825654
}
@@ -5625,7 +5697,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
56255697
var property = propertyAccessor.selector;
56265698
var result = js.call('#.#', [context, property]);
56275699
if (_reifyTearoff(target)) {
5628-
return _runtimeCall('staticTearoff(#, #)', [context, property]);
5700+
var targetLabel = js.string(fullyResolvedTargetLabel(target));
5701+
return _runtimeCall(
5702+
'staticTearoff(#, #, #)', [context, targetLabel, property]);
56295703
}
56305704
return result;
56315705
}
@@ -6259,8 +6333,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
62596333
/// Emits the [js_ast.PropertyAccess] for accessors or method calls to
62606334
/// [jsTarget].[jsName], replacing `super` if it is not allowed in scope.
62616335
js_ast.PropertyAccess _emitSuperTarget(Member member, {bool setter = false}) {
6262-
var jsName = _emitMemberName(member.name.text, member: member);
6263-
// Optimize access to non-virtual fields, if allowed in the current context.
6336+
var jsName = _declareMemberName(member);
62646337
if (_optimizeNonVirtualFieldAccess &&
62656338
member is Field &&
62666339
!_virtualFields.isVirtual(member)) {
@@ -6316,18 +6389,42 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
63166389
return js_ast.PropertyAccess(js_ast.This(), jsMethod.name);
63176390
}
63186391

6392+
/// Generates a special string used for identifying a torn off member [m].
6393+
///
6394+
/// This tag is used for determining tearoff equality. We attach these tags
6395+
/// at tearoff time for static tearoffs and in the method signature for
6396+
/// dynamic tearoffs.
6397+
String fullyResolvedTargetLabel(Member m) {
6398+
return '${m.enclosingLibrary.importUri}:${m.enclosingClass?.name ?? ""}';
6399+
}
6400+
6401+
/// Generates a special string used for identifying class [c]'s applied mixed
6402+
/// in members.
6403+
///
6404+
/// This tag is used for determining tearoff equality. We attach these tags
6405+
/// at tearoff time for static tearoffs and in the method signature for
6406+
/// dynamic tearoffs.
6407+
String fullyResolvedMixinClassLabel(Class c) {
6408+
return '${c.enclosingLibrary.importUri}:${c.name}';
6409+
}
6410+
63196411
/// Generates a helper method that is inserted into the class that binds a
63206412
/// tearoff of [member] from `super` and returns a call to the helper.
63216413
///
63226414
/// This method assumes `super` is not allowed in the current context.
63236415
// TODO(nshahan) Replace with a kernel transform and synthetic method filters
63246416
// for devtools.
63256417
js_ast.Expression _emitSuperTearoff(Member member) {
6326-
var jsName = _emitMemberName(member.name.text, member: member);
6418+
var jsName = _declareMemberName(member);
63276419
var name = '_#super#tearOff#${member.name.text}';
63286420
var jsMethod = _superHelpers.putIfAbsent(name, () {
6329-
var jsReturnValue =
6330-
_runtimeCall('bind(this, #, super[#])', [jsName, jsName]);
6421+
var superclass = member.enclosingClass?.superclass;
6422+
var supertypeReference = superclass == null
6423+
? js_ast.LiteralNull()
6424+
: _mixinSuperclassCache[member.enclosingClass!] ??
6425+
_emitTopLevelNameNoExternalInterop(superclass);
6426+
var jsReturnValue = _runtimeCall(
6427+
'bind(this, #, #, super[#])', [supertypeReference, jsName, jsName]);
63316428
var fn = js.fun('function() { return #; }', [jsReturnValue]);
63326429
name = js_ast.friendlyNameForDartOperator[name] ?? name;
63336430
return js_ast.Method(_emitScopedId(name), fn);

pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_shared.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ void runExpressionCompilationTests(ExpressionCompilerWorkerTestDriver driver) {
133133
'errors': isEmpty,
134134
'warnings': isEmpty,
135135
'infos': isEmpty,
136-
'compiledProcedure': contains('developer.postEvent'),
136+
'compiledProcedure':
137+
stringContainsInOrder(['developer', 'postEvent']),
137138
})
138139
]));
139140
});
@@ -167,7 +168,8 @@ void runExpressionCompilationTests(ExpressionCompilerWorkerTestDriver driver) {
167168
'errors': isEmpty,
168169
'warnings': isEmpty,
169170
'infos': isEmpty,
170-
'compiledProcedure': contains('developer.postEvent'),
171+
'compiledProcedure':
172+
stringContainsInOrder(['developer', 'postEvent']),
171173
})
172174
]));
173175
});

pkg/dev_compiler/test/expression_compiler/runtime_debugger_api_test.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,8 +752,7 @@ void runSharedTests(
752752
test('getFunctionName (static method)', () async {
753753
var getFunctionName =
754754
setup.emitLibraryBundle ? 'getFunctionName' : 'getFunctionMetadata';
755-
var expectedName =
756-
setup.emitLibraryBundle ? 'BaseClass.staticMethod' : 'staticMethod';
755+
var expectedName = 'BaseClass.staticMethod';
757756

758757
await driver.checkRuntimeInFrame(
759758
breakpointId: 'BP',

0 commit comments

Comments
 (0)