Skip to content

Commit

Permalink
Version 3.6.0-41.0.dev
Browse files Browse the repository at this point in the history
Merge b5ecb57 into dev
  • Loading branch information
Dart CI committed Jul 15, 2024
2 parents 6fcfea8 + b5ecb57 commit 27d3191
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 271 deletions.
7 changes: 7 additions & 0 deletions pkg/dart2wasm/lib/class_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,13 @@ class Range {
return Range(start + offset, end + offset);
}

@override
int get hashCode => Object.hash(start, end);

@override
bool operator ==(other) =>
other is Range && other.start == start && other.end == end;

@override
String toString() => isEmpty ? '[]' : '[$start, $end]';
}
Expand Down
78 changes: 14 additions & 64 deletions pkg/dart2wasm/lib/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2193,21 +2193,23 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>

pushReceiver(selector.signature);

if (selector.targetCount == 1) {
final target = selector.singularTarget!;
if (selector.targetRanges.length == 1) {
assert(selector.staticDispatchRanges.length == 1);
final target = selector.targetRanges[0].target;
final signature = translator.signatureForDirectCall(target);
final paramInfo = translator.paramInfoForDirectCall(target);
pushArguments(signature, paramInfo);
return translator.outputOrVoid(call(target));
}

int? offset = selector.offset;
if (offset == null) {
if (selector.targetRanges.isEmpty) {
// Unreachable call
assert(selector.targetCount == 0);
b.comment("Virtual call of ${selector.name} with no targets"
" at ${node.location}");
b.drop();
pushArguments(selector.signature, selector.paramInfo);
for (int i = 0; i < selector.signature.inputs.length; ++i) {
b.drop();
}
b.block(const [], selector.signature.outputs);
b.unreachable();
b.end();
Expand All @@ -2219,9 +2221,13 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
assert(!receiverVar.type.nullable);
b.local_tee(receiverVar);
pushArguments(selector.signature, selector.paramInfo);
if (options.polymorphicSpecialization) {
_polymorphicSpecialization(selector, receiverVar);

if (selector.staticDispatchRanges.isNotEmpty) {
final polymorphicDispatcher =
translator.polymorphicDispatchers.getPolymorphicDispatcher(selector);
b.call(polymorphicDispatcher);
} else {
final offset = selector.offset!;
b.comment("Instance $kind of '${selector.name}'");
b.local_get(receiverVar);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
Expand All @@ -2237,62 +2243,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
return translator.outputOrVoid(selector.signature.outputs);
}

void _polymorphicSpecialization(SelectorInfo selector, w.Local receiver) {
Map<int, Reference> implementations = Map.from(selector.targets);
implementations.removeWhere((id, target) => target.asMember.isAbstract);

w.Local idVar = addLocal(w.NumType.i32);
b.local_get(receiver);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_set(idVar);

w.Label block =
b.block(selector.signature.inputs, selector.signature.outputs);
calls:
while (Set.from(implementations.values).length > 1) {
for (int id in implementations.keys) {
Reference target = implementations[id]!;
if (implementations.values.where((t) => t == target).length == 1) {
// Single class id implements method.
b.local_get(idVar);
b.i32_const(id);
b.i32_eq();
b.if_(selector.signature.inputs, selector.signature.inputs);
call(target);
b.br(block);
b.end();
implementations.remove(id);
continue calls;
}
}
// Find class id that separates remaining classes in two.
List<int> sorted = implementations.keys.toList()..sort();
int pivotId = sorted.firstWhere(
(id) => implementations[id] != implementations[sorted.first]);
// Fail compilation if no such id exists.
assert(sorted.lastWhere(
(id) => implementations[id] != implementations[pivotId]) ==
pivotId - 1);
Reference target = implementations[sorted.first]!;
b.local_get(idVar);
b.i32_const(pivotId);
b.i32_lt_u();
b.if_(selector.signature.inputs, selector.signature.inputs);
call(target);
b.br(block);
b.end();
for (int id in sorted) {
if (id == pivotId) break;
implementations.remove(id);
}
continue calls;
}
// Call remaining implementation.
Reference target = implementations.values.first;
call(target);
b.end();
}

@override
w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
w.Local? local = locals[node.variable];
Expand Down
123 changes: 82 additions & 41 deletions pkg/dart2wasm/lib/dispatch_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,27 @@ class SelectorInfo {
/// Does this method have any tear-off uses?
bool hasTearOffUses = false;

/// Maps all concrete classes implementing this selector to the relevent
/// implementation member reference for the class.
final Map<int, Reference> targets = {};
late final Set<Reference> targetSet = targets.values.toSet();
/// Targets for all concrete classes implementing this selector.
///
/// As a subclass hierarchy often inherits the same target, we associate the
/// target with a range of class ids. The ranges are non-empty,
/// non-overlapping and sorted in ascending order.
late final List<({Range range, Reference target})> targetRanges;
late final Set<Reference> targetSet =
targetRanges.map((e) => e.target).toSet();
late final List<({Range range, Reference target})> staticDispatchRanges;

/// Wasm function type for the selector.
///
/// This should be read after all targets have been added to the selector.
late final w.FunctionType signature = _computeSignature();

/// IDs of classes that implement the member. This does not include abstract
/// classes.
final List<int> classIds = [];

/// Number of non-abstract references in [targets].
late final int targetCount;

/// When [targetCount] is 1, this holds the only non-abstract target of the
/// selector.
late final Reference? singularTarget;
/// Number of concrete classes that provide this selector.
late final int concreteClasses;

/// Offset of the selector in the dispatch table.
///
/// For a class in [targets], `class ID + offset` gives the offset of the
/// For a class in [targetRanges], `class ID + offset` gives the offset of the
/// class member for this selector.
int? offset;

Expand All @@ -91,7 +88,7 @@ class SelectorInfo {
List.generate(1 + paramInfo.paramCount, (_) => {});
List<Set<w.ValueType>> outputSets = List.generate(returnCount, (_) => {});
List<bool> ensureBoxed = List.filled(1 + paramInfo.paramCount, false);
targets.forEach((classId, target) {
for (final (range: _, :target) in targetRanges) {
Member member = target.asMember;
DartType receiver =
InterfaceType(member.enclosingClass!, Nullability.nonNullable);
Expand Down Expand Up @@ -154,7 +151,7 @@ class SelectorInfo {
outputSets[i].add(translator.topInfo.nullableType);
}
}
});
}

List<w.ValueType> typeParameters = List.filled(paramInfo.typeParamCount,
translator.classInfo[translator.typeClass]!.nonNullableType);
Expand Down Expand Up @@ -379,40 +376,82 @@ class DispatchTable {
selectorsInClass[cls] = selectors;
}

final selectorTargets = <SelectorInfo, Map<int, Reference>>{};
final maxConcreteClassId = translator.classIdNumbering.maxConcreteClassId;
for (int classId = 0; classId < maxConcreteClassId; ++classId) {
final cls = translator.classes[classId].cls;
if (cls != null) {
selectorsInClass[cls]!.forEach((selectorInfo, target) {
selectorInfo.targets[classId] = target;
selectorInfo.classIds.add(classId);
if (!target.asMember.isAbstract) {
selectorTargets.putIfAbsent(selectorInfo, () => {})[classId] =
target;
}
});
}
}

// Build lists of class IDs and count targets
for (SelectorInfo selector in _selectorInfo.values) {
final Set<Reference> targets = selector.targets.values.toSet();
selector.targetCount = targets.length;
selector.singularTarget = targets.length == 1 ? targets.single : null;
}
selectorTargets
.forEach((SelectorInfo selector, Map<int, Reference> targets) {
selector.concreteClasses = targets.length;

final List<({Range range, Reference target})> ranges = targets.entries
.map((entry) =>
(range: Range(entry.key, entry.key), target: entry.value))
.toList()
..sort((a, b) => a.range.start.compareTo(b.range.start));
assert(ranges.isNotEmpty);
int writeIndex = 0;
for (int readIndex = 1; readIndex < ranges.length; ++readIndex) {
final current = ranges[writeIndex];
final next = ranges[readIndex];
assert(next.range.length == 1);
if ((current.range.end + 1) == next.range.start &&
identical(current.target, next.target)) {
ranges[writeIndex] = (
range: Range(current.range.start, next.range.end),
target: current.target
);
} else {
ranges[++writeIndex] = next;
}
}
ranges.length = writeIndex + 1;

final staticDispatchRanges =
translator.options.polymorphicSpecialization || ranges.length == 1
? ranges
: <({Range range, Reference target})>[];
selector.targetRanges = ranges;
selector.staticDispatchRanges = staticDispatchRanges;
});

_selectorInfo.forEach((_, selector) {
if (!selectorTargets.containsKey(selector)) {
selector.concreteClasses = 0;
selector.targetRanges = [];
}
});

// Assign selector offsets

/// Whether the selector will be used in an instance invocation.
///
/// If not, then we don't add the selector to the dispatch table and don't
/// assign it a dispatch table offset.
///
/// Special case for `objectNoSuchMethod`: we introduce instance
/// invocations of `objectNoSuchMethod` in dynamic calls, so keep it alive
/// even if there was no references to it from the Dart code.
bool needsDispatch(SelectorInfo selector) =>
(selector.callCount > 0 && selector.targetCount > 1) ||
(selector.paramInfo.member! == translator.objectNoSuchMethod);
bool isUsedViaDispatchTableCall(SelectorInfo selector) {
// Special case for `objectNoSuchMethod`: we introduce instance
// invocations of `objectNoSuchMethod` in dynamic calls, so keep it alive
// even if there was no references to it from the Dart code.
if (selector.paramInfo.member! == translator.objectNoSuchMethod) {
return true;
}
if (selector.callCount == 0) return false;
if (selector.targetRanges.length <= 1) return false;
if (selector.staticDispatchRanges.length ==
selector.targetRanges.length) {
return false;
}
return true;
}

final List<SelectorInfo> selectors =
_selectorInfo.values.where(needsDispatch).toList();
selectorTargets.keys.where(isUsedViaDispatchTableCall).toList();

// Sort the selectors based on number of targets and number of use sites.
// This is a heuristic to keep the table small.
Expand All @@ -424,16 +463,18 @@ class DispatchTable {
// more used ones first, as the smaller selector offset will have a smaller
// instruction encoding.
int selectorSortWeight(SelectorInfo selector) =>
selector.classIds.length * 10 + selector.callCount;
selector.concreteClasses * 10 + selector.callCount;

selectors.sort((a, b) => selectorSortWeight(b) - selectorSortWeight(a));

final rows = <Row<Reference>>[];
for (final selector in selectors) {
final rowValues = <({int index, Reference value})>[];
selector.targets.forEach((int classId, Reference target) {
rowValues.add((index: classId, value: target));
});
for (final (:range, :target) in selector.targetRanges) {
for (int classId = range.start; classId <= range.end; ++classId) {
rowValues.add((index: classId, value: target));
}
}
rowValues.sort((a, b) => a.index.compareTo(b.index));
rows.add(Row(rowValues));
}
Expand Down
Loading

0 comments on commit 27d3191

Please sign in to comment.