[dart2wasm] Use concrete classes for wasm type representation selection

For example code that uses `Completer` in parameters/fields/...
used to be represented as `Object` in wasm type system, which we
can now represent instead as `_Completer`.

Change-Id: I35b2d8cb45717e58a813756dfa6278804552fbcb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365020
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Martin Kustermann 2024-05-02 12:28:48 +00:00 committed by Commit Queue
parent 2388c383ea
commit 74fbb380e2
6 changed files with 83 additions and 50 deletions

View file

@ -149,8 +149,9 @@ class ClassInfo {
/// The class whose struct is used as the type for variables of this type.
/// This is a type which is a superclass of all subtypes of this type.
late final ClassInfo repr = upperBound(
implementedBy.map((c) => identical(c, this) ? this : c.repr).toSet());
ClassInfo get repr => _repr!;
ClassInfo? _repr;
/// All classes which implement this class. This is used to compute `repr`.
final List<ClassInfo> implementedBy = [];
@ -185,25 +186,22 @@ class ClassInfo {
];
}
ClassInfo upperBound(Set<ClassInfo> classes) {
while (classes.length > 1) {
Set<ClassInfo> newClasses = {};
int minDepth = 999999999;
int maxDepth = 0;
for (ClassInfo info in classes) {
minDepth = min(minDepth, info.depth);
maxDepth = max(maxDepth, info.depth);
ClassInfo upperBound(ClassInfo a, ClassInfo b) {
if (a.depth < b.depth) {
while (b.depth > a.depth) {
b = b.superInfo!;
}
int targetDepth = minDepth == maxDepth ? minDepth - 1 : minDepth;
for (ClassInfo info in classes) {
while (info.depth > targetDepth) {
info = info.superInfo!;
}
newClasses.add(info);
} else {
while (a.depth > b.depth) {
a = a.superInfo!;
}
classes = newClasses;
}
return classes.single;
assert(a.depth == b.depth);
while (a != b) {
a = a.superInfo!;
b = b.superInfo!;
}
return a;
}
/// Constructs the Wasm type hierarchy.
@ -439,7 +437,7 @@ class ClassInfoCollector {
// `0` is occupied by artificial non-Dart top class.
const int firstClassId = 1;
translator.classIdNumbering =
final classIdNumbering = translator.classIdNumbering =
ClassIdNumbering._number(translator, masqueraded, firstClassId);
final classIds = translator.classIdNumbering.classIds;
final dfsOrder = translator.classIdNumbering.dfsOrder;
@ -476,6 +474,25 @@ class ClassInfoCollector {
}
}
// Create representations of the classes (i.e. wasm representation used to
// represent objects of that dart type).
for (final cls in dfsOrder) {
ClassInfo? representation;
for (final range in classIdNumbering.getConcreteClassIdRanges(cls)) {
for (int classId = range.start; classId <= range.end; ++classId) {
final current = translator.classes[classId];
if (representation == null) {
representation = current;
continue;
}
representation = upperBound(representation, current);
}
}
final classId = classIdNumbering.classIds[cls]!;
final info = translator.classes[classId];
info._repr = representation ?? translator.classes[classId];
}
// Now that the representation types for all classes have been computed,
// fill in the types of the fields in the generated Wasm structs.
for (final info in translator.classesSupersFirst) {

View file

@ -1041,6 +1041,7 @@ class Closures {
final Map<TreeNode, Capture> captures = {};
bool isThisCaptured = false;
final Map<FunctionNode, Lambda> lambdas = {};
late final w.RefType? nullableThisType;
// This [TreeNode] is the context owner, and can be a [FunctionNode],
// [Constructor], [ForStatement], [DoStatement] or a [WhileStatement].
@ -1048,7 +1049,12 @@ class Closures {
final Set<FunctionDeclaration> closurizedFunctions = {};
Closures(this.translator, Member member)
: enclosingClass = member.enclosingClass;
: enclosingClass = member.enclosingClass {
final hasThis = member is Constructor || member.isInstanceMember;
nullableThisType = hasThis
? translator.preciseThisFor(member, nullable: true) as w.RefType
: null;
}
w.ModuleBuilder get m => translator.m;
@ -1104,8 +1110,7 @@ class Closures {
}
if (context.containsThis) {
assert(enclosingClass != null);
struct.fields.add(
w.FieldType(translator.classInfo[enclosingClass!]!.nullableType));
struct.fields.add(w.FieldType(nullableThisType!));
}
for (VariableDeclaration variable in context.variables) {
int index = struct.fields.length;

View file

@ -724,23 +724,15 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// parameter if there are no type parameters).
int _initializeThis(Reference reference) {
Member member = reference.asMember;
bool hasThis =
final hasThis =
member.isInstanceMember || reference.isConstructorBodyReference;
if (hasThis) {
thisLocal = paramLocals[0];
assert(!thisLocal!.type.nullable);
Class cls = member.enclosingClass!;
w.StorageType? builtin = translator.builtinTypes[cls];
w.ValueType thisType = translator.boxedClasses.containsKey(builtin)
? builtin as w.ValueType
: translator.classInfo[cls]!.nonNullableType;
if (translator.needsConversion(thisLocal!.type, thisType) &&
!(cls == translator.objectInfo.cls ||
cls == translator.ffiPointerClass ||
translator.isWasmType(cls))) {
preciseThisLocal = addLocal(thisType);
final preciseThisType = translator.preciseThisFor(member);
if (translator.needsConversion(thisLocal!.type, preciseThisType)) {
preciseThisLocal = addLocal(preciseThisType);
b.local_get(thisLocal!);
translator.convertType(function, thisLocal!.type, thisType);
translator.convertType(function, thisLocal!.type, preciseThisType);
b.local_set(preciseThisLocal!);
} else {
preciseThisLocal = thisLocal!;
@ -1739,14 +1731,19 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.ValueType preciseThisType = preciseThisLocal!.type;
assert(!thisType.nullable);
assert(!preciseThisType.nullable);
if (!thisType.isSubtypeOf(expectedType) &&
preciseThisType.isSubtypeOf(expectedType)) {
b.local_get(preciseThisLocal!);
return preciseThisType;
} else {
if (thisType.isSubtypeOf(expectedType)) {
b.local_get(thisLocal!);
return thisType;
}
if (preciseThisType.isSubtypeOf(expectedType)) {
b.local_get(preciseThisLocal!);
return preciseThisType;
}
// A user of `this` may have more precise type information, in which case
// we downcast it here.
b.local_get(thisLocal!);
translator.convertType(function, thisType, expectedType);
return expectedType;
}
@override
@ -1787,7 +1784,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
_lookupSuperTarget(node.interfaceTarget, setter: false).reference;
w.FunctionType targetFunctionType =
translator.functions.getFunctionType(target);
w.ValueType receiverType = targetFunctionType.inputs[0];
final w.ValueType receiverType = translator.preciseThisFor(target.asMember);
// When calling `==` and the argument is potentially nullable, check if the
// argument is `null`.

View file

@ -104,8 +104,6 @@ class Forwarder {
if (targetMember.isAbstract) {
continue;
}
final targetClass = targetMember.enclosingClass!;
final targetClassInfo = translator.classInfo[targetClass]!;
b.local_get(receiverLocal);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
@ -113,7 +111,6 @@ class Forwarder {
b.i32_eq();
b.if_();
final w.ValueType receiverType = targetClassInfo.nonNullableType;
final Reference targetReference;
if (targetMember is Procedure) {
targetReference = targetMember.isGetter
@ -128,7 +125,8 @@ class Forwarder {
final w.BaseFunction targetFunction =
translator.functions.getFunction(targetReference);
b.local_get(receiverLocal);
translator.convertType(function, receiverLocal.type, receiverType);
translator.convertType(
function, receiverLocal.type, targetFunction.type.inputs.first);
b.call(targetFunction);
// Box return value if needed
translator.convertType(function, targetFunction.type.outputs.single,
@ -528,8 +526,6 @@ class Forwarder {
if (targetMember is Procedure && !targetMember.isGetter) {
continue;
}
final targetClass = targetMember.enclosingClass!;
final targetClassInfo = translator.classInfo[targetClass]!;
b.local_get(receiverLocal);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
@ -537,7 +533,6 @@ class Forwarder {
b.i32_eq();
b.if_();
final w.ValueType receiverType = targetClassInfo.nonNullableType;
final Reference targetReference;
if (targetMember is Procedure) {
assert(targetMember.isGetter); // methods are skipped above
@ -553,7 +548,8 @@ class Forwarder {
// Get field value
b.local_get(receiverLocal);
translator.convertType(function, receiverLocal.type, receiverType);
translator.convertType(
function, receiverLocal.type, targetFunction.type.inputs.first);
b.call(targetFunction);
translator.convertType(function, targetFunction.type.outputs.single,
translator.topInfo.nullableType);

View file

@ -902,6 +902,24 @@ class Translator with KernelNodes {
}
}
w.ValueType preciseThisFor(Member member, {bool nullable = false}) {
assert(member.isInstanceMember || member is Constructor);
Class cls = member.enclosingClass!;
final w.StorageType? builtin = builtinTypes[cls];
final boxClass = boxedClasses[builtin];
if (boxClass != null) {
// We represent `this` as an unboxed type.
if (!nullable) return builtin as w.ValueType;
// Otherwise we use [boxClass] to represent `this`.
cls = boxClass;
}
final representationClassInfo = classInfo[cls]!.repr;
return nullable
? representationClassInfo.nullableType
: representationClassInfo.nonNullableType;
}
/// Get the Wasm table declared by [field], or `null` if [field] is not a
/// declaration of a Wasm table.
///

View file

@ -36,7 +36,7 @@ void testCovariantMethodCheck() {
}
void testDynamicCall() {
final dynamic a = kTrue ? (String a) => 'closure($a)' : A();
final dynamic a = kTrue ? (List a) => 'closure($a)' : A();
Expect.equals('closure(B)', a(B()));
}