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.
- package/README.md +119 -41
- package/dist/aggregateSpecialization.js +406 -0
- package/dist/ast2lua.js +156 -68
- package/dist/astWalk.js +162 -0
- package/dist/callGraph.js +372 -0
- package/dist/cli.js +53 -58
- package/dist/cliOptions.js +36 -0
- package/dist/cliProgress.js +87 -0
- package/dist/config.js +73 -0
- package/dist/constantFold.js +798 -0
- package/dist/controlFlow.js +266 -0
- package/dist/functionRewrites.js +580 -0
- package/dist/generatedAst.js +108 -0
- package/dist/generatedNode.js +23 -0
- package/dist/interproceduralAnalysis.js +842 -0
- package/dist/interproceduralConstants.js +120 -0
- package/dist/luaString.js +157 -0
- package/dist/minifier.js +1178 -44
- package/dist/optimizerAnalysis.js +43 -0
- package/dist/optimizerDiagnostics.js +65 -0
- package/dist/optimizerFacts.js +529 -0
- package/dist/optimizerPass.js +96 -0
- package/dist/optimizerTransaction.js +56 -0
- package/dist/optimizerValueDomain.js +180 -0
- package/dist/options.js +233 -0
- package/dist/progress.js +2 -0
- package/dist/removeUnused.js +145 -0
- package/dist/renamer.js +223 -54
- package/dist/resolver.js +28 -11
- package/dist/runtimeEnvironment.js +105 -0
- package/dist/sourceMetadata.js +314 -0
- package/dist/statementDataflow.js +259 -0
- package/dist/statementScheduler.js +595 -0
- package/dist/symbolLiveness.js +92 -0
- package/dist/tableEffects.js +356 -0
- package/dist/transform.js +10 -371
- package/dist/valueFlow.js +409 -0
- package/dist/wholeProgramExports.js +646 -0
- package/dist/wholeProgramFieldRenames.js +583 -0
- package/dist/wholeProgramFields.js +672 -0
- package/dist/wholeProgramObjects.js +783 -0
- package/package.json +11 -2
- package/dist/index.js +0 -27
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeWholeProgramExports = analyzeWholeProgramExports;
|
|
4
|
+
exports.applyWholeProgramExportDce = applyWholeProgramExportDce;
|
|
5
|
+
const astWalk_1 = require("./astWalk");
|
|
6
|
+
const linker_1 = require("./linker");
|
|
7
|
+
function sourceRangeOf(node) {
|
|
8
|
+
return node.range;
|
|
9
|
+
}
|
|
10
|
+
function fieldKey(field) {
|
|
11
|
+
if (field.type === "TableKeyString")
|
|
12
|
+
return field.key.name;
|
|
13
|
+
return (0, linker_1.staticStringArgument)(field.key);
|
|
14
|
+
}
|
|
15
|
+
function isCallExpression(expression) {
|
|
16
|
+
return (expression.type === "CallExpression" ||
|
|
17
|
+
expression.type === "TableCallExpression" ||
|
|
18
|
+
expression.type === "StringCallExpression");
|
|
19
|
+
}
|
|
20
|
+
function topLevelReturn(module) {
|
|
21
|
+
const statement = module.chunk.body.at(-1);
|
|
22
|
+
return statement?.type === "ReturnStatement" &&
|
|
23
|
+
statement.arguments.length === 1
|
|
24
|
+
? statement
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
function sameObject(analysis, expression, object) {
|
|
28
|
+
return analysis.objectOf(expression)?.id === object.id;
|
|
29
|
+
}
|
|
30
|
+
function markExpressionOwner(expression, owner, owners) {
|
|
31
|
+
(0, astWalk_1.walkExpression)(expression, {
|
|
32
|
+
onExpression: (nested) => owners.set(nested, owner),
|
|
33
|
+
onBlock: (body) => {
|
|
34
|
+
(0, astWalk_1.walkBlockDeep)(body, {
|
|
35
|
+
onStatement: (statement) => owners.set(statement, owner),
|
|
36
|
+
onExpression: (nested) => owners.set(nested, owner),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compute export reachability from resolved module-return allocation identity.
|
|
43
|
+
* Static uses become graph edges; any operation that can observe the table shape
|
|
44
|
+
* roots the affected allocation instead of attempting a string-based guess.
|
|
45
|
+
*/
|
|
46
|
+
function analyzeWholeProgramExports(objects, entryModule, metadataOf) {
|
|
47
|
+
const exportObjects = objects.objects.filter((object) => object.kind === "module-return");
|
|
48
|
+
const exportObjectIds = new Set(exportObjects.map((object) => object.id));
|
|
49
|
+
const objectById = new Map(exportObjects.map((object) => [object.id, object]));
|
|
50
|
+
const fieldsByObject = new Map();
|
|
51
|
+
const definitionMembers = new WeakSet();
|
|
52
|
+
const ownerOfNode = new WeakMap();
|
|
53
|
+
const callableByDeclaration = new WeakMap();
|
|
54
|
+
const callSiteByExpression = new WeakMap();
|
|
55
|
+
const bodyOfStatement = new WeakMap();
|
|
56
|
+
const statementOfExpression = new WeakMap();
|
|
57
|
+
const boundaryDiagnostics = [];
|
|
58
|
+
const objectBoundaryReasons = new Map();
|
|
59
|
+
const addObjectBoundary = (object, reason) => {
|
|
60
|
+
const reasons = objectBoundaryReasons.get(object) ?? new Set();
|
|
61
|
+
reasons.add(reason);
|
|
62
|
+
objectBoundaryReasons.set(object, reasons);
|
|
63
|
+
};
|
|
64
|
+
objects.callGraph.calls.forEach((call) => callSiteByExpression.set(call.call, call));
|
|
65
|
+
objects.modules.forEach((module) => {
|
|
66
|
+
indexStatementOwners(module.chunk.body, bodyOfStatement, statementOfExpression);
|
|
67
|
+
module.analysis.callGraph.functions.forEach((callable) => {
|
|
68
|
+
callableByDeclaration.set(callable.declaration, callable);
|
|
69
|
+
callable.declaration.body.forEach((statement) => {
|
|
70
|
+
(0, astWalk_1.walkBlockDeep)([statement], {
|
|
71
|
+
onStatement: (nested) => ownerOfNode.set(nested, callable),
|
|
72
|
+
onExpression: (expression) => ownerOfNode.set(expression, callable),
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
const mutableField = (object, key) => {
|
|
78
|
+
let fields = fieldsByObject.get(object.id);
|
|
79
|
+
if (!fields) {
|
|
80
|
+
fields = new Map();
|
|
81
|
+
fieldsByObject.set(object.id, fields);
|
|
82
|
+
}
|
|
83
|
+
let field = fields.get(key);
|
|
84
|
+
if (!field) {
|
|
85
|
+
field = {
|
|
86
|
+
id: `${object.id}:field:${key}`,
|
|
87
|
+
moduleName: object.moduleName,
|
|
88
|
+
object,
|
|
89
|
+
key,
|
|
90
|
+
definitions: [],
|
|
91
|
+
dependencies: new Set(),
|
|
92
|
+
callableDependencies: new Set(),
|
|
93
|
+
shapeDependencies: new Set(),
|
|
94
|
+
rootReasons: new Set(),
|
|
95
|
+
live: false,
|
|
96
|
+
};
|
|
97
|
+
fields.set(key, field);
|
|
98
|
+
}
|
|
99
|
+
return field;
|
|
100
|
+
};
|
|
101
|
+
const exportObjectOf = (expression) => {
|
|
102
|
+
const object = objects.objectOf(expression);
|
|
103
|
+
return object && exportObjectIds.has(object.id)
|
|
104
|
+
? objectById.get(object.id)
|
|
105
|
+
: undefined;
|
|
106
|
+
};
|
|
107
|
+
// Collect every statically named export definition before classifying uses.
|
|
108
|
+
objects.modules.forEach((module) => {
|
|
109
|
+
const metadata = metadataOf(module.name);
|
|
110
|
+
(0, astWalk_1.walkBlockDeep)(module.chunk.body, {
|
|
111
|
+
onStatement: (statement) => {
|
|
112
|
+
if (statement.type === "FunctionDeclaration" &&
|
|
113
|
+
statement.identifier?.type === "MemberExpression") {
|
|
114
|
+
const object = exportObjectOf(statement.identifier.base);
|
|
115
|
+
if (!object)
|
|
116
|
+
return;
|
|
117
|
+
const field = mutableField(object, statement.identifier.identifier.name);
|
|
118
|
+
field.definitions.push({
|
|
119
|
+
kind: "member-function",
|
|
120
|
+
module,
|
|
121
|
+
body: bodyOfStatement.get(statement) ?? module.chunk.body,
|
|
122
|
+
statement,
|
|
123
|
+
value: statement,
|
|
124
|
+
});
|
|
125
|
+
definitionMembers.add(statement.identifier);
|
|
126
|
+
statement.body.forEach((bodyStatement) => {
|
|
127
|
+
(0, astWalk_1.walkBlockDeep)([bodyStatement], {
|
|
128
|
+
onStatement: (nested) => ownerOfNode.set(nested, field),
|
|
129
|
+
onExpression: (expression) => ownerOfNode.set(expression, field),
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
if (metadata.annotationsOf(statement).keep ||
|
|
133
|
+
metadata.annotationsOf(statement).exported)
|
|
134
|
+
field.rootReasons.add("annotation-root");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (statement.type === "AssignmentStatement") {
|
|
138
|
+
statement.variables.forEach((variable, index) => {
|
|
139
|
+
if (variable.type !== "MemberExpression" &&
|
|
140
|
+
variable.type !== "IndexExpression")
|
|
141
|
+
return;
|
|
142
|
+
const object = exportObjectOf(variable.base);
|
|
143
|
+
const value = statement.init.at(index);
|
|
144
|
+
if (!object || !value)
|
|
145
|
+
return;
|
|
146
|
+
const key = variable.type === "MemberExpression"
|
|
147
|
+
? variable.identifier.name
|
|
148
|
+
: (0, linker_1.staticStringArgument)(variable.index);
|
|
149
|
+
if (key === undefined) {
|
|
150
|
+
addObjectBoundary(object, "dynamic-key");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const field = mutableField(object, key);
|
|
154
|
+
field.definitions.push({
|
|
155
|
+
kind: "member-assignment",
|
|
156
|
+
module,
|
|
157
|
+
body: bodyOfStatement.get(statement) ?? module.chunk.body,
|
|
158
|
+
statement,
|
|
159
|
+
value,
|
|
160
|
+
});
|
|
161
|
+
definitionMembers.add(variable);
|
|
162
|
+
markExpressionOwner(value, field, ownerOfNode);
|
|
163
|
+
if (isDynamicRequire(value))
|
|
164
|
+
boundaryDiagnostics.push({
|
|
165
|
+
moduleName: module.name,
|
|
166
|
+
field: key,
|
|
167
|
+
reason: "unresolved-re-export",
|
|
168
|
+
sourceRange: sourceRangeOf(statement),
|
|
169
|
+
});
|
|
170
|
+
if (metadata.annotationsOf(statement).keep ||
|
|
171
|
+
metadata.annotationsOf(statement).exported)
|
|
172
|
+
field.rootReasons.add("annotation-root");
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (statement.type === "LocalStatement") {
|
|
176
|
+
statement.init.forEach((initializer) => {
|
|
177
|
+
const object = exportObjectOf(initializer);
|
|
178
|
+
if (object &&
|
|
179
|
+
(metadata.annotationsOf(statement).keep ||
|
|
180
|
+
metadata.annotationsOf(statement).exported)) {
|
|
181
|
+
fieldsByObject
|
|
182
|
+
.get(object.id)
|
|
183
|
+
?.forEach((field) => field.rootReasons.add("annotation-root"));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
onExpression: (expression) => {
|
|
189
|
+
if (expression.type !== "TableConstructorExpression")
|
|
190
|
+
return;
|
|
191
|
+
const object = exportObjectOf(expression);
|
|
192
|
+
if (!object)
|
|
193
|
+
return;
|
|
194
|
+
const statement = statementOfExpression.get(expression);
|
|
195
|
+
if (!statement)
|
|
196
|
+
return;
|
|
197
|
+
expression.fields.forEach((tableField) => {
|
|
198
|
+
if (tableField.type === "TableValue") {
|
|
199
|
+
addObjectBoundary(object, "shape-observation");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const key = fieldKey(tableField);
|
|
203
|
+
if (key === undefined) {
|
|
204
|
+
addObjectBoundary(object, "dynamic-key");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const field = mutableField(object, key);
|
|
208
|
+
field.definitions.push({
|
|
209
|
+
kind: "table-field",
|
|
210
|
+
module,
|
|
211
|
+
body: bodyOfStatement.get(statement) ?? module.chunk.body,
|
|
212
|
+
statement,
|
|
213
|
+
table: expression,
|
|
214
|
+
field: tableField,
|
|
215
|
+
value: tableField.value,
|
|
216
|
+
});
|
|
217
|
+
markExpressionOwner(tableField.value, field, ownerOfNode);
|
|
218
|
+
if (isDynamicRequire(tableField.value))
|
|
219
|
+
boundaryDiagnostics.push({
|
|
220
|
+
moduleName: module.name,
|
|
221
|
+
field: key,
|
|
222
|
+
reason: "unresolved-re-export",
|
|
223
|
+
sourceRange: sourceRangeOf(tableField),
|
|
224
|
+
});
|
|
225
|
+
const annotations = metadata.annotationsOf(statement);
|
|
226
|
+
if (annotations.keep || annotations.exported)
|
|
227
|
+
field.rootReasons.add("annotation-root");
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
const allFields = () => [...fieldsByObject.values()].flatMap((fields) => [...fields.values()]);
|
|
233
|
+
const rootObject = (object, reason) => {
|
|
234
|
+
fieldsByObject
|
|
235
|
+
.get(object.id)
|
|
236
|
+
?.forEach((field) => field.rootReasons.add(reason));
|
|
237
|
+
};
|
|
238
|
+
objectBoundaryReasons.forEach((reasons, object) => {
|
|
239
|
+
reasons.forEach((reason) => {
|
|
240
|
+
rootObject(object, reason);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
const addFieldUse = (object, key, owner) => {
|
|
244
|
+
const field = mutableField(object, key);
|
|
245
|
+
if (!owner)
|
|
246
|
+
field.rootReasons.add("static-field-root");
|
|
247
|
+
else if (isMutableField(owner))
|
|
248
|
+
owner.dependencies.add(field);
|
|
249
|
+
else
|
|
250
|
+
callableFieldDependencies(owner).add(field);
|
|
251
|
+
};
|
|
252
|
+
const fieldsOfCallable = new Map();
|
|
253
|
+
const callsOfCallable = new Map();
|
|
254
|
+
const shapeOfCallable = new Map();
|
|
255
|
+
const callableRoots = new Set();
|
|
256
|
+
const callableFieldDependencies = (callable) => {
|
|
257
|
+
let fields = fieldsOfCallable.get(callable);
|
|
258
|
+
if (!fields) {
|
|
259
|
+
fields = new Set();
|
|
260
|
+
fieldsOfCallable.set(callable, fields);
|
|
261
|
+
}
|
|
262
|
+
return fields;
|
|
263
|
+
};
|
|
264
|
+
const callableCallDependencies = (callable) => {
|
|
265
|
+
let calls = callsOfCallable.get(callable);
|
|
266
|
+
if (!calls) {
|
|
267
|
+
calls = new Set();
|
|
268
|
+
callsOfCallable.set(callable, calls);
|
|
269
|
+
}
|
|
270
|
+
return calls;
|
|
271
|
+
};
|
|
272
|
+
const callableShapeDependencies = (callable) => {
|
|
273
|
+
let dependencies = shapeOfCallable.get(callable);
|
|
274
|
+
if (!dependencies) {
|
|
275
|
+
dependencies = new Set();
|
|
276
|
+
shapeOfCallable.set(callable, dependencies);
|
|
277
|
+
}
|
|
278
|
+
return dependencies;
|
|
279
|
+
};
|
|
280
|
+
const addShapeUse = (object, owner, reason) => {
|
|
281
|
+
if (!owner)
|
|
282
|
+
rootObject(object, reason);
|
|
283
|
+
else if (isMutableField(owner))
|
|
284
|
+
owner.shapeDependencies.add(object);
|
|
285
|
+
else
|
|
286
|
+
callableShapeDependencies(owner).add(object);
|
|
287
|
+
};
|
|
288
|
+
objects.modules.forEach((module) => {
|
|
289
|
+
const returned = topLevelReturn(module);
|
|
290
|
+
(0, astWalk_1.walkBlockDeep)(module.chunk.body, {
|
|
291
|
+
onStatement: (statement) => {
|
|
292
|
+
const owner = ownerOfNode.get(statement);
|
|
293
|
+
if (statement.type === "ReturnStatement") {
|
|
294
|
+
statement.arguments.forEach((argument) => {
|
|
295
|
+
const object = exportObjectOf(argument);
|
|
296
|
+
if (!object)
|
|
297
|
+
return;
|
|
298
|
+
const isOwnTopLevelReturn = returned === statement && object.moduleName === module.name;
|
|
299
|
+
if (!isOwnTopLevelReturn)
|
|
300
|
+
addShapeUse(object, owner, "export-escape");
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (statement.type === "LocalStatement" ||
|
|
304
|
+
statement.type === "AssignmentStatement") {
|
|
305
|
+
const values = statement.type === "LocalStatement"
|
|
306
|
+
? statement.init
|
|
307
|
+
: statement.init;
|
|
308
|
+
values.forEach((value) => {
|
|
309
|
+
const object = exportObjectOf(value);
|
|
310
|
+
if (!object)
|
|
311
|
+
return;
|
|
312
|
+
if (statement.type === "AssignmentStatement") {
|
|
313
|
+
const stableAlias = statement.variables.some((variable) => variable.type === "Identifier" &&
|
|
314
|
+
sameObject(objects, variable, object));
|
|
315
|
+
if (!stableAlias)
|
|
316
|
+
addShapeUse(object, owner, "export-escape");
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
onExpression: (expression) => {
|
|
322
|
+
const owner = ownerOfNode.get(expression);
|
|
323
|
+
if (expression.type === "MemberExpression") {
|
|
324
|
+
if (definitionMembers.has(expression))
|
|
325
|
+
return;
|
|
326
|
+
const object = exportObjectOf(expression.base);
|
|
327
|
+
if (object)
|
|
328
|
+
addFieldUse(object, expression.identifier.name, owner);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (expression.type === "IndexExpression") {
|
|
332
|
+
if (definitionMembers.has(expression))
|
|
333
|
+
return;
|
|
334
|
+
const object = exportObjectOf(expression.base);
|
|
335
|
+
if (!object)
|
|
336
|
+
return;
|
|
337
|
+
const key = (0, linker_1.staticStringArgument)(expression.index);
|
|
338
|
+
if (key === undefined)
|
|
339
|
+
addShapeUse(object, owner, "dynamic-key");
|
|
340
|
+
else
|
|
341
|
+
addFieldUse(object, key, owner);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (expression.type === "UnaryExpression" &&
|
|
345
|
+
expression.operator === "#") {
|
|
346
|
+
const object = exportObjectOf(expression.argument);
|
|
347
|
+
if (object)
|
|
348
|
+
addShapeUse(object, owner, "shape-observation");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!isCallExpression(expression))
|
|
352
|
+
return;
|
|
353
|
+
const combinedCall = callSiteByExpression.get(expression);
|
|
354
|
+
if (combinedCall) {
|
|
355
|
+
if (!combinedCall.caller)
|
|
356
|
+
combinedCall.targets.forEach((target) => callableRoots.add(target));
|
|
357
|
+
else {
|
|
358
|
+
const caller = combinedCall.caller;
|
|
359
|
+
combinedCall.targets.forEach((target) => {
|
|
360
|
+
callableCallDependencies(caller).add(target);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (expression.type !== "CallExpression")
|
|
365
|
+
return;
|
|
366
|
+
const calleeName = expression.base.type === "Identifier"
|
|
367
|
+
? expression.base.name
|
|
368
|
+
: undefined;
|
|
369
|
+
const shapeObserver = calleeName === "pairs" || calleeName === "next";
|
|
370
|
+
const metatableObserver = calleeName === "setmetatable" ||
|
|
371
|
+
calleeName === "getmetatable" ||
|
|
372
|
+
calleeName === "rawget" ||
|
|
373
|
+
calleeName === "rawset";
|
|
374
|
+
const localCall = module.analysis.callGraph.callSiteOf(expression);
|
|
375
|
+
expression.arguments.forEach((argument, argumentIndex) => {
|
|
376
|
+
const object = exportObjectOf(argument);
|
|
377
|
+
if (!object)
|
|
378
|
+
return;
|
|
379
|
+
if (shapeObserver || metatableObserver)
|
|
380
|
+
addShapeUse(object, owner, "shape-observation");
|
|
381
|
+
else if (!localCall ||
|
|
382
|
+
localCall.hasUnknownTarget ||
|
|
383
|
+
localCall.targets.size === 0 ||
|
|
384
|
+
module.analysis.interprocedural.escapesArgument(localCall, argumentIndex))
|
|
385
|
+
addShapeUse(object, owner, "export-escape");
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
// A module with more than one possible return allocation cannot be field-pruned.
|
|
391
|
+
const objectsByModule = new Map();
|
|
392
|
+
exportObjects.forEach((object) => {
|
|
393
|
+
const group = objectsByModule.get(object.moduleName) ?? [];
|
|
394
|
+
group.push(object);
|
|
395
|
+
objectsByModule.set(object.moduleName, group);
|
|
396
|
+
});
|
|
397
|
+
objectsByModule.forEach((moduleObjects, moduleName) => {
|
|
398
|
+
if (moduleObjects.length > 1) {
|
|
399
|
+
moduleObjects.forEach((object) => {
|
|
400
|
+
rootObject(object, "multiple-return-allocations");
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (moduleName === entryModule) {
|
|
404
|
+
moduleObjects.forEach((object) => {
|
|
405
|
+
rootObject(object, "entry-contract");
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
// Object invalidation from #83 is also an export-shape observation boundary.
|
|
410
|
+
exportObjects.forEach((object) => {
|
|
411
|
+
if (object.invalidationReasons.size > 0)
|
|
412
|
+
rootObject(object, "export-escape");
|
|
413
|
+
});
|
|
414
|
+
// Function values and helper references participate in the same graph. A
|
|
415
|
+
// reference owned by an export field is an edge, while an unowned reference
|
|
416
|
+
// makes the callable externally reachable.
|
|
417
|
+
objects.modules.forEach((module) => {
|
|
418
|
+
module.analysis.callGraph.functions.forEach((callable) => {
|
|
419
|
+
callable.symbol?.references.forEach((reference) => {
|
|
420
|
+
const owner = ownerOfNode.get(reference);
|
|
421
|
+
if (!owner)
|
|
422
|
+
callableRoots.add(callable);
|
|
423
|
+
else if (isMutableField(owner))
|
|
424
|
+
owner.callableDependencies.add(callable);
|
|
425
|
+
else
|
|
426
|
+
callableCallDependencies(owner).add(callable);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
allFields().forEach((field) => {
|
|
431
|
+
field.definitions.forEach((definition) => {
|
|
432
|
+
const expression = definition.value;
|
|
433
|
+
if (expression.type !== "Identifier")
|
|
434
|
+
return;
|
|
435
|
+
const symbol = definition.module.resolved.symbolOf(expression);
|
|
436
|
+
const callable = symbol
|
|
437
|
+
? definition.module.analysis.callGraph.functionOfSymbol(symbol)
|
|
438
|
+
: undefined;
|
|
439
|
+
if (callable)
|
|
440
|
+
field.callableDependencies.add(callable);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
const liveCallables = new Set();
|
|
444
|
+
const callableQueue = [...callableRoots];
|
|
445
|
+
const fieldQueue = allFields().filter((field) => field.rootReasons.size > 0);
|
|
446
|
+
const enqueueObject = (object) => {
|
|
447
|
+
fieldsByObject.get(object.id)?.forEach((field) => {
|
|
448
|
+
if (!field.live)
|
|
449
|
+
fieldQueue.push(field);
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
while (fieldQueue.length > 0 || callableQueue.length > 0) {
|
|
453
|
+
while (fieldQueue.length > 0) {
|
|
454
|
+
const field = fieldQueue.shift();
|
|
455
|
+
if (!field)
|
|
456
|
+
break;
|
|
457
|
+
if (field.live)
|
|
458
|
+
continue;
|
|
459
|
+
field.live = true;
|
|
460
|
+
field.dependencies.forEach((dependency) => {
|
|
461
|
+
if (!dependency.live)
|
|
462
|
+
fieldQueue.push(dependency);
|
|
463
|
+
});
|
|
464
|
+
field.callableDependencies.forEach((callable) => callableQueue.push(callable));
|
|
465
|
+
field.shapeDependencies.forEach(enqueueObject);
|
|
466
|
+
field.definitions.forEach((definition) => {
|
|
467
|
+
const callable = definition.value.type === "FunctionDeclaration"
|
|
468
|
+
? callableByDeclaration.get(definition.value)
|
|
469
|
+
: undefined;
|
|
470
|
+
if (callable)
|
|
471
|
+
callableQueue.push(callable);
|
|
472
|
+
const reExport = exportObjectOf(definition.value);
|
|
473
|
+
if (reExport && reExport.id !== field.object.id)
|
|
474
|
+
enqueueObject(reExport);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
while (callableQueue.length > 0) {
|
|
478
|
+
const callable = callableQueue.shift();
|
|
479
|
+
if (!callable)
|
|
480
|
+
break;
|
|
481
|
+
if (liveCallables.has(callable))
|
|
482
|
+
continue;
|
|
483
|
+
liveCallables.add(callable);
|
|
484
|
+
callableFieldDependencies(callable).forEach((field) => {
|
|
485
|
+
if (!field.live)
|
|
486
|
+
fieldQueue.push(field);
|
|
487
|
+
});
|
|
488
|
+
callableCallDependencies(callable).forEach((target) => callableQueue.push(target));
|
|
489
|
+
callableShapeDependencies(callable).forEach(enqueueObject);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const fields = allFields().sort((left, right) => left.id.localeCompare(right.id));
|
|
493
|
+
const diagnostics = [...boundaryDiagnostics];
|
|
494
|
+
fields.forEach((field) => {
|
|
495
|
+
diagnostics.push({
|
|
496
|
+
moduleName: field.moduleName,
|
|
497
|
+
field: field.key,
|
|
498
|
+
reason: "export-field-candidate",
|
|
499
|
+
sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
|
|
500
|
+
});
|
|
501
|
+
[...field.rootReasons].sort().forEach((reason) => diagnostics.push({
|
|
502
|
+
moduleName: field.moduleName,
|
|
503
|
+
field: field.key,
|
|
504
|
+
reason,
|
|
505
|
+
sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
|
|
506
|
+
}));
|
|
507
|
+
diagnostics.push({
|
|
508
|
+
moduleName: field.moduleName,
|
|
509
|
+
field: field.key,
|
|
510
|
+
reason: field.live ? "field-live" : "field-unreachable",
|
|
511
|
+
sourceRange: sourceRangeOf(field.definitions[0]?.statement ?? {}),
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
return {
|
|
515
|
+
generation: objects.generation,
|
|
516
|
+
fields: fields.map((field) => ({
|
|
517
|
+
id: field.id,
|
|
518
|
+
moduleName: field.moduleName,
|
|
519
|
+
object: field.object,
|
|
520
|
+
key: field.key,
|
|
521
|
+
definitions: field.definitions,
|
|
522
|
+
live: field.live,
|
|
523
|
+
rootReasons: field.rootReasons,
|
|
524
|
+
})),
|
|
525
|
+
diagnostics,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function indexStatementOwners(body, bodyOfStatement, statementOfExpression) {
|
|
529
|
+
body.forEach((statement) => {
|
|
530
|
+
bodyOfStatement.set(statement, body);
|
|
531
|
+
(0, astWalk_1.walkStatement)(statement, {
|
|
532
|
+
onExpression: (expression) => {
|
|
533
|
+
statementOfExpression.set(expression, statement);
|
|
534
|
+
},
|
|
535
|
+
onBlock: (nested) => {
|
|
536
|
+
indexStatementOwners(nested, bodyOfStatement, statementOfExpression);
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function isDynamicRequire(expression) {
|
|
542
|
+
return (expression.type === "CallExpression" &&
|
|
543
|
+
expression.base.type === "Identifier" &&
|
|
544
|
+
expression.base.name === "require" &&
|
|
545
|
+
(0, linker_1.staticStringArgument)(expression.arguments[0]) === undefined);
|
|
546
|
+
}
|
|
547
|
+
function isMutableField(owner) {
|
|
548
|
+
return "definitions" in owner && "dependencies" in owner;
|
|
549
|
+
}
|
|
550
|
+
function callStatement(expression) {
|
|
551
|
+
return {
|
|
552
|
+
type: "CallStatement",
|
|
553
|
+
expression,
|
|
554
|
+
loc: expression.loc,
|
|
555
|
+
range: sourceRangeOf(expression),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/** Apply one already-proved reachability snapshot without rebuilding analysis. */
|
|
559
|
+
function applyWholeProgramExportDce(analysis, metadataOf, discardable) {
|
|
560
|
+
const replacements = new Map();
|
|
561
|
+
const removedTableFields = new Map();
|
|
562
|
+
const removedFields = [];
|
|
563
|
+
const preservedEffectFields = new Set();
|
|
564
|
+
const refusedEffectfulInitializerFields = [];
|
|
565
|
+
analysis.fields.forEach((field) => {
|
|
566
|
+
if (field.live || field.definitions.length === 0)
|
|
567
|
+
return;
|
|
568
|
+
const canRemove = field.definitions.every((definition) => {
|
|
569
|
+
if (definition.kind === "table-field")
|
|
570
|
+
return discardable(definition.module.name, definition.value);
|
|
571
|
+
if (definition.kind === "member-function")
|
|
572
|
+
return true;
|
|
573
|
+
return (definition.statement.variables.length === 1 &&
|
|
574
|
+
definition.statement.init.length === 1 &&
|
|
575
|
+
(discardable(definition.module.name, definition.value) ||
|
|
576
|
+
isCallExpression(definition.value)));
|
|
577
|
+
});
|
|
578
|
+
if (!canRemove) {
|
|
579
|
+
refusedEffectfulInitializerFields.push(field);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const planned = [];
|
|
583
|
+
field.definitions.forEach((definition) => {
|
|
584
|
+
if (definition.kind === "table-field") {
|
|
585
|
+
planned.push(() => {
|
|
586
|
+
const removed = removedTableFields.get(definition.table) ?? new Set();
|
|
587
|
+
removed.add(definition.field);
|
|
588
|
+
removedTableFields.set(definition.table, removed);
|
|
589
|
+
});
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (definition.kind === "member-function") {
|
|
593
|
+
planned.push(() => replacements.set(definition.statement, []));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (definition.statement.variables.length !== 1 ||
|
|
597
|
+
definition.statement.init.length !== 1) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (discardable(definition.module.name, definition.value)) {
|
|
601
|
+
planned.push(() => replacements.set(definition.statement, []));
|
|
602
|
+
}
|
|
603
|
+
else if (isCallExpression(definition.value)) {
|
|
604
|
+
planned.push(() => {
|
|
605
|
+
replacements.set(definition.statement, [
|
|
606
|
+
callStatement(definition.value),
|
|
607
|
+
]);
|
|
608
|
+
preservedEffectFields.add(field);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
planned.forEach((apply) => {
|
|
616
|
+
apply();
|
|
617
|
+
});
|
|
618
|
+
removedFields.push(field);
|
|
619
|
+
});
|
|
620
|
+
removedTableFields.forEach((removed, table) => {
|
|
621
|
+
table.fields = table.fields.filter((field) => field.type === "TableValue" || !removed.has(field));
|
|
622
|
+
});
|
|
623
|
+
analysis.fields.forEach((field) => {
|
|
624
|
+
field.definitions.forEach((definition) => {
|
|
625
|
+
const replacement = replacements.get(definition.statement);
|
|
626
|
+
if (replacement === undefined)
|
|
627
|
+
return;
|
|
628
|
+
const body = definition.body;
|
|
629
|
+
const index = body.indexOf(definition.statement);
|
|
630
|
+
if (index < 0)
|
|
631
|
+
return;
|
|
632
|
+
const metadata = metadataOf(definition.module.name);
|
|
633
|
+
if (replacement.length > 0)
|
|
634
|
+
metadata.replaceStatement(definition.statement, replacement);
|
|
635
|
+
else
|
|
636
|
+
metadata.removeStatement(definition.statement, body[index + 1]);
|
|
637
|
+
body.splice(index, 1, ...replacement);
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
changed: removedFields.length > 0,
|
|
642
|
+
removedFields,
|
|
643
|
+
preservedEffectFields: [...preservedEffectFields],
|
|
644
|
+
refusedEffectfulInitializerFields,
|
|
645
|
+
};
|
|
646
|
+
}
|