[dart2wasm] Refactor _Closure.operator == to use the new intrinsics

Change-Id: I6f43b41dea7b5d037193deb095ffee3238b5fcb2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/353265
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Ömer Sinan Ağacan 2024-02-22 09:15:33 +00:00 committed by Commit Queue
parent dc71c1ee4e
commit 8dcedd9edb
3 changed files with 102 additions and 262 deletions

View file

@ -3840,7 +3840,7 @@ extension MacroAssembler on w.InstructionsBuilder {
nullable: false));
}
/// `[ref _Closure] -> [ref _ClosureBase]`
/// `[ref _Closure] -> [ref #ClosureBase]`
///
/// Given an instantiation closure returns the instantiated closure.
void emitGetInstantiatedClosure(Translator translator) {
@ -3849,26 +3849,11 @@ extension MacroAssembler on w.InstructionsBuilder {
nullable: false));
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
ref_cast(w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false));
// instantiation.context.inner
struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
}
/// `[ref #ClosureBase] -> [ref #InstantiationContextBase]`
///
/// Given an instantiation closure returns the instantiated closure's
/// context.
void emitGetInstantiationContextInner(Translator translator) {
// instantiation.context
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
// instantiation.context.inner
ref_cast(w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false));
// instantiation.context.inner
struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
}
@ -3894,4 +3879,14 @@ extension MacroAssembler on w.InstructionsBuilder {
FieldIndex.closureContext);
ref_cast(translator.topInfo.nonNullableType);
}
/// `[ref _Closure] -> [ref Any]
///
/// Given a closure returns the vtable of the closure.
void emitGetClosureVtable(Translator translator) {
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(
translator.closureLayouter.closureBaseStruct, FieldIndex.closureVtable);
}
}

View file

@ -1423,225 +1423,6 @@ class Intrinsifier {
}
}
if (member.enclosingClass == translator.closureClass && name == "_equals") {
// Function equality works like this:
//
// - Function literals and local functions are only equal if they're the
// same reference.
//
// - Instance tear-offs are equal if they are tear-offs of the same
// method on the same object.
//
// - Tear-offs of static methods and top-level functions are identical
// (and thus equal) when they are tear-offs of the same function. Generic
// instantiations of these are identical when the tear-offs are identical
// and they are instantiated with identical types.
//
// To distinguish a function literal or local function from an instance
// tear-off we check type of the context:
//
// - If context's type is a subtype of the top type for Dart objects then
// the function is a tear-off and we compare the context using the
// `identical` function.
//
// The reason why we use `identical` (instead of `ref.eq`) is to handle
// bool, double, and int receivers in code like `1.toString ==
// 1.toString`, which should evaluate to `true` even if the receivers
// do not point to the same Wasm object.
//
// - Otherwise the function is a function literal or local function.
//
// In pseudo code:
//
// bool _equals(f1, f2) {
// if (identical(f1, f2) return true;
//
// if (<f1 and f2 are instantiations>
// ? f1.context.inner.vtable != f2.context.inner.vtable
// : f1.vtable != f2.vtable) {
// return false;
// }
//
// if (<f1 and f2 are instantiations>) {
// if (typesEqual(f1.context, f2.context)) {
// f1 = f1.context.inner;
// f2 = f2.context.inner;
// if (identical(f1, f2)) return true;
// goto outerClosureContext;
// }
// return false;
// }
//
// outerClosureContext:
// if (f1.context is #Top && f2.context is #Top) {
// return identical(f1.context, f2.context);
// }
//
// return false;
// }
// Check if the arguments are the same
b.local_get(function.locals[0]);
b.local_get(function.locals[1]);
b.ref_eq();
b.if_();
b.i32_const(1); // true
b.return_();
b.end();
// Arguments are different, compare context and vtable references
final w.StructType closureBaseStruct =
translator.closureLayouter.closureBaseStruct;
final w.RefType closureBaseStructRef =
w.RefType.def(closureBaseStruct, nullable: false);
final w.Local fun1 = codeGen.function.addLocal(closureBaseStructRef);
b.local_get(function.locals[0]);
translator.convertType(
function, function.locals[0].type, closureBaseStructRef);
b.local_set(fun1);
final w.Local fun2 = codeGen.function.addLocal(closureBaseStructRef);
b.local_get(function.locals[1]);
translator.convertType(
function, function.locals[1].type, closureBaseStructRef);
b.local_set(fun2);
// Compare vtable references. For instantiation closures compare the
// inner vtables
final instantiationContextBase = w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false);
final vtableRefType = w.RefType.def(
translator.closureLayouter.vtableBaseStruct,
nullable: false);
// Returns vtables of closures that we compare for equality.
final vtablesBlock = b.block([], [vtableRefType, vtableRefType]);
// `br` target when fun1 is not an instantiation
final fun1NotInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
// `br` target when fun1 is an instantiation, but fun2 is not
final fun1InstantiationFun2NotInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(fun1NotInstantiationBlock,
const w.RefType.struct(nullable: false), instantiationContextBase);
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(fun1InstantiationFun2NotInstantiationBlock,
const w.RefType.struct(nullable: false), instantiationContextBase);
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.br(vtablesBlock);
b.end(); // fun1InstantiationFun2NotInstantiationBlock
b.i32_const(0); // false
b.return_();
b.end(); // fun1NotInstantiationBlock
b.drop();
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
// To keep the generated code small and simple, instead of checking that
// fun2 is also not an instantiation, we can just return the outer
// (potentially instantiation) vtable here. In the rest of the code
// `ref.eq` will be `false` (as vtable of an instantiation and
// non-instantiation will never be equal) and the function will return
// `false` as expected.
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.end(); // vtablesBlock
b.ref_eq();
b.if_(); // fun1.vtable == fun2.vtable
// Check if closures are instantiations. Since they have the same vtable
// it's enough to check just one of them.
final instantiationCheckPassedBlock = b.block();
final notInstantiationBlock =
b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(notInstantiationBlock,
const w.RefType.struct(nullable: false), instantiationContextBase);
// Closures are instantiations of the same function, compare types.
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
b.local_get(fun1);
_getInstantiationContextInner(translator, b);
b.struct_get(closureBaseStruct, FieldIndex.closureVtable);
b.ref_cast(w.RefType.def(
translator.closureLayouter.genericVtableBaseStruct,
nullable: false));
b.struct_get(translator.closureLayouter.genericVtableBaseStruct,
FieldIndex.vtableInstantiationTypeComparisonFunction);
b.call_ref(translator
.closureLayouter.instantiationClosureTypeComparisonFunctionType);
b.if_();
b.local_get(fun1);
_getInstantiationContextInner(translator, b);
b.local_tee(fun1);
b.local_get(fun2);
_getInstantiationContextInner(translator, b);
b.local_tee(fun2);
b.ref_eq();
b.if_();
b.i32_const(1); // true
b.return_();
b.end();
b.br(instantiationCheckPassedBlock);
b.end();
b.i32_const(0); // false
b.return_();
b.i32_const(0); // false
b.return_();
b.end(); // notInstantiationBlock
b.drop();
b.end(); // instantiationCheckPassedBlock
// Compare context references. If context of a function has the top type
// then the function is an instance tear-off. Otherwise it's a closure.
final contextCheckFail = b.block([], [w.RefType.struct(nullable: false)]);
b.local_get(fun1);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(
contextCheckFail,
const w.RefType.struct(nullable: false),
translator.topInfo.nonNullableType);
b.local_get(fun2);
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.br_on_cast_fail(
contextCheckFail,
const w.RefType.struct(nullable: false),
translator.topInfo.nonNullableType);
// Both contexts are objects, compare for equality with `identical`. This
// handles identical `this` values in instance tear-offs.
codeGen.call(translator.coreTypes.identicalProcedure.reference);
b.return_();
b.end(); // contextCheckFail
b.i32_const(0); // false
b.return_();
b.end(); // fun1.vtable == fun2.vtable
b.i32_const(0); // false
return true;
}
if (member.enclosingClass == translator.closureClass &&
name == "_isInstantiationClosure") {
assert(function.locals.length == 1);
@ -1674,11 +1455,8 @@ class Intrinsifier {
// Hash function.
b.local_get(function.locals[0]); // ref _Closure
b.ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
_getInstantiationContextInner(translator, b);
b.struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureVtable);
b.emitGetInstantiatedClosure(translator);
b.emitGetClosureVtable(translator);
b.ref_cast(w.RefType.def(
translator.closureLayouter.genericVtableBaseStruct,
nullable: false));
@ -1690,6 +1468,42 @@ class Intrinsifier {
return true;
}
if (member.enclosingClass == translator.closureClass &&
name == "_instantiationClosureTypeEquals") {
assert(function.locals.length == 2);
final w.StructType closureBaseStruct =
translator.closureLayouter.closureBaseStruct;
final w.RefType instantiationContextBase = w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false);
b.local_get(function.locals[0]); // ref _Closure
b.ref_cast(w.RefType(closureBaseStruct, nullable: false));
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
b.local_get(function.locals[1]); // ref _Closure
b.ref_cast(w.RefType(closureBaseStruct, nullable: false));
b.struct_get(closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(instantiationContextBase);
b.local_get(function.locals[0]);
b.emitGetInstantiatedClosure(translator);
b.emitGetClosureVtable(translator);
b.ref_cast(w.RefType.def(
translator.closureLayouter.genericVtableBaseStruct,
nullable: false));
b.struct_get(translator.closureLayouter.genericVtableBaseStruct,
FieldIndex.vtableInstantiationTypeComparisonFunction);
b.call_ref(translator
.closureLayouter.instantiationClosureTypeComparisonFunctionType);
return true;
}
if (member.enclosingClass == translator.closureClass &&
name == "_isInstanceTearOff") {
assert(function.locals.length == 1);
@ -1706,6 +1520,13 @@ class Intrinsifier {
return true;
}
if (member.enclosingClass == translator.closureClass && name == "_vtable") {
assert(function.locals.length == 1);
b.local_get(function.locals[0]); // ref _Closure
b.emitGetClosureVtable(translator);
return true;
}
if (member.enclosingClass == translator.coreTypes.functionClass &&
name == "apply") {
assert(function.type.inputs.length == 3);
@ -1815,19 +1636,3 @@ class Intrinsifier {
return false;
}
}
/// Expects a `ref #ClosureBase` for an instantiation closure on stack. Pops
/// the value and pushes the instantiated closure's (not instantiation's!)
/// context.
void _getInstantiationContextInner(
Translator translator, w.InstructionsBuilder b) {
// instantiation.context
b.struct_get(
translator.closureLayouter.closureBaseStruct, FieldIndex.closureContext);
b.ref_cast(w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false));
// instantiation.context.inner
b.struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
}

View file

@ -14,13 +14,45 @@ final class _Closure implements Function {
@override
bool operator ==(Object other) {
if (other is! Function) {
if (other is! _Closure) {
return false;
}
return _equals(this, other);
}
external static bool _equals(Function a, Function b);
if (identical(this, other)) {
return true;
}
final thisIsInstantiation = _isInstantiationClosure;
final otherIsInstantiation = other._isInstantiationClosure;
if (thisIsInstantiation != otherIsInstantiation) {
return false;
}
if (thisIsInstantiation) {
final thisInstantiatedClosure = _instantiatedClosure;
final otherInstantiatedClosure = other._instantiatedClosure;
return thisInstantiatedClosure == otherInstantiatedClosure &&
_instantiationClosureTypeEquals(other);
}
if (_vtable != other._vtable) {
return false;
}
final thisIsTearOff = _isInstanceTearOff;
final otherIsTearOff = other._isInstanceTearOff;
if (thisIsTearOff != otherIsTearOff) {
return false;
}
if (thisIsTearOff) {
return _instanceTearOffReceiver == other._instanceTearOffReceiver;
}
return false;
}
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
@ -55,7 +87,7 @@ final class _Closure implements Function {
/// When the closure is an instantiation, get the instantiated closure.
///
/// Traps when the closure is not an instantiation.
external _Closure? get _instantiatedClosure;
external _Closure get _instantiatedClosure;
/// When the closure is an instantiation, returns the combined hash code of
/// the captured types.
@ -63,6 +95,11 @@ final class _Closure implements Function {
/// Traps when the closure is not an instantiation.
external int _instantiationClosureTypeHash();
/// When [this] and [other] are instantiations, compare captured types for equality.
///
/// Traps when one or both of the closures are not an instantiation.
external bool _instantiationClosureTypeEquals(_Closure other);
/// Whether the closure is an instance tear-off.
///
/// Instance tear-offs will have receivers.
@ -72,4 +109,7 @@ final class _Closure implements Function {
///
/// Traps when the closure is not an instance tear-off.
external Object? get _instanceTearOffReceiver;
/// The vtable of the closure.
external WasmAnyRef get _vtable;
}