From a484bb39737f343375b9a8f2a430ace7eda99647 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Thu, 19 Feb 2026 12:48:49 -0800 Subject: [PATCH] [dart2wasm] Use constants to represent dummy values. Today "dummy values" are generated per-module to stand in for things like default parameter sentinels (where a given selector has multiple default values for an optional parameter). However, these values can end up crossing between modules. The logic is set up to use ref_eq to check if an argument is one of these dummy values. However, if one of these dummy values crosses between modules, the passed value vs the ref_eq checked value will be different. Since each module has its own canonical dummy value per type. This new layout simplifies our handling of these dummy values by treating them as Constants so that our normal module canonicalization logic applies to them. We already have plenty of logic to canonicalize constants across modules. This avoids the need for custom handling of these dummy value globals. Change-Id: Ia9c79923c788d7712b16705193ffbf3142141b5d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480320 Reviewed-by: Martin Kustermann Commit-Queue: Nate Biggs --- pkg/dart2wasm/lib/async.dart | 2 +- pkg/dart2wasm/lib/code_generator.dart | 11 +- pkg/dart2wasm/lib/constants.dart | 141 ++++++++++++--- pkg/dart2wasm/lib/dynamic_modules.dart | 41 ++++- pkg/dart2wasm/lib/globals.dart | 6 +- pkg/dart2wasm/lib/kernel_nodes.dart | 2 + pkg/dart2wasm/lib/sync_star.dart | 2 +- pkg/dart2wasm/lib/translator.dart | 169 ++++++++---------- .../ir_tests/deferred.constant.type_use.wat | 6 +- .../ir_tests/deferred.constant_module1.wat | 6 +- .../ir_tests/deferred.constant_module2.wat | 4 +- ...red.fine_grained.devirtualized_module1.wat | 4 +- .../deferred.fine_grained_module5.wat | 4 +- .../ir_tests/deferred.type_checks_module1.wat | 4 +- pkg/dart2wasm/test/ir_tests/dyn_closure.wat | 12 +- .../ir_tests/dyn_closure_function_apply.wat | 12 +- .../dyn_closure_function_apply_named.wat | 14 +- pkg/dart2wasm/test/ir_tests/dynamic_call.wat | 6 +- .../test/ir_tests/interop.double.wat | 2 +- pkg/dart2wasm/test/ir_tests/memory_use.wat | 10 +- .../_internal/wasm/lib/dynamic_module.dart | 30 ++++ .../wasm/default_nullable_param_helper.dart | 27 +++ .../web/wasm/default_nullable_param_test.dart | 19 ++ 23 files changed, 365 insertions(+), 169 deletions(-) create mode 100644 tests/web/wasm/default_nullable_param_helper.dart create mode 100644 tests/web/wasm/default_nullable_param_test.dart diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index 03b18d5835d..aa5966dbc10 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart @@ -201,7 +201,7 @@ class AsyncStateMachineCodeGenerator extends StateMachineCodeGenerator { name: "this"); translator .getDummyValuesCollectorForModule(b.moduleBuilder) - .instantiateDummyValue(b, thisLocal!.type); + .instantiateLocalDummyValue(b, thisLocal!.type); b.local_set(thisLocal!); preciseThisLocal = thisLocal; diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index c3ecd365171..9549b254d40 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -832,7 +832,7 @@ abstract class AstCodeGenerator // variable is captured as the context is already initialized. translator .getDummyValuesCollectorForModule(b.moduleBuilder) - .instantiateDummyValue(b, local.type); + .instantiateLocalDummyValue(b, local.type); b.local_set(local); } } @@ -2393,11 +2393,10 @@ abstract class AstCodeGenerator b.ref_as_non_null(); } } else { - translator.globals.readGlobal( - b, - translator - .getDummyValuesCollectorForModule(b.moduleBuilder) - .dummyStructGlobal); // Dummy context + translator + .getDummyValuesCollectorForModule(b.moduleBuilder) + .instantiateLocalDummyValue( + b, const w.RefType.struct(nullable: false)); // Dummy context } } diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index 6e1c73b01c9..07b7579e558 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -6,8 +6,10 @@ import 'dart:typed_data'; import 'package:kernel/ast.dart'; import 'package:kernel/core_types.dart'; +import 'package:kernel/src/printer.dart'; import 'package:kernel/type_algebra.dart' show FunctionTypeInstantiator, substitute; +import 'package:kernel/type_environment.dart'; import 'package:wasm_builder/wasm_builder.dart' as w; import 'class_info.dart'; @@ -169,6 +171,7 @@ class Constants { late final _constantAccessor = _ConstantAccessor(translator); + final Map _dummyValueConstants = {}; final Map _loweredTypeConstants = {}; late final BoolConstant _cachedTrueConstant = BoolConstant(true); late final BoolConstant _cachedFalseConstant = BoolConstant(false); @@ -258,6 +261,26 @@ class Constants { ListConstant(elementType, entries), }); + Constant get dummyStructConstant => + _getDummyValueConstant(w.HeapType.struct, name: '#DummyStruct'); + + Constant _getDummyValueConstant(w.HeapType heapType, {String? name}) { + if (heapType == w.HeapType.eq || heapType == w.HeapType.any) { + heapType = w.HeapType.struct; + } + return _dummyValueConstants[heapType] ??= + DummyValueConstant(heapType, name ?? '$heapType'); + } + + void instantiateDummyValueConstant( + w.InstructionsBuilder b, w.ValueType type) { + instantiateDummyValue( + b, + type, + (ib, heapType) => + instantiateConstant(b, _getDummyValueConstant(heapType), type)); + } + /// Ensure that the constant has a Wasm global assigned. /// /// Sub-constants must have Wasm globals assigned before the global for the @@ -564,14 +587,8 @@ class ConstantInstantiator extends ConstantVisitor @override w.ValueType visitUnevaluatedConstant(UnevaluatedConstant constant) { if (constant == ParameterInfo.defaultValueSentinel) { - // Instantiate a sentinel value specific to the parameter type. - w.ValueType sentinelType = expectedType.withNullability(false); - assert(sentinelType is w.RefType, - "Default value sentinel for unboxed parameter"); - translator - .getDummyValuesCollectorForModule(b.moduleBuilder) - .instantiateDummyValue(b, sentinelType); - return sentinelType; + constants.instantiateDummyValueConstant(b, expectedType); + return expectedType; } return super.visitUnevaluatedConstant(constant); } @@ -682,13 +699,15 @@ class ConstantCreator extends ConstantVisitor assert(!type.nullable); bool exportByMainApp = false; - bool needsRuntimeCanonicalization = false; + // Dummy values always use runtime canonicalization. + bool needsRuntimeCanonicalization = constant is DummyValueConstant; if (translator.dynamicModuleSupportEnabled) { if (!translator.isDynamicSubmodule) { // This is main app compilation which allows loading dynamic modules at // runtime. We may have to export the constant. exportByMainApp = - constant.accept(_ConstantDynamicModuleSharedChecker(translator)); + constant.accept(_ConstantDynamicModuleSharedChecker(translator)) && + constant is! DummyValueConstant; } else { // This is a dynamic module compilation. // @@ -697,7 +716,7 @@ class ConstantCreator extends ConstantVisitor assert(!(translator.dynamicModuleConstants?.constantNames .containsKey(constant) ?? false)); - needsRuntimeCanonicalization = + needsRuntimeCanonicalization |= constant.accept(_ConstantDynamicModuleSharedChecker(translator)); } } @@ -1115,6 +1134,7 @@ class ConstantCreator extends ConstantVisitor final closureType = w.RefType.def(closure.representation.closureStruct, nullable: false); + return createConstant(constant, childConstants, closureType, canBeEager: canBeEager, forceLazyConstant: (cinfo, m) { final constantModule = m.module; @@ -1122,14 +1142,11 @@ class ConstantCreator extends ConstantVisitor return constantModule != vtableModule && vtableModule != translator.mainModule.module; }, (cinfo, b, __) { - // The dummy struct must be declared before the constant global so that - // the constant's initializer can reference it. - final dummyStructGlobal = translator - .getDummyValuesCollectorForModule(b.moduleBuilder) - .dummyStructGlobal; - b.pushObjectHeaderFields(translator, closureClassInfo); - translator.globals.readGlobal(b, dummyStructGlobal); // Dummy context + translator + .getDummyValuesCollectorForModule(b.moduleBuilder) + .instantiateLocalDummyValue( + b, const w.RefType.struct(nullable: false)); translator.globals.readGlobal(b, closure.vtable); constants.instantiateConstant( b, functionTypeInfo.constant, types.nonNullableTypeType); @@ -1384,6 +1401,67 @@ class ConstantCreator extends ConstantVisitor b.struct_new(recordClassInfo.struct); }); } + + @override + ConstantInfo? visitAuxiliaryConstant(AuxiliaryConstant constant) { + if (constant is DummyValueConstant) { + final type = constant.type; + + final childConstants = []; + if (type is w.DefType) { + if (type is w.StructType) { + for (w.FieldType field in type.fields) { + final unpackedType = field.type.unpacked; + if (unpackedType is w.RefType && !unpackedType.nullable) { + childConstants.add(ensureConstant( + constants._getDummyValueConstant(unpackedType.heapType))!); + } + } + } + } + + return createConstant( + constant, childConstants, w.RefType(type, nullable: false), + canBeEager: true, (_, b, __) { + translator.instantiateDummyValueHeapType(b, type, constant.name, + (ib, heapType) { + constants.instantiateConstant( + ib, + constants._getDummyValueConstant(heapType), + w.RefType(heapType, nullable: false)); + }); + }); + } + + throw UnsupportedError("Unsupported auxiliary constant: $constant"); + } +} + +class DummyValueConstant extends AuxiliaryConstant { + final w.HeapType type; + final String name; + + DummyValueConstant(this.type, this.name) : super(); + + @override + DartType getType(StaticTypeContext context) { + throw UnsupportedError('DummyValueConstant does not have a type.'); + } + + @override + void toTextInternal(AstPrinter printer) { + printer.write('Dummy value constant: $name'); + } + + @override + void visitChildren(Visitor v) {} + + @override + int get hashCode => type.hashCode; + + @override + bool operator ==(Object other) => + other is DummyValueConstant && other.type == type; } class TypeOfConstantVisitor extends ConstantVisitor @@ -1447,6 +1525,14 @@ class TypeOfConstantVisitor extends ConstantVisitor return translator.getRecordClassInfo(constant.recordType).nonNullableType; } + @override + w.RefType visitAuxiliaryConstant(AuxiliaryConstant constant) { + if (constant is DummyValueConstant) { + return w.RefType(constant.type, nullable: false); + } + throw StateError('Unexpected auxiliary constant: $constant'); + } + @override w.RefType visitInstanceConstant(InstanceConstant constant) { w.RefType wasmArrayType(InstanceConstant constant, @@ -1543,6 +1629,14 @@ class _ConstantAccessor { final Map lazySlotTables = {}; late final tableImporter = WasmTableImporter(translator, 'constant-table'); + final Map _dummyValueCanonicalizationCheckers = {}; + late final w.FunctionType _dummyValueCheckerType = + translator.typesBuilder.defineFunction([ + const w.RefType.any(nullable: false), + ], [ + w.NumType.i32 + ]); + _ConstantAccessor(this.translator); /// Reads a constant. @@ -1868,7 +1962,8 @@ class _ConstantAccessor { info._codeGen(info, b, true); if (info.needsRuntimeCanonicalization) { final valueLocal = b.addLocal(type); - info.constant.accept(ConstantCanonicalizer(translator, b, valueLocal)); + info.constant.accept(ConstantCanonicalizer(translator, b, valueLocal, + _dummyValueCanonicalizationCheckers, _dummyValueCheckerType)); } w.Local temp = b.addLocal(type); b.local_tee(temp); @@ -1891,7 +1986,8 @@ class _ConstantAccessor { info._codeGen(info, b, true); if (info.needsRuntimeCanonicalization) { final valueLocal = b.addLocal(type); - info.constant.accept(ConstantCanonicalizer(translator, b, valueLocal)); + info.constant.accept(ConstantCanonicalizer(translator, b, valueLocal, + _dummyValueCanonicalizationCheckers, _dummyValueCheckerType)); } w.Local temp = b.addLocal(type); b.local_tee(temp); @@ -1981,6 +2077,11 @@ class _ConstantAccessor { if (constant is TearOffConstant) { return '$prefix${constant.target.name} tear-off'; } + if (constant is AuxiliaryConstant) { + if (constant is DummyValueConstant) { + return '$prefix #Dummy(${constant.type})'; + } + } return '$prefix$constant'; } diff --git a/pkg/dart2wasm/lib/dynamic_modules.dart b/pkg/dart2wasm/lib/dynamic_modules.dart index 55ebd0d681d..c48590190b5 100644 --- a/pkg/dart2wasm/lib/dynamic_modules.dart +++ b/pkg/dart2wasm/lib/dynamic_modules.dart @@ -19,7 +19,7 @@ import 'package:wasm_builder/wasm_builder.dart' as w; import 'class_info.dart'; import 'code_generator.dart'; import 'compiler_options.dart'; -import 'constants.dart' show maxArrayNewFixedLength; +import 'constants.dart' show maxArrayNewFixedLength, DummyValueConstant; import 'dispatch_table.dart'; import 'dynamic_module_kernel_metadata.dart'; import 'intrinsics.dart' show MemberIntrinsic; @@ -1043,7 +1043,11 @@ class ConstantCanonicalizer extends ConstantVisitor { /// A local containing the value to be canonicalized. final w.Local valueLocal; - ConstantCanonicalizer(this.translator, this.b, this.valueLocal); + final Map _dummyValueCheckers; + final w.FunctionType _dummyValueCheckerType; + + ConstantCanonicalizer(this.translator, this.b, this.valueLocal, + this._dummyValueCheckers, this._dummyValueCheckerType); late final _checkerType = translator.typesBuilder.defineFunction([ translator.topTypeNonNullable, @@ -1364,9 +1368,38 @@ class ConstantCanonicalizer extends ConstantVisitor { } } + w.Global _initDummyValueChecker(w.HeapType heapType) { + final moduleBuilder = b.moduleBuilder; + final function = moduleBuilder.functions.define(_dummyValueCheckerType); + final global = moduleBuilder.globals.define( + w.GlobalType(w.RefType(_dummyValueCheckerType, nullable: false))); + global.initializer + ..ref_func(function) + ..end(); + final ib = function.body; + ib.local_get(ib.locals[0]); + // Any value which satisfies the wasm type system will do. We just need a + // consistent value across modules for a given heap type. So as long as we + // always use the first matching one, it doesn't matter if multiple types + // use the same dummy value. + ib.ref_test(w.RefType(heapType, nullable: false)); + ib.end(); + return global; + } + @override - Never visitAuxiliaryConstant(AuxiliaryConstant node) { - throw UnsupportedError('Cannot canonicalize auxiliary constants.'); + void visitAuxiliaryConstant(AuxiliaryConstant node) { + if (node is DummyValueConstant) { + final heapType = node.type; + // The value is already on the stack. + b.global_get( + _dummyValueCheckers[heapType] ??= _initDummyValueChecker(heapType)); + translator.callReference( + translator.dummyValueConstCanonicalize.reference, b); + b.ref_cast(w.RefType(heapType, nullable: false)); + return; + } + throw UnsupportedError('Cannot canonicalize auxiliary constant: $node'); } @override diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index 2b4638764d8..c2a089cfe6f 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart @@ -241,9 +241,9 @@ class DartGlobals { translator.constants .instantiateConstant(global.initializer, init, fieldType); } else { - final dummyCollector = - translator.getDummyValuesCollectorForModule(module); - dummyCollector.instantiateDummyValue(global.initializer, fieldType); + translator + .getDummyValuesCollectorForModule(module) + .instantiateLocalDummyValue(global.initializer, fieldType); } global.initializer.end(); return WasmGlobalDartGlobal(global, initializedFlag: initializerFlag); diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 0522b9363f8..f3854e89c14 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -411,6 +411,8 @@ mixin KernelNodes { 'dart:_internal', 'WasmConstCache', 'canonicalizeValue'); late final Procedure constCacheArrayCanonicalize = index.getProcedure( 'dart:_internal', 'WasmArrayConstCache', 'canonicalizeArrayValue'); + late final Procedure dummyValueConstCanonicalize = index.getProcedure( + 'dart:_internal', 'DummyValueConstCache', 'canonicalizeDummyValue'); late final Procedure registerUpdateableFuncRefs = index.getTopLevelProcedure( 'dart:_internal', 'registerUpdateableFuncRefs'); late final Procedure getUpdateableFuncRef = diff --git a/pkg/dart2wasm/lib/sync_star.dart b/pkg/dart2wasm/lib/sync_star.dart index 590e956d113..c01fba7f4d3 100644 --- a/pkg/dart2wasm/lib/sync_star.dart +++ b/pkg/dart2wasm/lib/sync_star.dart @@ -179,7 +179,7 @@ class SyncStarStateMachineCodeGenerator extends StateMachineCodeGenerator { name: "this"); translator .getDummyValuesCollectorForModule(b.moduleBuilder) - .instantiateDummyValue(b, thisLocal!.type); + .instantiateLocalDummyValue(b, thisLocal!.type); b.local_set(thisLocal!); preciseThisLocal = thisLocal; diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index a19ef16ef8e..c2bb7ff71b5 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -1434,7 +1434,7 @@ class Translator with KernelNodes { // but TFA didn't remove the dead code. In that case we synthesize a // dummy value. getDummyValuesCollectorForModule(b.moduleBuilder) - .instantiateDummyValue(b, to); + .instantiateLocalDummyValue(b, to); return; } } @@ -2248,6 +2248,34 @@ class Translator with KernelNodes { } return null; } + + void instantiateDummyValueHeapType( + w.InstructionsBuilder b, + w.HeapType type, + String name, + void Function(w.InstructionsBuilder b, w.HeapType heapType) + instantiateHeapType) { + if (type == w.HeapType.struct) { + final structType = typesBuilder.defineStruct(name); + b.struct_new(structType); + return; + } else if (type is w.DefType) { + if (type is w.StructType) { + for (w.FieldType field in type.fields) { + instantiateDummyValue(b, field.type.unpacked, instantiateHeapType); + } + b.struct_new(type); + return; + } else if (type is w.ArrayType) { + b.array_new_fixed(type, 0); + return; + } else if (type is w.FunctionType) { + b.ref_func(getDummyValuesCollectorForModule(b.moduleBuilder) + .getDummyFunction(type)); + return; + } + } + } } class CompilationQueue { @@ -3223,99 +3251,24 @@ class DummyValuesCollector { /// This can be used as the dummy value for contexts. late final w.Global dummyStructGlobal; - DummyValuesCollector(this.translator, this.module) { - _init(); - } + DummyValuesCollector(this.translator, this.module); - void _init() { - w.StructType structType = - translator.typesBuilder.defineStruct("#DummyStruct"); - final dummyStructGlobalInit = module.globals.define( - w.GlobalType(w.RefType.struct(nullable: false), mutable: false)); - final ib = dummyStructGlobalInit.initializer; - ib.struct_new(structType); - ib.end(); - _dummyValues[w.HeapType.any] = dummyStructGlobalInit; - _dummyValues[w.HeapType.eq] = dummyStructGlobalInit; - _dummyValues[w.HeapType.struct] = dummyStructGlobalInit; - dummyStructGlobal = dummyStructGlobalInit; - } - - /// When [type] is a non-nullable reference type, create a global in [module] - /// for its dummy value. - /// - /// Nullable references and non-reference types don't need dummy values. This - /// function returns [null] for nullable references and non-reference types. - w.Global? _prepareDummyValueGlobal(w.ModuleBuilder module, w.ValueType type) { - if (type is! w.RefType || type.nullable) return null; - - final w.HeapType heapType = type.heapType; - return _dummyValues.putIfAbsent(heapType, () { - if (heapType is w.DefType) { - if (heapType is w.StructType) { - for (w.FieldType field in heapType.fields) { - _prepareDummyValueGlobal(module, field.type.unpacked); - } - final global = - module.globals.define(w.GlobalType(type, mutable: false)); - final ib = global.initializer; - for (w.FieldType field in heapType.fields) { - instantiateDummyValue(ib, field.type.unpacked); - } - ib.struct_new(heapType); - ib.end(); - return global; - } else if (heapType is w.ArrayType) { - final global = - module.globals.define(w.GlobalType(type, mutable: false)); - final ib = global.initializer; - ib.array_new_fixed(heapType, 0); - ib.end(); - return global; - } else if (heapType is w.FunctionType) { - final global = - module.globals.define(w.GlobalType(type, mutable: false)); - final ib = global.initializer; - ib.ref_func(getDummyFunction(heapType)); - ib.end(); - return global; - } - } - throw 'Unexpected heapType: $heapType'; - }); - } - - /// Produce a dummy value of any Wasm type. For non-nullable reference types, - /// the value is constructed in a global initializer, and the instantiation of - /// the value merely reads the global. - void instantiateDummyValue(w.InstructionsBuilder b, w.ValueType type) { - switch (type) { - case w.NumType.i32: - b.i32_const(0); - break; - case w.NumType.i64: - b.i64_const(0); - break; - case w.NumType.f32: - b.f32_const(0); - break; - case w.NumType.f64: - b.f64_const(0); - break; - default: - if (type is w.RefType) { - w.HeapType heapType = type.heapType; - if (type.nullable) { - b.ref_null(heapType.bottomType); - } else { - translator.globals.readGlobal( - b, _prepareDummyValueGlobal(b.moduleBuilder, type)!); - } - } else { - throw "Unsupported global type $type ($type)"; - } - break; + void instantiateLocalDummyValue(w.InstructionsBuilder b, w.ValueType type) { + void initializeHeapType(ib, heapType) { + final moduleBuilder = b.moduleBuilder; + final global = _dummyValues.putIfAbsent(heapType, () { + final global = moduleBuilder.globals.define( + w.GlobalType(w.RefType(heapType, nullable: false), mutable: false)); + final init = global.initializer; + translator.instantiateDummyValueHeapType( + init, heapType, "dummy $heapType", initializeHeapType); + init.end(); + return global; + }); + ib.global_get(global); } + + instantiateDummyValue(b, type, initializeHeapType); } /// Provide a dummy function with the given signature. Used for empty entries @@ -3336,6 +3289,38 @@ class DummyValuesCollector { } } +void instantiateDummyValue( + w.InstructionsBuilder b, + w.ValueType type, + void Function(w.InstructionsBuilder b, w.HeapType type) + instantiateHeapType) { + switch (type) { + case w.NumType.i32: + b.i32_const(0); + break; + case w.NumType.i64: + b.i64_const(0); + break; + case w.NumType.f32: + b.f32_const(0); + break; + case w.NumType.f64: + b.f64_const(0); + break; + default: + if (type is w.RefType) { + w.HeapType heapType = type.heapType; + if (type.nullable) { + b.ref_null(heapType.bottomType); + } else { + instantiateHeapType(b, heapType); + } + } else { + throw "Unsupported global type $type ($type)"; + } + } +} + abstract class _WasmImporter { final Translator _translator; final String _exportPrefix; diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat index b6607cdf136..a36b220f49e 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant.type_use.wat @@ -5,7 +5,7 @@ (type $_DefaultSet&_HashFieldBase&SetMixin <...>) (type $_InterfaceType <...>) (table $cross-module-funcs-0 (export "cross-module-funcs-0") 3 funcref) - (global $_InterfaceType_440 (ref $_InterfaceType) <...>) + (global $_InterfaceType_441 (ref $_InterfaceType) <...>) (elem $cross-module-funcs-0 (set 1 (ref.func $JSStringImpl._interpolate2)) (set 2 (ref.func $print))) @@ -16,7 +16,7 @@ call $_DefaultSet&_HashFieldBase&SetMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashSetMixin.contains i32.eqz if - i32.const 37 + i32.const 51 i32.const 0 ref.null none i64.const 0 @@ -29,7 +29,7 @@ drop ) (func $"useFooAsType " - global.get $_InterfaceType_440 + global.get $_InterfaceType_441 call $print drop ) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat index 96bb286c91a..23a7158384c 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module1.wat @@ -1,7 +1,7 @@ (module $module1 + (type $"dummy struct" <...>) (type $#Closure-0-1 <...>) (type $#Closure-1-1 <...>) - (type $#DummyStruct <...>) (type $#InstantiationContext-1-1 <...>) (type $#Top <...>) (type $#Vtable-0-1 <...>) @@ -43,7 +43,7 @@ (global $H1 (mut (ref null $H1)) (ref.null none)) (global $_FunctionType (ref $_FunctionType) <...>) - (global $global0 (ref $#DummyStruct) <...>) + (global $global0 (ref $"dummy struct") <...>) (global $global2 (ref $#Vtable-1-1) <...>) (elem $module0.cross-module-funcs-0 (set 0 (ref.func $"modH1UseH1 "))) @@ -139,7 +139,7 @@ struct.get $H1 $fun local.tee $var0 struct.get $#Closure-0-1 $context - i32.const 68 + i32.const 69 i64.const 1 struct.new $BoxedInt local.get $var0 diff --git a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat index a9b6028f20f..dec2c70555f 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.constant_module2.wat @@ -1,6 +1,6 @@ (module $module2 + (type $"dummy struct" <...>) (type $#Closure-0-1 <...>) - (type $#DummyStruct <...>) (type $#Top <...>) (type $#Vtable-0-1 <...>) (type $Array<_NamedParameter> <...>) @@ -30,7 +30,7 @@ (ref.null none)) (global $H0 (mut (ref null $H0)) (ref.null none)) - (global $global0 (ref $#DummyStruct) <...>) + (global $global0 (ref $"dummy struct") <...>) (global $global2 (ref $#Vtable-0-1) <...>) (elem $module0.cross-module-funcs-0 (set 6 (ref.func $globalH0Foo)) diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat index f4d86cf555d..ccc54b5cdee 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained.devirtualized_module1.wat @@ -11,7 +11,7 @@ (global $baseObj (import "module0" "global0") (ref null $Object)) (global $foo1Obj (import "module0" "global2") (ref null $Object)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 5 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 808 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 776 funcref) (global $"\"Foo1.doitDevirt(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) @@ -31,7 +31,7 @@ global.get $1 local.get $var0 struct.get $Object $field0 - i32.const 459 + i32.const 403 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) (result (ref null $#Top)) drop diff --git a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat index c3608db22a0..565b80b9749 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.fine_grained_module5.wat @@ -14,7 +14,7 @@ (global $5 (import "module0" "global5") (ref $BoxedInt)) (global $FooConst0 (import "module0" "global7") (ref $Object)) (table $module0.cross-module-funcs-0 (import "module0" "cross-module-funcs-0") 34 funcref) - (table $module0.dispatch0 (import "module0" "dispatch0") 824 funcref) + (table $module0.dispatch0 (import "module0" "dispatch0") 792 funcref) (global $"\"FooConst5(\"" (ref $JSStringImpl) (i32.const 4) (i32.const 0) @@ -118,7 +118,7 @@ call $"fooGlobal5 implicit getter" local.get $var2 struct.get $Object $field0 - i32.const 415 + i32.const 435 i32.add call_indirect $module0.dispatch0 (param (ref $Object) (ref null $#Top)) (result (ref null $#Top)) drop 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 b8a2f1942b5..aadeb486e4b 100644 --- a/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat +++ b/pkg/dart2wasm/test/ir_tests/deferred.type_checks_module1.wat @@ -9,7 +9,7 @@ (type $_Type <...>) (global $"\")\"_11" (import "$" "1") (ref $JSStringImpl)) (global $_InterfaceType (import "$" "/") (ref $_InterfaceType)) - (table $$.$ (import "$" "$") 903 funcref) + (table $$.$ (import "$" "$") 915 funcref) (table $$.& (import "$" "&") 22 funcref) (global $"\">.takeT(\"" (ref $JSStringImpl) <...>) (global $"\"Foo<\"" (ref $JSStringImpl) <...>) @@ -161,7 +161,7 @@ local.get $var1 local.get $var2 struct.get $_Type $field0 - i32.const 573 + i32.const 575 i32.add call_indirect $$.$ (param (ref $_Type) (ref $#Top)) (result i32) end $label0 diff --git a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat index 47a17612c91..6e1d5b4ee88 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure.wat @@ -1,4 +1,5 @@ (module $module0 + (type $"dummy struct" <...>) (type $#Closure-0-0 <...>) (type $#Closure-0-2 (sub final $#Closure-0-0 (struct (field $field0 i32) @@ -6,9 +7,8 @@ (field $context (ref struct)) (field $vtable (ref $#Vtable-0-2)) (field $functionType (ref $_FunctionType))))) - (type $#DummyStruct <...>) (type $#Top <...>) - (type $#Vtable-0-2 (sub final $#DummyStruct (struct + (type $#Vtable-0-2 (sub final $"dummy struct" (struct (field $closureCallEntry-0-1 (ref $type0)) (field $closureCallEntry-0-2 (ref $type2))))) (type $Array<_NamedParameter> <...>) @@ -32,7 +32,7 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) (i32.const 10) (i32.const 0) @@ -57,7 +57,7 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) (i32.const 10) (i32.const 0) @@ -71,8 +71,8 @@ (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $_InterfaceType_30 (ref $_InterfaceType) <...>) - (global $_TopType_354 (ref $_TopType) <...>) - (global $global0 (ref $#DummyStruct) <...>) + (global $_TopType_355 (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_323 (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)) <...>) 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 8ba3c401767..3537239d0fc 100644 --- a/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat +++ b/pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.wat @@ -1,4 +1,5 @@ (module $module0 + (type $"dummy struct" <...>) (type $#Closure-0-0 <...>) (type $#Closure-0-2 (sub final $#Closure-0-0 (struct (field $field0 i32) @@ -6,9 +7,8 @@ (field $context (ref struct)) (field $vtable (ref $#Vtable-0-2)) (field $functionType (ref $_FunctionType))))) - (type $#DummyStruct <...>) (type $#Top <...>) - (type $#Vtable-0-2 (sub final $#DummyStruct (struct + (type $#Vtable-0-2 (sub final $"dummy struct" (struct (field $closureCallEntry-0-1 (ref $type0)) (field $closureCallEntry-0-2 (ref $type2))))) (type $Array<_NamedParameter> <...>) @@ -32,7 +32,7 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) (i32.const 10) (i32.const 0) @@ -57,7 +57,7 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) (i32.const 10) (i32.const 0) @@ -71,8 +71,8 @@ (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $_InterfaceType_30 (ref $_InterfaceType) <...>) - (global $_TopType_354 (ref $_TopType) <...>) - (global $global0 (ref $#DummyStruct) <...>) + (global $_TopType_355 (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_325 (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)) <...>) 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 81cdf2fa93f..b7d108ae7e7 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 @@ -1,4 +1,5 @@ (module $module0 + (type $"dummy struct" <...>) (rec (type $#Closure-0-0 <...>) (type $type0 <...>) @@ -10,7 +11,6 @@ (field $context (ref struct)) (field $vtable (ref $#Vtable-0-2)) (field $functionType (ref $_FunctionType))))) - (type $#DummyStruct <...>) (type $#Top <...>) (type $#Vtable-0-2 (sub final $#Vtable-0-0 (struct (field $dynamicClosureCallEntry (ref $type0)) @@ -36,9 +36,9 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) - (global.get $_InterfaceType_360) + (global.get $_InterfaceType_361) (array.new_fixed $Array<_Type> 2) (i64.const 1) (global.get $"WasmArray<_NamedParameter>[0]") @@ -56,7 +56,7 @@ (i32.const 0) (global.get $"WasmArray<_Type>[0]") (global.get $"WasmArray<_Type>[0]") - (global.get $_TopType_354) + (global.get $_TopType_355) (global.get $_InterfaceType_30) (i32.const 10) (i32.const 0) @@ -70,9 +70,9 @@ (struct.new $_FunctionType) (struct.new $#Closure-0-2)) (global $_InterfaceType_30 (ref $_InterfaceType) <...>) - (global $_InterfaceType_360 (ref $_InterfaceType) <...>) - (global $_TopType_354 (ref $_TopType) <...>) - (global $global0 (ref $#DummyStruct) <...>) + (global $_InterfaceType_361 (ref $_InterfaceType) <...>) + (global $_TopType_355 (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_334 (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)) <...>) diff --git a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat index 647613d3ff2..0aa9126138b 100644 --- a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat +++ b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat @@ -1,4 +1,5 @@ (module $module0 + (type $"dummy struct" (struct)) (type $#Closure-0-0 (sub $#ClosureBase (struct (field $field0 i32) (field $field1 (mut i32)) @@ -11,7 +12,6 @@ (field $context (ref struct)) (field $vtable (ref $#VtableBase)) (field $functionType (ref $_FunctionType))))) - (type $#DummyStruct (struct)) (type $#NonGenericVtableBase (sub $#VtableBase (struct))) (type $#Top (struct (field $field0 i32))) @@ -144,7 +144,7 @@ (global.get $_FunctionType) (struct.new $#Closure-0-0)) (global $1 (ref $BoxedInt) - (i32.const 67) + (i32.const 68) (i64.const 1) (struct.new $BoxedInt)) (global $_FunctionType (ref $_FunctionType) @@ -166,7 +166,7 @@ (i64.const 2) (struct.new $_TopType)) (global $global0 (ref struct) - (struct.new $#DummyStruct)) + (struct.new $"dummy struct")) (global $global2 (ref $#Vtable-0-0) (ref.func $"main tear-off trampoline") (struct.new $#Vtable-0-0)) diff --git a/pkg/dart2wasm/test/ir_tests/interop.double.wat b/pkg/dart2wasm/test/ir_tests/interop.double.wat index 69fe44ce6f3..a5a14f6d04b 100644 --- a/pkg/dart2wasm/test/ir_tests/interop.double.wat +++ b/pkg/dart2wasm/test/ir_tests/interop.double.wat @@ -38,7 +38,7 @@ else call $"ktrue implicit getter" if (result (ref null $BoxedDouble)) - i32.const 90 + i32.const 100 call $"doubleValue implicit getter" struct.new $BoxedDouble else diff --git a/pkg/dart2wasm/test/ir_tests/memory_use.wat b/pkg/dart2wasm/test/ir_tests/memory_use.wat index dfcec03880e..68047aa46ad 100644 --- a/pkg/dart2wasm/test/ir_tests/memory_use.wat +++ b/pkg/dart2wasm/test/ir_tests/memory_use.wat @@ -11,30 +11,30 @@ i32.const 1 memory.grow $foo.mem drop - i32.const 89 + i32.const 98 i32.const 0 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 89 + i32.const 98 i32.const 0 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 89 + i32.const 98 i32.const 0 f64.load align=8 struct.new $BoxedDouble call $print - i32.const 89 + i32.const 98 i32.const 1 f32.load align=4 f64.promote_f32 struct.new $BoxedDouble call $print - i32.const 89 + i32.const 98 i32.const 1 f32.load align=4 f64.promote_f32 diff --git a/sdk/lib/_internal/wasm/lib/dynamic_module.dart b/sdk/lib/_internal/wasm/lib/dynamic_module.dart index d438f17534a..b617e9bbde1 100644 --- a/sdk/lib/_internal/wasm/lib/dynamic_module.dart +++ b/sdk/lib/_internal/wasm/lib/dynamic_module.dart @@ -156,6 +156,36 @@ class WasmArrayConstCache { } } +class DummyValueConstCache { + static WasmArray _dummyValueConstCache = + WasmArray.literal([WasmAnyRef.fromObject(Object())]); + static int _nextDummyValueConstCacheIndex = 0; + + @pragma('dyn-module:callable') + static WasmAnyRef canonicalizeDummyValue( + WasmAnyRef value, + // Only takes one argument since we only check these types against a static + // type test and not against the new value. + WasmFunction check, + ) { + for (int i = 0; i < _nextDummyValueConstCacheIndex; i++) { + if (check.call(_dummyValueConstCache[i])) { + return value; + } + } + if (_nextDummyValueConstCacheIndex == _dummyValueConstCache.length) { + final newCache = WasmArray.filled( + _dummyValueConstCache.length * 2, + _dummyValueConstCache[0], + ); + newCache.copy(0, _dummyValueConstCache, 0, _dummyValueConstCache.length); + _dummyValueConstCache = newCache; + } + _dummyValueConstCache[_nextDummyValueConstCacheIndex++] = value; + return value; + } +} + /// A table where there is one row per module and each column represents the /// updateable function for the corresponding allocated key index. The compiler /// tracks updateable functions via a unique string key which is converted to an diff --git a/tests/web/wasm/default_nullable_param_helper.dart b/tests/web/wasm/default_nullable_param_helper.dart new file mode 100644 index 00000000000..90506b74f81 --- /dev/null +++ b/tests/web/wasm/default_nullable_param_helper.dart @@ -0,0 +1,27 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +String doSplit(String s) { + return s.split(',').join(';'); +} + +abstract class Base { + void format(String s, [String? p]); +} + +class A extends Base { + void format(String s, [String? p = 'a']) { + print('A: ${doSplit(s ?? p!)}'); + } +} + +class B extends Base { + final String bDefault; + + B(this.bDefault); + + void format(String s, [String? p]) { + print('B: ${doSplit(s + (p ?? bDefault))}'); + } +} diff --git a/tests/web/wasm/default_nullable_param_test.dart b/tests/web/wasm/default_nullable_param_test.dart new file mode 100644 index 00000000000..70435da7513 --- /dev/null +++ b/tests/web/wasm/default_nullable_param_test.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// dart2wasmOptions=--enable-deferred-loading + +import 'default_nullable_param_helper.dart' deferred as helper; +import 'package:async_helper/async_helper.dart'; + +helper.Base getBase(int i, String s) => i < 10 ? helper.A() : helper.B(s); + +Future main() async { + asyncStart(); + await helper.loadLibrary(); + getBase(9, 'world').format('hello'); + getBase(11, 'foo').format('hey', 'world'); + getBase(11, 'foo').format('hey'); + asyncEnd(); +}