| // Copyright (c) 2020, 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. | |
| library vm.transformations.deferred_loading; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/core_types.dart' show CoreTypes; | |
| import 'package:kernel/target/targets.dart' show Target; | |
| import '../dominators.dart'; | |
| import '../metadata/loading_units.dart'; | |
| import 'package:vm/modular/transformations/pragma.dart'; | |
| class _LoadingUnitBuilder { | |
| late int id; | |
| final _LibraryVertex root; | |
| final List<Library> members = <Library>[]; | |
| final List<_LoadingUnitBuilder> children = <_LoadingUnitBuilder>[]; | |
| _LoadingUnitBuilder(this.root); | |
| _LoadingUnitBuilder? get parent => root.dominator?.loadingUnit; | |
| int get parentId => parent == null ? 0 : parent!.id; | |
| LoadingUnit asLoadingUnit() { | |
| return new LoadingUnit( | |
| id, | |
| parentId, | |
| members.map((l) => l.importUri.toString()).toList(), | |
| ); | |
| } | |
| String toString() => | |
| "_LoadingUnitBuilder(id=$id, parent=${parentId}, size=${members.length})"; | |
| } | |
| class _LibraryVertex extends Vertex<_LibraryVertex> { | |
| final Library library; | |
| bool isLoadingRoot = true; | |
| _LoadingUnitBuilder? loadingUnit; | |
| _LibraryVertex(this.library); | |
| String toString() => "_LibraryVertex(${library.importUri})"; | |
| } | |
| class HasEntryPointVisitor extends RecursiveVisitor { | |
| final PragmaAnnotationParser parser; | |
| bool _hasEntryPoint = false; | |
| HasEntryPointVisitor(this.parser); | |
| visitAnnotations(List<Expression> annotations) { | |
| for (var ann in annotations) { | |
| ParsedPragma? pragma = parser.parsePragma(ann); | |
| if (pragma is ParsedEntryPointPragma) { | |
| _hasEntryPoint = true; | |
| return; | |
| } | |
| } | |
| } | |
| @override | |
| visitClass(Class klass) { | |
| visitAnnotations(klass.annotations); | |
| klass.visitChildren(this); | |
| } | |
| @override | |
| defaultMember(Member node) { | |
| visitAnnotations(node.annotations); | |
| } | |
| bool hasEntryPoint(Library lib) { | |
| _hasEntryPoint = false; | |
| visitLibrary(lib); | |
| return _hasEntryPoint; | |
| } | |
| } | |
| List<LoadingUnit> computeLoadingUnits( | |
| Component component, | |
| HasEntryPointVisitor visitor, | |
| ) { | |
| // 1. Build the dominator tree for the library import graph. | |
| final map = <Library, _LibraryVertex>{}; | |
| for (final lib in component.libraries) { | |
| map[lib] = new _LibraryVertex(lib); | |
| } | |
| for (final vertex in map.values) { | |
| for (final dep in vertex.library.dependencies) { | |
| final target = map[dep.targetLibrary]!; | |
| vertex.successors.add(target); | |
| } | |
| } | |
| final root = map[component.mainMethod!.enclosingLibrary]!; | |
| // Fake imports from root library to every core library or library containing | |
| // an entry point pragma so that they end up in the same loading unit | |
| // attributed to the user's root library. | |
| for (final vertex in map.values) { | |
| if (vertex == root) { | |
| continue; | |
| } | |
| if (vertex.library.importUri.isScheme("dart") || | |
| visitor.hasEntryPoint(vertex.library)) { | |
| root.successors.add(vertex); | |
| vertex.isLoadingRoot = false; | |
| } | |
| } | |
| computeDominators(root); | |
| // 2. Find loading unit roots. | |
| for (var importer in map.values) { | |
| if ((importer != root) && (importer.dominator == null)) { | |
| continue; // Unreachable library. | |
| } | |
| for (var dep in importer.library.dependencies) { | |
| if (dep.isDeferred) { | |
| continue; | |
| } | |
| var importee = map[dep.targetLibrary]!; | |
| if (importer.isDominatedBy(importee)) { | |
| continue; | |
| } | |
| importee.isLoadingRoot = false; | |
| } | |
| } | |
| assert(root.isLoadingRoot); | |
| final List<_LoadingUnitBuilder> loadingUnits = <_LoadingUnitBuilder>[]; | |
| for (var vertex in map.values) { | |
| if (vertex.isLoadingRoot) { | |
| var unit = new _LoadingUnitBuilder(vertex); | |
| vertex.loadingUnit = unit; | |
| unit.members.add(vertex.library); | |
| loadingUnits.add(unit); | |
| } | |
| } | |
| // 3. Attribute every library to the dominating loading unit. | |
| for (var vertex in map.values) { | |
| if (vertex.isLoadingRoot) { | |
| continue; // Already processed. | |
| } | |
| var dom = vertex.dominator; | |
| if (dom == null) { | |
| continue; // Unreachable library. | |
| } | |
| while (dom!.loadingUnit == null) { | |
| dom = dom.dominator; | |
| } | |
| vertex.loadingUnit = dom.loadingUnit; | |
| vertex.loadingUnit!.members.add(vertex.library); | |
| } | |
| // 4. Sort loading units so parents are before children. Normally this order | |
| // would already exist as a side effect of loading sources in import order, | |
| // but this isn't guaranteed when combining separately produced kernel files. | |
| for (var unit in loadingUnits) { | |
| var parent = unit.parent; | |
| if (parent != null) { | |
| parent.children.add(unit); | |
| } | |
| } | |
| var index = 0; | |
| loadingUnits.clear(); | |
| loadingUnits.add(root.loadingUnit!); | |
| while (index < loadingUnits.length) { | |
| var unit = loadingUnits[index]; | |
| unit.id = ++index; | |
| loadingUnits.addAll(unit.children); | |
| } | |
| return loadingUnits.map((u) => u.asLoadingUnit()).toList(); | |
| } | |
| Component transformComponent( | |
| Component component, | |
| CoreTypes coreTypes, | |
| Target target, | |
| ) { | |
| final parser = ConstantPragmaAnnotationParser(coreTypes, target); | |
| final visitor = HasEntryPointVisitor(parser); | |
| final metadata = new LoadingUnitsMetadata( | |
| computeLoadingUnits(component, visitor), | |
| ); | |
| final repo = new LoadingUnitsMetadataRepository(); | |
| component.addMetadataRepository(repo); | |
| repo.mapping[component] = metadata; | |
| return component; | |
| } |