From 1d8d507384fb14641211eb51143593e1dc0e67fa Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Wed, 29 Apr 2026 05:16:01 -0700 Subject: [PATCH] [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 Commit-Queue: Martin Kustermann --- pkg/dart2wasm/lib/code_generator.dart | 27 +- pkg/dart2wasm/lib/translator.dart | 351 +++++++++++++----- pkg/dart2wasm/lib/types.dart | 16 +- .../ir_tests/deferred.constant_module1.wat | 15 +- .../ir_tests/deferred.constant_module2.wat | 18 +- .../test/ir_tests/deferred.fine_grained.wat | 8 +- .../ir_tests/deferred.init_at_startup.wat | 34 +- .../deferred.init_at_startup_module1.wat | 4 +- .../ir_tests/deferred.type_checks_module1.wat | 33 +- pkg/dart2wasm/test/ir_tests/dyn_closure.wat | 8 +- .../ir_tests/dyn_closure_function_apply.wat | 8 +- .../dyn_closure_function_apply_named.wat | 8 +- .../lib/src/builder/instructions.dart | 20 + .../_internal/wasm/lib/boxed_int_patch.dart | 1 - sdk/lib/_internal/wasm/lib/type.dart | 1 + sdk/lib/internal/iterable.dart | 1 + 16 files changed, 378 insertions(+), 175 deletions(-) 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")