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