storm-lua-minify 0.3.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.
Files changed (43) hide show
  1. package/README.md +119 -41
  2. package/dist/aggregateSpecialization.js +406 -0
  3. package/dist/ast2lua.js +156 -68
  4. package/dist/astWalk.js +162 -0
  5. package/dist/callGraph.js +372 -0
  6. package/dist/cli.js +53 -58
  7. package/dist/cliOptions.js +36 -0
  8. package/dist/cliProgress.js +87 -0
  9. package/dist/config.js +73 -0
  10. package/dist/constantFold.js +798 -0
  11. package/dist/controlFlow.js +266 -0
  12. package/dist/functionRewrites.js +580 -0
  13. package/dist/generatedAst.js +108 -0
  14. package/dist/generatedNode.js +23 -0
  15. package/dist/interproceduralAnalysis.js +842 -0
  16. package/dist/interproceduralConstants.js +120 -0
  17. package/dist/luaString.js +157 -0
  18. package/dist/minifier.js +1178 -44
  19. package/dist/optimizerAnalysis.js +43 -0
  20. package/dist/optimizerDiagnostics.js +65 -0
  21. package/dist/optimizerFacts.js +529 -0
  22. package/dist/optimizerPass.js +96 -0
  23. package/dist/optimizerTransaction.js +56 -0
  24. package/dist/optimizerValueDomain.js +180 -0
  25. package/dist/options.js +233 -0
  26. package/dist/progress.js +2 -0
  27. package/dist/removeUnused.js +145 -0
  28. package/dist/renamer.js +223 -54
  29. package/dist/resolver.js +28 -11
  30. package/dist/runtimeEnvironment.js +105 -0
  31. package/dist/sourceMetadata.js +314 -0
  32. package/dist/statementDataflow.js +259 -0
  33. package/dist/statementScheduler.js +595 -0
  34. package/dist/symbolLiveness.js +92 -0
  35. package/dist/tableEffects.js +356 -0
  36. package/dist/transform.js +10 -371
  37. package/dist/valueFlow.js +409 -0
  38. package/dist/wholeProgramExports.js +646 -0
  39. package/dist/wholeProgramFieldRenames.js +583 -0
  40. package/dist/wholeProgramFields.js +672 -0
  41. package/dist/wholeProgramObjects.js +783 -0
  42. package/package.json +11 -2
  43. package/dist/index.js +0 -27
package/dist/minifier.js CHANGED
@@ -7,18 +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");
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
+ 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
+ }
21
45
  class Minifier {
46
+ entryFilePath;
22
47
  identifiersInUse;
23
48
  moduleSourceText;
24
49
  moduleAST;
@@ -35,44 +60,931 @@ class Minifier {
35
60
  renameCache = new Map();
36
61
  // #8a: プログラム全体を横断して決定された「内部グローバル名 -> 短縮名」の対応
37
62
  globalRenames = new Map();
38
- constructor(entryFilePath, luaParseSettings, mode) {
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;
39
93
  this.identifiersInUse = new Set();
40
94
  this.moduleSourceText = new Map();
41
95
  this.moduleAST = new Map();
42
96
  this.moduleNameAndFileName = new Map();
43
- this.luaParseSettings = luaParseSettings;
44
- this.mode = mode;
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
+ }
45
109
  const pn = path_1.default.parse(entryFilePath);
46
110
  this.dir = pn.dir;
47
111
  this.entryModule = pn.name;
48
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
+ }
49
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");
50
333
  this.link();
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();
51
355
  this.computeGlobalRenames();
356
+ this.progress?.tick();
357
+ this.progress?.startStep("Transform statements");
52
358
  this.transformAll();
53
- this.renameAll();
54
- const parts = [];
55
- const entryComments = this.moduleAST.get(this.entryModule)?.comments;
56
- if (entryComments) {
57
- entryComments
58
- .filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
59
- .forEach((comment) => {
60
- parts.push(new source_map_1.SourceNode(comment.loc?.start.line ?? null, comment.loc?.start.column ?? null, this.moduleNameAndFileName.get(this.entryModule) ?? null, comment.raw), "\n");
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),
61
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
+ }
62
377
  }
63
- if (this.mode.moduleLikeLua) {
64
- parts.push(this.buildRequireWrapper());
65
- }
66
- parts.push(this.printModule(this.entryModule));
67
- const result = new source_map_1.SourceNode(null, null, null, parts);
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);
68
386
  this.moduleSourceText.forEach((v, k) => {
69
387
  const fileName = this.moduleNameAndFileName.get(k);
70
388
  if (fileName) {
71
389
  result.setSourceContent(fileName, v);
72
390
  }
391
+ this.progress?.tick();
73
392
  });
74
393
  return result;
75
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
+ }
76
988
  /**
77
989
  * dofileの呼び出し箇所ごとに、キャッシュ済みASTから新規にSourceNodeを作り直す。
78
990
  * 同じSourceNodeインスタンスを複数箇所へ挿入すると壊れるため、常に作り直す(#18)。
@@ -91,14 +1003,14 @@ class Minifier {
91
1003
  if (!ast || !fileName) {
92
1004
  throw new Error(moduleName + " is not found");
93
1005
  }
94
- return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parseAsStatementsAndFinalExpression(moduleName === this.entryModule);
1006
+ return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parseAsStatementsAndFinalExpression();
95
1007
  }
96
1008
  /**
97
1009
  * 指定モジュールのRenameパス結果を返す。`renameAll`で事前に計算済みの
98
1010
  * ものをそのまま返すだけの参照用アクセサ。
99
1011
  */
100
1012
  getRenameResult(moduleName) {
101
- if (this.mode.rename === false) {
1013
+ if (!this.mode.localRenaming && !this.mode.globalRenaming) {
102
1014
  return NO_RENAME;
103
1015
  }
104
1016
  const cached = this.renameCache.get(moduleName);
@@ -107,6 +1019,18 @@ class Minifier {
107
1019
  }
108
1020
  return cached;
109
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
+ }
110
1034
  /**
111
1035
  * Renameパス(#20): linkOrder(依存されている側が先)の順にモジュールごとの
112
1036
  * 短縮名を割り当てる。
@@ -120,17 +1044,25 @@ class Minifier {
120
1044
  * 犠牲になるが、モジュール内でのスコープに基づく再利用は維持される)。
121
1045
  */
122
1046
  renameAll() {
123
- if (this.mode.rename === false) {
1047
+ if (!this.mode.localRenaming && !this.mode.globalRenaming) {
124
1048
  return;
125
1049
  }
126
1050
  this.linkOrder.forEach((moduleName) => {
1051
+ const ast = this.moduleAST.get(moduleName);
127
1052
  const resolved = this.moduleResolve.get(moduleName);
128
- if (!resolved) {
1053
+ if (!ast || !resolved) {
129
1054
  throw new Error(moduleName + " is not found");
130
1055
  }
131
- const result = (0, renamer_1.assignRenames)(resolved, this.identifiersInUse, this.globalRenames);
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
+ });
132
1063
  this.renameCache.set(moduleName, result);
133
1064
  result.usedNames.forEach((name) => this.identifiersInUse.add(name));
1065
+ this.progress?.tick();
134
1066
  });
135
1067
  }
136
1068
  /**
@@ -158,25 +1090,137 @@ class Minifier {
158
1090
  const excludeGlobalNames = new Set([
159
1091
  ...this.globalRenames.keys(),
160
1092
  ...(this.mode.neverRenameGlobals ?? []),
1093
+ ...this.annotationProtectedGlobals(),
161
1094
  ]);
1095
+ // renameAllと同じmodule順・予約名更新で仮Renameを行い、plannerのbyte costを
1096
+ // 実際の出力名長に対する保守的な見積りにする。
1097
+ const plannedIdentifiersInUse = new Set(this.identifiersInUse);
162
1098
  this.linkOrder.forEach((moduleName) => {
163
1099
  const ast = this.moduleAST.get(moduleName);
164
1100
  let resolved = this.moduleResolve.get(moduleName);
165
1101
  if (!ast || !resolved) {
166
1102
  throw new Error(moduleName + " is not found");
167
1103
  }
168
- if (this.mode.rename !== false && this.mode.globalAlias !== false) {
169
- (0, transform_1.insertGlobalAliases)(ast, resolved, {
170
- excludeNames: excludeGlobalNames,
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 };
171
1111
  });
172
- resolved = (0, resolver_1.resolveScopes)(ast);
173
1112
  }
174
- if (this.mode.mergeLocals !== false) {
175
- (0, transform_1.mergeLocalDeclarations)(ast, resolved, {
176
- preserveRequireSplice: !this.mode.moduleLikeLua,
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));
177
1207
  });
178
1208
  }
1209
+ resolved = passes.resolved;
179
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();
180
1224
  });
181
1225
  }
182
1226
  /**
@@ -191,10 +1235,13 @@ class Minifier {
191
1235
  * ローカルの短縮名がグローバルの新しい短縮名と衝突しうる。
192
1236
  */
193
1237
  computeGlobalRenames() {
194
- if (this.mode.rename === false || this.mode.globalRename === false) {
1238
+ if (!this.mode.globalRenaming) {
195
1239
  return;
196
1240
  }
197
- const neverRename = this.mode.neverRenameGlobals ?? new Set();
1241
+ const neverRename = new Set([
1242
+ ...(this.mode.neverRenameGlobals ?? []),
1243
+ ...this.annotationProtectedGlobals(),
1244
+ ]);
198
1245
  this.globalRenames = (0, globalRename_1.classifyAndRenameGlobals)(this.moduleResolve, neverRename, this.identifiersInUse);
199
1246
  this.globalRenames.forEach((shortName, originalName) => {
200
1247
  this.identifiersInUse.add(shortName);
@@ -207,7 +1254,92 @@ class Minifier {
207
1254
  if (!ast || !fileName) {
208
1255
  throw new Error(moduleName + " is not found");
209
1256
  }
210
- return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parse(moduleName === this.entryModule);
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
+ });
211
1343
  }
212
1344
  /**
213
1345
  * エントリファイルから到達可能な全モジュールをASTレベルで解決するLinkパス(#18)。
@@ -236,12 +1368,12 @@ class Minifier {
236
1368
  }
237
1369
  const code = fs_1.default.readFileSync(fullResolvePath).toString();
238
1370
  const ast = luaparse_1.default.parse(code, this.luaParseSettings);
1371
+ this.moduleMetadata.set(moduleName, new sourceMetadata_1.SourceMetadata(ast, code));
239
1372
  // Resolveパス(#19): このモジュールのスコープ/シンボルを解析し、Renameパスの
240
1373
  // 入力として使い回せるようキャッシュする。グローバル参照はプログラム全体で
241
1374
  // 予約すべき名前(identifiersInUse)としてここで集計する。
242
1375
  const resolved = (0, resolver_1.resolveScopes)(ast);
243
1376
  this.moduleResolve.set(moduleName, resolved);
244
- resolved.globals.forEach((binding) => this.identifiersInUse.add(binding.name));
245
1377
  this.moduleSourceText.set(moduleName, code);
246
1378
  this.moduleAST.set(moduleName, ast);
247
1379
  // Source Mapの`sources`はURLとして解釈されるため、OS依存のpath.sepではなく
@@ -253,6 +1385,7 @@ class Minifier {
253
1385
  visiting.delete(moduleName);
254
1386
  stack.pop();
255
1387
  this.linkOrder.push(moduleName);
1388
+ this.progress?.tick();
256
1389
  };
257
1390
  visit(this.entryModule);
258
1391
  }
@@ -275,19 +1408,20 @@ class Minifier {
275
1408
  });
276
1409
  return targets;
277
1410
  }
278
- buildRequireWrapper() {
1411
+ printModuleWithRequireWrapper() {
279
1412
  const targets = this.collectRequireTargets();
280
- const parts = [
281
- "function require(m,r)package=package or{loaded={}};if package.loaded[m]then return package.loaded[m]end\n",
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 },
282
1423
  ];
283
- this.linkOrder.forEach((moduleName) => {
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);
1424
+ return new ast2lua_1.MinifyFile(entryFileName, this.entryModule, entryAst, this, this.mode).printGeneratedStatements(statements);
291
1425
  }
292
1426
  }
293
1427
  exports.Minifier = Minifier;