[dart2wasm] Prune library imports after TFA for better deferred module splits
This significantly prunes the imports in the library graph which in return significantly reduces code size of main module. - gallery: -27% (-677 KB) - essentials: -45% (-8.7 MB) The main insight here is that for the purpose of splitting up Dart libraries into wasm modules we only have a dependency of one dart library on another if the former uses something from the latter "statically". Other uses of `Reference`s from other libraries (e.g. interface targets, dart types, etc) don't require an import. The other insight is that a library A may import library B which may re-export library C1, C2, C3. If A only uses things from C2 then we can remove import B and inject import C2 instead. That means library A no longer needs B, C1, C2. For dart types specifically: Compared to other backends, dart2wasm stores all RTI (runtime type information) in the main module - i.e. deferred modules don't bring extra RTI - which is why we don't have to load deferred module before we can access it's types. Change-Id: I9e486bb4de165e4b23644a84838ace06d2f35bce Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464901 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
57ea885496
commit
ec83c5918a
@@ -39,6 +39,7 @@ import 'dynamic_modules.dart';
|
||||
import 'io_util.dart';
|
||||
import 'js/method_collector.dart' show JSMethods;
|
||||
import 'js/runtime_generator.dart' as js;
|
||||
import 'library_dependencies_pruner.dart';
|
||||
import 'modules.dart';
|
||||
import 'record_class_generator.dart';
|
||||
import 'records.dart';
|
||||
@@ -548,6 +549,30 @@ Future<CompilationResult> _runTfaPhase(
|
||||
libraryIndex = LibraryIndex(component, _librariesToIndex);
|
||||
}
|
||||
|
||||
// NOTE: The [Library.dependencies] will be in a weird state after TFA:
|
||||
//
|
||||
// * a library may use members of other libraries without an import that
|
||||
// provides the member
|
||||
//
|
||||
// * a library may have many unused imports
|
||||
//
|
||||
// -> See https://dartbug.com/62112 & https://dartbug.com/62111 for details.
|
||||
//
|
||||
// At this point dart2wasm uses library dependencies only for one purpose,
|
||||
// namely for partitioning all libraries into deferred wasm modules. So we now
|
||||
// perform a dart2wasm specific pruning of library imports.
|
||||
//
|
||||
// See [pruneLibraryDependencies] for more information.
|
||||
//
|
||||
// NOTE: In stress test mode the component is manually split into one wasm
|
||||
// module per library without making imports of libraries deferred. To ensure
|
||||
// all modules get loaded before main it injects dummy [LoadLibrary]
|
||||
// expressions that would be optimized out by [pruneLibraryDependencies]. So
|
||||
// we disable the pruning in this case.
|
||||
if (!options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
pruneLibraryDependencies(libraryIndex, component);
|
||||
}
|
||||
|
||||
if (options.emitTfa) {
|
||||
// Store metadata needed for codegen so that it can be serialized.
|
||||
final recordClassesRepo = _RecordClassesRepository();
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:kernel/core_types.dart';
|
||||
|
||||
import 'await_transformer.dart' as await_transformer;
|
||||
import 'compiler_options.dart';
|
||||
import 'library_dependencies_pruner.dart';
|
||||
import 'modules.dart';
|
||||
import 'target.dart';
|
||||
import 'util.dart' show addPragma;
|
||||
@@ -139,8 +140,32 @@ class DeferredLoadingModuleStrategy extends ModuleStrategy {
|
||||
});
|
||||
});
|
||||
|
||||
moduleOutputData =
|
||||
ModuleOutputData([mainModule, ...rootSetToModule.values]);
|
||||
// Some libraries may not have gotten a module assigned in the above
|
||||
// procedure. This can have a varity of reasons:
|
||||
//
|
||||
// - A class that's never really used but still in the program because TFA
|
||||
// left it there (this happens occasionally because we enable RTA before
|
||||
// TFA, RTA is less precised and may mark a class as allocated but TFA
|
||||
// later on optimizes usages away which leave the class as non-abstract
|
||||
// but unused).
|
||||
// - A class is only used in type expressions
|
||||
// - ...
|
||||
//
|
||||
// The code generator still requires every library to have a corresponding
|
||||
// module, so we make an artificial one here.
|
||||
final assignedLibraries = <Library>{
|
||||
...mainModule.libraries,
|
||||
for (final module in rootSetToModule.values) ...module.libraries,
|
||||
};
|
||||
final unassignedLibraries = component.libraries.toSet()
|
||||
..removeAll(assignedLibraries);
|
||||
|
||||
moduleOutputData = ModuleOutputData([
|
||||
mainModule,
|
||||
...rootSetToModule.values,
|
||||
if (unassignedLibraries.isNotEmpty)
|
||||
builder.buildModuleMetadata()..libraries.addAll(unassignedLibraries)
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -195,6 +220,10 @@ class DeferredLoadingModuleStrategy extends ModuleStrategy {
|
||||
for (final dependency in currentLibrary.dependencies) {
|
||||
final targetLibrary = dependency.importedLibraryReference.asLibrary;
|
||||
if (dependency.isDeferred) {
|
||||
if (dependency.name!.startsWith(unusedDeferredLibraryPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newDeferredRoots.add(targetLibrary);
|
||||
(importTargetMap[currentLibrary] ??= {})[dependency.name!] =
|
||||
targetLibrary;
|
||||
@@ -401,7 +430,7 @@ class DeferredLoadingLowering extends Transformer {
|
||||
_checkLibraryIsLoadedFromLoadId, Arguments([IntLiteral(loadId)]));
|
||||
}
|
||||
|
||||
static void addEntryPointPragma(CoreTypes coreTypes, Procedure node) {
|
||||
static void addEntryPointPragma(CoreTypes coreTypes, Annotatable node) {
|
||||
addPragma(node, 'wasm:entry-point', coreTypes, value: BoolConstant(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
import 'package:kernel/kernel.dart';
|
||||
import 'package:kernel/library_index.dart';
|
||||
|
||||
/// If a deferred library import has this name prefix it isn't used to load
|
||||
/// anything. It only serves to maintain that any [CheckLibraryIsLoaded] throws
|
||||
/// if the [LoadLibrary] call was not called.
|
||||
const unusedDeferredLibraryPrefix = 'unused-';
|
||||
|
||||
/// Prunes [Library.dependencies] to contain precisely those imports needed.
|
||||
///
|
||||
/// Dart2wasm only uses library dependencies for one purpose, namely for
|
||||
/// computing deferred loading units. This computation is done based on the
|
||||
/// import graph and the granularity is on a library level.
|
||||
///
|
||||
/// We make the following overvations:
|
||||
///
|
||||
/// a) Using a type from a import
|
||||
/// -> The main wasm module has all Dart runtime type information atm
|
||||
/// -> No need to import a library to use classes from it in types.
|
||||
///
|
||||
/// b) Using a constant from an import
|
||||
/// -> If we use a [InstanceConstant] / [TearOffConstant] in a library then
|
||||
/// we have to ensure the enclosing library of the
|
||||
/// [InstanceConstant.classNode] / [TearOffConstant.target] is imported.
|
||||
/// -> This ensures that the code for closure/method is available when
|
||||
/// invoking it.
|
||||
/// -> Any other constant doesn't require an import.
|
||||
///
|
||||
/// c) Using static elements from an import
|
||||
/// -> If we invoke a constructor, a static method, static getter, super
|
||||
/// constructor, etc we need to import the target's enclosing library.
|
||||
/// -> This will guarantee we have the code for target loaded when we perform
|
||||
/// the call.
|
||||
///
|
||||
/// d) Instance/Dynamic invocations, other uses of [Reference]s
|
||||
/// -> Does not require an import of the [Reference]s enclosing library.
|
||||
/// -> We have sound type system: If the call is executed we know that the
|
||||
/// receiver was allocated (and whoever allocated it has ensured - via c)
|
||||
/// above - that the code for methods of the receiver is loaded).
|
||||
///
|
||||
/// So we establish the following invariants
|
||||
///
|
||||
/// * We only have imports for [Reference]s which are used for "static"-like
|
||||
/// calls
|
||||
///
|
||||
/// * We import the library containing the definition of a [Reference] and not
|
||||
/// e.g. a library that may re-export it
|
||||
///
|
||||
/// * If a [Reference] was usable via a deferred import (possibly via
|
||||
/// a deferred library that re-exported the [Reference]) we ensure the newly
|
||||
/// inserted import will also be deferred.
|
||||
///
|
||||
/// The transform will
|
||||
///
|
||||
/// * prune [Library.dependencies] to be exact, i.e. have a import iff
|
||||
/// something is used from the imported library
|
||||
///
|
||||
/// * will remove all exports
|
||||
///
|
||||
/// * may insert more precise [LoadLibrary]/[CheckLibraryIsLoaded] if we don't
|
||||
/// use a deferred library directly but things it re-exported
|
||||
///
|
||||
void pruneLibraryDependencies(LibraryIndex libraryIndex, Component component) {
|
||||
final constantToLibrarySet = _ConstantToLibrarySet();
|
||||
for (final library in component.libraries) {
|
||||
final usedLibraries =
|
||||
_Collector(library, constantToLibrarySet).usedLibraries;
|
||||
_ImportPruner(libraryIndex, usedLibraries, library);
|
||||
}
|
||||
for (final library in component.libraries) {
|
||||
library.dependencies.removeWhere((dep) {
|
||||
if (dep.isExport) {
|
||||
dep.parent = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _ImportPruner extends Transformer {
|
||||
final LibraryIndex libraryIndex;
|
||||
|
||||
final Set<Library> usedLibraries;
|
||||
final Library library;
|
||||
final additionalDeferredImports =
|
||||
<LibraryDependency, List<LibraryDependency>>{};
|
||||
|
||||
late final futureImmediate =
|
||||
libraryIndex.getConstructor('dart:async', '_Future', 'immediate');
|
||||
|
||||
_ImportPruner(this.libraryIndex, this.usedLibraries, this.library) {
|
||||
// Step 1) Prune existing library imports.
|
||||
|
||||
// The set of libraries that we need to import and are already covered via
|
||||
// an existing library import.
|
||||
final librariesOfExistingImports = <Library>{};
|
||||
|
||||
// Maps a library to all the deferred imports that made this library
|
||||
// available.
|
||||
final libraryToDeferredImports = <Library, List<LibraryDependency>>{};
|
||||
|
||||
// The new set of imports (possibly smaller - removing unused dependencies,
|
||||
// possibly larger - adding used but not yet imported dependencies).
|
||||
final prunedDependencies = <LibraryDependency>[];
|
||||
for (final dep in library.dependencies) {
|
||||
if (dep.isExport) {
|
||||
prunedDependencies.add(dep);
|
||||
continue;
|
||||
}
|
||||
if (usedLibraries.contains(dep.targetLibrary)) {
|
||||
librariesOfExistingImports.add(dep.targetLibrary);
|
||||
prunedDependencies.add(dep);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dep.isDeferred) {
|
||||
// Although the deferred dependency isn't used, for making sure
|
||||
// [CheckLibraryIsLoaded] nodes throw if no preceding
|
||||
// [LoadLibrary] was called we have to maintain a dummy import. This
|
||||
// will also ensure the exception mentions the right name.
|
||||
prunedDependencies.add(dep);
|
||||
dep.name = '$unusedDeferredLibraryPrefix${dep.name!}';
|
||||
|
||||
// Loop over all libraries available via the deferred import that are
|
||||
// used. The transformer will then issue individual [LoadLibrary] calls
|
||||
// to them.
|
||||
for (final available in _transitiveLibrarySet(dep.targetLibrary)) {
|
||||
if (usedLibraries.contains(available)) {
|
||||
libraryToDeferredImports.putIfAbsent(available, () => []).add(dep);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// The [dep] isn't directly used, remove it.
|
||||
assert(!dep.isDeferred);
|
||||
dep.parent = null;
|
||||
}
|
||||
library.dependencies = prunedDependencies;
|
||||
|
||||
// Step 2) Add missing imports.
|
||||
for (final used in usedLibraries) {
|
||||
// Maybe we already import the [used] library.
|
||||
if (librariesOfExistingImports.contains(used)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Never emit a library import to `dart:core`, it's special.
|
||||
if (used.importUri.scheme == 'dart' && used.importUri.path == 'core') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to inject a new import of the [used] library.
|
||||
final oldDeferredImports = libraryToDeferredImports[used];
|
||||
if (oldDeferredImports == null) {
|
||||
// This library was not accessible via old deferred imports, so we emit
|
||||
// a normal import.
|
||||
library.addDependency(LibraryDependency.import(used));
|
||||
continue;
|
||||
}
|
||||
|
||||
// The library was accessible (via a re-export) from an deferred
|
||||
// import. Let's make a new deferred import for that particular library.
|
||||
final newDep = LibraryDependency.deferredImport(
|
||||
used, 'PreciseDeferredDep-${used.dependencies.length}');
|
||||
library.addDependency(newDep);
|
||||
for (final oldImport in oldDeferredImports) {
|
||||
// Any [LoadLibrary] or [CheckLibraryIsLoaded] node that operated on the
|
||||
// old (unused) deferred import needs to cover the [newDep] (possibly in
|
||||
// addition to the existing dep (if used) and others).
|
||||
additionalDeferredImports.putIfAbsent(oldImport, () => []).add(newDep);
|
||||
}
|
||||
}
|
||||
|
||||
// We only have to transform the body of the library if any [LoadLibrary] or
|
||||
// [CheckLibraryIsLoaded] has to be modified.
|
||||
if (additionalDeferredImports.isNotEmpty) {
|
||||
library.transformChildren(this);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitLoadLibrary(LoadLibrary node) {
|
||||
node = super.visitLoadLibrary(node) as LoadLibrary;
|
||||
final additional = additionalDeferredImports[node.import];
|
||||
if (additional == null) return node;
|
||||
return BlockExpression(
|
||||
Block([
|
||||
// This may be a dummy/unused which we only omit for throwing correct
|
||||
// errors if a access (e.g. of a type) is used before the load call.
|
||||
ExpressionStatement(node),
|
||||
|
||||
for (final replacement in additional.skip(1))
|
||||
ExpressionStatement(LoadLibrary(replacement)),
|
||||
]),
|
||||
LoadLibrary(additional.last));
|
||||
}
|
||||
|
||||
@override
|
||||
TreeNode visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
|
||||
node = super.visitCheckLibraryIsLoaded(node) as CheckLibraryIsLoaded;
|
||||
final additional = additionalDeferredImports[node.import];
|
||||
if (additional == null) return node;
|
||||
return BlockExpression(
|
||||
Block([
|
||||
// This may be a dummy/unused which we only omit for throwing correct
|
||||
// errors if a access (e.g. of a type) is used before the load call.
|
||||
ExpressionStatement(node),
|
||||
|
||||
for (final replacement in additional.skip(1))
|
||||
ExpressionStatement(CheckLibraryIsLoaded(replacement)),
|
||||
]),
|
||||
CheckLibraryIsLoaded(additional.last));
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the AST of a [Library] and collects the set of libraries we need
|
||||
/// to import due to accessing elements "statically" (see
|
||||
/// [pruneLibraryDependencies] for more information)
|
||||
class _Collector extends RecursiveVisitor {
|
||||
final Library library;
|
||||
final _ConstantToLibrarySet constantToLibrarySet;
|
||||
|
||||
/// The libraries that need to be imported.
|
||||
final Set<Library> usedLibraries = {};
|
||||
|
||||
_Collector(this.library, this.constantToLibrarySet) {
|
||||
library.accept(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStaticGet(StaticGet node) {
|
||||
super.visitStaticGet(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStaticSet(StaticSet node) {
|
||||
super.visitStaticSet(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStaticInvocation(StaticInvocation node) {
|
||||
super.visitStaticInvocation(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstructorInvocation(ConstructorInvocation node) {
|
||||
super.visitConstructorInvocation(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperInitializer(SuperInitializer node) {
|
||||
super.visitSuperInitializer(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRedirectingInitializer(RedirectingInitializer node) {
|
||||
super.visitRedirectingInitializer(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStaticTearOff(StaticTearOff node) {
|
||||
super.visitStaticTearOff(node);
|
||||
addLibrary(node.target.enclosingLibrary);
|
||||
}
|
||||
|
||||
@override
|
||||
void defaultDartType(DartType node) {
|
||||
// Ignore due to compiler always able to construct runtime type objects when
|
||||
// needed (see also [pruneLibraryDependencies]).
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSupertype(Supertype node) {
|
||||
// Ignore due to compiler always able to construct runtime type objects when
|
||||
// needed (see also [pruneLibraryDependencies]).
|
||||
}
|
||||
|
||||
@override
|
||||
void visitConstantExpression(ConstantExpression node) {
|
||||
usedLibraries.addAll(constantToLibrarySet.librariesFor(node.constant));
|
||||
}
|
||||
|
||||
void addLibrary(Library used) {
|
||||
if (used != library) {
|
||||
usedLibraries.add(used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ConstantToLibrarySet {
|
||||
final _constantToTransitiveLibraries = <Constant, Set<Library>>{};
|
||||
|
||||
/// Collects the set of libraries one needs to import when accessing
|
||||
/// [constant].
|
||||
///
|
||||
/// This include enclosing libraries of all [InstanceConstant]s
|
||||
/// and [TearOffConstant]s of the transitive constant graph of [constant].
|
||||
Set<Library> librariesFor(Constant constant) {
|
||||
final existing = _constantToTransitiveLibraries[constant];
|
||||
if (existing != null) return existing;
|
||||
|
||||
final transitiveLibraries = <Library>{
|
||||
if (constant is InstanceConstant) constant.classNode.enclosingLibrary,
|
||||
if (constant is TearOffConstant) constant.target.enclosingLibrary,
|
||||
// Collect all transitive libraries for direct child constants.
|
||||
for (final childConstant
|
||||
in _ChildConstantCollector.directChildrenOf(constant))
|
||||
...librariesFor(childConstant),
|
||||
};
|
||||
|
||||
return _constantToTransitiveLibraries[constant] =
|
||||
transitiveLibraries.isEmpty ? const <Library>{} : transitiveLibraries;
|
||||
}
|
||||
}
|
||||
|
||||
class _ChildConstantCollector extends RecursiveVisitor {
|
||||
/// Returns the set of constants referred to by the (possibly composed)
|
||||
/// [constant].
|
||||
static Set<Constant> directChildrenOf(Constant constant) {
|
||||
final children = <Constant>{};
|
||||
constant.visitChildren(_ChildConstantCollector._(children));
|
||||
return children;
|
||||
}
|
||||
|
||||
final Set<Constant> _directChildren;
|
||||
_ChildConstantCollector._(this._directChildren);
|
||||
|
||||
@override
|
||||
void defaultConstantReference(Constant node) {
|
||||
_directChildren.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects the set of libraries transitively imported via importing [library].
|
||||
///
|
||||
/// This includes [library] and any other library it transitively re-exports.
|
||||
Set<Library> _transitiveLibrarySet(Library library) {
|
||||
final transitiveLibrarySet = <Library>{library};
|
||||
final worklist = <Library>[library];
|
||||
while (worklist.isNotEmpty) {
|
||||
final toBeExpanded = worklist.removeLast();
|
||||
assert(transitiveLibrarySet.contains(toBeExpanded));
|
||||
for (final dep in toBeExpanded.dependencies) {
|
||||
if (dep.isExport) {
|
||||
final reExportedLibrary = dep.targetLibrary;
|
||||
if (transitiveLibrarySet.add(reExportedLibrary)) {
|
||||
worklist.add(reExportedLibrary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return transitiveLibrarySet;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
(module $module4
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
// functionFilter=useFoo
|
||||
// tableFilter=static[0-9]+
|
||||
// globalFilter=Foo
|
||||
// typeFilter=Foo
|
||||
// compilerOption=--enable-deferred-loading
|
||||
// compilerOption=--no-minify
|
||||
|
||||
import 'deferred.constant.type_use.h.0.dart';
|
||||
import 'deferred.constant.type_use.h.1.dart' deferred as h1;
|
||||
|
||||
void main() async {
|
||||
// Ensure the deferred libraries are loaded.
|
||||
await h1.loadLibrary();
|
||||
|
||||
useFoo();
|
||||
}
|
||||
|
||||
@pragma('wasm:never-inline')
|
||||
void useFoo() {
|
||||
// We use `Foo` as type but that doesn't mean
|
||||
// => This shouldn't require the code for `Foo` to be bundled with the main
|
||||
// module.
|
||||
useFooAsType();
|
||||
|
||||
// We use `Foo` code, but only via deferred library which uses `Foo`
|
||||
// directly.
|
||||
// => This should require the code for `Foo` to end up in the deferred
|
||||
// library.
|
||||
h1.useFooAsObject();
|
||||
}
|
||||
|
||||
@pragma('wasm:never-inline')
|
||||
void useFooAsType() {
|
||||
print(Foo);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
class Foo {
|
||||
int i = 0;
|
||||
void printFoo() {
|
||||
print('Foo called ${i++}');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2025, 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.
|
||||
|
||||
import 'deferred.constant.type_use.h.0.dart';
|
||||
|
||||
@pragma('wasm:never-inline')
|
||||
void useFooAsObject() {
|
||||
final f = Foo();
|
||||
f.printFoo();
|
||||
f.printFoo();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
(module $module0
|
||||
(type $#Top <...>)
|
||||
(type $DefaultSet <...>)
|
||||
(type $DeferredLoadIdNotLoadedError <...>)
|
||||
(type $_InterfaceType <...>)
|
||||
(type $type0 <...>)
|
||||
(table $static0-0 (export "static0-0") 1 (ref null $type0))
|
||||
(global $"C414 _InterfaceType" (ref $_InterfaceType) <...>)
|
||||
(global $_loaded (mut (ref null $DefaultSet)) <...>)
|
||||
(func $_loaded implicit getter (result (ref $DefaultSet)) <...>)
|
||||
(func $"useFoo <noInline>"
|
||||
call $"useFooAsType <noInline>"
|
||||
block $label0 (result (ref $DefaultSet))
|
||||
global.get $_loaded
|
||||
br_on_non_null $label0
|
||||
call $"_loaded implicit getter"
|
||||
end $label0
|
||||
call $_DefaultSet&_HashFieldBase&SetMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashSetMixin.contains
|
||||
i32.eqz
|
||||
if
|
||||
i32.const 49
|
||||
i32.const 0
|
||||
ref.null none
|
||||
i64.const 0
|
||||
struct.new $DeferredLoadIdNotLoadedError
|
||||
call $Error._throwWithCurrentStackTrace
|
||||
unreachable
|
||||
end
|
||||
i32.const 0
|
||||
call_indirect $static0-0 (result (ref null $#Top))
|
||||
drop
|
||||
)
|
||||
(func $"useFooAsType <noInline>"
|
||||
global.get $"C414 _InterfaceType"
|
||||
call $print
|
||||
drop
|
||||
)
|
||||
(func $Error._throwWithCurrentStackTrace (param $var0 (ref $#Top)) <...>)
|
||||
(func $_DefaultSet&_HashFieldBase&SetMixin&_HashBase&_OperatorEqualsAndHashCode&_LinkedHashSetMixin.contains (param $var0 (ref $DefaultSet)) (result i32) <...>)
|
||||
(func $print (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>)
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
(module $module1
|
||||
(type $#Top <...>)
|
||||
(type $Foo (sub final $Object (struct
|
||||
(field $field0 i32)
|
||||
(field $field1 (mut i32))
|
||||
(field $i (mut i64)))))
|
||||
(type $JSStringImpl <...>)
|
||||
(type $Object <...>)
|
||||
(global $".Foo called " (import "" "Foo called ") (ref extern))
|
||||
(global $"C462 \"Foo called \"" (ref $JSStringImpl)
|
||||
(i32.const 4)
|
||||
(i32.const 0)
|
||||
(global.get $".Foo called ")
|
||||
(struct.new $JSStringImpl))
|
||||
(func $"useFooAsObject <noInline>" (result (ref null $#Top))
|
||||
(local $var0 (ref $Foo))
|
||||
i32.const 114
|
||||
i32.const 0
|
||||
i64.const 0
|
||||
struct.new $Foo
|
||||
local.tee $var0
|
||||
call $Foo.printFoo
|
||||
local.get $var0
|
||||
call $Foo.printFoo
|
||||
ref.null none
|
||||
)
|
||||
(func $Foo.printFoo (param $var0 (ref $Foo)) <...>)
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
(module $module2
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
(module $module3
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
(module $module2
|
||||
)
|
||||
Reference in New Issue
Block a user