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(); +}