diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index 8bc7d8a6a6e..a999b9b30bb 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -5992,8 +5992,18 @@ extension MacroAssembler on w.InstructionsBuilder { } List 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( diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 5111cd831aa..44a15c0b361 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -757,10 +757,16 @@ class Translator with KernelNodes { w.InstructionsBuilder b, ) { final callTarget = directCallTarget(reference); - late final List 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(member, "wasm:never-inline", true) == true) { - return false; + return InliningDecision(false, '@pragma("wasm:never-inline")'); } if (getPragma(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 + // i32.const 0 + // + // 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 { // Iterator get iterator => FooIterator(...) // } - // class ... implements Iterator { - // 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 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 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 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 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 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( diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 09ac114991d..fe1b32ea494 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -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 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index 89fcda386b7..fa63dae5b82 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -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)) ) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index 3b1cc861dea..5c992dffc41 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -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)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat index c67de7092f7..49689cd4414 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.wat @@ -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)) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat index 055d3698cbc..87dcbc86c7d 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup.wat @@ -6,14 +6,12 @@ (type $Array <...>) (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)) <...>) (global $BoxedDouble._cacheValues (mut (ref $Array)) <...>) @@ -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 ")) - (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 ")) - (set 12 (ref.func $JSStringImpl.fromRefUnchecked)) - (set 13 (ref.func $"_throwRangeError ")) - (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 "))) + (set 5 (ref.func $"_throwIndexError ")) + (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 ")) + (set 11 (ref.func $JSStringImpl.fromRefUnchecked)) + (set 12 (ref.func $"_throwRangeError ")) + (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 "))) (func $Error._throwWithCurrentStackTrace (param $var0 (ref $#Top)) <...>) (func $_TypeError._throwNullCheckErrorWithCurrentStack <...>) (func $_throwIndexError (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)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat index fcc66dcad0b..eb69cb7d138 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.init_at_startup_module1.wat @@ -3,7 +3,7 @@ (type $Array <...>) (type $Array <...>) (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)) <...>) (global $array (mut (ref $Array)) @@ -27,7 +27,7 @@ i32.const 0 array.get $Array br_on_non_null $label0 - i32.const 19 + i32.const 17 call_indirect $module0.cross-module-funcs-0 unreachable end $label0 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat index 85c35e06d2d..350fc250f18 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat @@ -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 5 - i32.const 16 + i32.const 14 call_indirect $$.' (param (ref $Array)) (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 diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat index 5b5fc275d1f..a7176c5fc5d 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat @@ -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)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat index 5a262b03dc9..593d32426be 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat @@ -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)) <...>) ) \ No newline at end of file diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat index 568d5301501..32d5e0cb02e 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.wat @@ -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)) (param $var3 (ref $Array)) (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)) (param $var3 (ref $Array)) (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)) <...>) ) \ No newline at end of file diff --git a/pkg/wasm_builder/lib/src/builder/instructions.dart b/pkg/wasm_builder/lib/src/builder/instructions.dart index b87ec920989..b06bf513746 100644 --- a/pkg/wasm_builder/lib/src/builder/instructions.dart +++ b/pkg/wasm_builder/lib/src/builder/instructions.dart @@ -202,6 +202,7 @@ class InstructionsBuilder with Builder { final List? _sourceMappings; int _indent = 1; + final List _inlinedFrames = []; final List _traceLines = []; int _labelCount = 0; @@ -648,6 +649,25 @@ class InstructionsBuilder with Builder { assert(_comment(text)); } + /// Pushes `name` to inlining stack and emit the current inlining stack as + /// a comment. + T withInlinedFrame(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. diff --git a/sdk/lib/_internal/wasm/lib/boxed_int_patch.dart b/sdk/lib/_internal/wasm/lib/boxed_int_patch.dart index ecd933beb20..9d463ad0769 100644 --- a/sdk/lib/_internal/wasm/lib/boxed_int_patch.dart +++ b/sdk/lib/_internal/wasm/lib/boxed_int_patch.dart @@ -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) diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index 09212ca5461..5153a137ef5 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -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`. diff --git a/sdk/lib/internal/iterable.dart b/sdk/lib/internal/iterable.dart index 65a6585b395..ec53266ad1a 100644 --- a/sdk/lib/internal/iterable.dart +++ b/sdk/lib/internal/iterable.dart @@ -355,6 +355,7 @@ class ListIterator implements Iterator { _length = iterable.length, _index = 0; + @pragma("wasm:prefer-inline") E get current => _current as E; @pragma("vm:prefer-inline")