storm-lua-minify 0.2.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +157 -37
- package/dist/aggregateSpecialization.js +406 -0
- package/dist/ast2lua.js +223 -82
- package/dist/astWalk.js +162 -0
- package/dist/callGraph.js +372 -0
- package/dist/cli.js +53 -32
- package/dist/cliOptions.js +36 -0
- package/dist/cliProgress.js +87 -0
- package/dist/config.js +73 -0
- package/dist/constantFold.js +798 -0
- package/dist/controlFlow.js +266 -0
- package/dist/functionRewrites.js +580 -0
- package/dist/generatedAst.js +108 -0
- package/dist/generatedNode.js +23 -0
- package/dist/globalRename.js +35 -0
- package/dist/interproceduralAnalysis.js +842 -0
- package/dist/interproceduralConstants.js +120 -0
- package/dist/keywordLocator.js +68 -0
- package/dist/linker.js +13 -0
- package/dist/luaString.js +157 -0
- package/dist/minifier.js +1243 -35
- package/dist/optimizerAnalysis.js +43 -0
- package/dist/optimizerDiagnostics.js +65 -0
- package/dist/optimizerFacts.js +529 -0
- package/dist/optimizerPass.js +96 -0
- package/dist/optimizerTransaction.js +56 -0
- package/dist/optimizerValueDomain.js +180 -0
- package/dist/options.js +233 -0
- package/dist/progress.js +2 -0
- package/dist/removeUnused.js +145 -0
- package/dist/renamer.js +236 -53
- package/dist/resolver.js +37 -14
- package/dist/runtimeEnvironment.js +105 -0
- package/dist/sourceMetadata.js +314 -0
- package/dist/statementDataflow.js +259 -0
- package/dist/statementScheduler.js +595 -0
- package/dist/symbolLiveness.js +92 -0
- package/dist/tableEffects.js +356 -0
- package/dist/transform.js +40 -0
- package/dist/valueFlow.js +409 -0
- package/dist/wholeProgramExports.js +646 -0
- package/dist/wholeProgramFieldRenames.js +583 -0
- package/dist/wholeProgramFields.js +672 -0
- package/dist/wholeProgramObjects.js +783 -0
- package/package.json +50 -33
- package/.github/workflows/ci.yml +0 -39
- package/.github/workflows/publish.yml +0 -39
- package/.prettierignore +0 -3
- package/.prettierrc.json +0 -1
- package/dist/index.js +0 -27
- package/eslint.config.js +0 -29
- package/src/ast2lua.ts +0 -940
- package/src/cli.ts +0 -86
- package/src/linker.ts +0 -108
- package/src/minifier.ts +0 -284
- package/src/output.ts +0 -71
- package/src/renamer.ts +0 -134
- package/src/resolver.ts +0 -378
- package/test/circular-require.test.ts +0 -19
- package/test/fixtures/bare-require/main.lua +0 -2
- package/test/fixtures/bare-require/mod.lua +0 -1
- package/test/fixtures/bitwise-precedence/main.lua +0 -10
- package/test/fixtures/circular-require/a.lua +0 -2
- package/test/fixtures/circular-require/b.lua +0 -2
- package/test/fixtures/circular-require/main.lua +0 -2
- package/test/fixtures/dofile/greet.lua +0 -1
- package/test/fixtures/dofile/main.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_delta.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_echo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_golf.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_india.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +0 -2
- package/test/fixtures/entry-scope-many-requires/main.lua +0 -25
- package/test/fixtures/multi-require/common.lua +0 -1
- package/test/fixtures/multi-require/main.lua +0 -3
- package/test/fixtures/nested-module/main.lua +0 -2
- package/test/fixtures/nested-module/sub/deep.lua +0 -1
- package/test/fixtures/require-call/main.lua +0 -2
- package/test/fixtures/require-call/mod.lua +0 -5
- package/test/fixtures/require-in-expression/main.lua +0 -1
- package/test/fixtures/require-in-expression/mod.lua +0 -1
- package/test/fixtures/require-string-call/main.lua +0 -2
- package/test/fixtures/require-string-call/mod.lua +0 -5
- package/test/fixtures/single-file/main.lua +0 -15
- package/test/identifier-collision.test.ts +0 -28
- package/test/lib/collision.ts +0 -131
- package/test/lib/helpers.ts +0 -124
- package/test/no-rename.test.ts +0 -19
- package/test/output.test.ts +0 -95
- package/test/precedence.test.ts +0 -28
- package/test/renamer.test.ts +0 -130
- package/test/resolver.test.ts +0 -206
- package/test/roundtrip.test.ts +0 -26
- package/test/snapshot.test.ts +0 -27
- package/test/snapshots/bare-require.sl.lua +0 -1
- package/test/snapshots/bitwise-precedence.sl.lua +0 -2
- package/test/snapshots/dofile.sl.lua +0 -1
- package/test/snapshots/entry-scope-many-requires.m.lua +0 -14
- package/test/snapshots/multi-require.m.lua +0 -4
- package/test/snapshots/multi-require.sl.lua +0 -1
- package/test/snapshots/nested-module.m.lua +0 -4
- package/test/snapshots/require-call.m.lua +0 -5
- package/test/snapshots/require-call.sl.lua +0 -1
- package/test/snapshots/require-in-expression.sl.lua +0 -1
- package/test/snapshots/require-string-call.m.lua +0 -5
- package/test/snapshots/single-file.sl.lua +0 -6
- package/test/sourcemap.test.ts +0 -105
- package/tsconfig.eslint.json +0 -8
- package/tsconfig.json +0 -111
package/dist/minifier.js
CHANGED
|
@@ -7,16 +7,43 @@ exports.Minifier = void 0;
|
|
|
7
7
|
const luaparse_1 = __importDefault(require("luaparse"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const source_map_1 = require("source-map");
|
|
11
10
|
const ast2lua_1 = require("./ast2lua");
|
|
12
11
|
const linker_1 = require("./linker");
|
|
13
12
|
const resolver_1 = require("./resolver");
|
|
14
13
|
const renamer_1 = require("./renamer");
|
|
14
|
+
const globalRename_1 = require("./globalRename");
|
|
15
|
+
const transform_1 = require("./transform");
|
|
16
|
+
const sourceMetadata_1 = require("./sourceMetadata");
|
|
17
|
+
const removeUnused_1 = require("./removeUnused");
|
|
18
|
+
const constantFold_1 = require("./constantFold");
|
|
19
|
+
const generatedAst_1 = require("./generatedAst");
|
|
20
|
+
const statementScheduler_1 = require("./statementScheduler");
|
|
21
|
+
const optimizerPass_1 = require("./optimizerPass");
|
|
22
|
+
const runtimeEnvironment_1 = require("./runtimeEnvironment");
|
|
23
|
+
const optimizerDiagnostics_1 = require("./optimizerDiagnostics");
|
|
24
|
+
const optimizerFacts_1 = require("./optimizerFacts");
|
|
25
|
+
const optimizerAnalysis_1 = require("./optimizerAnalysis");
|
|
26
|
+
const interproceduralConstants_1 = require("./interproceduralConstants");
|
|
27
|
+
const functionRewrites_1 = require("./functionRewrites");
|
|
28
|
+
const wholeProgramObjects_1 = require("./wholeProgramObjects");
|
|
29
|
+
const wholeProgramFields_1 = require("./wholeProgramFields");
|
|
30
|
+
const aggregateSpecialization_1 = require("./aggregateSpecialization");
|
|
31
|
+
const wholeProgramExports_1 = require("./wholeProgramExports");
|
|
32
|
+
const wholeProgramFieldRenames_1 = require("./wholeProgramFieldRenames");
|
|
33
|
+
const options_1 = require("./options");
|
|
15
34
|
const NO_RENAME = {
|
|
16
35
|
nameOf: () => undefined,
|
|
17
36
|
usedNames: new Set(),
|
|
18
37
|
};
|
|
38
|
+
function sourceRangeOf(node) {
|
|
39
|
+
return node.range;
|
|
40
|
+
}
|
|
41
|
+
function copyMap(source, target) {
|
|
42
|
+
target.clear();
|
|
43
|
+
source.forEach((value, key) => target.set(key, value));
|
|
44
|
+
}
|
|
19
45
|
class Minifier {
|
|
46
|
+
entryFilePath;
|
|
20
47
|
identifiersInUse;
|
|
21
48
|
moduleSourceText;
|
|
22
49
|
moduleAST;
|
|
@@ -31,42 +58,933 @@ class Minifier {
|
|
|
31
58
|
moduleResolve = new Map();
|
|
32
59
|
// モジュールごとのRenameパスの結果(初回アクセス時に計算しキャッシュする)
|
|
33
60
|
renameCache = new Map();
|
|
34
|
-
|
|
61
|
+
// #8a: プログラム全体を横断して決定された「内部グローバル名 -> 短縮名」の対応
|
|
62
|
+
globalRenames = new Map();
|
|
63
|
+
moduleMetadata = new Map();
|
|
64
|
+
diagnosticCollector;
|
|
65
|
+
schedulerVariant;
|
|
66
|
+
functionRewriteVariant;
|
|
67
|
+
fieldFactVariant;
|
|
68
|
+
aggregateSpecializationVariant;
|
|
69
|
+
exportDceVariant;
|
|
70
|
+
fieldRenameVariant;
|
|
71
|
+
progress;
|
|
72
|
+
linkedAstGeneration = 0;
|
|
73
|
+
wholeProgramObjectsValue;
|
|
74
|
+
wholeProgramFieldsValue;
|
|
75
|
+
wholeProgramExportsValue;
|
|
76
|
+
wholeProgramFieldRenamesValue;
|
|
77
|
+
exportDceChanged = false;
|
|
78
|
+
constructor(entryFilePath, luaParseSettings, mode, schedulerVariantOrProgress, functionRewriteVariant, fieldFactVariant, aggregateSpecializationVariant, exportDceVariant, fieldRenameVariant, progress) {
|
|
79
|
+
this.schedulerVariant =
|
|
80
|
+
typeof schedulerVariantOrProgress === "string"
|
|
81
|
+
? schedulerVariantOrProgress
|
|
82
|
+
: undefined;
|
|
83
|
+
this.functionRewriteVariant = functionRewriteVariant;
|
|
84
|
+
this.fieldFactVariant = fieldFactVariant;
|
|
85
|
+
this.aggregateSpecializationVariant = aggregateSpecializationVariant;
|
|
86
|
+
this.exportDceVariant = exportDceVariant;
|
|
87
|
+
this.fieldRenameVariant = fieldRenameVariant;
|
|
88
|
+
this.progress =
|
|
89
|
+
typeof schedulerVariantOrProgress === "object"
|
|
90
|
+
? schedulerVariantOrProgress
|
|
91
|
+
: progress;
|
|
92
|
+
this.entryFilePath = entryFilePath;
|
|
35
93
|
this.identifiersInUse = new Set();
|
|
36
94
|
this.moduleSourceText = new Map();
|
|
37
95
|
this.moduleAST = new Map();
|
|
38
96
|
this.moduleNameAndFileName = new Map();
|
|
39
|
-
|
|
40
|
-
|
|
97
|
+
// コメントの所有関係と自己再帰参照の判定は位置情報を不変条件とする。
|
|
98
|
+
// 呼び出し側が省略・無効化しても、内部パスに必要な情報は常に収集する。
|
|
99
|
+
this.luaParseSettings = {
|
|
100
|
+
...luaParseSettings,
|
|
101
|
+
comments: true,
|
|
102
|
+
locations: true,
|
|
103
|
+
ranges: true,
|
|
104
|
+
};
|
|
105
|
+
this.mode = (0, options_1.resolveMinifierMode)(mode);
|
|
106
|
+
if (mode.collectOptimizationDiagnostics) {
|
|
107
|
+
this.diagnosticCollector = new optimizerDiagnostics_1.OptimizationDiagnosticCollector();
|
|
108
|
+
}
|
|
41
109
|
const pn = path_1.default.parse(entryFilePath);
|
|
42
110
|
this.dir = pn.dir;
|
|
43
111
|
this.entryModule = pn.name;
|
|
44
112
|
}
|
|
113
|
+
get optimizationDiagnostics() {
|
|
114
|
+
return this.diagnosticCollector?.diagnostics ?? [];
|
|
115
|
+
}
|
|
116
|
+
get wholeProgramObjects() {
|
|
117
|
+
return this.wholeProgramObjectsValue;
|
|
118
|
+
}
|
|
119
|
+
get wholeProgramFields() {
|
|
120
|
+
return this.wholeProgramFieldsValue;
|
|
121
|
+
}
|
|
122
|
+
get wholeProgramExports() {
|
|
123
|
+
return this.wholeProgramExportsValue;
|
|
124
|
+
}
|
|
125
|
+
get wholeProgramFieldRenames() {
|
|
126
|
+
return this.wholeProgramFieldRenamesValue;
|
|
127
|
+
}
|
|
45
128
|
parse() {
|
|
129
|
+
if (this.fieldRenameVariant === undefined && this.fieldRenamesEnabled()) {
|
|
130
|
+
return this.parseWithFieldRenameSelection();
|
|
131
|
+
}
|
|
132
|
+
if (this.exportDceVariant === undefined && this.exportDceEnabled()) {
|
|
133
|
+
return this.parseWithExportDceSelection();
|
|
134
|
+
}
|
|
135
|
+
if (this.fieldFactVariant === undefined && this.fieldFactsEnabled()) {
|
|
136
|
+
return this.parseWithFieldFactSelection();
|
|
137
|
+
}
|
|
138
|
+
if (this.aggregateSpecializationVariant === undefined &&
|
|
139
|
+
this.functionSpecializationEnabled()) {
|
|
140
|
+
return this.parseWithAggregateSpecializationSelection();
|
|
141
|
+
}
|
|
142
|
+
if (this.functionRewriteVariant === undefined &&
|
|
143
|
+
this.functionRewritesEnabled()) {
|
|
144
|
+
return this.parseWithFunctionRewriteSelection();
|
|
145
|
+
}
|
|
146
|
+
if (this.schedulerVariant === undefined &&
|
|
147
|
+
this.requiresSchedulerSelection()) {
|
|
148
|
+
return this.parseWithSchedulerSelection();
|
|
149
|
+
}
|
|
150
|
+
return this.parseOnce();
|
|
151
|
+
}
|
|
152
|
+
parseWithFieldRenameSelection() {
|
|
153
|
+
this.progress?.addSteps(2);
|
|
154
|
+
this.progress?.startStep("Evaluate field renaming baseline");
|
|
155
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, undefined, undefined, undefined, "baseline", this.progress);
|
|
156
|
+
const baseline = baselineMinifier.parse();
|
|
157
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
158
|
+
let trialMinifier;
|
|
159
|
+
let trial;
|
|
160
|
+
try {
|
|
161
|
+
this.progress?.startStep("Evaluate field renaming trial");
|
|
162
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, undefined, undefined, undefined, "trial", this.progress);
|
|
163
|
+
trial = trialMinifier.parse();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
167
|
+
this.recordFinalFieldRenameDecision("rejected", "trial-failed");
|
|
168
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
169
|
+
}
|
|
170
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
171
|
+
if (trialBytes >= baselineBytes) {
|
|
172
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
173
|
+
this.recordFinalFieldRenameDecision("rejected", "final-output-not-shorter");
|
|
174
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
175
|
+
}
|
|
176
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
177
|
+
this.recordFinalFieldRenameDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
178
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
179
|
+
}
|
|
180
|
+
parseWithExportDceSelection() {
|
|
181
|
+
this.progress?.addSteps(1);
|
|
182
|
+
let trialMinifier;
|
|
183
|
+
let trial;
|
|
184
|
+
try {
|
|
185
|
+
this.progress?.startStep("Evaluate unused export removal trial");
|
|
186
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, undefined, undefined, "trial", this.fieldRenameVariant, this.progress);
|
|
187
|
+
trial = trialMinifier.parse();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
this.progress?.addSteps(1);
|
|
191
|
+
this.progress?.startStep("Evaluate unused export removal baseline");
|
|
192
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, undefined, undefined, "baseline", this.fieldRenameVariant, this.progress);
|
|
193
|
+
const baseline = baselineMinifier.parse();
|
|
194
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
195
|
+
this.recordFinalExportDceDecision("rejected", "trial-failed");
|
|
196
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
197
|
+
}
|
|
198
|
+
if (!trialMinifier.exportDceChanged) {
|
|
199
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
200
|
+
this.recordFinalExportDceDecision("rejected", "final-output-not-shorter");
|
|
201
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
202
|
+
}
|
|
203
|
+
this.progress?.addSteps(1);
|
|
204
|
+
this.progress?.startStep("Evaluate unused export removal baseline");
|
|
205
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, undefined, undefined, "baseline", this.fieldRenameVariant, this.progress);
|
|
206
|
+
const baseline = baselineMinifier.parse();
|
|
207
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
208
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
209
|
+
if (trialBytes >= baselineBytes) {
|
|
210
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
211
|
+
this.recordFinalExportDceDecision("rejected", "final-output-not-shorter");
|
|
212
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
213
|
+
}
|
|
214
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
215
|
+
this.recordFinalExportDceDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
216
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
217
|
+
}
|
|
218
|
+
parseWithFieldFactSelection() {
|
|
219
|
+
this.progress?.addSteps(2);
|
|
220
|
+
this.progress?.startStep("Evaluate field optimization baseline");
|
|
221
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, "baseline", this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
222
|
+
const baseline = baselineMinifier.parse();
|
|
223
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
224
|
+
let trialMinifier;
|
|
225
|
+
let trial;
|
|
226
|
+
try {
|
|
227
|
+
this.progress?.startStep("Evaluate field optimization trial");
|
|
228
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, "trial", this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
229
|
+
trial = trialMinifier.parse();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
233
|
+
this.recordFinalFieldFactDecision("rejected", "trial-failed");
|
|
234
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
235
|
+
}
|
|
236
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
237
|
+
if (trialBytes >= baselineBytes) {
|
|
238
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
239
|
+
this.recordFinalFieldFactDecision("rejected", "final-output-not-shorter");
|
|
240
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
241
|
+
}
|
|
242
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
243
|
+
this.recordFinalFieldFactDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
244
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
245
|
+
}
|
|
246
|
+
parseWithAggregateSpecializationSelection() {
|
|
247
|
+
this.progress?.addSteps(2);
|
|
248
|
+
this.progress?.startStep("Evaluate function specialization baseline");
|
|
249
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, this.fieldFactVariant, "baseline", this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
250
|
+
const baseline = baselineMinifier.parse();
|
|
251
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
252
|
+
let trialMinifier;
|
|
253
|
+
let trial;
|
|
254
|
+
try {
|
|
255
|
+
this.progress?.startStep("Evaluate function specialization trial");
|
|
256
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, undefined, this.fieldFactVariant, "trial", this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
257
|
+
trial = trialMinifier.parse();
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
261
|
+
this.recordFinalAggregateSpecializationDecision("rejected", "trial-failed");
|
|
262
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
263
|
+
}
|
|
264
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
265
|
+
if (trialBytes >= baselineBytes) {
|
|
266
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
267
|
+
this.recordFinalAggregateSpecializationDecision("rejected", "final-output-not-shorter");
|
|
268
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
269
|
+
}
|
|
270
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
271
|
+
this.recordFinalAggregateSpecializationDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
272
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
273
|
+
}
|
|
274
|
+
parseWithFunctionRewriteSelection() {
|
|
275
|
+
this.progress?.addSteps(2);
|
|
276
|
+
this.progress?.startStep("Evaluate function rewrites baseline");
|
|
277
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, "baseline", this.fieldFactVariant, this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
278
|
+
const baseline = baselineMinifier.parse();
|
|
279
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
280
|
+
let trialMinifier;
|
|
281
|
+
let trial;
|
|
282
|
+
try {
|
|
283
|
+
this.progress?.startStep("Evaluate function rewrites trial");
|
|
284
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, undefined, "trial", this.fieldFactVariant, this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
285
|
+
trial = trialMinifier.parse();
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
289
|
+
this.recordFinalFunctionRewriteDecision("rejected", "trial-failed");
|
|
290
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
291
|
+
}
|
|
292
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
293
|
+
if (trialBytes >= baselineBytes) {
|
|
294
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
295
|
+
this.recordFinalFunctionRewriteDecision("rejected", "final-output-not-shorter");
|
|
296
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
297
|
+
}
|
|
298
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
299
|
+
this.recordFinalFunctionRewriteDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
300
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
301
|
+
}
|
|
302
|
+
parseWithSchedulerSelection() {
|
|
303
|
+
this.progress?.addSteps(2);
|
|
304
|
+
this.progress?.startStep("Evaluate statement scheduling baseline");
|
|
305
|
+
const baselineMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, "baseline", this.functionRewriteVariant, this.fieldFactVariant, this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
306
|
+
const baseline = baselineMinifier.parseOnce();
|
|
307
|
+
const baselineBytes = new TextEncoder().encode(baseline.toString()).length;
|
|
308
|
+
let trialMinifier;
|
|
309
|
+
let trial;
|
|
310
|
+
try {
|
|
311
|
+
this.progress?.startStep("Evaluate statement scheduling trial");
|
|
312
|
+
trialMinifier = new Minifier(this.entryFilePath, this.luaParseSettings, this.mode, "trial", this.functionRewriteVariant, this.fieldFactVariant, this.aggregateSpecializationVariant, this.exportDceVariant, this.fieldRenameVariant, this.progress);
|
|
313
|
+
trial = trialMinifier.parseOnce();
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
this.copyDiagnosticsFrom(baselineMinifier);
|
|
317
|
+
this.recordFinalSchedulerDecision("rejected", "trial-failed");
|
|
318
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
319
|
+
}
|
|
320
|
+
const trialBytes = new TextEncoder().encode(trial.toString()).length;
|
|
321
|
+
if (trialBytes >= baselineBytes) {
|
|
322
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
323
|
+
this.recordFinalSchedulerDecision("rejected", "final-output-not-shorter");
|
|
324
|
+
return this.adoptVariant(baselineMinifier, baseline);
|
|
325
|
+
}
|
|
326
|
+
this.copyDiagnosticsFrom(trialMinifier);
|
|
327
|
+
this.recordFinalSchedulerDecision("accepted", "final-output-shorter", baselineBytes - trialBytes);
|
|
328
|
+
return this.adoptVariant(trialMinifier, trial);
|
|
329
|
+
}
|
|
330
|
+
parseOnce() {
|
|
331
|
+
this.progress?.addSteps(11);
|
|
332
|
+
this.progress?.startStep("Load and parse modules");
|
|
46
333
|
this.link();
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
334
|
+
this.progress?.tick();
|
|
335
|
+
// #85 consumes #84 function-valued field facts before field DCE can remove
|
|
336
|
+
// callback storage. The field pass then sees specialized calls and includes
|
|
337
|
+
// storage/wrapper cleanup in the same final-output trial.
|
|
338
|
+
this.progress?.startStep("Rewrite functions");
|
|
339
|
+
this.rewriteFunctionsAll();
|
|
340
|
+
this.progress?.tick();
|
|
341
|
+
this.progress?.startStep("Analyze and rewrite fields");
|
|
342
|
+
this.rewriteWholeProgramFieldsAll();
|
|
343
|
+
this.progress?.tick();
|
|
344
|
+
this.progress?.startStep("Fold constants");
|
|
345
|
+
this.foldConstantsAll();
|
|
346
|
+
this.progress?.tick();
|
|
347
|
+
this.progress?.startStep("Remove unused exports");
|
|
348
|
+
this.rewriteWholeProgramExportsAll();
|
|
349
|
+
this.progress?.tick();
|
|
350
|
+
this.progress?.startStep("Remove unused code");
|
|
351
|
+
this.removeUnusedAll();
|
|
352
|
+
this.progress?.tick();
|
|
353
|
+
this.progress?.startStep("Plan global names");
|
|
354
|
+
this.rebuildIdentifiersInUse();
|
|
355
|
+
this.computeGlobalRenames();
|
|
356
|
+
this.progress?.tick();
|
|
357
|
+
this.progress?.startStep("Transform statements");
|
|
358
|
+
this.transformAll();
|
|
359
|
+
this.progress?.tick();
|
|
360
|
+
this.progress?.startStep("Finalize whole-program analyses");
|
|
361
|
+
if (this.functionRewritesEnabled() ||
|
|
362
|
+
this.fieldFactsEnabled() ||
|
|
363
|
+
this.fieldRenamesEnabled()) {
|
|
364
|
+
// fold/remove/schedule may have changed any module after method rewrites. Publish only a
|
|
365
|
+
// snapshot rebuilt from the complete linked AST generation consumed by final Rename/Print.
|
|
366
|
+
this.linkedAstGeneration++;
|
|
367
|
+
this.wholeProgramObjectsValue = this.analyzeWholeProgramObjects();
|
|
368
|
+
this.wholeProgramFieldsValue = (0, wholeProgramFields_1.analyzeWholeProgramFields)(this.wholeProgramObjectsValue, {
|
|
369
|
+
trustAnnotations: this.mode.assumeAnnotations === true,
|
|
370
|
+
metadataOf: (moduleName) => this.getSourceMetadata(moduleName),
|
|
55
371
|
});
|
|
372
|
+
this.wholeProgramExportsValue = (0, wholeProgramExports_1.analyzeWholeProgramExports)(this.wholeProgramObjectsValue, this.entryModule, (moduleName) => this.getSourceMetadata(moduleName));
|
|
373
|
+
if (this.fieldRenameVariant === "trial") {
|
|
374
|
+
this.wholeProgramFieldRenamesValue = (0, wholeProgramFieldRenames_1.planWholeProgramFieldRenames)(this.wholeProgramObjectsValue, this.wholeProgramFieldsValue, this.wholeProgramExportsValue, this.entryModule, (moduleName) => this.getSourceMetadata(moduleName));
|
|
375
|
+
this.recordWholeProgramFieldRenameDiagnostics(this.wholeProgramFieldRenamesValue);
|
|
376
|
+
}
|
|
56
377
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
378
|
+
this.progress?.tick();
|
|
379
|
+
this.progress?.startStep("Rename identifiers");
|
|
380
|
+
this.renameAll();
|
|
381
|
+
this.progress?.tick();
|
|
382
|
+
this.progress?.startStep("Generate Lua and source map");
|
|
383
|
+
const result = this.mode.requireWrapper
|
|
384
|
+
? this.printModuleWithRequireWrapper()
|
|
385
|
+
: this.printModule(this.entryModule);
|
|
62
386
|
this.moduleSourceText.forEach((v, k) => {
|
|
63
387
|
const fileName = this.moduleNameAndFileName.get(k);
|
|
64
388
|
if (fileName) {
|
|
65
389
|
result.setSourceContent(fileName, v);
|
|
66
390
|
}
|
|
391
|
+
this.progress?.tick();
|
|
67
392
|
});
|
|
68
393
|
return result;
|
|
69
394
|
}
|
|
395
|
+
rewriteWholeProgramExportsAll() {
|
|
396
|
+
if (this.exportDceVariant !== "trial" || !this.exportDceEnabled())
|
|
397
|
+
return;
|
|
398
|
+
const objectAnalysis = this.analyzeWholeProgramObjects();
|
|
399
|
+
const exportAnalysis = (0, wholeProgramExports_1.analyzeWholeProgramExports)(objectAnalysis, this.entryModule, (moduleName) => this.getSourceMetadata(moduleName));
|
|
400
|
+
this.wholeProgramObjectsValue = objectAnalysis;
|
|
401
|
+
this.wholeProgramExportsValue = exportAnalysis;
|
|
402
|
+
exportAnalysis.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
403
|
+
pass: "whole-program-export-reachability",
|
|
404
|
+
moduleName: diagnostic.moduleName,
|
|
405
|
+
fieldName: diagnostic.field,
|
|
406
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
407
|
+
decision: diagnostic.reason === "export-field-candidate" ||
|
|
408
|
+
diagnostic.reason === "field-live" ||
|
|
409
|
+
diagnostic.reason === "field-unreachable"
|
|
410
|
+
? "accepted"
|
|
411
|
+
: "rejected",
|
|
412
|
+
reason: diagnostic.reason,
|
|
413
|
+
candidateSize: 1,
|
|
414
|
+
sourceRange: diagnostic.sourceRange,
|
|
415
|
+
}));
|
|
416
|
+
const analysisByModule = new Map(objectAnalysis.modules.map((module) => [module.name, module.analysis]));
|
|
417
|
+
const result = (0, wholeProgramExports_1.applyWholeProgramExportDce)(exportAnalysis, (moduleName) => this.getSourceMetadata(moduleName), (moduleName, expression) => analysisByModule.get(moduleName)?.facts.discardabilityOf(expression)
|
|
418
|
+
.discardable === true);
|
|
419
|
+
result.refusedEffectfulInitializerFields.forEach((field) => this.diagnosticCollector?.record({
|
|
420
|
+
pass: "whole-program-export-dce",
|
|
421
|
+
moduleName: field.moduleName,
|
|
422
|
+
fieldName: field.key,
|
|
423
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
424
|
+
decision: "rejected",
|
|
425
|
+
reason: "effectful-initializer",
|
|
426
|
+
candidateSize: 1,
|
|
427
|
+
}));
|
|
428
|
+
if (!result.changed)
|
|
429
|
+
return;
|
|
430
|
+
this.exportDceChanged = true;
|
|
431
|
+
this.linkOrder.forEach((moduleName) => {
|
|
432
|
+
const ast = this.moduleAST.get(moduleName);
|
|
433
|
+
if (!ast)
|
|
434
|
+
throw new Error(moduleName + " is not found");
|
|
435
|
+
this.moduleResolve.set(moduleName, (0, resolver_1.resolveScopes)(ast));
|
|
436
|
+
});
|
|
437
|
+
this.linkedAstGeneration++;
|
|
438
|
+
this.wholeProgramObjectsValue = undefined;
|
|
439
|
+
this.wholeProgramFieldsValue = undefined;
|
|
440
|
+
this.wholeProgramExportsValue = undefined;
|
|
441
|
+
result.removedFields.forEach((field) => this.diagnosticCollector?.record({
|
|
442
|
+
pass: "whole-program-export-dce",
|
|
443
|
+
moduleName: field.moduleName,
|
|
444
|
+
fieldName: field.key,
|
|
445
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
446
|
+
decision: "accepted",
|
|
447
|
+
reason: "field-removed",
|
|
448
|
+
candidateSize: 1,
|
|
449
|
+
}));
|
|
450
|
+
result.preservedEffectFields.forEach((field) => this.diagnosticCollector?.record({
|
|
451
|
+
pass: "whole-program-export-dce",
|
|
452
|
+
moduleName: field.moduleName,
|
|
453
|
+
fieldName: field.key,
|
|
454
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
455
|
+
decision: "accepted",
|
|
456
|
+
reason: "field-effect-preserved",
|
|
457
|
+
candidateSize: 1,
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
rewriteWholeProgramFieldsAll() {
|
|
461
|
+
if (this.fieldFactVariant === "baseline" || !this.fieldFactsEnabled())
|
|
462
|
+
return;
|
|
463
|
+
const objectAnalysis = this.analyzeWholeProgramObjects();
|
|
464
|
+
const fieldAnalysis = (0, wholeProgramFields_1.analyzeWholeProgramFields)(objectAnalysis, {
|
|
465
|
+
trustAnnotations: this.mode.assumeAnnotations === true,
|
|
466
|
+
metadataOf: (moduleName) => this.getSourceMetadata(moduleName),
|
|
467
|
+
});
|
|
468
|
+
this.wholeProgramObjectsValue = objectAnalysis;
|
|
469
|
+
this.wholeProgramFieldsValue = fieldAnalysis;
|
|
470
|
+
fieldAnalysis.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
471
|
+
pass: "whole-program-constructor-fields",
|
|
472
|
+
moduleName: diagnostic.moduleName,
|
|
473
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
474
|
+
decision: diagnostic.reason === "field-fact" ? "accepted" : "rejected",
|
|
475
|
+
reason: diagnostic.reason,
|
|
476
|
+
candidateSize: 1,
|
|
477
|
+
sourceRange: diagnostic.sourceRange,
|
|
478
|
+
}));
|
|
479
|
+
const result = (0, wholeProgramFields_1.applyWholeProgramFieldRewrites)(objectAnalysis, fieldAnalysis, (moduleName) => this.getSourceMetadata(moduleName), {
|
|
480
|
+
replaceReads: this.mode.fieldValuePropagation,
|
|
481
|
+
removeInitializers: this.mode.unusedFieldInitializerRemoval,
|
|
482
|
+
});
|
|
483
|
+
if (!result.changed)
|
|
484
|
+
return;
|
|
485
|
+
this.linkOrder.forEach((moduleName) => {
|
|
486
|
+
const ast = this.moduleAST.get(moduleName);
|
|
487
|
+
if (!ast)
|
|
488
|
+
throw new Error(moduleName + " is not found");
|
|
489
|
+
const passes = new optimizerPass_1.PassOrchestrator(ast, (0, resolver_1.resolveScopes)(ast));
|
|
490
|
+
passes.runUntilStable("fold-constructor-field-constants", (resolved) => {
|
|
491
|
+
const facts = passes.analysis(optimizerFacts_1.OPTIMIZER_FACTS_CACHE_KEY, optimizerFacts_1.analyzeOptimizerFactsAtGeneration);
|
|
492
|
+
const changed = (0, constantFold_1.foldConstants)(ast, resolved, this.getSourceMetadata(moduleName), facts, {
|
|
493
|
+
evaluateExpressions: this.mode.constantExpressionEvaluation,
|
|
494
|
+
propagateLocals: this.mode.localConstantPropagation,
|
|
495
|
+
});
|
|
496
|
+
return { changed, invalidatesResolve: changed };
|
|
497
|
+
});
|
|
498
|
+
this.moduleResolve.set(moduleName, passes.resolved);
|
|
499
|
+
});
|
|
500
|
+
this.linkedAstGeneration++;
|
|
501
|
+
this.wholeProgramObjectsValue = undefined;
|
|
502
|
+
this.wholeProgramFieldsValue = undefined;
|
|
503
|
+
this.wholeProgramExportsValue = undefined;
|
|
504
|
+
this.diagnosticCollector?.record({
|
|
505
|
+
pass: "whole-program-constructor-field-rewrite",
|
|
506
|
+
moduleName: this.entryModule,
|
|
507
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
508
|
+
decision: "accepted",
|
|
509
|
+
reason: "field-rewrite-applied",
|
|
510
|
+
candidateSize: result.replacedReads + result.removedInitializers,
|
|
511
|
+
});
|
|
512
|
+
const recordRewrite = (reason, count) => {
|
|
513
|
+
if (count === 0)
|
|
514
|
+
return;
|
|
515
|
+
this.diagnosticCollector?.record({
|
|
516
|
+
pass: "whole-program-constructor-field-rewrite",
|
|
517
|
+
moduleName: this.entryModule,
|
|
518
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
519
|
+
decision: "accepted",
|
|
520
|
+
reason,
|
|
521
|
+
candidateSize: count,
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
recordRewrite("field-read-replaced", result.replacedReads);
|
|
525
|
+
recordRewrite("dead-field-write", result.removedInitializers);
|
|
526
|
+
recordRewrite("field-write-effect-preserved", result.preservedEffects);
|
|
527
|
+
}
|
|
528
|
+
/** Function-summary consumers run before scheduling and final rename/print. */
|
|
529
|
+
rewriteFunctionsAll() {
|
|
530
|
+
if (this.functionRewriteVariant === "baseline" ||
|
|
531
|
+
!this.functionRewritesEnabled())
|
|
532
|
+
return;
|
|
533
|
+
let initialWholeProgram = this.analyzeWholeProgramObjects();
|
|
534
|
+
this.recordWholeProgramObjectDiagnostics(initialWholeProgram);
|
|
535
|
+
const initiallyResolvedMethodDeclarations = new Set(initialWholeProgram.resolvedMethods.map((method) => method.target.declaration));
|
|
536
|
+
if (this.mode.functionSpecialization &&
|
|
537
|
+
this.aggregateSpecializationVariant !== "baseline") {
|
|
538
|
+
const initialFieldFacts = (0, wholeProgramFields_1.analyzeWholeProgramFields)(initialWholeProgram, {
|
|
539
|
+
trustAnnotations: this.mode.assumeAnnotations === true,
|
|
540
|
+
metadataOf: (moduleName) => this.getSourceMetadata(moduleName),
|
|
541
|
+
});
|
|
542
|
+
const specialization = (0, aggregateSpecialization_1.applyAggregateSpecialization)(initialWholeProgram, initialFieldFacts, this.linkOrder.map((name) => {
|
|
543
|
+
const chunk = this.moduleAST.get(name);
|
|
544
|
+
const resolved = this.moduleResolve.get(name);
|
|
545
|
+
if (!chunk || !resolved)
|
|
546
|
+
throw new Error(name + " is not found");
|
|
547
|
+
const resources = (0, runtimeEnvironment_1.analyzeLocalResourceUsage)(chunk);
|
|
548
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
549
|
+
return {
|
|
550
|
+
name,
|
|
551
|
+
chunk,
|
|
552
|
+
resolved,
|
|
553
|
+
metadata: this.getSourceMetadata(name),
|
|
554
|
+
maxIntroducedLocalsAt: (statement) => {
|
|
555
|
+
const active = resources.activeLocalsBefore(statement);
|
|
556
|
+
if (active === undefined)
|
|
557
|
+
return 0;
|
|
558
|
+
return Math.max(0, Math.min(runtime.resources.maxActiveLocalsPerFunction - active, runtime.resources.maxRegistersPerFunction - active));
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}));
|
|
562
|
+
specialization.diagnostics.forEach((diagnostic) => {
|
|
563
|
+
const module = initialWholeProgram.modules.find((candidate) => candidate.analysis.callGraph.functions.includes(diagnostic.callable));
|
|
564
|
+
this.diagnosticCollector?.record({
|
|
565
|
+
pass: "aggregate-function-specialization",
|
|
566
|
+
moduleName: module?.name,
|
|
567
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
568
|
+
decision: diagnostic.reason === "variant-created" ? "accepted" : "rejected",
|
|
569
|
+
reason: diagnostic.reason,
|
|
570
|
+
candidateSize: diagnostic.count,
|
|
571
|
+
sourceRange: sourceRangeOf(diagnostic.callable.declaration),
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
if (specialization.changed) {
|
|
575
|
+
this.linkOrder.forEach((moduleName) => {
|
|
576
|
+
const ast = this.moduleAST.get(moduleName);
|
|
577
|
+
if (!ast)
|
|
578
|
+
throw new Error(moduleName + " is not found");
|
|
579
|
+
this.moduleResolve.set(moduleName, (0, resolver_1.resolveScopes)(ast));
|
|
580
|
+
});
|
|
581
|
+
this.linkedAstGeneration++;
|
|
582
|
+
this.wholeProgramObjectsValue = undefined;
|
|
583
|
+
this.wholeProgramFieldsValue = undefined;
|
|
584
|
+
this.wholeProgramExportsValue = undefined;
|
|
585
|
+
initialWholeProgram = this.analyzeWholeProgramObjects();
|
|
586
|
+
const specializedFields = (0, wholeProgramFields_1.analyzeWholeProgramFields)(initialWholeProgram, {
|
|
587
|
+
trustAnnotations: this.mode.assumeAnnotations === true,
|
|
588
|
+
metadataOf: (moduleName) => this.getSourceMetadata(moduleName),
|
|
589
|
+
});
|
|
590
|
+
const downstream = (0, wholeProgramFields_1.applyWholeProgramFieldRewrites)(initialWholeProgram, specializedFields, (moduleName) => this.getSourceMetadata(moduleName), {
|
|
591
|
+
replaceReads: this.mode.fieldValuePropagation,
|
|
592
|
+
removeInitializers: this.mode.unusedFieldInitializerRemoval,
|
|
593
|
+
});
|
|
594
|
+
if (downstream.removedInitializers > 0)
|
|
595
|
+
this.diagnosticCollector?.record({
|
|
596
|
+
pass: "aggregate-function-specialization",
|
|
597
|
+
moduleName: this.entryModule,
|
|
598
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
599
|
+
decision: "accepted",
|
|
600
|
+
reason: "dead-field-write",
|
|
601
|
+
candidateSize: downstream.removedInitializers,
|
|
602
|
+
});
|
|
603
|
+
if (downstream.changed) {
|
|
604
|
+
this.linkOrder.forEach((moduleName) => {
|
|
605
|
+
const ast = this.moduleAST.get(moduleName);
|
|
606
|
+
if (!ast)
|
|
607
|
+
throw new Error(moduleName + " is not found");
|
|
608
|
+
this.moduleResolve.set(moduleName, (0, resolver_1.resolveScopes)(ast));
|
|
609
|
+
});
|
|
610
|
+
this.linkedAstGeneration++;
|
|
611
|
+
this.wholeProgramObjectsValue = undefined;
|
|
612
|
+
this.wholeProgramFieldsValue = undefined;
|
|
613
|
+
this.wholeProgramExportsValue = undefined;
|
|
614
|
+
initialWholeProgram = this.analyzeWholeProgramObjects();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const resolvedMethodDeclarations = new Set([
|
|
619
|
+
...initiallyResolvedMethodDeclarations,
|
|
620
|
+
...initialWholeProgram.resolvedMethods.map((method) => method.target.declaration),
|
|
621
|
+
]);
|
|
622
|
+
if (this.mode.parameterPruning) {
|
|
623
|
+
initialWholeProgram = this.pruneWholeProgramParameters(initialWholeProgram, resolvedMethodDeclarations);
|
|
624
|
+
}
|
|
625
|
+
this.linkOrder.forEach((moduleName) => {
|
|
626
|
+
const ast = this.moduleAST.get(moduleName);
|
|
627
|
+
const resolved = this.moduleResolve.get(moduleName);
|
|
628
|
+
if (!ast || !resolved)
|
|
629
|
+
throw new Error(moduleName + " is not found");
|
|
630
|
+
const passes = new optimizerPass_1.PassOrchestrator(ast, resolved);
|
|
631
|
+
const runtimeProfile = this.mode.runtimeProfile;
|
|
632
|
+
const moduleRange = sourceRangeOf(ast) ??
|
|
633
|
+
[0, this.moduleSourceText.get(moduleName)?.length ?? 0];
|
|
634
|
+
const recordAccepted = (pass, count) => {
|
|
635
|
+
if (count === 0)
|
|
636
|
+
return;
|
|
637
|
+
this.diagnosticCollector?.record({
|
|
638
|
+
pass,
|
|
639
|
+
moduleName,
|
|
640
|
+
runtimeProfile,
|
|
641
|
+
decision: "accepted",
|
|
642
|
+
reason: "function-rewrite-applied",
|
|
643
|
+
candidateSize: count,
|
|
644
|
+
sourceRange: moduleRange,
|
|
645
|
+
});
|
|
646
|
+
};
|
|
647
|
+
const initialAnalysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
648
|
+
const recursive = new Set(initialAnalysis.callGraph.sccs
|
|
649
|
+
.filter((scc) => scc.recursive)
|
|
650
|
+
.flatMap((scc) => scc.functions));
|
|
651
|
+
initialAnalysis.interprocedural.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
652
|
+
pass: "interprocedural-summary",
|
|
653
|
+
moduleName,
|
|
654
|
+
runtimeProfile,
|
|
655
|
+
decision: diagnostic.reason === "unknown-call-target"
|
|
656
|
+
? "rejected"
|
|
657
|
+
: "accepted",
|
|
658
|
+
reason: diagnostic.reason,
|
|
659
|
+
candidateSize: 1,
|
|
660
|
+
sourceRange: diagnostic.sourceRange ?? moduleRange,
|
|
661
|
+
}));
|
|
662
|
+
initialAnalysis.callGraph.functions.forEach((callable) => {
|
|
663
|
+
if (!callable.symbol || !callable.declaration.isLocal)
|
|
664
|
+
return;
|
|
665
|
+
const calls = initialAnalysis.callGraph.calls.filter((call) => call.targets.has(callable)).length;
|
|
666
|
+
const reason = recursive.has(callable)
|
|
667
|
+
? "recursive-function"
|
|
668
|
+
: callable.declaration.parameters.some((parameter) => parameter.type === "VarargLiteral")
|
|
669
|
+
? "vararg-function"
|
|
670
|
+
: callable.symbol.references.length > calls
|
|
671
|
+
? "function-escape"
|
|
672
|
+
: undefined;
|
|
673
|
+
if (!reason)
|
|
674
|
+
return;
|
|
675
|
+
this.diagnosticCollector?.record({
|
|
676
|
+
pass: "function-rewrite",
|
|
677
|
+
moduleName,
|
|
678
|
+
runtimeProfile,
|
|
679
|
+
decision: "rejected",
|
|
680
|
+
reason,
|
|
681
|
+
candidateSize: 1,
|
|
682
|
+
sourceRange: sourceRangeOf(callable.declaration),
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
if (this.mode.functionInlining)
|
|
686
|
+
passes.run("inline-closed-single-use-functions", (currentResolve) => {
|
|
687
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
688
|
+
const result = (0, functionRewrites_1.inlineClosedSingleUseFunctions)(analysis.interprocedural, currentResolve, this.getSourceMetadata(moduleName));
|
|
689
|
+
recordAccepted("inline-closed-single-use-functions", result.inlinedFunctions);
|
|
690
|
+
return {
|
|
691
|
+
changed: result.changed,
|
|
692
|
+
invalidatesResolve: result.changed,
|
|
693
|
+
};
|
|
694
|
+
});
|
|
695
|
+
if (this.mode.functionInlining)
|
|
696
|
+
passes.run("inline-literal-argument-functions", (currentResolve) => {
|
|
697
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
698
|
+
const result = (0, functionRewrites_1.inlineLiteralArgumentFunctions)(analysis.interprocedural, currentResolve, this.getSourceMetadata(moduleName));
|
|
699
|
+
recordAccepted("inline-literal-argument-functions", result.inlinedFunctions);
|
|
700
|
+
return {
|
|
701
|
+
changed: result.changed,
|
|
702
|
+
invalidatesResolve: result.changed,
|
|
703
|
+
};
|
|
704
|
+
});
|
|
705
|
+
if (this.mode.functionInlining)
|
|
706
|
+
passes.run("inline-tail-call-functions", (currentResolve) => {
|
|
707
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
708
|
+
const localResources = (0, runtimeEnvironment_1.analyzeLocalResourceUsage)(ast);
|
|
709
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
710
|
+
const result = (0, functionRewrites_1.inlineTailCallFunctions)(ast, analysis.interprocedural, currentResolve, this.getSourceMetadata(moduleName), {
|
|
711
|
+
maxIntroducedLocalsAt: (statement) => {
|
|
712
|
+
const active = localResources.activeLocalsBefore(statement);
|
|
713
|
+
if (active === undefined)
|
|
714
|
+
return 0;
|
|
715
|
+
return Math.max(0, Math.min(runtime.resources.maxActiveLocalsPerFunction - active, runtime.resources.maxRegistersPerFunction - active));
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
recordAccepted("inline-tail-call-functions", result.inlinedFunctions);
|
|
719
|
+
return {
|
|
720
|
+
changed: result.changed,
|
|
721
|
+
invalidatesResolve: result.changed,
|
|
722
|
+
};
|
|
723
|
+
});
|
|
724
|
+
if (this.mode.functionInlining)
|
|
725
|
+
passes.run("inline-closed-statement-functions", (currentResolve) => {
|
|
726
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
727
|
+
const result = (0, functionRewrites_1.inlineClosedStatementFunctions)(ast, analysis.interprocedural, currentResolve, this.getSourceMetadata(moduleName));
|
|
728
|
+
recordAccepted("inline-closed-statement-functions", result.inlinedFunctions);
|
|
729
|
+
return {
|
|
730
|
+
changed: result.changed,
|
|
731
|
+
invalidatesResolve: result.changed,
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
if (this.mode.functionInlining)
|
|
735
|
+
passes.run("inline-bound-statement-functions", (currentResolve) => {
|
|
736
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
737
|
+
const localResources = (0, runtimeEnvironment_1.analyzeLocalResourceUsage)(ast);
|
|
738
|
+
const result = (0, functionRewrites_1.inlineBoundStatementFunctions)(ast, analysis.interprocedural, currentResolve, this.getSourceMetadata(moduleName), {
|
|
739
|
+
maxIntroducedLocalsAt: (statement) => {
|
|
740
|
+
const active = localResources.activeLocalsBefore(statement);
|
|
741
|
+
if (active === undefined)
|
|
742
|
+
return 0;
|
|
743
|
+
return Math.max(0, Math.min((0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile).resources
|
|
744
|
+
.maxActiveLocalsPerFunction - active, (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile).resources
|
|
745
|
+
.maxRegistersPerFunction - active));
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
recordAccepted("inline-bound-statement-functions", result.inlinedFunctions);
|
|
749
|
+
return {
|
|
750
|
+
changed: result.changed,
|
|
751
|
+
invalidatesResolve: result.changed,
|
|
752
|
+
};
|
|
753
|
+
});
|
|
754
|
+
this.moduleResolve.set(moduleName, passes.resolved);
|
|
755
|
+
if (passes.astGeneration > 0) {
|
|
756
|
+
this.linkedAstGeneration += passes.astGeneration;
|
|
757
|
+
this.wholeProgramObjectsValue = undefined;
|
|
758
|
+
this.wholeProgramFieldsValue = undefined;
|
|
759
|
+
this.wholeProgramExportsValue = undefined;
|
|
760
|
+
}
|
|
761
|
+
this.progress?.tick();
|
|
762
|
+
});
|
|
763
|
+
this.wholeProgramObjectsValue = this.analyzeWholeProgramObjects();
|
|
764
|
+
this.recordWholeProgramObjectDiagnostics(this.wholeProgramObjectsValue);
|
|
765
|
+
}
|
|
766
|
+
pruneWholeProgramParameters(analysis, resolvedMethodDeclarations) {
|
|
767
|
+
const functionsByModule = new Map(analysis.modules.map((module) => [
|
|
768
|
+
module.name,
|
|
769
|
+
new Set(module.analysis.callGraph.functions),
|
|
770
|
+
]));
|
|
771
|
+
const changedModules = new Set();
|
|
772
|
+
this.linkOrder.forEach((moduleName) => {
|
|
773
|
+
const functions = functionsByModule.get(moduleName);
|
|
774
|
+
if (!functions)
|
|
775
|
+
return;
|
|
776
|
+
const result = (0, functionRewrites_1.pruneTrailingUnusedParameters)(analysis.callGraph, this.getSourceMetadata(moduleName), (callable) => functions.has(callable) &&
|
|
777
|
+
(callable.declaration.identifier?.type !== "MemberExpression" ||
|
|
778
|
+
callable.declaration.identifier.indexer !== ":" ||
|
|
779
|
+
resolvedMethodDeclarations.has(callable.declaration)));
|
|
780
|
+
if (!result.changed)
|
|
781
|
+
return;
|
|
782
|
+
changedModules.add(moduleName);
|
|
783
|
+
this.diagnosticCollector?.record({
|
|
784
|
+
pass: "prune-trailing-unused-parameters",
|
|
785
|
+
moduleName,
|
|
786
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
787
|
+
decision: "accepted",
|
|
788
|
+
reason: "function-rewrite-applied",
|
|
789
|
+
candidateSize: result.prunedParameters,
|
|
790
|
+
sourceRange: sourceRangeOf(this.moduleAST.get(moduleName) ?? {}),
|
|
791
|
+
});
|
|
792
|
+
if (result.prunedMethodParameters > 0)
|
|
793
|
+
this.diagnosticCollector?.record({
|
|
794
|
+
pass: "whole-program-method-parameter-pruning",
|
|
795
|
+
moduleName,
|
|
796
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
797
|
+
decision: "accepted",
|
|
798
|
+
reason: "function-rewrite-applied",
|
|
799
|
+
candidateSize: result.prunedMethodParameters,
|
|
800
|
+
sourceRange: sourceRangeOf(this.moduleAST.get(moduleName) ?? {}),
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
if (changedModules.size === 0)
|
|
804
|
+
return analysis;
|
|
805
|
+
changedModules.forEach((moduleName) => {
|
|
806
|
+
const ast = this.moduleAST.get(moduleName);
|
|
807
|
+
if (!ast)
|
|
808
|
+
throw new Error(moduleName + " is not found");
|
|
809
|
+
this.moduleResolve.set(moduleName, (0, resolver_1.resolveScopes)(ast));
|
|
810
|
+
});
|
|
811
|
+
this.linkedAstGeneration++;
|
|
812
|
+
this.wholeProgramObjectsValue = undefined;
|
|
813
|
+
this.wholeProgramFieldsValue = undefined;
|
|
814
|
+
this.wholeProgramExportsValue = undefined;
|
|
815
|
+
return this.analyzeWholeProgramObjects();
|
|
816
|
+
}
|
|
817
|
+
analyzeWholeProgramObjects() {
|
|
818
|
+
const modules = this.linkOrder.map((name) => {
|
|
819
|
+
const chunk = this.moduleAST.get(name);
|
|
820
|
+
const resolved = this.moduleResolve.get(name);
|
|
821
|
+
if (!chunk || !resolved)
|
|
822
|
+
throw new Error(name + " is not found");
|
|
823
|
+
const module = {
|
|
824
|
+
name,
|
|
825
|
+
chunk,
|
|
826
|
+
resolved,
|
|
827
|
+
analysis: (0, optimizerAnalysis_1.analyzeOptimizer)(chunk, resolved, {
|
|
828
|
+
generation: this.linkedAstGeneration,
|
|
829
|
+
}),
|
|
830
|
+
};
|
|
831
|
+
this.progress?.tick();
|
|
832
|
+
return module;
|
|
833
|
+
});
|
|
834
|
+
return (0, wholeProgramObjects_1.analyzeWholeProgramObjects)(modules, this.linkedAstGeneration);
|
|
835
|
+
}
|
|
836
|
+
recordWholeProgramObjectDiagnostics(analysis) {
|
|
837
|
+
analysis.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
838
|
+
pass: "whole-program-method-resolution",
|
|
839
|
+
moduleName: diagnostic.moduleName,
|
|
840
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
841
|
+
decision: diagnostic.reason === "resolved-method-target"
|
|
842
|
+
? "accepted"
|
|
843
|
+
: "rejected",
|
|
844
|
+
reason: diagnostic.reason,
|
|
845
|
+
candidateSize: 1,
|
|
846
|
+
sourceRange: diagnostic.sourceRange,
|
|
847
|
+
}));
|
|
848
|
+
}
|
|
849
|
+
requiresSchedulerSelection() {
|
|
850
|
+
if (this.mode.localDeclarationMerging)
|
|
851
|
+
return true;
|
|
852
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
853
|
+
const lifetimeAllowed = !runtime.semantics.debugLocalIntrospection ||
|
|
854
|
+
this.mode.allowIntrospectionChanges === true;
|
|
855
|
+
return (lifetimeAllowed &&
|
|
856
|
+
(this.mode.localDeclarationHoisting || this.mode.tableReadMerging));
|
|
857
|
+
}
|
|
858
|
+
functionRewritesEnabled() {
|
|
859
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
860
|
+
return ((this.mode.parameterPruning ||
|
|
861
|
+
this.mode.functionInlining ||
|
|
862
|
+
this.mode.functionSpecialization) &&
|
|
863
|
+
(!runtime.semantics.debugLocalIntrospection ||
|
|
864
|
+
this.mode.allowIntrospectionChanges === true));
|
|
865
|
+
}
|
|
866
|
+
functionSpecializationEnabled() {
|
|
867
|
+
if (!this.mode.functionSpecialization)
|
|
868
|
+
return false;
|
|
869
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
870
|
+
return (!runtime.semantics.debugLocalIntrospection ||
|
|
871
|
+
this.mode.allowIntrospectionChanges === true);
|
|
872
|
+
}
|
|
873
|
+
fieldFactsEnabled() {
|
|
874
|
+
return (this.mode.fieldValuePropagation || this.mode.unusedFieldInitializerRemoval);
|
|
875
|
+
}
|
|
876
|
+
fieldRenamesEnabled() {
|
|
877
|
+
return this.mode.fieldRenaming;
|
|
878
|
+
}
|
|
879
|
+
exportDceEnabled() {
|
|
880
|
+
return this.mode.unusedExportRemoval;
|
|
881
|
+
}
|
|
882
|
+
copyDiagnosticsFrom(minifier) {
|
|
883
|
+
minifier.optimizationDiagnostics.forEach((diagnostic) => this.diagnosticCollector?.record(diagnostic));
|
|
884
|
+
}
|
|
885
|
+
adoptVariant(minifier, output) {
|
|
886
|
+
this.identifiersInUse.clear();
|
|
887
|
+
minifier.identifiersInUse.forEach((name) => this.identifiersInUse.add(name));
|
|
888
|
+
copyMap(minifier.moduleSourceText, this.moduleSourceText);
|
|
889
|
+
copyMap(minifier.moduleAST, this.moduleAST);
|
|
890
|
+
copyMap(minifier.moduleNameAndFileName, this.moduleNameAndFileName);
|
|
891
|
+
this.linkOrder.splice(0, this.linkOrder.length, ...minifier.linkOrder);
|
|
892
|
+
copyMap(minifier.moduleResolve, this.moduleResolve);
|
|
893
|
+
copyMap(minifier.renameCache, this.renameCache);
|
|
894
|
+
this.globalRenames = new Map(minifier.globalRenames);
|
|
895
|
+
copyMap(minifier.moduleMetadata, this.moduleMetadata);
|
|
896
|
+
this.linkedAstGeneration = minifier.linkedAstGeneration;
|
|
897
|
+
this.wholeProgramObjectsValue = minifier.wholeProgramObjectsValue;
|
|
898
|
+
this.wholeProgramFieldsValue = minifier.wholeProgramFieldsValue;
|
|
899
|
+
this.wholeProgramExportsValue = minifier.wholeProgramExportsValue;
|
|
900
|
+
this.wholeProgramFieldRenamesValue = minifier.wholeProgramFieldRenamesValue;
|
|
901
|
+
this.exportDceChanged = minifier.exportDceChanged;
|
|
902
|
+
return output;
|
|
903
|
+
}
|
|
904
|
+
recordFinalSchedulerDecision(decision, reason, byteSavings) {
|
|
905
|
+
this.diagnosticCollector?.record({
|
|
906
|
+
pass: "statement-scheduler-final-cost",
|
|
907
|
+
decision,
|
|
908
|
+
reason,
|
|
909
|
+
candidateSize: 1,
|
|
910
|
+
estimatedByteSavings: byteSavings,
|
|
911
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
912
|
+
moduleName: this.entryModule,
|
|
913
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
recordFinalFunctionRewriteDecision(decision, reason, byteSavings) {
|
|
917
|
+
this.diagnosticCollector?.record({
|
|
918
|
+
pass: "function-rewrite-final-cost",
|
|
919
|
+
decision,
|
|
920
|
+
reason,
|
|
921
|
+
candidateSize: 1,
|
|
922
|
+
estimatedByteSavings: byteSavings,
|
|
923
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
924
|
+
moduleName: this.entryModule,
|
|
925
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
recordFinalAggregateSpecializationDecision(decision, reason, byteSavings) {
|
|
929
|
+
this.diagnosticCollector?.record({
|
|
930
|
+
pass: "aggregate-specialization-final-cost",
|
|
931
|
+
decision,
|
|
932
|
+
reason,
|
|
933
|
+
candidateSize: 1,
|
|
934
|
+
estimatedByteSavings: byteSavings,
|
|
935
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
936
|
+
moduleName: this.entryModule,
|
|
937
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
recordFinalFieldFactDecision(decision, reason, byteSavings) {
|
|
941
|
+
this.diagnosticCollector?.record({
|
|
942
|
+
pass: "constructor-field-final-cost",
|
|
943
|
+
decision,
|
|
944
|
+
reason,
|
|
945
|
+
candidateSize: 1,
|
|
946
|
+
estimatedByteSavings: byteSavings,
|
|
947
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
948
|
+
moduleName: this.entryModule,
|
|
949
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
recordFinalExportDceDecision(decision, reason, byteSavings) {
|
|
953
|
+
this.diagnosticCollector?.record({
|
|
954
|
+
pass: "module-export-dce-final-cost",
|
|
955
|
+
decision,
|
|
956
|
+
reason,
|
|
957
|
+
candidateSize: 1,
|
|
958
|
+
estimatedByteSavings: byteSavings,
|
|
959
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
960
|
+
moduleName: this.entryModule,
|
|
961
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
recordFinalFieldRenameDecision(decision, reason, byteSavings) {
|
|
965
|
+
this.diagnosticCollector?.record({
|
|
966
|
+
pass: "whole-program-field-rename-final-cost",
|
|
967
|
+
decision,
|
|
968
|
+
reason,
|
|
969
|
+
candidateSize: 1,
|
|
970
|
+
estimatedByteSavings: byteSavings,
|
|
971
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
972
|
+
moduleName: this.entryModule,
|
|
973
|
+
sourceRange: [0, fs_1.default.readFileSync(this.entryFilePath, "utf8").length],
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
recordWholeProgramFieldRenameDiagnostics(plan) {
|
|
977
|
+
plan.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
978
|
+
pass: "whole-program-field-rename",
|
|
979
|
+
moduleName: diagnostic.moduleName,
|
|
980
|
+
fieldName: diagnostic.field,
|
|
981
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
982
|
+
decision: diagnostic.accepted ? "accepted" : "rejected",
|
|
983
|
+
reason: diagnostic.reason,
|
|
984
|
+
candidateSize: 1,
|
|
985
|
+
sourceRange: diagnostic.sourceRange,
|
|
986
|
+
}));
|
|
987
|
+
}
|
|
70
988
|
/**
|
|
71
989
|
* dofileの呼び出し箇所ごとに、キャッシュ済みASTから新規にSourceNodeを作り直す。
|
|
72
990
|
* 同じSourceNodeインスタンスを複数箇所へ挿入すると壊れるため、常に作り直す(#18)。
|
|
@@ -85,14 +1003,14 @@ class Minifier {
|
|
|
85
1003
|
if (!ast || !fileName) {
|
|
86
1004
|
throw new Error(moduleName + " is not found");
|
|
87
1005
|
}
|
|
88
|
-
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parseAsStatementsAndFinalExpression(
|
|
1006
|
+
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parseAsStatementsAndFinalExpression();
|
|
89
1007
|
}
|
|
90
1008
|
/**
|
|
91
1009
|
* 指定モジュールのRenameパス結果を返す。`renameAll`で事前に計算済みの
|
|
92
1010
|
* ものをそのまま返すだけの参照用アクセサ。
|
|
93
1011
|
*/
|
|
94
1012
|
getRenameResult(moduleName) {
|
|
95
|
-
if (this.mode.
|
|
1013
|
+
if (!this.mode.localRenaming && !this.mode.globalRenaming) {
|
|
96
1014
|
return NO_RENAME;
|
|
97
1015
|
}
|
|
98
1016
|
const cached = this.renameCache.get(moduleName);
|
|
@@ -101,6 +1019,18 @@ class Minifier {
|
|
|
101
1019
|
}
|
|
102
1020
|
return cached;
|
|
103
1021
|
}
|
|
1022
|
+
getFieldRename(node) {
|
|
1023
|
+
const name = this.wholeProgramFieldRenamesValue?.nameOf(node);
|
|
1024
|
+
const originalName = this.wholeProgramFieldRenamesValue?.originalNameOf(node);
|
|
1025
|
+
return name && originalName ? { name, originalName } : undefined;
|
|
1026
|
+
}
|
|
1027
|
+
getSourceMetadata(moduleName) {
|
|
1028
|
+
const metadata = this.moduleMetadata.get(moduleName);
|
|
1029
|
+
if (!metadata) {
|
|
1030
|
+
throw new Error(moduleName + " is not found");
|
|
1031
|
+
}
|
|
1032
|
+
return metadata;
|
|
1033
|
+
}
|
|
104
1034
|
/**
|
|
105
1035
|
* Renameパス(#20): linkOrder(依存されている側が先)の順にモジュールごとの
|
|
106
1036
|
* 短縮名を割り当てる。
|
|
@@ -114,17 +1044,208 @@ class Minifier {
|
|
|
114
1044
|
* 犠牲になるが、モジュール内でのスコープに基づく再利用は維持される)。
|
|
115
1045
|
*/
|
|
116
1046
|
renameAll() {
|
|
117
|
-
if (this.mode.
|
|
1047
|
+
if (!this.mode.localRenaming && !this.mode.globalRenaming) {
|
|
118
1048
|
return;
|
|
119
1049
|
}
|
|
120
1050
|
this.linkOrder.forEach((moduleName) => {
|
|
1051
|
+
const ast = this.moduleAST.get(moduleName);
|
|
121
1052
|
const resolved = this.moduleResolve.get(moduleName);
|
|
122
|
-
if (!resolved) {
|
|
1053
|
+
if (!ast || !resolved) {
|
|
123
1054
|
throw new Error(moduleName + " is not found");
|
|
124
1055
|
}
|
|
125
|
-
const
|
|
1056
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
1057
|
+
const result = this.renameCache.get(moduleName) ??
|
|
1058
|
+
(0, renamer_1.assignRenames)(ast, resolved, this.identifiersInUse, this.globalRenames, new Set(resolved.symbols.filter((symbol) => this.getSourceMetadata(moduleName).annotationsOfIdentifier(symbol.declaration).keepName)), {
|
|
1059
|
+
allowLocalNameReuse: !runtime.semantics.debugLocalIntrospection ||
|
|
1060
|
+
this.mode.allowIntrospectionChanges === true,
|
|
1061
|
+
renameLocals: this.mode.localRenaming,
|
|
1062
|
+
});
|
|
126
1063
|
this.renameCache.set(moduleName, result);
|
|
127
1064
|
result.usedNames.forEach((name) => this.identifiersInUse.add(name));
|
|
1065
|
+
this.progress?.tick();
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Transformパス(#8b, #9): モジュールごとにASTレベルの最適化を適用する。
|
|
1070
|
+
* computeGlobalRenamesの後・renameAllの前に実行する必要がある(renameは
|
|
1071
|
+
* このパスが確定させた最終的な文構造を前提に短縮名を割り当てるため)。
|
|
1072
|
+
*
|
|
1073
|
+
* 実行順序は 8b(エイリアス挿入)→ #9(local宣言のまとめ上げ)。
|
|
1074
|
+
* 8bが挿入する複数の1変数1式local文は、#9のまとめ上げ対象としてそのまま
|
|
1075
|
+
* 束ねられるため、8bを先に行うことで両者の効果が重なる。
|
|
1076
|
+
*
|
|
1077
|
+
* 8bは新しい識別子ノード(エイリアスの宣言と、書き換えられた参照)を生成する。
|
|
1078
|
+
* これらのノードは元のResolveパス結果には存在しないため、#9のハザード1判定
|
|
1079
|
+
* (「候補文がグループ内で宣言済みの変数を参照していないか」)が正しく働くには、
|
|
1080
|
+
* 8bの直後・#9の直前でResolveパスを再実行しておく必要がある。この順序を
|
|
1081
|
+
* 誤ると、8bがrequire等の頻出グローバルをエイリアス化した際に、そのエイリアス
|
|
1082
|
+
* 宣言自体と「エイリアス経由で呼び出す側」の文を#9が誤って1つのlocal文に
|
|
1083
|
+
* まとめてしまい(エイリアス変数がまだ束縛される前のスコープで参照される形に
|
|
1084
|
+
* なり)、意味が壊れる(要修正が発覚した実例)。
|
|
1085
|
+
*/
|
|
1086
|
+
transformAll() {
|
|
1087
|
+
// globalRenames.keys()は8aが実際にリネームした(=代入もされていた)名前のみ。
|
|
1088
|
+
// neverRenameGlobalsは代入されていない名前にも及ぶ保護指定なので、8bのエイリアス化
|
|
1089
|
+
// が誤ってそれらを書き換えてしまわないよう、必ず両方をあわせてexcludeNamesに渡す。
|
|
1090
|
+
const excludeGlobalNames = new Set([
|
|
1091
|
+
...this.globalRenames.keys(),
|
|
1092
|
+
...(this.mode.neverRenameGlobals ?? []),
|
|
1093
|
+
...this.annotationProtectedGlobals(),
|
|
1094
|
+
]);
|
|
1095
|
+
// renameAllと同じmodule順・予約名更新で仮Renameを行い、plannerのbyte costを
|
|
1096
|
+
// 実際の出力名長に対する保守的な見積りにする。
|
|
1097
|
+
const plannedIdentifiersInUse = new Set(this.identifiersInUse);
|
|
1098
|
+
this.linkOrder.forEach((moduleName) => {
|
|
1099
|
+
const ast = this.moduleAST.get(moduleName);
|
|
1100
|
+
let resolved = this.moduleResolve.get(moduleName);
|
|
1101
|
+
if (!ast || !resolved) {
|
|
1102
|
+
throw new Error(moduleName + " is not found");
|
|
1103
|
+
}
|
|
1104
|
+
const passes = new optimizerPass_1.PassOrchestrator(ast, resolved);
|
|
1105
|
+
if (this.mode.globalAliasing) {
|
|
1106
|
+
passes.run("global-alias", (currentResolve) => {
|
|
1107
|
+
const changed = (0, transform_1.insertGlobalAliases)(ast, currentResolve, {
|
|
1108
|
+
excludeNames: excludeGlobalNames,
|
|
1109
|
+
});
|
|
1110
|
+
return { changed, invalidatesResolve: changed };
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
resolved = passes.resolved;
|
|
1114
|
+
const runtime = (0, runtimeEnvironment_1.runtimeEnvironmentOf)(this.mode.runtimeProfile);
|
|
1115
|
+
const optimizerAnalysis = () => passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, (chunk, currentResolve, generation) => (0, optimizerAnalysis_1.analyzeOptimizer)(chunk, currentResolve, {
|
|
1116
|
+
generation,
|
|
1117
|
+
runtime,
|
|
1118
|
+
assumptions: this.mode.allowObservableTableReadChanges === true
|
|
1119
|
+
? new Map([
|
|
1120
|
+
[
|
|
1121
|
+
"allow-observable-table-read-changes",
|
|
1122
|
+
"explicit allowObservableTableReadChanges opt-in",
|
|
1123
|
+
],
|
|
1124
|
+
])
|
|
1125
|
+
: undefined,
|
|
1126
|
+
}));
|
|
1127
|
+
const localResources = (0, runtimeEnvironment_1.analyzeLocalResourceUsage)(ast);
|
|
1128
|
+
const lifetimeChangesAllowed = !runtime.semantics.debugLocalIntrospection ||
|
|
1129
|
+
this.mode.allowIntrospectionChanges === true;
|
|
1130
|
+
const localNameReuseEnabled = this.mode.localNameReuse && lifetimeChangesAllowed;
|
|
1131
|
+
const keepNames = new Set(resolved.symbols.filter((symbol) => this.getSourceMetadata(moduleName).annotationsOfIdentifier(symbol.declaration).keepName));
|
|
1132
|
+
if (this.schedulerVariant !== "baseline" &&
|
|
1133
|
+
(this.mode.localDeclarationMerging ||
|
|
1134
|
+
(lifetimeChangesAllowed &&
|
|
1135
|
+
(this.mode.localDeclarationHoisting || this.mode.tableReadMerging)))) {
|
|
1136
|
+
const provisionalAnalysis = !this.mode.localRenaming && !this.mode.globalRenaming
|
|
1137
|
+
? undefined
|
|
1138
|
+
: optimizerAnalysis();
|
|
1139
|
+
const provisionalRenames = !this.mode.localRenaming && !this.mode.globalRenaming
|
|
1140
|
+
? NO_RENAME
|
|
1141
|
+
: (0, renamer_1.assignRenames)(ast, resolved, plannedIdentifiersInUse, this.globalRenames, keepNames, {
|
|
1142
|
+
allowLocalNameReuse: localNameReuseEnabled,
|
|
1143
|
+
renameLocals: this.mode.localRenaming,
|
|
1144
|
+
analysis: provisionalAnalysis
|
|
1145
|
+
? {
|
|
1146
|
+
facts: provisionalAnalysis.facts,
|
|
1147
|
+
liveness: provisionalAnalysis.statementDataflow.symbolLiveness,
|
|
1148
|
+
}
|
|
1149
|
+
: undefined,
|
|
1150
|
+
});
|
|
1151
|
+
passes.run("statement-scheduler", (currentResolve) => {
|
|
1152
|
+
const analysis = optimizerAnalysis();
|
|
1153
|
+
analysis.interprocedural.diagnostics.forEach((diagnostic) => this.diagnosticCollector?.record({
|
|
1154
|
+
pass: "interprocedural-summary",
|
|
1155
|
+
moduleName,
|
|
1156
|
+
runtimeProfile: runtime.profile,
|
|
1157
|
+
decision: diagnostic.reason === "unknown-call-target"
|
|
1158
|
+
? "rejected"
|
|
1159
|
+
: "accepted",
|
|
1160
|
+
reason: diagnostic.reason,
|
|
1161
|
+
candidateSize: 1,
|
|
1162
|
+
sourceRange: diagnostic.sourceRange,
|
|
1163
|
+
}));
|
|
1164
|
+
const metadata = this.getSourceMetadata(moduleName);
|
|
1165
|
+
const canMoveAnnotatedStatement = (statement) => {
|
|
1166
|
+
const annotations = metadata.annotationsOf(statement);
|
|
1167
|
+
return (metadata.beforeOf(statement).length === 0 &&
|
|
1168
|
+
metadata.trailingOf(statement).length === 0 &&
|
|
1169
|
+
!annotations.keep &&
|
|
1170
|
+
!annotations.keepName &&
|
|
1171
|
+
!annotations.exported);
|
|
1172
|
+
};
|
|
1173
|
+
const plan = (0, statementScheduler_1.planStatementSchedule)(ast, currentResolve, {
|
|
1174
|
+
facts: analysis.facts,
|
|
1175
|
+
dataflow: analysis.statementDataflow,
|
|
1176
|
+
outputNameLengthOf: (symbol) => (provisionalRenames.nameOf(symbol.declaration) ?? symbol.name)
|
|
1177
|
+
.length,
|
|
1178
|
+
preserveRequireSplice: !this.mode.requireWrapper,
|
|
1179
|
+
enableLocalPacking: lifetimeChangesAllowed && this.mode.localDeclarationHoisting,
|
|
1180
|
+
enableLexicalLocalMerge: this.mode.localDeclarationMerging,
|
|
1181
|
+
tableEffects: lifetimeChangesAllowed && this.mode.tableReadMerging
|
|
1182
|
+
? analysis.tableEffects
|
|
1183
|
+
: undefined,
|
|
1184
|
+
dirtyGranularity: !this.mode.fieldSensitiveTableEffects
|
|
1185
|
+
? "table"
|
|
1186
|
+
: "static-key",
|
|
1187
|
+
allowObservableTableValueChanges: this.mode.allowObservableTableReadChanges === true,
|
|
1188
|
+
maxTableMergeArity: runtime.resources.conservativeParallelValueLimit,
|
|
1189
|
+
maxTableMergeArityAt: (statement) => (0, runtimeEnvironment_1.checkParallelEvaluation)(runtime, {
|
|
1190
|
+
activeLocalsBefore: localResources.activeLocalsBefore(statement) ??
|
|
1191
|
+
runtime.resources.maxActiveLocalsPerFunction,
|
|
1192
|
+
parallelValueCount: runtime.resources.conservativeParallelValueLimit,
|
|
1193
|
+
}).limit,
|
|
1194
|
+
canMoveTableRead: canMoveAnnotatedStatement,
|
|
1195
|
+
maxHoistedLocalsAt: (statement) => {
|
|
1196
|
+
const active = localResources.activeLocalsBefore(statement);
|
|
1197
|
+
if (active === undefined)
|
|
1198
|
+
return 0;
|
|
1199
|
+
return Math.max(0, Math.min(runtime.resources.maxActiveLocalsPerFunction - active, runtime.resources.maxRegistersPerFunction - active));
|
|
1200
|
+
},
|
|
1201
|
+
canChangeLocalLifetime: canMoveAnnotatedStatement,
|
|
1202
|
+
diagnostics: this.diagnosticCollector,
|
|
1203
|
+
moduleName,
|
|
1204
|
+
runtimeProfile: runtime.profile,
|
|
1205
|
+
});
|
|
1206
|
+
return (0, statementScheduler_1.applyStatementSchedule)(plan, this.getSourceMetadata(moduleName));
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
resolved = passes.resolved;
|
|
1210
|
+
this.moduleResolve.set(moduleName, resolved);
|
|
1211
|
+
if (this.mode.localRenaming || this.mode.globalRenaming) {
|
|
1212
|
+
const finalKeepNames = new Set(resolved.symbols.filter((symbol) => this.getSourceMetadata(moduleName).annotationsOfIdentifier(symbol.declaration).keepName));
|
|
1213
|
+
// plannedIdentifiersInUse advances in the same link order as renameAll.
|
|
1214
|
+
// Cache this final-generation result so Print does not rebuild the same
|
|
1215
|
+
// facts, CFG, liveness, graph, and binding proof a second time.
|
|
1216
|
+
const finalRename = (0, renamer_1.assignRenames)(ast, resolved, plannedIdentifiersInUse, this.globalRenames, finalKeepNames, {
|
|
1217
|
+
allowLocalNameReuse: localNameReuseEnabled,
|
|
1218
|
+
renameLocals: this.mode.localRenaming,
|
|
1219
|
+
});
|
|
1220
|
+
this.renameCache.set(moduleName, finalRename);
|
|
1221
|
+
finalRename.usedNames.forEach((name) => plannedIdentifiersInUse.add(name));
|
|
1222
|
+
}
|
|
1223
|
+
this.progress?.tick();
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* #8aのGlobal Renameパス: リンクされた全モジュールを横断して、代入されている
|
|
1228
|
+
* グローバル(neverRenameGlobalsに含まれるものを除く)に短縮名を割り当てる。
|
|
1229
|
+
* グローバルは1つのランタイム束縛をモジュール間で共有するため、この判定・採番は
|
|
1230
|
+
* renameAll(モジュールごとに独立して行うローカルのリネーム)より前に、
|
|
1231
|
+
* 一度だけ行う必要がある。
|
|
1232
|
+
*
|
|
1233
|
+
* 選ばれた短縮名はrenameAllの前にidentifiersInUseへ予約し、元の長い名前の予約は
|
|
1234
|
+
* 解除する(もう出力に現れないため)。順序を誤ると、
|
|
1235
|
+
* ローカルの短縮名がグローバルの新しい短縮名と衝突しうる。
|
|
1236
|
+
*/
|
|
1237
|
+
computeGlobalRenames() {
|
|
1238
|
+
if (!this.mode.globalRenaming) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const neverRename = new Set([
|
|
1242
|
+
...(this.mode.neverRenameGlobals ?? []),
|
|
1243
|
+
...this.annotationProtectedGlobals(),
|
|
1244
|
+
]);
|
|
1245
|
+
this.globalRenames = (0, globalRename_1.classifyAndRenameGlobals)(this.moduleResolve, neverRename, this.identifiersInUse);
|
|
1246
|
+
this.globalRenames.forEach((shortName, originalName) => {
|
|
1247
|
+
this.identifiersInUse.add(shortName);
|
|
1248
|
+
this.identifiersInUse.delete(originalName);
|
|
128
1249
|
});
|
|
129
1250
|
}
|
|
130
1251
|
printModule(moduleName) {
|
|
@@ -133,7 +1254,92 @@ class Minifier {
|
|
|
133
1254
|
if (!ast || !fileName) {
|
|
134
1255
|
throw new Error(moduleName + " is not found");
|
|
135
1256
|
}
|
|
136
|
-
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parse(
|
|
1257
|
+
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parse();
|
|
1258
|
+
}
|
|
1259
|
+
annotationProtectedGlobals() {
|
|
1260
|
+
const protectedNames = new Set();
|
|
1261
|
+
this.moduleResolve.forEach((resolved, moduleName) => {
|
|
1262
|
+
const metadata = this.getSourceMetadata(moduleName);
|
|
1263
|
+
resolved.globals.forEach((binding) => {
|
|
1264
|
+
if (binding.writes.some((write) => metadata.annotationsOfIdentifier(write).keepName)) {
|
|
1265
|
+
protectedNames.add(binding.name);
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
return protectedNames;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* 定数畳み込みパス(#44): opt-inオプション。既定では無効。
|
|
1273
|
+
* link()の直後、removeUnusedAll()の前に実行する。伝搬で参照が消えたローカル
|
|
1274
|
+
* 宣言はこのパス自身では消さず、直後に実行される(既定で有効な)未使用ローカル
|
|
1275
|
+
* 削除に任せる。
|
|
1276
|
+
*/
|
|
1277
|
+
foldConstantsAll() {
|
|
1278
|
+
if (!this.mode.constantExpressionEvaluation &&
|
|
1279
|
+
!this.mode.localConstantPropagation &&
|
|
1280
|
+
!this.mode.interproceduralConstantPropagation)
|
|
1281
|
+
return;
|
|
1282
|
+
this.linkOrder.forEach((moduleName) => {
|
|
1283
|
+
const ast = this.moduleAST.get(moduleName);
|
|
1284
|
+
const resolved = this.moduleResolve.get(moduleName);
|
|
1285
|
+
if (!ast || !resolved)
|
|
1286
|
+
throw new Error(moduleName + " is not found");
|
|
1287
|
+
const passes = new optimizerPass_1.PassOrchestrator(ast, resolved);
|
|
1288
|
+
if (this.mode.interproceduralConstantPropagation)
|
|
1289
|
+
passes.run("interprocedural-constants", () => {
|
|
1290
|
+
const analysis = passes.analysis(optimizerAnalysis_1.OPTIMIZER_ANALYSIS_CACHE_KEY, optimizerAnalysis_1.analyzeOptimizerAtGeneration);
|
|
1291
|
+
const changed = (0, interproceduralConstants_1.propagateInterproceduralConstants)(ast, analysis.interprocedural);
|
|
1292
|
+
return { changed, invalidatesResolve: changed };
|
|
1293
|
+
});
|
|
1294
|
+
if (this.mode.constantExpressionEvaluation ||
|
|
1295
|
+
this.mode.localConstantPropagation)
|
|
1296
|
+
passes.runUntilStable("fold-constants", (currentResolve) => {
|
|
1297
|
+
const facts = passes.analysis(optimizerFacts_1.OPTIMIZER_FACTS_CACHE_KEY, optimizerFacts_1.analyzeOptimizerFactsAtGeneration);
|
|
1298
|
+
const changed = (0, constantFold_1.foldConstants)(ast, currentResolve, this.getSourceMetadata(moduleName), facts, {
|
|
1299
|
+
evaluateExpressions: this.mode.constantExpressionEvaluation,
|
|
1300
|
+
propagateLocals: this.mode.localConstantPropagation,
|
|
1301
|
+
});
|
|
1302
|
+
return { changed, invalidatesResolve: changed };
|
|
1303
|
+
});
|
|
1304
|
+
this.moduleResolve.set(moduleName, passes.resolved);
|
|
1305
|
+
this.progress?.tick();
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
removeUnusedAll() {
|
|
1309
|
+
if (!this.mode.unusedLocalRemoval && !this.mode.unusedFunctionRemoval)
|
|
1310
|
+
return;
|
|
1311
|
+
this.linkOrder.forEach((moduleName) => {
|
|
1312
|
+
const ast = this.moduleAST.get(moduleName);
|
|
1313
|
+
const metadata = this.getSourceMetadata(moduleName);
|
|
1314
|
+
const resolved = this.moduleResolve.get(moduleName);
|
|
1315
|
+
if (!ast || !resolved)
|
|
1316
|
+
throw new Error(moduleName + " is not found");
|
|
1317
|
+
const passes = new optimizerPass_1.PassOrchestrator(ast, resolved);
|
|
1318
|
+
passes.runUntilStable("remove-unused", (currentResolve) => {
|
|
1319
|
+
const facts = passes.analysis(optimizerFacts_1.OPTIMIZER_FACTS_CACHE_KEY, optimizerFacts_1.analyzeOptimizerFactsAtGeneration);
|
|
1320
|
+
const changed = (0, removeUnused_1.removeUnusedLocals)(ast, currentResolve, metadata, facts, (statement) => this.diagnosticCollector?.record({
|
|
1321
|
+
pass: "function-dce",
|
|
1322
|
+
moduleName,
|
|
1323
|
+
runtimeProfile: this.mode.runtimeProfile,
|
|
1324
|
+
decision: "accepted",
|
|
1325
|
+
reason: "unused-function",
|
|
1326
|
+
candidateSize: 1,
|
|
1327
|
+
sourceRange: sourceRangeOf(statement),
|
|
1328
|
+
}), {
|
|
1329
|
+
removeLocals: this.mode.unusedLocalRemoval,
|
|
1330
|
+
removeFunctions: this.mode.unusedFunctionRemoval,
|
|
1331
|
+
});
|
|
1332
|
+
return { changed, invalidatesResolve: changed };
|
|
1333
|
+
});
|
|
1334
|
+
this.moduleResolve.set(moduleName, passes.resolved);
|
|
1335
|
+
this.progress?.tick();
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
rebuildIdentifiersInUse() {
|
|
1339
|
+
this.identifiersInUse.clear();
|
|
1340
|
+
this.moduleResolve.forEach((resolved) => {
|
|
1341
|
+
resolved.globals.forEach((binding) => this.identifiersInUse.add(binding.name));
|
|
1342
|
+
});
|
|
137
1343
|
}
|
|
138
1344
|
/**
|
|
139
1345
|
* エントリファイルから到達可能な全モジュールをASTレベルで解決するLinkパス(#18)。
|
|
@@ -162,12 +1368,12 @@ class Minifier {
|
|
|
162
1368
|
}
|
|
163
1369
|
const code = fs_1.default.readFileSync(fullResolvePath).toString();
|
|
164
1370
|
const ast = luaparse_1.default.parse(code, this.luaParseSettings);
|
|
1371
|
+
this.moduleMetadata.set(moduleName, new sourceMetadata_1.SourceMetadata(ast, code));
|
|
165
1372
|
// Resolveパス(#19): このモジュールのスコープ/シンボルを解析し、Renameパスの
|
|
166
1373
|
// 入力として使い回せるようキャッシュする。グローバル参照はプログラム全体で
|
|
167
1374
|
// 予約すべき名前(identifiersInUse)としてここで集計する。
|
|
168
1375
|
const resolved = (0, resolver_1.resolveScopes)(ast);
|
|
169
1376
|
this.moduleResolve.set(moduleName, resolved);
|
|
170
|
-
resolved.globals.forEach((binding) => this.identifiersInUse.add(binding.name));
|
|
171
1377
|
this.moduleSourceText.set(moduleName, code);
|
|
172
1378
|
this.moduleAST.set(moduleName, ast);
|
|
173
1379
|
// Source Mapの`sources`はURLとして解釈されるため、OS依存のpath.sepではなく
|
|
@@ -179,6 +1385,7 @@ class Minifier {
|
|
|
179
1385
|
visiting.delete(moduleName);
|
|
180
1386
|
stack.pop();
|
|
181
1387
|
this.linkOrder.push(moduleName);
|
|
1388
|
+
this.progress?.tick();
|
|
182
1389
|
};
|
|
183
1390
|
visit(this.entryModule);
|
|
184
1391
|
}
|
|
@@ -201,19 +1408,20 @@ class Minifier {
|
|
|
201
1408
|
});
|
|
202
1409
|
return targets;
|
|
203
1410
|
}
|
|
204
|
-
|
|
1411
|
+
printModuleWithRequireWrapper() {
|
|
205
1412
|
const targets = this.collectRequireTargets();
|
|
206
|
-
const
|
|
207
|
-
|
|
1413
|
+
const moduleNames = this.linkOrder.filter((moduleName) => moduleName !== this.entryModule && targets.has(moduleName));
|
|
1414
|
+
const wrapperAst = (0, generatedAst_1.buildRequireWrapperAst)(moduleNames);
|
|
1415
|
+
const entryAst = this.moduleAST.get(this.entryModule);
|
|
1416
|
+
const entryFileName = this.moduleNameAndFileName.get(this.entryModule);
|
|
1417
|
+
if (!entryAst || !entryFileName) {
|
|
1418
|
+
throw new Error(this.entryModule + " is not found");
|
|
1419
|
+
}
|
|
1420
|
+
const statements = [
|
|
1421
|
+
wrapperAst,
|
|
1422
|
+
{ type: "ModuleSplice", moduleName: this.entryModule },
|
|
208
1423
|
];
|
|
209
|
-
this.
|
|
210
|
-
if (moduleName === this.entryModule || !targets.has(moduleName)) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
parts.push('if m=="', moduleName, '"then r=(function() ', this.printModule(moduleName), " end)()end\n");
|
|
214
|
-
});
|
|
215
|
-
parts.push("package.loaded[m]=package.loaded[m]or r or true;return package.loaded[m]end\n");
|
|
216
|
-
return new source_map_1.SourceNode(null, null, null, parts);
|
|
1424
|
+
return new ast2lua_1.MinifyFile(entryFileName, this.entryModule, entryAst, this, this.mode).printGeneratedStatements(statements);
|
|
217
1425
|
}
|
|
218
1426
|
}
|
|
219
1427
|
exports.Minifier = Minifier;
|