storm-lua-minify 0.2.0 → 0.9.0

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