[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:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
5a6b815789
commit
1d8d507384
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user