[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 <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs
2026-02-19 12:48:49 -08:00
committed by Commit Queue
parent 832581d6ff
commit a484bb3973
23 changed files with 365 additions and 169 deletions
+1 -1
View File
@@ -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;
+5 -6
View File
@@ -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
}
}
+121 -20
View File
@@ -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<w.HeapType, DummyValueConstant> _dummyValueConstants = {};
final Map<DartType, InstanceConstant> _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<w.ValueType>
@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<ConstantInfo?>
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<ConstantInfo?>
assert(!(translator.dynamicModuleConstants?.constantNames
.containsKey(constant) ??
false));
needsRuntimeCanonicalization =
needsRuntimeCanonicalization |=
constant.accept(_ConstantDynamicModuleSharedChecker(translator));
}
}
@@ -1115,6 +1134,7 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
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<ConstantInfo?>
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<ConstantInfo?>
b.struct_new(recordClassInfo.struct);
});
}
@override
ConstantInfo? visitAuxiliaryConstant(AuxiliaryConstant constant) {
if (constant is DummyValueConstant) {
final type = constant.type;
final childConstants = <ConstantInfo>[];
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<dynamic> v) {}
@override
int get hashCode => type.hashCode;
@override
bool operator ==(Object other) =>
other is DummyValueConstant && other.type == type;
}
class TypeOfConstantVisitor extends ConstantVisitor<w.RefType>
@@ -1447,6 +1525,14 @@ class TypeOfConstantVisitor extends ConstantVisitor<w.RefType>
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<w.RefType, w.TableBuilder> lazySlotTables = {};
late final tableImporter = WasmTableImporter(translator, 'constant-table');
final Map<w.HeapType, w.Global> _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';
}
+37 -4
View File
@@ -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<void> {
/// A local containing the value to be canonicalized.
final w.Local valueLocal;
ConstantCanonicalizer(this.translator, this.b, this.valueLocal);
final Map<w.HeapType, w.Global> _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<void> {
}
}
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
+3 -3
View File
@@ -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);
+2
View File
@@ -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 =
+1 -1
View File
@@ -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;
+77 -92
View File
@@ -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<T extends w.Exportable> {
final Translator _translator;
final String _exportPrefix;
@@ -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 <noInline>"
global.get $_InterfaceType_440
global.get $_InterfaceType_441
call $print
drop
)
@@ -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 <noInline>")))
@@ -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
@@ -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))
@@ -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
@@ -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
@@ -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
+6 -6
View File
@@ -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)) <...>)
@@ -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)) <...>)
@@ -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<Object?>)) (param $var3 (ref $Array<Object?>)) (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<Object?>)) (param $var3 (ref $Array<Object?>)) (result (ref null $#Top)) <...>)
+3 -3
View File
@@ -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))
@@ -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
+5 -5
View File
@@ -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
@@ -156,6 +156,36 @@ class WasmArrayConstCache {
}
}
class DummyValueConstCache {
static WasmArray<WasmAnyRef> _dummyValueConstCache =
WasmArray<WasmAnyRef>.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<bool Function(WasmAnyRef val2)> check,
) {
for (int i = 0; i < _nextDummyValueConstCacheIndex; i++) {
if (check.call(_dummyValueConstCache[i])) {
return value;
}
}
if (_nextDummyValueConstCacheIndex == _dummyValueConstCache.length) {
final newCache = WasmArray<WasmAnyRef>.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
@@ -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))}');
}
}
@@ -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<void> main() async {
asyncStart();
await helper.loadLibrary();
getBase(9, 'world').format('hello');
getBase(11, 'foo').format('hey', 'world');
getBase(11, 'foo').format('hey');
asyncEnd();
}