move usedExports into ExportsInfo

This commit is contained in:
Tobias Koppers 2019-01-23 12:12:44 +01:00
parent 923e16dd5a
commit 739fef4fda
9 changed files with 157 additions and 97 deletions

View File

@ -44,7 +44,6 @@ const StatsFactory = require("./stats/StatsFactory");
const StatsPrinter = require("./stats/StatsPrinter");
const AsyncQueue = require("./util/AsyncQueue");
const Queue = require("./util/Queue");
const SortableSet = require("./util/SortableSet");
const {
compareLocations,
concatComparators,
@ -1497,7 +1496,7 @@ class Compilation {
this.chunkGraph.connectChunkAndRuntimeModule(chunk, module);
// Setup internals
this.moduleGraph.setUsedExports(module, new SortableSet());
this.moduleGraph.getExportsInfo(module).setUsedForSideEffectsOnly();
this.chunkGraph.addModuleRuntimeRequirements(module, [
RuntimeGlobals.require
]);

View File

@ -6,30 +6,13 @@
"use strict";
const { STAGE_DEFAULT } = require("./OptimizationStages");
const SortableSet = require("./util/SortableSet");
const Queue = require("./util/Queue");
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */
/** @typedef {false | true | SortableSet<string>} UsedExports */
/**
* @param {UsedExports} moduleUsedExports the current used exports of the module
* @param {false | true | string[]} newUsedExports the new used exports
* @returns {boolean} true, if the newUsedExports is part of the moduleUsedExports
*/
const isContained = (moduleUsedExports, newUsedExports) => {
if (moduleUsedExports === null) return false;
if (moduleUsedExports === true) return true;
if (newUsedExports === true) return false;
if (newUsedExports === false) return true;
if (moduleUsedExports === false) return false;
if (newUsedExports.length > moduleUsedExports.size) return false;
return newUsedExports.every(item => moduleUsedExports.has(item));
};
class FlagDependencyUsagePlugin {
/**
* @param {Compiler} compiler the compiler instance
@ -51,79 +34,62 @@ class FlagDependencyUsagePlugin {
* @returns {void}
*/
const processModule = (module, usedExports) => {
let ue = moduleGraph.getUsedExports(module);
if (ue === true) return;
const exportsInfo = moduleGraph.getExportsInfo(module);
let changed = false;
if (usedExports === true) {
moduleGraph.setUsedExports(module, (ue = true));
} else if (Array.isArray(usedExports)) {
if (!ue) {
moduleGraph.setUsedExports(
module,
(ue = new SortableSet(usedExports))
);
} else {
const old = ue ? ue.size : -1;
for (const exportName of usedExports) {
ue.add(exportName);
}
if (ue.size === old) {
return;
changed = exportsInfo.setUsedInUnknownWay();
} else if (usedExports) {
for (const exportName of usedExports) {
const exportInfo = exportsInfo.getExportInfo(exportName);
if (exportInfo.used !== true) {
exportInfo.used = true;
changed = true;
}
}
} else {
if (ue !== false) return;
moduleGraph.setUsedExports(module, (ue = new SortableSet()));
// for a module without side effects we stop tracking usage here when no export is used
// This module won't be evaluated in this case
if (module.factoryMeta.sideEffectFree) return;
changed = exportsInfo.setUsedForSideEffectsOnly();
}
// for a module without side effects we stop tracking usage here when no export is used
// This module won't be evaluated in this case
if (module.factoryMeta.sideEffectFree) {
if (ue !== true && ue.size === 0) return;
if (changed) {
queue.enqueue(module);
}
queue.push([module, module, ue]);
};
/**
* @param {Module} module the module
* @param {DependenciesBlock} depBlock the dependencies block
* @param {UsedExports} usedExports the used exports
* @returns {void}
*/
const processDependenciesBlock = (module, depBlock, usedExports) => {
const processDependenciesBlock = depBlock => {
for (const dep of depBlock.dependencies) {
processDependency(module, dep);
processDependency(dep);
}
for (const block of depBlock.blocks) {
queue.push([module, block, usedExports]);
queue.enqueue(block);
}
};
/**
* @param {Module} module the module
* @param {Dependency} dep the dependency
* @returns {void}
*/
const processDependency = (module, dep) => {
const processDependency = dep => {
const reference = compilation.getDependencyReference(dep);
if (!reference) return;
const referenceModule = reference.module;
const importedNames = reference.importedNames;
const oldUsedExports = moduleGraph.getUsedExports(referenceModule);
if (
!oldUsedExports ||
!isContained(oldUsedExports, importedNames)
) {
processModule(referenceModule, importedNames);
}
processModule(referenceModule, importedNames);
};
for (const module of modules) {
moduleGraph.setUsedExports(module, false);
moduleGraph.getExportsInfo(module).setHasUseInfo();
}
/** @type {[Module, DependenciesBlock, UsedExports][]} */
const queue = [];
/** @type {Queue<DependenciesBlock>} */
const queue = new Queue();
for (const deps of compilation.entryDependencies.values()) {
for (const dep of deps) {
const module = moduleGraph.getModule(dep);
@ -134,8 +100,8 @@ class FlagDependencyUsagePlugin {
}
while (queue.length) {
const queueItem = queue.pop();
processDependenciesBlock(queueItem[0], queueItem[1], queueItem[2]);
const depBlock = queue.dequeue();
processDependenciesBlock(depBlock);
}
}
);

View File

@ -30,7 +30,7 @@ class FlagInitialModulesAsUsedPlugin {
return;
}
for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
moduleGraph.setUsedExports(module, true);
moduleGraph.getExportsInfo(module).setUsedInUnknownWay();
moduleGraph.addExtraReason(module, this.explanation);
}
}

View File

@ -218,13 +218,6 @@ class Module extends DependenciesBlock {
).getUsedExports(this);
}
set usedExports(value) {
ModuleGraph.getModuleGraphForModule(
this,
"Module.usedExports"
).setUsedExports(this, value);
}
get optimizationBailout() {
return ModuleGraph.getModuleGraphForModule(
this,
@ -403,7 +396,7 @@ class Module extends DependenciesBlock {
* @returns {boolean} true, if the module is used
*/
isModuleUsed(moduleGraph) {
return moduleGraph.getUsedExports(this) !== false;
return moduleGraph.getExportsInfo(this).isUsed() !== false;
}
/**
@ -412,10 +405,8 @@ class Module extends DependenciesBlock {
* @returns {boolean} true, if the export is used
*/
isExportUsed(moduleGraph, exportName) {
const usedExports = moduleGraph.getUsedExports(this);
if (usedExports === null || usedExports === true) return true;
if (usedExports === false) return false;
return usedExports.has(exportName);
const exportInfo = moduleGraph.getExportInfo(this, exportName);
return exportInfo.used !== false;
}
// TODO move to ModuleGraph

View File

@ -7,13 +7,13 @@
const util = require("util");
const ModuleGraphConnection = require("./ModuleGraphConnection");
const SortableSet = require("./util/SortableSet");
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleProfile")} ModuleProfile */
/** @typedef {import("./RequestShortener")} RequestShortener */
/** @template T @typedef {import("./util/SortableSet")<T>} SortableSet<T> */
/**
* @callback OptimizationBailoutFunction
@ -26,6 +26,7 @@ class ExportsInfo {
/** @type {Map<string, ExportInfo>} */
this._exports = new Map();
this._otherExportsInfo = new ExportInfo(null);
this._sideEffectsOnlyInfo = new ExportInfo("*side effects only*");
this._exportsIsOrdered = false;
}
@ -63,6 +64,20 @@ class ExportsInfo {
}
}
setHasUseInfo() {
for (const exportInfo of this._exports.values()) {
if (exportInfo.used === undefined) {
exportInfo.used = false;
}
}
if (this._otherExportsInfo.used === undefined) {
this._otherExportsInfo.used = false;
}
if (this._sideEffectsOnlyInfo.used === undefined) {
this._sideEffectsOnlyInfo.used = false;
}
}
getExportInfo(name) {
const info = this._exports.get(name);
if (info !== undefined) return info;
@ -107,6 +122,90 @@ class ExportsInfo {
return changed;
}
setUsedInUnknownWay() {
let changed = false;
if (this._isUsed === false) {
this._isUsed = true;
changed = true;
}
for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== true && exportInfo.used !== null) {
exportInfo.used = null;
changed = true;
}
if (exportInfo.canMangle !== false) {
exportInfo.canMangle = false;
changed = true;
}
}
if (
this._otherExportsInfo.used !== true &&
this._otherExportsInfo.used !== null
) {
this._otherExportsInfo.used = null;
changed = true;
}
if (this._otherExportsInfo.canMangle !== false) {
this._otherExportsInfo.canMangle = false;
changed = true;
}
return changed;
}
setUsedForSideEffectsOnly() {
if (this._sideEffectsOnlyInfo.used === false) {
this._sideEffectsOnlyInfo.used = true;
return true;
}
return false;
}
isUsed() {
if (this._otherExportsInfo.used !== false) {
return true;
}
if (this._sideEffectsOnlyInfo.used !== false) {
return true;
}
for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== false) {
return true;
}
}
}
getUsedExports() {
switch (this._otherExportsInfo.used) {
case undefined:
return null;
case null:
return true;
case true:
return true;
}
const array = [];
if (!this._exportsIsOrdered) this._sortExports();
for (const exportInfo of this._exports.values()) {
switch (exportInfo.used) {
case undefined:
return null;
case null:
return true;
case true:
array.push(exportInfo.name);
}
}
if (array.length === 0) {
switch (this._sideEffectsOnlyInfo.used) {
case undefined:
return null;
case false:
return false;
}
}
return new SortableSet(array);
}
getProvidedExports() {
switch (this._otherExportsInfo.provided) {
case undefined:
@ -183,6 +282,14 @@ class ExportInfo {
constructor(name, initFrom) {
/** @type {string} */
this.name = name;
/**
* true: it is used
* false: it is not used
* null: only the runtime knows if it is used
* undefined: it was not determined if it is used
* @type {boolean | null | undefined}
*/
this.used = initFrom ? initFrom.used : undefined;
/**
* true: it is provided
* false: it is not provided
@ -201,7 +308,16 @@ class ExportInfo {
}
getUsedInfo() {
return "no usage info";
switch (this.used) {
case undefined:
return "no usage info";
case null:
return "maybe used (runtime-defined)";
case true:
return "used";
case false:
return "unused";
}
}
getProvidedInfo() {
@ -241,8 +357,6 @@ class ModuleGraphModule {
this.optimizationBailout = [];
/** @type {ExportsInfo} */
this.exports = new ExportsInfo();
/** @type {false | true | SortableSet<string> | null} */
this.usedExports = null;
/** @type {number} */
this.preOrderIndex = null;
/** @type {number} */
@ -390,7 +504,6 @@ class ModuleGraph {
newMgm.postOrderIndex = oldMgm.postOrderIndex;
newMgm.preOrderIndex = oldMgm.preOrderIndex;
newMgm.depth = oldMgm.depth;
newMgm.usedExports = oldMgm.usedExports;
// TODO optimize this
newMgm.exports.restoreProvided(oldMgm.exports.getRestoreProvidedData());
}
@ -621,17 +734,7 @@ class ModuleGraph {
*/
getUsedExports(module) {
const mgm = this._getModuleGraphModule(module);
return mgm.usedExports;
}
/**
* @param {Module} module the module
* @param {false | true | SortableSet<string>} usedExports the used exports
* @returns {void}
*/
setUsedExports(module, usedExports) {
const mgm = this._getModuleGraphModule(module);
mgm.usedExports = usedExports;
return mgm.exports.getUsedExports();
}
/**

View File

@ -33,7 +33,7 @@ class RequireIncludeDependency extends ModuleDependency {
// This doesn't use any export
return new DependencyReference(
() => moduleGraph.getModule(this),
[],
false,
false
);
}

View File

@ -849,8 +849,7 @@ class ConcatenatedModule extends Module {
const result = new ConcatSource();
// add harmony compatibility flag (must be first because of possible circular dependencies)
const usedExports = moduleGraph.getUsedExports(this.rootModule);
if (usedExports === true) {
if (moduleGraph.getExportsInfo(this).otherExportsInfo.used !== false) {
result.add(
runtimeTemplate.defineEsModuleFlagStatement({
exportsArgument: this.exportsArgument,

View File

@ -18,9 +18,10 @@ module.exports = {
Array.isArray(ref.importedNames) &&
ref.importedNames.includes("unused")
) {
const newExports = ref.importedNames.filter(item => item !== "unused");
return new DependencyReference(
() => ref.module,
ref.importedNames.filter(item => item !== "unused"),
newExports.length > 0 ? newExports : false,
ref.weak,
ref.order
);

View File

@ -18,9 +18,10 @@ module.exports = {
Array.isArray(ref.importedNames) &&
ref.importedNames.includes("unused")
) {
const newExports = ref.importedNames.filter(item => item !== "unused");
return new DependencyReference(
() => ref.module,
ref.importedNames.filter(item => item !== "unused"),
newExports.length > 0 ? newExports : false,
ref.weak,
ref.order
);