[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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) <...>)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user