[dart2wasm] Reuse dispatch table entries for static calls
This has slight e main size improvements (-0.1% uncompressed, -0.2% compressed) Significant portion of instance calls can be devirtualized. Doing so will make us issue direct calls to the target. But there may also be non-devirtualized calls to the selector, so the target may be in the dispatch table. If we can issue a direct call to the target but the target is in a different module we have to go via a wasm table. Currently we use an extra wasm table for such static calls across modules. => If the function we want to call is already in the dispatch table we may call it via the existing slot instead of having to add it to another table as well. => This shrinks the size of the static wasm table. Change-Id: Icc594b9490f2f0573190ea5a3a21b63b3ddb388a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499580 Reviewed-by: Srujan Gaddam <srujzs@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
3662682cd0
commit
95c3cab03b
@@ -400,6 +400,10 @@ class DispatchTable {
|
||||
/// class member for the selector.
|
||||
late final List<Reference?> _table;
|
||||
|
||||
/// For direct calls across modules one can also use the existing table slots
|
||||
/// in the dispatch table (instead of adding more slots to static call table).
|
||||
late final Map<Reference, int> _tableIndexForReference;
|
||||
|
||||
late final w.TableBuilder _definedWasmTable;
|
||||
late final WasmTableImporter _importedWasmTables = WasmTableImporter(
|
||||
translator,
|
||||
@@ -429,6 +433,13 @@ class DispatchTable {
|
||||
return _selectorInfo[selectorId]!;
|
||||
}
|
||||
|
||||
/// Returns a dispatch table index if the [target] is going to be in the
|
||||
/// dispatch table.
|
||||
///
|
||||
/// NOTE: The [target] can occur in multiple slots in the dispatch table and
|
||||
/// we return the first such index.
|
||||
int? indexForTarget(Reference target) => _tableIndexForReference[target];
|
||||
|
||||
SelectorInfo _createSelectorForTarget(Reference target) {
|
||||
Member member = target.asMember;
|
||||
bool isGetter = target.isGetter || target.isTearOffReference;
|
||||
@@ -756,6 +767,12 @@ class DispatchTable {
|
||||
}
|
||||
|
||||
_table = buildRowDisplacementTable<Reference>(rows);
|
||||
_tableIndexForReference = {};
|
||||
for (int i = 0; i < _table.length; ++i) {
|
||||
final entry = _table[i];
|
||||
if (entry == null) continue;
|
||||
_tableIndexForReference[entry] ??= i;
|
||||
}
|
||||
|
||||
int rowIndex = 0;
|
||||
for (final selector in selectors) {
|
||||
|
||||
@@ -765,10 +765,10 @@ class Translator with KernelNodes {
|
||||
outputs = b.inlineCallTo(callTarget);
|
||||
} else {
|
||||
b.comment('Not inlining, reason: ${decision.reason}');
|
||||
outputs = callFunction(callTarget.function, b);
|
||||
outputs = callFunction(callTarget.function, b, reference);
|
||||
}
|
||||
} else {
|
||||
outputs = callFunction(callTarget.function, b);
|
||||
outputs = callFunction(callTarget.function, b, reference);
|
||||
}
|
||||
if (callTarget.synthesizeNullReturnValue) {
|
||||
assert(outputs.isEmpty);
|
||||
@@ -789,18 +789,37 @@ class Translator with KernelNodes {
|
||||
/// module. Otherwise does an indirect call through the static dispatch table.
|
||||
List<w.ValueType> callFunction(
|
||||
w.BaseFunction function,
|
||||
w.InstructionsBuilder b,
|
||||
) {
|
||||
w.InstructionsBuilder b, [
|
||||
Reference? target,
|
||||
]) {
|
||||
// If the target function is defined in the same module as the caller, just
|
||||
// invoke it.
|
||||
final targetModuleBuilder = moduleToBuilder[function.enclosingModule]!;
|
||||
if (targetModuleBuilder == b.moduleBuilder) {
|
||||
b.call(function);
|
||||
} else {
|
||||
b.i32_const(crossModuleFunctionTable.indexForFunction(function));
|
||||
b.call_indirect(
|
||||
function.type,
|
||||
crossModuleFunctionTable.getWasmTable(b.moduleBuilder),
|
||||
);
|
||||
return b.emitUnreachableIfNoResult(function.type.outputs);
|
||||
}
|
||||
|
||||
// If the target function is already available via the dispatch table, use
|
||||
// it from there.
|
||||
if (target != null) {
|
||||
final dispatchTableIndex = dispatchTable.indexForTarget(target);
|
||||
if (dispatchTableIndex != null) {
|
||||
b.i32_const(dispatchTableIndex);
|
||||
b.call_indirect(
|
||||
function.type,
|
||||
dispatchTable.getWasmTable(b.moduleBuilder),
|
||||
);
|
||||
return b.emitUnreachableIfNoResult(function.type.outputs);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise add & call via the cross module function table.
|
||||
b.i32_const(crossModuleFunctionTable.indexForFunction(function));
|
||||
b.call_indirect(
|
||||
function.type,
|
||||
crossModuleFunctionTable.getWasmTable(b.moduleBuilder),
|
||||
);
|
||||
return b.emitUnreachableIfNoResult(function.type.outputs);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// functionFilter=runTest
|
||||
// typeFilter=NoMatch
|
||||
// globalFilter=NoMatch
|
||||
// compilerOption=--enable-deferred-loading
|
||||
|
||||
import '' deferred as D;
|
||||
|
||||
void main() async {
|
||||
print('main($all)');
|
||||
all.forEach((x) => x.foo(always10));
|
||||
await D.loadLibrary();
|
||||
runTest();
|
||||
}
|
||||
|
||||
@pragma('wasm:never-inline')
|
||||
void runTest() {
|
||||
final arg = always10;
|
||||
for (final object in all) {
|
||||
if (object is Sub1) {
|
||||
object.foo(arg);
|
||||
} else {
|
||||
object.foo(arg);
|
||||
}
|
||||
}
|
||||
print(D.all.toString());
|
||||
}
|
||||
|
||||
final always10 = int.parse('10');
|
||||
final all = <Base>[Base(), Sub1(), Sub2(), Sub3(), Sub4(), Sub5()];
|
||||
|
||||
class Base {
|
||||
void foo(int arg) {
|
||||
print('Base.foo($arg)');
|
||||
}
|
||||
}
|
||||
|
||||
class Sub1 extends Base {
|
||||
void foo(int arg) {
|
||||
print('Sub1.foo($arg)');
|
||||
super.foo(arg);
|
||||
}
|
||||
|
||||
void bar(int arg) {
|
||||
print('Sub1.bar($arg)');
|
||||
}
|
||||
}
|
||||
|
||||
class Sub2 extends Base {
|
||||
void foo(int arg) {
|
||||
print('Sub2.foo($arg)');
|
||||
super.foo(arg);
|
||||
}
|
||||
}
|
||||
|
||||
class Sub4 extends Base {
|
||||
void foo(int arg) {
|
||||
print('Sub4.foo($arg)');
|
||||
super.foo(arg);
|
||||
}
|
||||
}
|
||||
|
||||
class Sub5 extends Base {
|
||||
void foo(int arg) {
|
||||
print('Sub5.foo($arg)');
|
||||
super.foo(arg);
|
||||
}
|
||||
}
|
||||
|
||||
class Sub3 extends Base {
|
||||
void foo(int arg) {
|
||||
print('Sub3.foo($arg)');
|
||||
super.foo(arg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
(module $module0
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
(module $module1
|
||||
(type $#Top <...>)
|
||||
(type $Array<Object?> <...>)
|
||||
(type $ConcurrentModificationError <...>)
|
||||
(type $JSExternWrapper <...>)
|
||||
(type $Object <...>)
|
||||
(type $WasmListBase <...>)
|
||||
(type $_Type <...>)
|
||||
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 12 funcref)
|
||||
(table $module0.dispatch0 (import "module0" "dispatch0") 666 funcref)
|
||||
(elem $module0.cross-module-funcs-0
|
||||
(set 0 (ref.func $"runTest <noInline>")))
|
||||
(func $"runTest <noInline>"
|
||||
(local $var0 (ref $WasmListBase))
|
||||
(local $var1 (ref null $#Top))
|
||||
(local $var2 (ref $Object))
|
||||
(local $var3 (ref $_Type))
|
||||
(local $var4 i64)
|
||||
(local $var5 i64)
|
||||
(local $var6 i64)
|
||||
i32.const 1
|
||||
call_indirect $module0.cross-module-funcs-0 (result i64)
|
||||
local.set $var5
|
||||
i32.const 2
|
||||
call_indirect $module0.cross-module-funcs-0 (result (ref $WasmListBase))
|
||||
local.tee $var0
|
||||
struct.get $WasmListBase $field2
|
||||
local.set $var3
|
||||
local.get $var0
|
||||
struct.get $WasmListBase $_length
|
||||
local.set $var6
|
||||
loop $label0
|
||||
block $label1 (result i32)
|
||||
local.get $var6
|
||||
local.get $var0
|
||||
struct.get $WasmListBase $_length
|
||||
i64.ne
|
||||
if
|
||||
local.get $var0
|
||||
i32.const 3
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top)) (result (ref $ConcurrentModificationError))
|
||||
i32.const 4
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref $#Top))
|
||||
unreachable
|
||||
end
|
||||
local.get $var4
|
||||
local.get $var6
|
||||
i64.ge_s
|
||||
if
|
||||
ref.null none
|
||||
local.set $var1
|
||||
i32.const 0
|
||||
br $label1
|
||||
end
|
||||
local.get $var0
|
||||
struct.get $WasmListBase $_data
|
||||
local.get $var4
|
||||
i32.wrap_i64
|
||||
array.get $Array<Object?>
|
||||
local.set $var1
|
||||
local.get $var4
|
||||
i64.const 1
|
||||
i64.add
|
||||
local.set $var4
|
||||
i32.const 1
|
||||
end $label1
|
||||
if
|
||||
local.get $var3
|
||||
struct.get $_Type $isDeclaredNullable
|
||||
i32.const 1
|
||||
local.get $var1
|
||||
ref.is_null
|
||||
select
|
||||
i32.eqz
|
||||
if
|
||||
local.get $var1
|
||||
local.get $var3
|
||||
i32.const 5
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top) (ref $_Type))
|
||||
end
|
||||
local.get $var1
|
||||
ref.cast $Object
|
||||
local.tee $var2
|
||||
struct.get $Object $field0
|
||||
i32.const 109
|
||||
i32.eq
|
||||
if
|
||||
local.get $var2
|
||||
local.get $var5
|
||||
i32.const 487
|
||||
call_indirect $module0.dispatch0 (param (ref $Object) i64)
|
||||
else
|
||||
local.get $var2
|
||||
local.get $var5
|
||||
local.get $var2
|
||||
struct.get $Object $field0
|
||||
i32.const 378
|
||||
i32.add
|
||||
call_indirect $module0.dispatch0 (param (ref $Object) i64)
|
||||
end
|
||||
br $label0
|
||||
end
|
||||
end $label0
|
||||
i64.const 0
|
||||
i32.const 6
|
||||
call_indirect $module0.cross-module-funcs-0 (param i64) (result i32)
|
||||
drop
|
||||
i32.const 2
|
||||
call_indirect $module0.cross-module-funcs-0 (result (ref $WasmListBase))
|
||||
i32.const 7
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref $Object)) (result (ref $JSExternWrapper))
|
||||
i32.const 8
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top))
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user