[dart2wasm] More precise inlining

This reduces e main module by around -0.3%.

We make the inliner have more precision when estimating the
callee size, especially around constructors but also other
cases.

We are also less generous with budget for iterators, modulo list
iterators which we want to be always inlined.

We also print the inlining decisions in `--print-wasm`

In deferred loading scenario this now inlines constructor bodies
more often than before as the size estimate is better. That in
return avoids us e.g. exporting empty constructor body functions
in the main module to be imported by other modules.

CoreLibraryReviewExempt: Backend-specific function changes.
Change-Id: I320db6f976a5d8c036b40908a5f72e7019d420ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498562
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2026-04-29 05:16:01 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 5a6b815789
commit 1d8d507384
16 changed files with 378 additions and 175 deletions
+20 -7
View File
@@ -5992,8 +5992,18 @@ extension MacroAssembler on w.InstructionsBuilder {
}
List<w.ValueType> invoke(CallTarget target, {bool forceInline = false}) {
if (target.supportsInlining && (target.shouldInline || forceInline)) {
return inlineCallTo(target);
if (target.supportsInlining) {
if (forceInline) {
comment('Inlining ${target.name}, reason: forced');
return inlineCallTo(target);
}
final decision = target.shouldInline;
if (decision.shouldInline) {
comment('Inlining ${target.name}, reason: ${decision.reason}');
return inlineCallTo(target);
} else {
comment('Not inlining, reason: ${decision.reason}');
}
}
comment('Direct call to ${target.name}');
call(target.function);
@@ -6009,9 +6019,10 @@ extension MacroAssembler on w.InstructionsBuilder {
local_set(local);
}
final w.Label callBlock = block(const [], target.signature.outputs);
comment('Inlined ${target.name}');
target.inliningCodeGen.generate(this, inlinedLocals, callBlock);
return emitUnreachableIfNoResult(target.signature.outputs);
return withInlinedFrame(target.name, () {
target.inliningCodeGen.generate(this, inlinedLocals, callBlock);
return emitUnreachableIfNoResult(target.signature.outputs);
});
}
/// Pushes fields common to all Dart objects (class id, id hash).
@@ -6106,7 +6117,8 @@ abstract class CallTarget {
/// Whether we should inline (different call targets may have semantic
/// knowledge about how big the body would be and whether we should inline or
/// not).
bool get shouldInline => false;
InliningDecision get shouldInline =>
InliningDecision(false, 'no CallTarget support');
/// The code generator to use for inlining the body.
CodeGenerator get inliningCodeGen => throw 'No inlining support (yet).';
@@ -6140,7 +6152,8 @@ class AstCallTarget extends CallTarget {
bool get supportsInlining => _translator.supportsInlining(_reference);
@override
bool get shouldInline => _translator.shouldInline(_reference, signature);
InliningDecision get shouldInline =>
_translator.shouldInline(_reference, signature);
@override
CodeGenerator get inliningCodeGen => getInlinableMemberCodeGenerator(
+267 -84
View File
@@ -757,10 +757,16 @@ class Translator with KernelNodes {
w.InstructionsBuilder b,
) {
final callTarget = directCallTarget(reference);
late final List<w.ValueType> outputs;
if (callTarget.supportsInlining && callTarget.shouldInline) {
outputs = b.inlineCallTo(callTarget);
if (callTarget.supportsInlining) {
final decision = callTarget.shouldInline;
if (decision.shouldInline) {
b.comment('Inlining ${callTarget.name}, reason: ${decision.reason}');
outputs = b.inlineCallTo(callTarget);
} else {
b.comment('Not inlining, reason: ${decision.reason}');
outputs = callFunction(callTarget.function, b);
}
} else {
outputs = callFunction(callTarget.function, b);
}
@@ -2063,85 +2069,189 @@ class Translator with KernelNodes {
return InterfaceType(concreteClass, nullability, typeArguments);
}
bool shouldInline(Reference target, w.FunctionType signature) {
if (!options.inlining) return false;
InliningDecision shouldInline(Reference target, w.FunctionType signature) {
if (!options.inlining) return InliningDecision(false, 'inlining disabled');
// Unchecked entry point functions perform very little, mainly optional
// parameter handling and then call the real body function.
//
// By inlining them we can often avoid downcasts and sometimes boxing. The
// force inlining here seem to even lead to overall size decreases.
if (target.isUncheckedEntryReference) return true;
if (target.isUncheckedEntryReference) {
return InliningDecision(true, 'unchecked entry');
}
final member = target.asMember;
if (member.isExternal) return InliningDecision(false, 'external');
if (getPragma<bool>(member, "wasm:never-inline", true) == true) {
return false;
return InliningDecision(false, '@pragma("wasm:never-inline")');
}
if (getPragma<bool>(member, "wasm:prefer-inline", true) == true) {
return true;
return InliningDecision(true, '@pragma("wasm:prefer-inline")');
}
if (member is Field) {
// Implicit getter/setter for instance fields are just loads/stores.
if (member.isInstanceMember) return true;
return _shouldInlineFieldAccessor(target, signature, member);
}
if (member is Constructor) {
return _shouldInlineConstructorCall(target, signature, member);
}
return _shouldInlineProcedureCall(target, signature, member as Procedure);
}
// Implicit setter for static fields are just stores.
if (target == member.setterReference) return true;
InliningDecision _shouldInlineFieldAccessor(
Reference target,
w.FunctionType signature,
Field field,
) {
if (field.isInstanceMember) {
// Implicit instance getters are just loads.
if (target.isImplicitGetter) {
return InliningDecision(true, 'Implicit getter.');
}
// Implicit instance setters are just stores, except if the value needs
// to be type checked.
assert(target.isImplicitSetter);
if (target == field.checkedEntryReference) {
return InliningDecision(false, 'Implicit setter with type check.');
}
return InliningDecision(true, 'Implicit setter without type check.');
}
// Implicit setter for static fields are just stores.
if (target == field.setterReference) {
return InliningDecision(true, 'Implicit static setter');
}
if (target == field.getterReference) {
// Implicit getter for static fields may invoke lazy static initializer.
if (dartGlobals.getConstantInitializer(member) != null) {
if (dartGlobals.getConstantInitializer(field) != null) {
// This global will get it's initializer eagerly set, so no lazy init
// function to be called.
return true;
return InliningDecision(
true,
'Implicit static getter without initializer',
);
}
return false;
return InliningDecision(false, 'static getter with initializer');
}
if (target.isInitializerReference) return true;
throw UnimplementedError();
}
final function = member.function!;
if (function.body == null) return false;
// We never want to inline throwing functions (as they are slow paths).
if (member is Procedure && member.function.returnType is NeverType) {
return false;
InliningDecision _shouldInlineConstructorCall(
Reference target,
w.FunctionType signature,
Constructor constructor,
) {
final callOverhead = signature.inputs.length + /* call instruction = */ 1;
if (target.isInitializerReference) {
return InliningDecision(true, 'Initializer');
}
if (target.isConstructorBodyReference) {
final nodeCounter = NodeCounter(this);
for (final init in constructor.initializers) {
// The body will have to call the super body with evaluated arguments
// supplied as to the body function.
if (init is SuperInitializer) {
nodeCounter.count += getConstructorInfo(
init.target,
).bodyParameters.length;
break;
}
if (init is RedirectingInitializer) {
nodeCounter.count += getConstructorInfo(
init.target,
).bodyParameters.length;
break;
}
}
// If we think the overhead of pushing arguments is around the same as the
// body itself, we always inline.
constructor.function.body?.accept(nodeCounter);
return InliningDecision(
nodeCounter.count < callOverhead,
'SizeEstimate=${nodeCounter.count} < CallOverhead=$callOverhead',
);
}
final nodeCount = NodeCounter(
options.omitImplicitTypeChecks || target.isUncheckedEntryReference,
).countNodes(member);
// The size of the constructor allocator is always guaranteed to be
// larger than the caller as it comes with this base cost:
//
// i32.const <classid>
// i32.const 0
// <N fields>
// struct.new
assert(constructor.reference == target);
return InliningDecision(false, 'Constructor allocator');
}
InliningDecision _shouldInlineProcedureCall(
Reference target,
w.FunctionType signature,
Procedure member,
) {
final callOverhead = signature.inputs.length + /* call instruction = */ 1;
final function = member.function;
if (function.returnType is NeverType) {
// Procedure always throws.
return InliningDecision(false, 'Throwing function');
}
if (target.isUncheckedEntryReference) {
// Unchecked entry point functions perform very little, mainly optional
// parameter handling and then call the real body function.
//
// By inlining them we can often avoid downcasts and sometimes boxing. The
// force inlining here seem to even lead to overall size decreases.
return InliningDecision(true, 'Unchecked entry');
}
if (target.isCheckedEntryReference) {
// Checked entry point functions have to perform extra type checks on
// parameters.
return InliningDecision(false, 'Checked entry');
}
if (target.isTearOffReference) {
// This has to perform closure allocation.
return InliningDecision(false, 'TearOff');
}
assert(target.isBodyReference || target == member.reference);
final nodeCounter = NodeCounter(this);
function.body?.accept(nodeCounter);
int nodeCount = nodeCounter.count;
// Special cases for iterator inlining:
// class ... implements Iterable<T> {
// Iterator<T> get iterator => FooIterator(...)
// }
// class ... implements Iterator<T> {
// T get current => _current as E;
// }
final klass = member.enclosingClass;
if (klass != null) {
final name = member.name.text;
if (name == 'iterator' && nodeCount <= 20) {
if (name == 'iterator') {
if (typeEnvironment.isSubtypeOf(
klass.getThisType(coreTypes, Nullability.nonNullable),
coreTypes.iterableRawType(Nullability.nonNullable),
)) {
return true;
}
}
if (name == 'current' && nodeCount <= 5) {
if (typeEnvironment.isSubtypeOf(
klass.getThisType(coreTypes, Nullability.nonNullable),
coreTypes.iteratorRawType(Nullability.nonNullable),
)) {
return true;
nodeCount--; // Give slightly more budget.
}
}
}
// If we think the overhead of pushing arguments is around the same as the
// body itself, we always inline.
if (nodeCount <= signature.inputs.length) return true;
if (nodeCount <= callOverhead) {
return InliningDecision(
true,
'SizeEstimate=$nodeCount <= CallOverhead=$callOverhead',
);
}
return nodeCount <= options.inliningLimit;
return InliningDecision(
nodeCount <= options.inliningLimit,
'$nodeCount <= inliningLimit=${options.inliningLimit}',
);
}
bool supportsInlining(Reference target) {
@@ -3146,46 +3256,13 @@ class _ClosureArgumentsToVtableEntryDispatcherGenerator
}
class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin {
final bool omitCovarianceChecks;
final Translator translator;
NodeCounter(this.translator);
bool hadReturn = false;
int count = 0;
NodeCounter(this.omitCovarianceChecks);
int countNodes(Member member) {
count = 0;
if (member is Constructor) {
count += 2; // object creation overhead
for (final init in member.initializers) {
init.accept(this);
}
for (final field in member.enclosingClass.fields) {
field.initializer?.accept(this);
}
}
final function = member.function!;
if (!omitCovarianceChecks) {
for (final parameter in function.positionalParameters) {
if (parameter.isCovariantByDeclaration ||
parameter.isCovariantByClass) {
count++;
}
}
}
for (final parameter in function.positionalParameters) {
if (!omitCovarianceChecks) {
if (parameter.isCovariantByDeclaration ||
parameter.isCovariantByClass) {
count++;
}
}
if (!parameter.isRequired) count++;
}
function.body?.accept(this);
return count;
}
// We only count tree nodes and do not recurse into things that aren't part of
// the tree (e.g. constants, variable types, ...)
@@ -3195,6 +3272,35 @@ class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin {
node.visitChildren(this);
}
// Constructor initializers
@override
void visitFieldInitializer(FieldInitializer node) {
handleFieldInitializerValue(node.value);
}
@override
void visitLocalInitializer(LocalInitializer node) {
node.variable.initializer!.accept(this);
}
@override
void visitSuperInitializer(SuperInitializer node) {
node.arguments.accept(this);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
node.arguments.accept(this);
}
void handleFieldInitializerValue(Expression? value) {
// These compress very well, let's not count those field initializer
// expressions for the size of the initializer function.
if (value == null || value is NullLiteral || value is NullConstant) return;
if (value is BoolLiteral || value is BoolConstant) return;
value.accept(this);
}
// The following AST nodes do not actually emit any code, so we don't count
// those nodes but we recurse into children that do emit code and therefore
// should count.
@@ -3209,6 +3315,17 @@ class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin {
node.visitChildren(this);
}
@override
void visitReturnStatement(ReturnStatement node) {
node.expression?.accept(this);
if (!hadReturn) {
// The first return is free.
hadReturn = true;
return;
}
count++;
}
@override
void visitLabeledStatement(LabeledStatement node) {
node.visitChildren(this);
@@ -3224,6 +3341,11 @@ class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin {
node.visitChildren(this);
}
@override
void visitLet(Let node) {
node.visitChildren(this);
}
@override
void visitArguments(Arguments node) {
count += node.types.length;
@@ -3234,6 +3356,65 @@ class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin {
void visitNamedExpression(NamedExpression node) {
node.visitChildren(this);
}
@override
void visitIsExpression(IsExpression node) {
node.operand.accept(this);
count += 2;
}
@override
void visitAsExpression(AsExpression node) {
node.operand.accept(this);
count += 3;
}
@override
void defaultDartType(DartType node) {
// The only [DartType]s we care about are those passed in calls and they are
// handled already in [visitArguments].
return;
}
// Some nodes are more costly.
@override
void visitInstanceGet(InstanceGet node) {
node.visitChildren(this);
_countInstanceCallCost(node);
}
@override
void visitInstanceSet(InstanceSet node) {
node.visitChildren(this);
_countInstanceCallCost(node);
}
@override
void visitInstanceInvocation(InstanceInvocation node) {
node.visitChildren(this);
_countInstanceCallCost(node);
}
@override
void visitEqualsCall(EqualsCall node) {
node.visitChildren(this);
_countInstanceCallCost(node);
}
void _countInstanceCallCost(TreeNode node) {
count++; // Call cost.
// Indirect calls are more costly.
if (translator.singleTarget(node) == null) {
count += 2; // Additional cost for indirect calls.
}
}
}
class InliningDecision {
final bool shouldInline;
final String? reason;
InliningDecision(this.shouldInline, this.reason);
}
/// Creates forwarders for generic functions where the caller passes a constant
@@ -3397,12 +3578,14 @@ class PolymorphicDispatcherCallTarget extends CallTarget {
bool get supportsInlining => true;
@override
bool get shouldInline =>
selector
.targets(unchecked: useUncheckedEntry)
.staticDispatchRanges
.length <=
1;
InliningDecision get shouldInline => InliningDecision(
selector
.targets(unchecked: useUncheckedEntry)
.staticDispatchRanges
.length <=
1,
'staticDispatchRanges <= 1',
);
@override
CodeGenerator get inliningCodeGen => PolymorphicDispatcherCodeGenerator(
+10 -6
View File
@@ -840,25 +840,29 @@ class IsCheckerCallTarget extends CallTarget {
bool get supportsInlining => true;
@override
bool get shouldInline {
if (checkArguments) return false;
InliningDecision get shouldInline {
if (checkArguments) return InliningDecision(false, 'checkArguments');
final interfaceClass = testedAgainstType.classNode;
// Can emit a single class-id range check for those, so we prefer to inline
// them.
if (interfaceClass == translator.coreTypes.objectClass) return true;
if (interfaceClass == translator.coreTypes.functionClass) return true;
if (interfaceClass == translator.coreTypes.objectClass) {
return InliningDecision(true, 'is Object');
}
if (interfaceClass == translator.coreTypes.functionClass) {
return InliningDecision(true, 'is Function');
}
// Checking the receiver for null emits more code (block, save receiver to
// local, conditional branch) - so it's likely to regress size.
if (operandIsNullable) return false;
if (operandIsNullable) return InliningDecision(false, 'operandIsNullable');
// Always inline single class-id range checks (no branching, simply loads,
// arithmetic and unsigned compare).
final ranges = translator.classIdNumbering.getConcreteClassIdRange(
interfaceClass,
);
return ranges.length <= 1;
return InliningDecision(ranges.length <= 1, 'ranges <= 1');
}
@override
@@ -159,10 +159,10 @@
call_indirect $module0.cross-module-funcs-0 (param i64) (result i32)
drop
block $label0 (result (ref $H0))
i32.const 9
i32.const 8
call_indirect $module0.cross-module-funcs-0 (result (ref null $H0))
br_on_non_null $label0
i32.const 10
i32.const 9
call_indirect $module0.cross-module-funcs-0 (result (ref $H0))
end $label0
i32.const 5
@@ -172,15 +172,16 @@
call_indirect $module0.cross-module-funcs-0 (param i64) (result i32)
drop
block $label1 (result (ref $H0))
i32.const 9
i32.const 8
call_indirect $module0.cross-module-funcs-0 (result (ref null $H0))
br_on_non_null $label1
i32.const 10
i32.const 9
call_indirect $module0.cross-module-funcs-0 (result (ref $H0))
end $label1
drop
i64.const 1
i32.const 8
call_indirect $module0.cross-module-funcs-0 (param i64)
i32.const 10
call_indirect $module0.cross-module-funcs-0 (result (ref $JSExternWrapper))
i32.const 5
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top))
)
)
@@ -5,7 +5,6 @@
(type $#Vtable-0-1 <...>)
(type $Array<_NamedParameter> <...>)
(type $Array<_Type> <...>)
(type $BoxedInt <...>)
(type $H0 (sub final $Object (struct
(field $field0 i32)
(field $field1 (mut i32))
@@ -33,9 +32,9 @@
(global $global0 (ref $"dummy struct") <...>)
(global $global2 (ref $#Vtable-0-1) <...>)
(elem $module0.cross-module-funcs-0
(set 8 (ref.func $globalH0Foo))
(set 9 (ref.func $0))
(set 10 (ref.func $"H0 (lazy initializer)")))
(set 8 (ref.func $0))
(set 9 (ref.func $"H0 (lazy initializer)"))
(set 10 (ref.func $1)))
(func $"H0 (lazy initializer)" (result (ref $H0))
(local $var0 (ref $_FunctionType))
(local $var1 (ref $#Closure-0-1))
@@ -80,16 +79,11 @@
local.get $var2
)
(func $"globalH0Foo tear-off trampoline" (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top))
local.get $var1
ref.cast $BoxedInt
struct.get $BoxedInt $value
call $globalH0Foo
ref.null none
)
(func $null (result (ref null $H0)) <...>)
(func $globalH0Foo (param $var0 i64)
global.get $"\"globalH0Foo\""
i32.const 5
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top))
ref.null none
)
(func $null (result (ref null $H0)) <...>)
(func $null (result (ref $JSExternWrapper)) <...>)
)
@@ -28,8 +28,8 @@
(global $fooGlobal0 (mut (ref null $#Top))
(ref.null none))
(elem $cross-module-funcs-0
(set 1 (ref.func $_makeFuture))
(set 2 (ref.func $_newAsyncSuspendState))
(set 1 (ref.func $_Future))
(set 2 (ref.func $_AsyncSuspendState))
(set 4 (ref.func $loadLibraryFromLoadId))
(set 5 (ref.func $_awaitHelper))
(set 6 (ref.func $checkLibraryIsLoadedFromLoadId))
@@ -73,11 +73,11 @@
(func $JSStringImpl._interpolate3 (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl.fromRefUnchecked (param $var0 externref) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl.substring (param $var0 (ref $JSExternWrapper)) (param $var1 i64) (param $var2 i64) (result (ref $JSExternWrapper)) <...>)
(func $_AsyncSuspendState (param $var0 (ref $type0)) (param $var1 structref) (param $var2 (ref $_Future)) (result (ref $_AsyncSuspendState)) <...>)
(func $_AsyncSuspendState._complete (param $var0 (ref $_AsyncSuspendState)) (param $var1 (ref null $#Top)) <...>)
(func $_AsyncSuspendState._completeError (param $var0 (ref $_AsyncSuspendState)) (param $var1 (ref $#Top)) (param $var2 (ref $Object)) <...>)
(func $_Future (param $var0 (ref $_Type)) (result (ref $_Future)) <...>)
(func $_awaitHelper (param $var0 (ref $_AsyncSuspendState)) (param $var1 (ref $_Future)) <...>)
(func $_makeFuture (param $var0 (ref $_Type)) (result (ref $_Future)) <...>)
(func $_newAsyncSuspendState (param $var0 (ref $type0)) (param $var1 structref) (param $var2 (ref $_Future)) (result (ref $_AsyncSuspendState)) <...>)
(func $boxJsException (param $var0 externref) (result (ref $#Top)) <...>)
(func $checkLibraryIsLoadedFromLoadId (param $var0 i64) (result i32) <...>)
(func $jsExceptionStackTrace (param $var0 externref) (result (ref $JavaScriptStack)) <...>)
@@ -6,14 +6,12 @@
(type $Array<int> <...>)
(type $JSExternWrapper <...>)
(type $Object <...>)
(func $"dart2wasm.H (import)" (import "dart2wasm" "H") (param i32 i32) (result externref))
(func $"dart2wasm.I (import)" (import "dart2wasm" "I") (param i64 i32) (result externref))
(func $"wasm:js-string.charCodeAt (import)" (import "wasm:js-string" "charCodeAt") (param externref i32) (result i32))
(@binaryen.removable.if.unused)
(func $"wasm:js-string.equals (import)" (import "wasm:js-string" "equals") (param externref externref) (result i32))
(@binaryen.removable.if.unused)
(func $"wasm:js-string.length (import)" (import "wasm:js-string" "length") (param externref) (result i32))
(table $cross-module-funcs-0 (export "cross-module-funcs-0") 20 funcref)
(table $cross-module-funcs-0 (export "cross-module-funcs-0") 18 funcref)
(global $"\"1.0\"" (ref $JSExternWrapper) <...>)
(global $BoxedDouble._cacheKeys (mut (ref $Array<int>)) <...>)
(global $BoxedDouble._cacheValues (mut (ref $Array<String>)) <...>)
@@ -21,21 +19,19 @@
(elem $cross-module-funcs-0
(set 3 (ref.func $"wasm:js-string.length (import)"))
(set 4 (ref.func $JSStringImpl._interpolate))
(set 5 (ref.func $JSStringImpl.toString))
(set 6 (ref.func $"_throwIndexError <noInline>"))
(set 7 (ref.func $"wasm:js-string.charCodeAt (import)"))
(set 8 (ref.func $JSStringImpl.substring))
(set 9 (ref.func $JSStringImpl.+))
(set 10 (ref.func $ArgumentError))
(set 11 (ref.func $"Error._throwWithCurrentStackTrace <noInline>"))
(set 12 (ref.func $JSStringImpl.fromRefUnchecked))
(set 13 (ref.func $"_throwRangeError <noInline>"))
(set 14 (ref.func $"dart2wasm.H (import)"))
(set 15 (ref.func $"dart2wasm.I (import)"))
(set 16 (ref.func $"wasm:js-string.equals (import)"))
(set 17 (ref.func $JSStringImpl._interpolate4))
(set 18 (ref.func $IntegerDivisionByZeroException))
(set 19 (ref.func $"_TypeError._throwNullCheckErrorWithCurrentStack <noInline>")))
(set 5 (ref.func $"_throwIndexError <noInline>"))
(set 6 (ref.func $"wasm:js-string.charCodeAt (import)"))
(set 7 (ref.func $JSStringImpl.substring))
(set 8 (ref.func $JSStringImpl.+))
(set 9 (ref.func $ArgumentError))
(set 10 (ref.func $"Error._throwWithCurrentStackTrace <noInline>"))
(set 11 (ref.func $JSStringImpl.fromRefUnchecked))
(set 12 (ref.func $"_throwRangeError <noInline>"))
(set 13 (ref.func $_jsBigIntToString))
(set 14 (ref.func $"wasm:js-string.equals (import)"))
(set 15 (ref.func $JSStringImpl._interpolate4))
(set 16 (ref.func $IntegerDivisionByZeroException))
(set 17 (ref.func $"_TypeError._throwNullCheckErrorWithCurrentStack <noInline>")))
(func $Error._throwWithCurrentStackTrace <noInline> (param $var0 (ref $#Top)) <...>)
(func $_TypeError._throwNullCheckErrorWithCurrentStack <noInline> <...>)
(func $_throwIndexError <noInline> (param $var0 i64) (param $var1 i64) (param $var2 (ref null $JSExternWrapper)) <...>)
@@ -61,5 +57,5 @@
(func $JSStringImpl._interpolate4 (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (param $var3 (ref null $#Top)) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl.fromRefUnchecked (param $var0 externref) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl.substring (param $var0 (ref $JSExternWrapper)) (param $var1 i64) (param $var2 i64) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl.toString (param $var0 (ref $#Top)) (result (ref $JSExternWrapper)) <...>)
(func $_jsBigIntToString (param $var0 i64) (param $var1 i64) (result (ref $JSExternWrapper)) <...>)
)
@@ -3,7 +3,7 @@
(type $Array<String?> <...>)
(type $Array<WasmI16> <...>)
(type $JSExternWrapper <...>)
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 20 funcref)
(table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 18 funcref)
(global $"\"hello\"" (ref $JSExternWrapper) <...>)
(global $JSStringImpl._stringFromCodePointBuffer (mut (ref $Array<WasmI16>)) <...>)
(global $array (mut (ref $Array<String?>))
@@ -27,7 +27,7 @@
i32.const 0
array.get $Array<String?>
br_on_non_null $label0
i32.const 19
i32.const 17
call_indirect $module0.cross-module-funcs-0
unreachable
end $label0
@@ -11,7 +11,7 @@
(global $"\"Attempt to execute code remove<...>\"" (import "$" "(") (ref $JSExternWrapper))
(global $_InterfaceType (import "$" "0") (ref $_InterfaceType))
(table $$.% (import "$" "%") 742 funcref)
(table $$.' (import "$" "'") 22 funcref)
(table $$.' (import "$" "'") 20 funcref)
(global $"\">.takeT(\"" (ref $JSExternWrapper) <...>)
(global $"\"Foo<\"" (ref $JSExternWrapper) <...>)
(elem $$.' <...>)
@@ -25,9 +25,9 @@
local.get $var1
global.get $"\")\"_11"
array.new_fixed $Array<Object?> 5
i32.const 16
i32.const 14
call_indirect $$.' (param (ref $Array<Object?>)) (result (ref $JSExternWrapper))
i32.const 20
i32.const 18
call_indirect $$.' (param (ref null $#Top))
global.get $_InterfaceType
local.set $var2
@@ -51,7 +51,7 @@
ref.null none
local.get $var2
ref.null none
i32.const 21
i32.const 19
call_indirect $$.' (param (ref $_Type) (ref null $_Environment) (ref $_Type) (ref null $_Environment)) (result i32)
i32.const 1
i32.ne
@@ -66,7 +66,7 @@
unreachable
end
local.get $var0
i32.const 20
i32.const 18
call_indirect $$.' (param (ref null $#Top))
)
(func $"Foo.takeT (checked entry)" (param $var0 (ref $Foo)) (param $var1 (ref $#Top))
@@ -91,24 +91,15 @@
i32.const 4
i32.le_u
if
i32.const 0
local.get $var2
i32.const 4
i32.eq
if
local.get $var3
ref.as_non_null
local.get $var1
i32.const 4
call_indirect $$.' (param (ref $_Type) (ref $#Top)) (result i32)
br $label0
end
br_if $label0
drop
br $label1
end
local.get $var3
ref.as_non_null
local.get $var1
i32.const 5
call_indirect $$.' (param (ref $_Type) (ref $#Top)) (result i32)
i32.const 1
br $label0
end
local.get $var2
@@ -118,7 +109,7 @@
local.get $var3
ref.as_non_null
local.get $var1
i32.const 6
i32.const 4
call_indirect $$.' (param (ref $_Type) (ref $#Top)) (result i32)
br $label0
end
@@ -135,7 +126,7 @@
local.get $var3
ref.as_non_null
local.get $var1
i32.const 7
i32.const 5
call_indirect $$.' (param (ref $_Type) (ref $#Top)) (result i32)
br $label0
end
@@ -148,7 +139,7 @@
local.get $var3
ref.as_non_null
local.get $var1
i32.const 8
i32.const 6
call_indirect $$.' (param (ref $_Type) (ref $#Top)) (result i32)
br $label0
end
+4 -4
View File
@@ -25,7 +25,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"bar tear-off trampoline")
(ref.func $"bar tear-off trampoline_138")
(ref.func $"bar tear-off trampoline_118")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -50,7 +50,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"foo tear-off trampoline")
(ref.func $"foo tear-off trampoline_135")
(ref.func $"foo tear-off trampoline_115")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -74,7 +74,7 @@
(global $_TopType_290 (ref $_TopType) <...>)
(global $global0 (ref $"dummy struct") <...>)
(func $bar tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_138 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_118 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_135 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_115 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
)
@@ -25,7 +25,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"bar tear-off trampoline")
(ref.func $"bar tear-off trampoline_141")
(ref.func $"bar tear-off trampoline_121")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -50,7 +50,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"foo tear-off trampoline")
(ref.func $"foo tear-off trampoline_136")
(ref.func $"foo tear-off trampoline_116")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -74,7 +74,7 @@
(global $_TopType_290 (ref $_TopType) <...>)
(global $global0 (ref $"dummy struct") <...>)
(func $bar tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_141 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_121 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_136 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_116 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
)
@@ -29,7 +29,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"bar tear-off dynamic call entry")
(ref.func $"bar tear-off trampoline_145")
(ref.func $"bar tear-off trampoline_125")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -49,7 +49,7 @@
(i32.const 0)
(global.get $global0)
(ref.func $"foo tear-off dynamic call entry")
(ref.func $"foo tear-off trampoline_139")
(ref.func $"foo tear-off trampoline_119")
(struct.new $#Vtable-0-2)
(i32.const 11)
(i32.const 0)
@@ -74,7 +74,7 @@
(global $_TopType_290 (ref $_TopType) <...>)
(global $global0 (ref $"dummy struct") <...>)
(func $bar tear-off dynamic call entry (param $var0 (ref $#Closure-0-0)) (param $var1 (ref $Array<_Type>)) (param $var2 (ref $Array<Object?>)) (param $var3 (ref $Array<Object?>)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_145 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $bar tear-off trampoline_125 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off dynamic call entry (param $var0 (ref $#Closure-0-0)) (param $var1 (ref $Array<_Type>)) (param $var2 (ref $Array<Object?>)) (param $var3 (ref $Array<Object?>)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_139 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
(func $foo tear-off trampoline_119 (param $var0 (ref struct)) (param $var1 (ref null $#Top)) (param $var2 (ref null $#Top)) (result (ref null $#Top)) <...>)
)
@@ -202,6 +202,7 @@ class InstructionsBuilder with Builder<ir.Instructions> {
final List<SourceMapping>? _sourceMappings;
int _indent = 1;
final List<String> _inlinedFrames = [];
final List<String> _traceLines = [];
int _labelCount = 0;
@@ -648,6 +649,25 @@ class InstructionsBuilder with Builder<ir.Instructions> {
assert(_comment(text));
}
/// Pushes `name` to inlining stack and emit the current inlining stack as
/// a comment.
T withInlinedFrame<T>(String name, T Function() fun) {
bool assertsEnabled = false;
assert(assertsEnabled = true);
if (!assertsEnabled) {
return fun();
}
_inlinedFrames.add(name);
try {
final inliningStack = _inlinedFrames.map((p) => '[$p]').join(' ');
comment(inliningStack);
return fun();
} finally {
_inlinedFrames.removeLast();
}
}
// Control instructions
/// Emit an `unreachable` instruction.
@@ -21,7 +21,6 @@ class BoxedInt {
String toString() => _jsBigIntToString(this, 10);
}
@pragma("wasm:prefer-inline")
String _jsBigIntToString(int i, int radix) {
final upperBits = (i >> 31);
final result = (upperBits == -1 || upperBits == 0)
+1
View File
@@ -116,6 +116,7 @@ class _BottomType extends _Type {
@pragma("wasm:entry-point")
class _TopType extends _Type {
@pragma("wasm:entry-point")
final int _kind;
// Values for the `_kind` field. Must match the definitions in `TopTypeKind`.
+1
View File
@@ -355,6 +355,7 @@ class ListIterator<E> implements Iterator<E> {
_length = iterable.length,
_index = 0;
@pragma("wasm:prefer-inline")
E get current => _current as E;
@pragma("vm:prefer-inline")