Skip to content

Commit 27f26e5

Browse files
mkustermannCommit Queue
authored andcommitted
[dart2wasm] Optimize initialization of dispatch table
This reduces essentials main module by 8.3% (-730 KB) The dispatch table contains displaced selector rows. Each selector row contains an entry for each class that provides the selector. This can lead to very large dispatch tables with repeated elements: Especially common is a base class with selectors that get inherited by many subclasses where few subclasses override the selector. This is common e.g. for `Object.{hashCode,operator==,noSuchMethod}` but also for user defined base classes that have many subclasses. This led to the element section being very large: It often contains large consecutive sub-ranges which refer to the same target function. To shrink the element size we instead move the initialization of those large regions homogenious regions to the module init function: We can utilize the `table.fill` instruction which can initialize a large range of the table with the same value. Change-Id: I9fc308969264f4855a514e7d36c761891bb0cb52 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467843 Commit-Queue: Martin Kustermann <[email protected]> Reviewed-by: Ömer Ağacan <[email protected]>
1 parent 4cd7378 commit 27f26e5

File tree

6 files changed

+117
-43
lines changed

6 files changed

+117
-43
lines changed

pkg/dart2wasm/lib/dispatch_table.dart

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -827,45 +827,86 @@ class DispatchTable {
827827
void output() {
828828
final Map<w.BaseFunction, w.BaseFunction> wrappedDynamicSubmoduleImports =
829829
{};
830-
for (int i = 0; i < _table.length; i++) {
831-
Reference? target = _table[i];
832-
if (target != null) {
833-
w.BaseFunction? fun = translator.functions.getExistingFunction(target);
834-
// Any call to the dispatch table is guaranteed to hit a target.
835-
//
836-
// If a target is in a deferred module and that deferred module hasn't
837-
// been loaded yet, then the entry is `null`.
838-
//
839-
// Though we can only hit a target if that target's class has been
840-
// allocated. In order for the class to be allocated, the deferred
841-
// module must've been loaded to call the constructor.
842-
if (fun != null) {
843-
final targetModule = fun.enclosingModule;
844-
final targetModuleBuilder =
845-
translator.moduleToBuilder[fun.enclosingModule]!;
846-
if (targetModule == _definedWasmTable.enclosingModule) {
847-
if (isDynamicSubmoduleTable &&
848-
targetModuleBuilder == translator.dynamicSubmodule &&
849-
fun is w.ImportedFunction) {
850-
// Functions imported into submodules may need to be wrapped to
851-
// match the updated dispatch table signature.
852-
fun = wrappedDynamicSubmoduleImports[fun] ??=
853-
_wrapDynamicSubmoduleFunction(target, fun);
854-
}
830+
int start = 0;
831+
while (start < _table.length) {
832+
Reference? target = _table[start];
833+
if (target == null) {
834+
start++;
835+
continue;
836+
}
837+
w.BaseFunction? fun = translator.functions.getExistingFunction(target);
838+
if (fun == null) {
839+
start++;
840+
continue;
841+
}
842+
843+
// Any call to the dispatch table is guaranteed to hit a target.
844+
//
845+
// If a target is in a deferred module and that deferred module hasn't
846+
// been loaded yet, then the entry is `null`.
847+
//
848+
// Though we can only hit a target if that target's class has been
849+
// allocated. In order for the class to be allocated, the deferred
850+
// module must've been loaded to call the constructor.
851+
int end = start + 1;
852+
while (end < _table.length && _table[end] == target) {
853+
end++;
854+
}
855+
final strideWidth = end - start;
856+
857+
// If the stride of the current function is more than this (i.e. the table
858+
// contains a large subsection with identical function entries) we
859+
// initialize that section in #start function.
860+
const strideElementTableLimit = 100;
861+
862+
final targetModule = fun.enclosingModule;
863+
final targetModuleBuilder =
864+
translator.moduleToBuilder[fun.enclosingModule]!;
865+
if (targetModule == _definedWasmTable.enclosingModule) {
866+
if (isDynamicSubmoduleTable &&
867+
targetModuleBuilder == translator.dynamicSubmodule &&
868+
fun is w.ImportedFunction) {
869+
// Functions imported into submodules may need to be wrapped to
870+
// match the updated dispatch table signature.
871+
fun = wrappedDynamicSubmoduleImports[fun] ??=
872+
_wrapDynamicSubmoduleFunction(target, fun);
873+
}
874+
875+
if (strideWidth < strideElementTableLimit) {
876+
for (int i = start; i < end; ++i) {
855877
_definedWasmTable.moduleBuilder.elements
856878
.activeFunctionSegmentBuilderFor(_definedWasmTable)
857879
.setFunctionAt(i, fun);
858-
} else {
859-
// This will generate the imported table if it doesn't already
860-
// exist.
861-
final importedTable =
862-
getWasmTable(targetModuleBuilder) as w.ImportedTable;
880+
}
881+
} else {
882+
targetModuleBuilder.elements.declarativeSegmentBuilder.declare(fun);
883+
final b = targetModuleBuilder.startFunction.body;
884+
b.i32_const(start);
885+
b.ref_func(fun);
886+
b.i32_const(strideWidth);
887+
b.table_fill(_definedWasmTable);
888+
}
889+
} else {
890+
// This will generate the imported table if it doesn't already
891+
// exist.
892+
final importedTable =
893+
getWasmTable(targetModuleBuilder) as w.ImportedTable;
894+
if (strideWidth < strideElementTableLimit) {
895+
for (int i = start; i < end; ++i) {
863896
targetModuleBuilder.elements
864897
.activeFunctionSegmentBuilderFor(importedTable)
865898
.setFunctionAt(i, fun);
866899
}
900+
} else {
901+
targetModuleBuilder.elements.declarativeSegmentBuilder.declare(fun);
902+
final b = targetModuleBuilder.startFunction.body;
903+
b.i32_const(start);
904+
b.ref_func(fun);
905+
b.i32_const(strideWidth);
906+
b.table_fill(importedTable);
867907
}
868908
}
909+
start += strideWidth;
869910
}
870911
}
871912

pkg/dart2wasm/lib/dynamic_modules.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,7 @@ class DynamicModuleInfo {
425425
DynamicModuleInfo(this.translator, this.metadata);
426426

427427
void initSubmodule() {
428-
submodule.startFunction = initFunction = submodule.functions.define(
429-
translator.typesBuilder.defineFunction(const [], const []), "#init");
428+
initFunction = submodule.startFunction;
430429

431430
// Make sure the exception tag is exported from the main module.
432431
translator.getExceptionTag(submodule);
@@ -653,8 +652,6 @@ class DynamicModuleInfo {
653652
void finishDynamicModule() {
654653
_registerModuleRefs(
655654
isSubmodule ? initFunction.body : translator.initFunction.body);
656-
657-
initFunction.body.end();
658655
}
659656

660657
void _registerModuleRefs(w.InstructionsBuilder b) {

pkg/dart2wasm/lib/translator.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,9 +558,7 @@ class Translator with KernelNodes {
558558
Map<ModuleMetadata, w.Module> translate(
559559
Uri Function(String moduleName)? sourceMapUrlGenerator) {
560560
_initModules(sourceMapUrlGenerator);
561-
initFunction = mainModule.functions
562-
.define(typesBuilder.defineFunction(const [], const []), "#init");
563-
mainModule.startFunction = initFunction;
561+
initFunction = mainModule.startFunction;
564562

565563
closureLayouter.collect();
566564
classInfoCollector.collect();
@@ -587,7 +585,6 @@ class Translator with KernelNodes {
587585
constructorClosures.clear();
588586
dispatchTable.output();
589587
staticTablesPerType.outputTables();
590-
initFunction.body.end();
591588

592589
for (ConstantInfo info in constants.constantInfo.values) {
593590
info.printInitializer((function) {

pkg/wasm_builder/lib/src/builder/instructions.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ class InstructionsBuilder with Builder<ir.Instructions> {
245245

246246
bool get recordSourceMaps => _sourceMappings != null;
247247

248+
bool get isEmpty => _instructions.isEmpty;
249+
248250
void collectUsedTypes(Set<ir.DefType> usedTypes) {
249251
for (final local in locals) {
250252
final localDefType = local.type.containedDefType;
@@ -923,6 +925,14 @@ class InstructionsBuilder with Builder<ir.Instructions> {
923925
_add(ir.TableSet(table));
924926
}
925927

928+
/// Emit a `table.size` instruction.
929+
void table_fill(ir.Table table) {
930+
assert(_verifyTypes([ir.NumType.i32, table.type, ir.NumType.i32], const [],
931+
trace: ['table.fill', table.name]));
932+
assert(table.enclosingModule == module);
933+
_add(ir.TableFill(table));
934+
}
935+
926936
/// Emit a `table.size` instruction.
927937
void table_size(ir.Table table) {
928938
assert(_verifyTypes(const [], const [ir.NumType.i32],

pkg/wasm_builder/lib/src/builder/module.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ModuleBuilder with Builder<ir.Module> {
3333
final dataSegments = DataSegmentsBuilder();
3434
late final globals = GlobalsBuilder(this);
3535
final exports = ExportsBuilder();
36-
ir.BaseFunction? _startFunction;
36+
FunctionBuilder? _startFunction;
3737

3838
/// Create a new, initially empty, module.
3939
///
@@ -46,13 +46,14 @@ class ModuleBuilder with Builder<ir.Module> {
4646
types = TypesBuilder(this, parent: parent?.types);
4747
}
4848

49-
set startFunction(ir.BaseFunction init) {
50-
assert(_startFunction == null);
51-
_startFunction = init;
52-
}
49+
FunctionBuilder get startFunction => _startFunction ??=
50+
functions.define(types.defineFunction(const [], const []), "#init");
5351

5452
@override
5553
ir.Module forceBuild() {
54+
if (_startFunction case final start?) {
55+
start.body.end();
56+
}
5657
final finalFunctions = functions.build();
5758
final finalTables = tables.build();
5859
final finalElements = elements.build();

pkg/wasm_builder/lib/src/ir/instruction.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ abstract class Instruction implements Serializable {
554554
return I64TruncSatF64U.deserialize(d);
555555
case 0x10:
556556
return TableSize.deserialize(d, tables);
557+
case 0x11:
558+
return TableFill.deserialize(d, tables);
557559
default:
558560
throw "Invalid instruction byte: 0xFC $opcode";
559561
}
@@ -1485,6 +1487,32 @@ class TableGet extends Instruction {
14851487
}
14861488
}
14871489

1490+
class TableFill extends Instruction {
1491+
final Table table;
1492+
1493+
TableFill(this.table);
1494+
1495+
static TableFill deserialize(Deserializer d, Tables tables) {
1496+
return TableFill(tables[d.readUnsigned()]);
1497+
}
1498+
1499+
@override
1500+
void serialize(Serializer s) {
1501+
s.writeByte(0xFC);
1502+
s.writeByte(0x11);
1503+
s.writeUnsigned(table.index);
1504+
}
1505+
1506+
@override
1507+
String get name => 'table.fill';
1508+
1509+
@override
1510+
void printTo(IrPrinter p) {
1511+
p.write(name);
1512+
p.writeTableReference(table);
1513+
}
1514+
}
1515+
14881516
class TableSize extends Instruction {
14891517
final Table table;
14901518

0 commit comments

Comments
 (0)