// 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 'pragma.dart'; class _LoadingUnitBuilder { late int id; final _LibraryVertex root; final List members = []; 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 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 computeLoadingUnits( Component component, HasEntryPointVisitor visitor, ) { // 1. Build the dominator tree for the library import graph. final map = {}; 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; }