@markw65/monkeyc-optimizer 1.0.28 → 1.0.29
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 +27 -0
- package/build/api.cjs +2042 -223
- package/build/optimizer.cjs +1965 -169
- package/build/src/api.d.ts +2 -4
- package/build/src/ast.d.ts +7 -0
- package/build/src/control-flow.d.ts +20 -0
- package/build/src/inliner.d.ts +3 -3
- package/build/src/mc-rewrite.d.ts +1 -1
- package/build/src/optimizer.d.ts +1 -0
- package/package.json +4 -2
package/build/optimizer.cjs
CHANGED
|
@@ -2172,6 +2172,185 @@ function pendGo(self, fn) {
|
|
|
2172
2172
|
}
|
|
2173
2173
|
|
|
2174
2174
|
|
|
2175
|
+
/***/ }),
|
|
2176
|
+
|
|
2177
|
+
/***/ 2789:
|
|
2178
|
+
/***/ ((module) => {
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* Expose `PriorityQueue`.
|
|
2182
|
+
*/
|
|
2183
|
+
module.exports = PriorityQueue;
|
|
2184
|
+
|
|
2185
|
+
/**
|
|
2186
|
+
* Initializes a new empty `PriorityQueue` with the given `comparator(a, b)`
|
|
2187
|
+
* function, uses `.DEFAULT_COMPARATOR()` when no function is provided.
|
|
2188
|
+
*
|
|
2189
|
+
* The comparator function must return a positive number when `a > b`, 0 when
|
|
2190
|
+
* `a == b` and a negative number when `a < b`.
|
|
2191
|
+
*
|
|
2192
|
+
* @param {Function}
|
|
2193
|
+
* @return {PriorityQueue}
|
|
2194
|
+
* @api public
|
|
2195
|
+
*/
|
|
2196
|
+
function PriorityQueue(comparator) {
|
|
2197
|
+
this._comparator = comparator || PriorityQueue.DEFAULT_COMPARATOR;
|
|
2198
|
+
this._elements = [];
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
/**
|
|
2202
|
+
* Compares `a` and `b`, when `a > b` it returns a positive number, when
|
|
2203
|
+
* it returns 0 and when `a < b` it returns a negative number.
|
|
2204
|
+
*
|
|
2205
|
+
* @param {String|Number} a
|
|
2206
|
+
* @param {String|Number} b
|
|
2207
|
+
* @return {Number}
|
|
2208
|
+
* @api public
|
|
2209
|
+
*/
|
|
2210
|
+
PriorityQueue.DEFAULT_COMPARATOR = function(a, b) {
|
|
2211
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
2212
|
+
return a - b;
|
|
2213
|
+
} else {
|
|
2214
|
+
a = a.toString();
|
|
2215
|
+
b = b.toString();
|
|
2216
|
+
|
|
2217
|
+
if (a == b) return 0;
|
|
2218
|
+
|
|
2219
|
+
return (a > b) ? 1 : -1;
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* Returns whether the priority queue is empty or not.
|
|
2225
|
+
*
|
|
2226
|
+
* @return {Boolean}
|
|
2227
|
+
* @api public
|
|
2228
|
+
*/
|
|
2229
|
+
PriorityQueue.prototype.isEmpty = function() {
|
|
2230
|
+
return this.size() === 0;
|
|
2231
|
+
};
|
|
2232
|
+
|
|
2233
|
+
/**
|
|
2234
|
+
* Peeks at the top element of the priority queue.
|
|
2235
|
+
*
|
|
2236
|
+
* @return {Object}
|
|
2237
|
+
* @throws {Error} when the queue is empty.
|
|
2238
|
+
* @api public
|
|
2239
|
+
*/
|
|
2240
|
+
PriorityQueue.prototype.peek = function() {
|
|
2241
|
+
if (this.isEmpty()) throw new Error('PriorityQueue is empty');
|
|
2242
|
+
|
|
2243
|
+
return this._elements[0];
|
|
2244
|
+
};
|
|
2245
|
+
|
|
2246
|
+
/**
|
|
2247
|
+
* Dequeues the top element of the priority queue.
|
|
2248
|
+
*
|
|
2249
|
+
* @return {Object}
|
|
2250
|
+
* @throws {Error} when the queue is empty.
|
|
2251
|
+
* @api public
|
|
2252
|
+
*/
|
|
2253
|
+
PriorityQueue.prototype.deq = function() {
|
|
2254
|
+
var first = this.peek();
|
|
2255
|
+
var last = this._elements.pop();
|
|
2256
|
+
var size = this.size();
|
|
2257
|
+
|
|
2258
|
+
if (size === 0) return first;
|
|
2259
|
+
|
|
2260
|
+
this._elements[0] = last;
|
|
2261
|
+
var current = 0;
|
|
2262
|
+
|
|
2263
|
+
while (current < size) {
|
|
2264
|
+
var largest = current;
|
|
2265
|
+
var left = (2 * current) + 1;
|
|
2266
|
+
var right = (2 * current) + 2;
|
|
2267
|
+
|
|
2268
|
+
if (left < size && this._compare(left, largest) >= 0) {
|
|
2269
|
+
largest = left;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
if (right < size && this._compare(right, largest) >= 0) {
|
|
2273
|
+
largest = right;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
if (largest === current) break;
|
|
2277
|
+
|
|
2278
|
+
this._swap(largest, current);
|
|
2279
|
+
current = largest;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
return first;
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
/**
|
|
2286
|
+
* Enqueues the `element` at the priority queue and returns its new size.
|
|
2287
|
+
*
|
|
2288
|
+
* @param {Object} element
|
|
2289
|
+
* @return {Number}
|
|
2290
|
+
* @api public
|
|
2291
|
+
*/
|
|
2292
|
+
PriorityQueue.prototype.enq = function(element) {
|
|
2293
|
+
var size = this._elements.push(element);
|
|
2294
|
+
var current = size - 1;
|
|
2295
|
+
|
|
2296
|
+
while (current > 0) {
|
|
2297
|
+
var parent = Math.floor((current - 1) / 2);
|
|
2298
|
+
|
|
2299
|
+
if (this._compare(current, parent) <= 0) break;
|
|
2300
|
+
|
|
2301
|
+
this._swap(parent, current);
|
|
2302
|
+
current = parent;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
return size;
|
|
2306
|
+
};
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Returns the size of the priority queue.
|
|
2310
|
+
*
|
|
2311
|
+
* @return {Number}
|
|
2312
|
+
* @api public
|
|
2313
|
+
*/
|
|
2314
|
+
PriorityQueue.prototype.size = function() {
|
|
2315
|
+
return this._elements.length;
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
/**
|
|
2319
|
+
* Iterates over queue elements
|
|
2320
|
+
*
|
|
2321
|
+
* @param {Function} fn
|
|
2322
|
+
*/
|
|
2323
|
+
PriorityQueue.prototype.forEach = function(fn) {
|
|
2324
|
+
return this._elements.forEach(fn);
|
|
2325
|
+
};
|
|
2326
|
+
|
|
2327
|
+
/**
|
|
2328
|
+
* Compares the values at position `a` and `b` in the priority queue using its
|
|
2329
|
+
* comparator function.
|
|
2330
|
+
*
|
|
2331
|
+
* @param {Number} a
|
|
2332
|
+
* @param {Number} b
|
|
2333
|
+
* @return {Number}
|
|
2334
|
+
* @api private
|
|
2335
|
+
*/
|
|
2336
|
+
PriorityQueue.prototype._compare = function(a, b) {
|
|
2337
|
+
return this._comparator(this._elements[a], this._elements[b]);
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
/**
|
|
2341
|
+
* Swaps the values at position `a` and `b` in the priority queue.
|
|
2342
|
+
*
|
|
2343
|
+
* @param {Number} a
|
|
2344
|
+
* @param {Number} b
|
|
2345
|
+
* @api private
|
|
2346
|
+
*/
|
|
2347
|
+
PriorityQueue.prototype._swap = function(a, b) {
|
|
2348
|
+
var aux = this._elements[a];
|
|
2349
|
+
this._elements[a] = this._elements[b];
|
|
2350
|
+
this._elements[b] = aux;
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
|
|
2175
2354
|
/***/ }),
|
|
2176
2355
|
|
|
2177
2356
|
/***/ 4286:
|
|
@@ -9874,17 +10053,17 @@ async function build_project(product, options, lineCallback) {
|
|
|
9874
10053
|
if (!product) {
|
|
9875
10054
|
extraArgs.push("-e");
|
|
9876
10055
|
}
|
|
9877
|
-
switch (typeCheckLevel) {
|
|
9878
|
-
case "
|
|
10056
|
+
switch (typeCheckLevel?.toLowerCase()) {
|
|
10057
|
+
case "off":
|
|
9879
10058
|
extraArgs.push("-l", "0");
|
|
9880
10059
|
break;
|
|
9881
|
-
case "
|
|
10060
|
+
case "gradual":
|
|
9882
10061
|
extraArgs.push("-l", "1");
|
|
9883
10062
|
break;
|
|
9884
|
-
case "
|
|
10063
|
+
case "informative":
|
|
9885
10064
|
extraArgs.push("-l", "2");
|
|
9886
10065
|
break;
|
|
9887
|
-
case "
|
|
10066
|
+
case "strict":
|
|
9888
10067
|
extraArgs.push("-l", "3");
|
|
9889
10068
|
break;
|
|
9890
10069
|
}
|
|
@@ -10812,62 +10991,68 @@ function _check(x) {
|
|
|
10812
10991
|
x = y;
|
|
10813
10992
|
}
|
|
10814
10993
|
const mctreeTypeInfo = {
|
|
10815
|
-
ArrayExpression: ["elements"],
|
|
10816
|
-
AssignmentExpression: ["left", "right"],
|
|
10817
|
-
AttributeList: ["attributes"],
|
|
10818
|
-
Attributes: ["elements"],
|
|
10819
|
-
BinaryExpression: ["left", "right"],
|
|
10820
|
-
Block: [],
|
|
10821
|
-
BlockStatement: ["body", "innerComments"],
|
|
10822
|
-
BreakStatement: [],
|
|
10823
|
-
CallExpression: ["callee", "arguments"],
|
|
10824
|
-
CatchClause: ["param", "body"],
|
|
10825
|
-
CatchClauses: ["catches"],
|
|
10826
|
-
ClassBody: ["body"],
|
|
10827
|
-
ClassDeclaration: ["attrs", "id", "superClass", "body"],
|
|
10828
|
-
ClassElement: ["item"],
|
|
10829
|
-
ConditionalExpression:
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10994
|
+
ArrayExpression: { keys: ["elements"], expr: true },
|
|
10995
|
+
AssignmentExpression: { keys: ["left", "right"], expr: true },
|
|
10996
|
+
AttributeList: { keys: ["attributes"] },
|
|
10997
|
+
Attributes: { keys: ["elements"] },
|
|
10998
|
+
BinaryExpression: { keys: ["left", "right"], expr: true },
|
|
10999
|
+
Block: { keys: [] },
|
|
11000
|
+
BlockStatement: { keys: ["body", "innerComments"], stmt: true },
|
|
11001
|
+
BreakStatement: { keys: [], stmt: true },
|
|
11002
|
+
CallExpression: { keys: ["callee", "arguments"], expr: true },
|
|
11003
|
+
CatchClause: { keys: ["param", "body"] },
|
|
11004
|
+
CatchClauses: { keys: ["catches"] },
|
|
11005
|
+
ClassBody: { keys: ["body"] },
|
|
11006
|
+
ClassDeclaration: { keys: ["attrs", "id", "superClass", "body"], stmt: true },
|
|
11007
|
+
ClassElement: { keys: ["item"] },
|
|
11008
|
+
ConditionalExpression: {
|
|
11009
|
+
keys: ["test", "consequent", "alternate"],
|
|
11010
|
+
expr: true,
|
|
11011
|
+
},
|
|
11012
|
+
ContinueStatement: { keys: [], stmt: true },
|
|
11013
|
+
DoWhileStatement: { keys: ["body", "test"], stmt: true },
|
|
11014
|
+
EnumDeclaration: { keys: ["attrs", "id", "body"], stmt: true },
|
|
11015
|
+
EnumStringBody: { keys: ["members"] },
|
|
11016
|
+
EnumStringMember: { keys: ["id", "init"] },
|
|
11017
|
+
ExpressionStatement: { keys: ["expression"], stmt: true },
|
|
11018
|
+
ForStatement: { keys: ["init", "test", "body", "update"], stmt: true },
|
|
11019
|
+
FunctionDeclaration: {
|
|
11020
|
+
keys: ["attrs", "id", "params", "returnType", "body"],
|
|
11021
|
+
stmt: true,
|
|
11022
|
+
},
|
|
11023
|
+
Identifier: { keys: [], expr: true },
|
|
11024
|
+
IfStatement: { keys: ["test", "consequent", "alternate"], stmt: true },
|
|
11025
|
+
ImportModule: { keys: ["id"] },
|
|
11026
|
+
InstanceOfCase: { keys: ["id"] },
|
|
11027
|
+
Line: { keys: [] },
|
|
11028
|
+
Literal: { keys: [], expr: true },
|
|
11029
|
+
LogicalExpression: { keys: ["left", "right"], expr: true },
|
|
11030
|
+
MemberExpression: { keys: ["object", "property"], expr: true },
|
|
11031
|
+
MethodDefinition: { keys: ["params", "returnType"] },
|
|
11032
|
+
ModuleDeclaration: { keys: ["attrs", "id", "body"], stmt: true },
|
|
11033
|
+
MultiLine: { keys: [] },
|
|
11034
|
+
NewExpression: { keys: ["callee", "arguments"], expr: true },
|
|
11035
|
+
ObjectExpression: { keys: ["properties"], expr: true },
|
|
11036
|
+
ParenthesizedExpression: { keys: ["expression"], expr: true },
|
|
11037
|
+
Program: { keys: ["body", "comments"] },
|
|
11038
|
+
Property: { keys: ["key", "value"] },
|
|
11039
|
+
ReturnStatement: { keys: ["argument"], stmt: true },
|
|
11040
|
+
SequenceExpression: { keys: ["expressions"], expr: true },
|
|
11041
|
+
SizedArrayExpression: { keys: ["size", "ts"], expr: true },
|
|
11042
|
+
SwitchCase: { keys: ["test", "consequent"] },
|
|
11043
|
+
SwitchStatement: { keys: ["discriminant", "cases"], stmt: true },
|
|
11044
|
+
ThisExpression: { keys: [], expr: true },
|
|
11045
|
+
ThrowStatement: { keys: ["argument"], stmt: true },
|
|
11046
|
+
TryStatement: { keys: ["block", "handler", "finalizer"], stmt: true },
|
|
11047
|
+
TypedefDeclaration: { keys: ["attrs", "id", "ts"], stmt: true },
|
|
11048
|
+
TypeSpecList: { keys: ["ts"] },
|
|
11049
|
+
TypeSpecPart: { keys: ["name", "body", "callspec", "generics"] },
|
|
11050
|
+
UnaryExpression: { keys: ["argument"], expr: true },
|
|
11051
|
+
UpdateExpression: { keys: ["argument"], expr: true },
|
|
11052
|
+
Using: { keys: ["id", "as"] },
|
|
11053
|
+
VariableDeclaration: { keys: ["attrs", "declarations"], stmt: true },
|
|
11054
|
+
VariableDeclarator: { keys: ["id", "init"] },
|
|
11055
|
+
WhileStatement: { keys: ["test", "body"], stmt: true },
|
|
10871
11056
|
};
|
|
10872
11057
|
function isMCTreeNode(node) {
|
|
10873
11058
|
return node ? typeof node === "object" && "type" in node : false;
|
|
@@ -10892,7 +11077,7 @@ function traverseAst(node, pre, post) {
|
|
|
10892
11077
|
if (!mctreeTypeInfo[node.type]) {
|
|
10893
11078
|
throw new Error("what?");
|
|
10894
11079
|
}
|
|
10895
|
-
for (const key of nodes || mctreeTypeInfo[node.type]) {
|
|
11080
|
+
for (const key of nodes || mctreeTypeInfo[node.type].keys) {
|
|
10896
11081
|
const value = node[key];
|
|
10897
11082
|
if (!value)
|
|
10898
11083
|
continue;
|
|
@@ -10919,13 +11104,21 @@ function traverseAst(node, pre, post) {
|
|
|
10919
11104
|
}
|
|
10920
11105
|
}
|
|
10921
11106
|
else if (isMCTreeNode(value)) {
|
|
10922
|
-
|
|
11107
|
+
let repl = traverseAst(value, pre, post);
|
|
10923
11108
|
if (repl === false) {
|
|
10924
11109
|
delete node[key];
|
|
10925
11110
|
}
|
|
10926
11111
|
else if (repl != null) {
|
|
10927
11112
|
if (Array.isArray(repl)) {
|
|
10928
|
-
|
|
11113
|
+
if (isStatement(value) && repl.every((s) => isStatement(s))) {
|
|
11114
|
+
repl = withLoc({
|
|
11115
|
+
type: "BlockStatement",
|
|
11116
|
+
body: repl,
|
|
11117
|
+
}, repl[0], repl[repl.length - 1]);
|
|
11118
|
+
}
|
|
11119
|
+
else {
|
|
11120
|
+
throw new Error("Array returned by traverseAst in Node context");
|
|
11121
|
+
}
|
|
10929
11122
|
}
|
|
10930
11123
|
node[key] = repl;
|
|
10931
11124
|
}
|
|
@@ -10933,6 +11126,55 @@ function traverseAst(node, pre, post) {
|
|
|
10933
11126
|
}
|
|
10934
11127
|
return post && post(node);
|
|
10935
11128
|
}
|
|
11129
|
+
function isStatement(node) {
|
|
11130
|
+
return hasProperty(mctreeTypeInfo[node.type], "stmt");
|
|
11131
|
+
}
|
|
11132
|
+
function isExpression(node) {
|
|
11133
|
+
return hasProperty(mctreeTypeInfo[node.type], "expr");
|
|
11134
|
+
}
|
|
11135
|
+
function mayThrow(node) {
|
|
11136
|
+
switch (node.type) {
|
|
11137
|
+
case "BinaryExpression":
|
|
11138
|
+
case "CallExpression":
|
|
11139
|
+
case "ConditionalExpression":
|
|
11140
|
+
case "LogicalExpression":
|
|
11141
|
+
case "NewExpression":
|
|
11142
|
+
case "ThrowStatement":
|
|
11143
|
+
case "UnaryExpression":
|
|
11144
|
+
case "UpdateExpression":
|
|
11145
|
+
return true;
|
|
11146
|
+
default:
|
|
11147
|
+
return false;
|
|
11148
|
+
}
|
|
11149
|
+
}
|
|
11150
|
+
function hasProperty(obj, prop) {
|
|
11151
|
+
return obj ? Object.prototype.hasOwnProperty.call(obj, prop) : false;
|
|
11152
|
+
}
|
|
11153
|
+
function withLoc(node, start, end) {
|
|
11154
|
+
if (start && start.loc) {
|
|
11155
|
+
node.start = start.start;
|
|
11156
|
+
if (!node.end)
|
|
11157
|
+
node.end = start.end;
|
|
11158
|
+
node.loc = { ...(node.loc || start.loc), start: start.loc.start };
|
|
11159
|
+
}
|
|
11160
|
+
if (end && end.loc) {
|
|
11161
|
+
node.end = end.end;
|
|
11162
|
+
node.loc = { ...(node.loc || end.loc), end: end.loc.end };
|
|
11163
|
+
}
|
|
11164
|
+
return node;
|
|
11165
|
+
}
|
|
11166
|
+
function withLocDeep(node, start, end) {
|
|
11167
|
+
node = withLoc({ ...node }, start, end);
|
|
11168
|
+
for (const key of mctreeTypeInfo[node.type].keys) {
|
|
11169
|
+
const value = node[key];
|
|
11170
|
+
if (!value)
|
|
11171
|
+
continue;
|
|
11172
|
+
const fix = (v) => isMCTreeNode(v) ? withLocDeep(v, start, end) : v;
|
|
11173
|
+
const repl = Array.isArray(value) ? value.map(fix) : fix(value);
|
|
11174
|
+
node[key] = repl;
|
|
11175
|
+
}
|
|
11176
|
+
return node;
|
|
11177
|
+
}
|
|
10936
11178
|
|
|
10937
11179
|
;// CONCATENATED MODULE: ./src/variable-renamer.ts
|
|
10938
11180
|
|
|
@@ -11238,7 +11480,7 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11238
11480
|
if ((0,external_api_cjs_namespaceObject.hasProperty)(params, node.name)) {
|
|
11239
11481
|
const ix = params[node.name];
|
|
11240
11482
|
if (ix >= 0) {
|
|
11241
|
-
replacement = call.arguments[ix];
|
|
11483
|
+
replacement = { ...call.arguments[ix] };
|
|
11242
11484
|
replacements.add(replacement);
|
|
11243
11485
|
return replacement;
|
|
11244
11486
|
}
|
|
@@ -11277,6 +11519,10 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11277
11519
|
}
|
|
11278
11520
|
}
|
|
11279
11521
|
function unused(expression, top) {
|
|
11522
|
+
const estmt = (expression) => withLoc({
|
|
11523
|
+
type: "ExpressionStatement",
|
|
11524
|
+
expression,
|
|
11525
|
+
}, expression);
|
|
11280
11526
|
switch (expression.type) {
|
|
11281
11527
|
case "Literal":
|
|
11282
11528
|
return [];
|
|
@@ -11288,9 +11534,50 @@ function unused(expression, top) {
|
|
|
11288
11534
|
if (expression.operator === "as") {
|
|
11289
11535
|
return unused(expression.left);
|
|
11290
11536
|
}
|
|
11291
|
-
// fall through
|
|
11292
|
-
case "LogicalExpression":
|
|
11293
11537
|
return unused(expression.left).concat(unused(expression.right));
|
|
11538
|
+
case "LogicalExpression": {
|
|
11539
|
+
const right = unused(expression.right);
|
|
11540
|
+
if (!right.length)
|
|
11541
|
+
return unused(expression.left);
|
|
11542
|
+
const consequent = withLoc({
|
|
11543
|
+
type: "BlockStatement",
|
|
11544
|
+
body: [estmt(expression.right)],
|
|
11545
|
+
}, expression.right);
|
|
11546
|
+
let alternate;
|
|
11547
|
+
if (expression.operator == "||") {
|
|
11548
|
+
alternate = { ...consequent };
|
|
11549
|
+
consequent.body = [];
|
|
11550
|
+
}
|
|
11551
|
+
return [
|
|
11552
|
+
withLoc({
|
|
11553
|
+
type: "IfStatement",
|
|
11554
|
+
test: expression.left,
|
|
11555
|
+
consequent,
|
|
11556
|
+
alternate,
|
|
11557
|
+
}, expression),
|
|
11558
|
+
];
|
|
11559
|
+
}
|
|
11560
|
+
case "ConditionalExpression": {
|
|
11561
|
+
const consequentExprs = unused(expression.consequent);
|
|
11562
|
+
const alternateExprs = unused(expression.alternate);
|
|
11563
|
+
if (!consequentExprs.length && !alternateExprs.length) {
|
|
11564
|
+
return unused(expression.test);
|
|
11565
|
+
}
|
|
11566
|
+
return [
|
|
11567
|
+
withLoc({
|
|
11568
|
+
type: "IfStatement",
|
|
11569
|
+
test: expression.test,
|
|
11570
|
+
consequent: withLoc({
|
|
11571
|
+
type: "BlockStatement",
|
|
11572
|
+
body: consequentExprs,
|
|
11573
|
+
}, expression.consequent),
|
|
11574
|
+
alternate: withLoc({
|
|
11575
|
+
type: "BlockStatement",
|
|
11576
|
+
body: alternateExprs,
|
|
11577
|
+
}, expression.alternate),
|
|
11578
|
+
}, expression),
|
|
11579
|
+
];
|
|
11580
|
+
}
|
|
11294
11581
|
case "UnaryExpression":
|
|
11295
11582
|
return unused(expression.argument);
|
|
11296
11583
|
case "MemberExpression":
|
|
@@ -11305,17 +11592,7 @@ function unused(expression, top) {
|
|
|
11305
11592
|
.map((p) => unused(p.key).concat(unused(p.value)))
|
|
11306
11593
|
.flat(1);
|
|
11307
11594
|
}
|
|
11308
|
-
return top
|
|
11309
|
-
? null
|
|
11310
|
-
: [
|
|
11311
|
-
{
|
|
11312
|
-
type: "ExpressionStatement",
|
|
11313
|
-
expression,
|
|
11314
|
-
start: expression.start,
|
|
11315
|
-
end: expression.end,
|
|
11316
|
-
loc: expression.loc,
|
|
11317
|
-
},
|
|
11318
|
-
];
|
|
11595
|
+
return top ? null : [estmt(expression)];
|
|
11319
11596
|
}
|
|
11320
11597
|
function diagnostic(state, loc, message, type = "INFO") {
|
|
11321
11598
|
if (!loc || !loc.source)
|
|
@@ -11363,7 +11640,9 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11363
11640
|
if (retStmtCount > 1) {
|
|
11364
11641
|
inlineDiagnostic(state, func, call, "Function had more than one return statement");
|
|
11365
11642
|
}
|
|
11366
|
-
else if (context.type === "AssignmentExpression"
|
|
11643
|
+
else if ((context.type === "AssignmentExpression" ||
|
|
11644
|
+
context.type === "VariableDeclarator") &&
|
|
11645
|
+
retStmtCount !== 1) {
|
|
11367
11646
|
inlineDiagnostic(state, func, call, "Function did not have a return statement");
|
|
11368
11647
|
return null;
|
|
11369
11648
|
}
|
|
@@ -11371,7 +11650,9 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11371
11650
|
const last = func.node.body.body.slice(-1)[0];
|
|
11372
11651
|
if (!last ||
|
|
11373
11652
|
last.type !== "ReturnStatement" ||
|
|
11374
|
-
(context.type === "AssignmentExpression"
|
|
11653
|
+
((context.type === "AssignmentExpression" ||
|
|
11654
|
+
context.type === "VariableDeclarator") &&
|
|
11655
|
+
!last.argument)) {
|
|
11375
11656
|
inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
|
|
11376
11657
|
return null;
|
|
11377
11658
|
}
|
|
@@ -11395,16 +11676,32 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11395
11676
|
if (last.type != "ReturnStatement") {
|
|
11396
11677
|
throw new Error("ReturnStatement got lost!");
|
|
11397
11678
|
}
|
|
11398
|
-
if (
|
|
11399
|
-
context.
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11679
|
+
if (last.argument) {
|
|
11680
|
+
if (context.type === "AssignmentExpression") {
|
|
11681
|
+
context.right = last.argument;
|
|
11682
|
+
body.body[body.body.length - 1] = {
|
|
11683
|
+
type: "ExpressionStatement",
|
|
11684
|
+
expression: context,
|
|
11685
|
+
};
|
|
11686
|
+
}
|
|
11687
|
+
else if (context.type === "VariableDeclarator") {
|
|
11688
|
+
const { id, init: _init, kind: _kind, ...rest } = context;
|
|
11689
|
+
body.body[body.body.length - 1] = {
|
|
11690
|
+
...rest,
|
|
11691
|
+
type: "ExpressionStatement",
|
|
11692
|
+
expression: {
|
|
11693
|
+
...rest,
|
|
11694
|
+
type: "AssignmentExpression",
|
|
11695
|
+
operator: "=",
|
|
11696
|
+
left: id.type === "Identifier" ? id : id.left,
|
|
11697
|
+
right: last.argument,
|
|
11698
|
+
},
|
|
11699
|
+
};
|
|
11700
|
+
}
|
|
11701
|
+
else {
|
|
11702
|
+
const side_exprs = unused(last.argument);
|
|
11703
|
+
body.body.splice(body.body.length - 1, 1, ...side_exprs);
|
|
11704
|
+
}
|
|
11408
11705
|
}
|
|
11409
11706
|
else {
|
|
11410
11707
|
--body.body.length;
|
|
@@ -11526,89 +11823,1429 @@ function fixNodeScope(state, lookupNode, nodeStack) {
|
|
|
11526
11823
|
return null;
|
|
11527
11824
|
}
|
|
11528
11825
|
|
|
11529
|
-
;// CONCATENATED MODULE: ./src/
|
|
11826
|
+
;// CONCATENATED MODULE: ./src/control-flow.ts
|
|
11530
11827
|
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11828
|
+
|
|
11829
|
+
const Terminals = {
|
|
11830
|
+
BreakStatement: "break",
|
|
11831
|
+
ContinueStatement: "continue",
|
|
11832
|
+
ReturnStatement: null,
|
|
11833
|
+
ThrowStatement: "throw",
|
|
11834
|
+
};
|
|
11835
|
+
class LocalState {
|
|
11836
|
+
constructor(func) {
|
|
11837
|
+
this.stack = [];
|
|
11838
|
+
this.info = new Map();
|
|
11839
|
+
this.curBlock = {};
|
|
11840
|
+
this.unreachable = false;
|
|
11841
|
+
this.push(func);
|
|
11842
|
+
}
|
|
11843
|
+
push(node) {
|
|
11844
|
+
const top = { node };
|
|
11845
|
+
this.stack.push(top);
|
|
11846
|
+
return top;
|
|
11847
|
+
}
|
|
11848
|
+
pop() {
|
|
11849
|
+
return this.stack.pop();
|
|
11850
|
+
}
|
|
11851
|
+
top(depth) {
|
|
11852
|
+
return this.stack[this.stack.length - (depth || 1)];
|
|
11853
|
+
}
|
|
11854
|
+
addEdge(from, to) {
|
|
11855
|
+
if (!from.succs) {
|
|
11856
|
+
from.succs = [to];
|
|
11857
|
+
}
|
|
11858
|
+
else {
|
|
11859
|
+
(0,external_util_cjs_namespaceObject.pushUnique)(from.succs, to);
|
|
11860
|
+
}
|
|
11861
|
+
if (!to.preds) {
|
|
11862
|
+
to.preds = [from];
|
|
11863
|
+
}
|
|
11864
|
+
else {
|
|
11865
|
+
(0,external_util_cjs_namespaceObject.pushUnique)(to.preds, from);
|
|
11866
|
+
}
|
|
11867
|
+
}
|
|
11868
|
+
newBlock(block) {
|
|
11869
|
+
if (!block)
|
|
11870
|
+
block = {};
|
|
11871
|
+
if (!this.unreachable) {
|
|
11872
|
+
this.addEdge(this.curBlock, block);
|
|
11873
|
+
}
|
|
11874
|
+
this.unreachable = false;
|
|
11875
|
+
for (let i = this.stack.length; i--;) {
|
|
11876
|
+
const si = this.stack[i];
|
|
11877
|
+
if (si.throw) {
|
|
11878
|
+
block.exsucc = si.throw;
|
|
11879
|
+
if (!si.throw.expreds) {
|
|
11880
|
+
si.throw.expreds = [block];
|
|
11881
|
+
}
|
|
11882
|
+
else {
|
|
11883
|
+
si.throw.expreds.push(block);
|
|
11537
11884
|
}
|
|
11885
|
+
break;
|
|
11538
11886
|
}
|
|
11539
11887
|
}
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11888
|
+
return (this.curBlock = block);
|
|
11889
|
+
}
|
|
11890
|
+
terminal(type) {
|
|
11891
|
+
const re = Terminals[type];
|
|
11892
|
+
if (re) {
|
|
11893
|
+
for (let i = this.stack.length; i--;) {
|
|
11894
|
+
const target = this.stack[i][re];
|
|
11895
|
+
if (target) {
|
|
11896
|
+
this.addEdge(this.curBlock, target);
|
|
11897
|
+
break;
|
|
11898
|
+
}
|
|
11543
11899
|
}
|
|
11544
11900
|
}
|
|
11545
|
-
|
|
11546
|
-
}
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11901
|
+
this.unreachable = true;
|
|
11902
|
+
}
|
|
11903
|
+
}
|
|
11904
|
+
function buildReducedGraph(state, func, notice) {
|
|
11905
|
+
const { stack, pre, post } = state;
|
|
11906
|
+
try {
|
|
11907
|
+
const localState = new LocalState(func.node);
|
|
11908
|
+
const ret = localState.curBlock;
|
|
11909
|
+
state.stack = func.stack;
|
|
11910
|
+
const stmtStack = [func.node];
|
|
11911
|
+
let tryActive = 0;
|
|
11912
|
+
state.pre = (node) => {
|
|
11913
|
+
if (state.inType || localState.unreachable) {
|
|
11550
11914
|
return [];
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11915
|
+
}
|
|
11916
|
+
if (!localState.curBlock.node &&
|
|
11917
|
+
(isStatement(node) || isExpression(node))) {
|
|
11918
|
+
localState.curBlock.node = node;
|
|
11919
|
+
}
|
|
11920
|
+
if (isStatement(node)) {
|
|
11921
|
+
stmtStack.push(node);
|
|
11922
|
+
}
|
|
11923
|
+
switch (node.type) {
|
|
11924
|
+
case "AttributeList":
|
|
11554
11925
|
return [];
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
type: "MemberExpression",
|
|
11567
|
-
object: node.left,
|
|
11568
|
-
property: node.right.argument,
|
|
11569
|
-
computed: false,
|
|
11570
|
-
}), node.right.argument);
|
|
11926
|
+
case "SwitchStatement": {
|
|
11927
|
+
const top = localState.push(node);
|
|
11928
|
+
top.break = {};
|
|
11929
|
+
state.traverse(node.discriminant);
|
|
11930
|
+
const testBlocks = [];
|
|
11931
|
+
let defaultSeen = false;
|
|
11932
|
+
node.cases.forEach((sc, i) => {
|
|
11933
|
+
if (sc.test) {
|
|
11934
|
+
state.traverse(sc.test);
|
|
11935
|
+
testBlocks[i] = localState.curBlock;
|
|
11936
|
+
localState.newBlock();
|
|
11571
11937
|
}
|
|
11938
|
+
else {
|
|
11939
|
+
defaultSeen = true;
|
|
11940
|
+
}
|
|
11941
|
+
});
|
|
11942
|
+
const endOfTests = localState.curBlock;
|
|
11943
|
+
if (!defaultSeen) {
|
|
11944
|
+
localState.addEdge(endOfTests, top.break);
|
|
11572
11945
|
}
|
|
11946
|
+
localState.unreachable = true;
|
|
11947
|
+
node.cases.forEach((sc, i) => {
|
|
11948
|
+
localState.newBlock();
|
|
11949
|
+
localState.addEdge(testBlocks[i] || endOfTests, localState.curBlock);
|
|
11950
|
+
sc.consequent.every((s) => {
|
|
11951
|
+
state.traverse(s);
|
|
11952
|
+
return !localState.unreachable;
|
|
11953
|
+
});
|
|
11954
|
+
});
|
|
11955
|
+
localState.newBlock(top.break);
|
|
11956
|
+
localState.unreachable = !top.break.preds;
|
|
11957
|
+
return [];
|
|
11573
11958
|
}
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
if (
|
|
11582
|
-
|
|
11583
|
-
|
|
11959
|
+
case "DoWhileStatement":
|
|
11960
|
+
case "WhileStatement": {
|
|
11961
|
+
localState.push(node);
|
|
11962
|
+
const top = localState.top();
|
|
11963
|
+
top.break = {};
|
|
11964
|
+
top.continue = {};
|
|
11965
|
+
let head;
|
|
11966
|
+
if (node.type === "WhileStatement") {
|
|
11967
|
+
head = localState.newBlock(top.continue);
|
|
11968
|
+
state.traverse(node.test);
|
|
11969
|
+
localState.addEdge(localState.newBlock(), top.break);
|
|
11584
11970
|
}
|
|
11585
|
-
|
|
11971
|
+
else {
|
|
11972
|
+
head = localState.newBlock();
|
|
11973
|
+
}
|
|
11974
|
+
state.traverse(node.body);
|
|
11975
|
+
if (node.type === "DoWhileStatement") {
|
|
11976
|
+
localState.newBlock(top.continue);
|
|
11977
|
+
state.traverse(node.test);
|
|
11978
|
+
localState.addEdge(localState.curBlock, top.break);
|
|
11979
|
+
}
|
|
11980
|
+
localState.addEdge(localState.curBlock, head);
|
|
11981
|
+
localState.curBlock = top.break;
|
|
11982
|
+
return [];
|
|
11586
11983
|
}
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11984
|
+
case "TryStatement": {
|
|
11985
|
+
const top = localState.push(node);
|
|
11986
|
+
const catches = (top.throw = {});
|
|
11987
|
+
// This edge shouldn't exist, but we can trigger
|
|
11988
|
+
// (incorrect) "variable may not be initialized" errors
|
|
11989
|
+
// in the monkey c compiler without it.
|
|
11990
|
+
// https://forums.garmin.com/developer/connect-iq/i/bug-reports/incorrect-maybe-uninitialized-error
|
|
11991
|
+
localState.addEdge(localState.curBlock, top.throw);
|
|
11992
|
+
localState.newBlock();
|
|
11993
|
+
tryActive++;
|
|
11994
|
+
state.traverse(node.block);
|
|
11995
|
+
tryActive--;
|
|
11996
|
+
delete top.throw;
|
|
11997
|
+
top.posttry = {};
|
|
11998
|
+
const tryFallsThrough = !localState.unreachable;
|
|
11999
|
+
if (node.finalizer) {
|
|
12000
|
+
tryActive++;
|
|
12001
|
+
top.throw = top.finally = {};
|
|
12002
|
+
// curBlock branches to finally, no matter how it exits.
|
|
12003
|
+
localState.addEdge(localState.curBlock, top.finally);
|
|
12004
|
+
}
|
|
12005
|
+
else {
|
|
12006
|
+
if (!localState.unreachable) {
|
|
12007
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
12010
|
+
localState.unreachable = true;
|
|
12011
|
+
localState.newBlock(catches);
|
|
12012
|
+
if (node.handler) {
|
|
12013
|
+
state.traverse(node.handler);
|
|
12014
|
+
if (top.throw) {
|
|
12015
|
+
tryActive--;
|
|
12016
|
+
delete top.throw;
|
|
12017
|
+
}
|
|
12018
|
+
// Each "catch (ex instanceof Foo)" chains to the next,
|
|
12019
|
+
// but "catch (ex)" terminates the list. If the end
|
|
12020
|
+
// of the chain has a predecessor, its possible that
|
|
12021
|
+
// none of the conditions matched, so the exception
|
|
12022
|
+
// will propagate from there.
|
|
12023
|
+
if (localState.curBlock.preds) {
|
|
12024
|
+
localState.terminal("ThrowStatement");
|
|
12025
|
+
}
|
|
12026
|
+
}
|
|
12027
|
+
if (top.throw) {
|
|
12028
|
+
tryActive--;
|
|
12029
|
+
delete top.throw;
|
|
12030
|
+
}
|
|
12031
|
+
if (node.finalizer) {
|
|
12032
|
+
localState.unreachable = true;
|
|
12033
|
+
localState.newBlock(top.finally);
|
|
12034
|
+
delete top.finally;
|
|
12035
|
+
state.traverse(node.finalizer);
|
|
12036
|
+
if (tryFallsThrough && !localState.unreachable) {
|
|
12037
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
12038
|
+
}
|
|
12039
|
+
localState.terminal("ThrowStatement");
|
|
12040
|
+
}
|
|
12041
|
+
localState.unreachable = true;
|
|
12042
|
+
localState.newBlock(top.posttry);
|
|
12043
|
+
return [];
|
|
11591
12044
|
}
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
return checkResults(state.lookup(node), node) || ["object"];
|
|
12045
|
+
case "CatchClause": {
|
|
12046
|
+
const top = localState.top();
|
|
12047
|
+
if (!localState.curBlock.preds && !localState.curBlock.expreds) {
|
|
12048
|
+
return [];
|
|
11597
12049
|
}
|
|
11598
|
-
|
|
12050
|
+
const next = {};
|
|
12051
|
+
if (node.param && node.param.type === "BinaryExpression") {
|
|
12052
|
+
state.traverse(node.param);
|
|
12053
|
+
localState.addEdge(localState.curBlock, next);
|
|
12054
|
+
localState.newBlock();
|
|
12055
|
+
}
|
|
12056
|
+
state.traverse(node.body);
|
|
12057
|
+
if (top.finally) {
|
|
12058
|
+
// this edge exists even if this point is unreachable
|
|
12059
|
+
localState.addEdge(localState.curBlock, top.finally);
|
|
12060
|
+
}
|
|
12061
|
+
if (!localState.unreachable) {
|
|
12062
|
+
if (!top.posttry)
|
|
12063
|
+
top.posttry = {};
|
|
12064
|
+
localState.addEdge(localState.curBlock, top.posttry);
|
|
12065
|
+
}
|
|
12066
|
+
localState.unreachable = true;
|
|
12067
|
+
localState.newBlock(next);
|
|
12068
|
+
return [];
|
|
11599
12069
|
}
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
12070
|
+
case "ForStatement": {
|
|
12071
|
+
const top = localState.push(node);
|
|
12072
|
+
if (node.init)
|
|
12073
|
+
state.traverse(node.init);
|
|
12074
|
+
const head = localState.newBlock();
|
|
12075
|
+
top.break = {};
|
|
12076
|
+
top.continue = {};
|
|
12077
|
+
if (node.test) {
|
|
12078
|
+
state.traverse(node.test);
|
|
12079
|
+
localState.addEdge(localState.curBlock, top.break);
|
|
12080
|
+
localState.newBlock();
|
|
12081
|
+
}
|
|
12082
|
+
state.traverse(node.body);
|
|
12083
|
+
localState.newBlock(top.continue);
|
|
12084
|
+
if (node.update) {
|
|
12085
|
+
state.traverse(node.update);
|
|
12086
|
+
}
|
|
12087
|
+
if (!localState.unreachable) {
|
|
12088
|
+
localState.addEdge(localState.curBlock, head);
|
|
12089
|
+
}
|
|
12090
|
+
// there is no fall through from the end of the loop
|
|
12091
|
+
// to the next block. The only way there is via break
|
|
12092
|
+
// or the test failing.
|
|
12093
|
+
localState.unreachable = true;
|
|
12094
|
+
localState.newBlock(top.break);
|
|
12095
|
+
if (!top.break.preds) {
|
|
12096
|
+
localState.unreachable = true;
|
|
12097
|
+
}
|
|
12098
|
+
return [];
|
|
11604
12099
|
}
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
12100
|
+
case "IfStatement":
|
|
12101
|
+
case "ConditionalExpression": {
|
|
12102
|
+
state.traverse(node.test);
|
|
12103
|
+
const alternate = {};
|
|
12104
|
+
localState.addEdge(localState.curBlock, alternate);
|
|
12105
|
+
localState.newBlock();
|
|
12106
|
+
state.traverse(node.consequent);
|
|
12107
|
+
const consequent = localState.unreachable
|
|
12108
|
+
? null
|
|
12109
|
+
: localState.curBlock;
|
|
12110
|
+
localState.unreachable = true;
|
|
12111
|
+
localState.newBlock(alternate);
|
|
12112
|
+
if (node.alternate) {
|
|
12113
|
+
state.traverse(node.alternate);
|
|
12114
|
+
if (!localState.unreachable) {
|
|
12115
|
+
localState.newBlock();
|
|
11610
12116
|
}
|
|
11611
|
-
}
|
|
12117
|
+
}
|
|
12118
|
+
if (consequent) {
|
|
12119
|
+
if (localState.unreachable) {
|
|
12120
|
+
localState.newBlock();
|
|
12121
|
+
}
|
|
12122
|
+
localState.addEdge(consequent, localState.curBlock);
|
|
12123
|
+
}
|
|
12124
|
+
return [];
|
|
12125
|
+
}
|
|
12126
|
+
case "LogicalExpression": {
|
|
12127
|
+
state.traverse(node.left);
|
|
12128
|
+
if (localState.unreachable)
|
|
12129
|
+
break;
|
|
12130
|
+
const mid = localState.curBlock;
|
|
12131
|
+
localState.newBlock();
|
|
12132
|
+
state.traverse(node.right);
|
|
12133
|
+
localState.newBlock();
|
|
12134
|
+
localState.addEdge(mid, localState.curBlock);
|
|
12135
|
+
return [];
|
|
12136
|
+
}
|
|
12137
|
+
case "VariableDeclarator":
|
|
12138
|
+
return ["init"];
|
|
12139
|
+
case "MemberExpression":
|
|
12140
|
+
if (!node.computed) {
|
|
12141
|
+
return ["object"];
|
|
12142
|
+
}
|
|
12143
|
+
break;
|
|
12144
|
+
case "UnaryExpression":
|
|
12145
|
+
if (node.operator === ":") {
|
|
12146
|
+
return [];
|
|
12147
|
+
}
|
|
12148
|
+
break;
|
|
12149
|
+
case "UpdateExpression":
|
|
12150
|
+
// We don't want to traverse the argument, since then it would
|
|
12151
|
+
// look like a ref, rather than a def. But if its a
|
|
12152
|
+
// MemberExpression, we *do* want to traverse the subexpressions
|
|
12153
|
+
// as potential refs.
|
|
12154
|
+
if (node.argument.type === "MemberExpression") {
|
|
12155
|
+
state.traverse(node.argument.object);
|
|
12156
|
+
if (node.argument.computed) {
|
|
12157
|
+
state.traverse(node.argument.property);
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
12160
|
+
return [];
|
|
12161
|
+
case "AssignmentExpression":
|
|
12162
|
+
if (node.left.type === "MemberExpression") {
|
|
12163
|
+
state.traverse(node.left.object);
|
|
12164
|
+
if (node.left.computed) {
|
|
12165
|
+
state.traverse(node.left.property);
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
12168
|
+
return ["right"];
|
|
12169
|
+
case "ThrowStatement":
|
|
12170
|
+
case "ReturnStatement":
|
|
12171
|
+
if (node.argument) {
|
|
12172
|
+
state.traverse(node.argument);
|
|
12173
|
+
}
|
|
12174
|
+
// fall through
|
|
12175
|
+
case "BreakStatement":
|
|
12176
|
+
case "ContinueStatement":
|
|
12177
|
+
localState.terminal(node.type);
|
|
12178
|
+
return [];
|
|
12179
|
+
}
|
|
12180
|
+
return null;
|
|
12181
|
+
};
|
|
12182
|
+
const addEvent = (block, event) => {
|
|
12183
|
+
if (!block.events) {
|
|
12184
|
+
block.events = [event];
|
|
12185
|
+
}
|
|
12186
|
+
else {
|
|
12187
|
+
block.events.push(event);
|
|
12188
|
+
}
|
|
12189
|
+
};
|
|
12190
|
+
state.post = (node) => {
|
|
12191
|
+
const curStmt = stmtStack[stmtStack.length - 1];
|
|
12192
|
+
if (!state.inType) {
|
|
12193
|
+
const throws = tryActive > 0 && mayThrow(node);
|
|
12194
|
+
const event = notice(node, curStmt, throws);
|
|
12195
|
+
if (throws) {
|
|
12196
|
+
if (!event) {
|
|
12197
|
+
throw new Error("mayThrow expression in try/catch must generate an event");
|
|
12198
|
+
}
|
|
12199
|
+
}
|
|
12200
|
+
else if (event) {
|
|
12201
|
+
event.mayThrow = false;
|
|
12202
|
+
}
|
|
12203
|
+
if (event) {
|
|
12204
|
+
if (event.mayThrow) {
|
|
12205
|
+
for (let i = localState.stack.length; i--;) {
|
|
12206
|
+
const target = localState.stack[i].throw;
|
|
12207
|
+
if (target) {
|
|
12208
|
+
if (localState.curBlock.exsucc) {
|
|
12209
|
+
if (localState.curBlock.exsucc !== target) {
|
|
12210
|
+
throw new Error(`Block has multiple throw targets`);
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
else {
|
|
12214
|
+
localState.curBlock.exsucc = target;
|
|
12215
|
+
if (!target.expreds) {
|
|
12216
|
+
target.expreds = [localState.curBlock];
|
|
12217
|
+
}
|
|
12218
|
+
else {
|
|
12219
|
+
target.expreds.push(localState.curBlock);
|
|
12220
|
+
}
|
|
12221
|
+
}
|
|
12222
|
+
break;
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
addEvent(localState.curBlock, event);
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
if (curStmt === node) {
|
|
12230
|
+
stmtStack.pop();
|
|
12231
|
+
}
|
|
12232
|
+
if (localState.top().node === node) {
|
|
12233
|
+
localState.pop();
|
|
12234
|
+
}
|
|
12235
|
+
return null;
|
|
12236
|
+
};
|
|
12237
|
+
state.traverse(func.node);
|
|
12238
|
+
return cleanCfg(ret);
|
|
12239
|
+
}
|
|
12240
|
+
finally {
|
|
12241
|
+
state.pre = pre;
|
|
12242
|
+
state.post = post;
|
|
12243
|
+
state.stack = stack;
|
|
12244
|
+
}
|
|
12245
|
+
}
|
|
12246
|
+
function cleanCfg(head) {
|
|
12247
|
+
preOrderTraverse(head, (cur) => {
|
|
12248
|
+
if (cur.succs && cur.succs.length === 1) {
|
|
12249
|
+
const succ = cur.succs[0];
|
|
12250
|
+
if (succ !== head &&
|
|
12251
|
+
succ.preds.length === 1 &&
|
|
12252
|
+
(!cur.exsucc || cur.exsucc === succ.exsucc) &&
|
|
12253
|
+
(!succ.succs ||
|
|
12254
|
+
succ.succs.length === 1 ||
|
|
12255
|
+
(cur.preds && cur.preds.length === 1))) {
|
|
12256
|
+
if (cur.events) {
|
|
12257
|
+
if (succ.events) {
|
|
12258
|
+
cur.events.push(...succ.events);
|
|
12259
|
+
}
|
|
12260
|
+
}
|
|
12261
|
+
else if (succ.events) {
|
|
12262
|
+
cur.events = succ.events;
|
|
12263
|
+
}
|
|
12264
|
+
if (succ.exsucc) {
|
|
12265
|
+
const preds = succ.exsucc.expreds;
|
|
12266
|
+
for (let i = preds.length; i--;) {
|
|
12267
|
+
if (preds[i] === succ) {
|
|
12268
|
+
// If cur has an exsucc, we already
|
|
12269
|
+
// checked that its the same as succ's,
|
|
12270
|
+
// so we can just delete the edge.
|
|
12271
|
+
// Otherwise, we need to point it at cur.
|
|
12272
|
+
if (cur.exsucc) {
|
|
12273
|
+
preds.splice(i, 1);
|
|
12274
|
+
}
|
|
12275
|
+
else {
|
|
12276
|
+
preds[i] = cur;
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
}
|
|
12280
|
+
}
|
|
12281
|
+
cur.exsucc = succ.exsucc;
|
|
12282
|
+
cur.succs = succ.succs;
|
|
12283
|
+
if (cur.succs) {
|
|
12284
|
+
cur.succs.forEach((s) => s.preds.forEach((p, i, arr) => {
|
|
12285
|
+
if (p === succ) {
|
|
12286
|
+
arr[i] = cur;
|
|
12287
|
+
}
|
|
12288
|
+
}));
|
|
12289
|
+
}
|
|
12290
|
+
}
|
|
12291
|
+
}
|
|
12292
|
+
});
|
|
12293
|
+
return head;
|
|
12294
|
+
}
|
|
12295
|
+
function postOrderTraverse(head, visitor) {
|
|
12296
|
+
const visited = new Set();
|
|
12297
|
+
const helper = (cur) => {
|
|
12298
|
+
if (visited.has(cur))
|
|
12299
|
+
return;
|
|
12300
|
+
visited.add(cur);
|
|
12301
|
+
if (cur.succs) {
|
|
12302
|
+
cur.succs.forEach((block) => helper(block));
|
|
12303
|
+
}
|
|
12304
|
+
if (cur.exsucc)
|
|
12305
|
+
helper(cur.exsucc);
|
|
12306
|
+
visitor(cur);
|
|
12307
|
+
};
|
|
12308
|
+
helper(head);
|
|
12309
|
+
}
|
|
12310
|
+
function preOrderTraverse(head, visitor) {
|
|
12311
|
+
const visited = new Set();
|
|
12312
|
+
const helper = (cur) => {
|
|
12313
|
+
if (visited.has(cur))
|
|
12314
|
+
return;
|
|
12315
|
+
visited.add(cur);
|
|
12316
|
+
visitor(cur);
|
|
12317
|
+
if (cur.succs) {
|
|
12318
|
+
cur.succs.forEach((block) => helper(block));
|
|
12319
|
+
}
|
|
12320
|
+
if (cur.exsucc)
|
|
12321
|
+
helper(cur.exsucc);
|
|
12322
|
+
};
|
|
12323
|
+
helper(head);
|
|
12324
|
+
}
|
|
12325
|
+
function getPostOrder(head) {
|
|
12326
|
+
const blocks = [];
|
|
12327
|
+
postOrderTraverse(head, (block) => blocks.push(block));
|
|
12328
|
+
return blocks;
|
|
12329
|
+
}
|
|
12330
|
+
function getPreOrder(head) {
|
|
12331
|
+
const blocks = [];
|
|
12332
|
+
postOrderTraverse(head, (block) => blocks.push(block));
|
|
12333
|
+
return blocks;
|
|
12334
|
+
}
|
|
12335
|
+
|
|
12336
|
+
// EXTERNAL MODULE: ./node_modules/priorityqueuejs/index.js
|
|
12337
|
+
var priorityqueuejs = __webpack_require__(2789);
|
|
12338
|
+
;// CONCATENATED MODULE: ./src/pre.ts
|
|
12339
|
+
|
|
12340
|
+
|
|
12341
|
+
|
|
12342
|
+
|
|
12343
|
+
/**
|
|
12344
|
+
* This implements a pseudo Partial Redundancy Elimination
|
|
12345
|
+
* pass. It isn't quite like traditional PRE because we're
|
|
12346
|
+
* aiming to minimize size, not dynamic instructions. So
|
|
12347
|
+
* for us, its worthwhile to take something like:
|
|
12348
|
+
*
|
|
12349
|
+
* switch (x) {
|
|
12350
|
+
* case 1: foo(A.B); break;
|
|
12351
|
+
* case 2: foo(C); break;
|
|
12352
|
+
* case 3: bar(A.B); break;
|
|
12353
|
+
* }
|
|
12354
|
+
*
|
|
12355
|
+
* and rewrite it as
|
|
12356
|
+
*
|
|
12357
|
+
* var tmp = A.B;
|
|
12358
|
+
* switch (x) {
|
|
12359
|
+
* case 1: foo(tmp); break;
|
|
12360
|
+
* case 2: foo(C); break;
|
|
12361
|
+
* case 3: bar(tmp); break;
|
|
12362
|
+
* }
|
|
12363
|
+
*
|
|
12364
|
+
* because even though A.B wasn't used on all paths where we
|
|
12365
|
+
* inserted the temporary, we still reduced the code size.
|
|
12366
|
+
*/
|
|
12367
|
+
const logging = false;
|
|
12368
|
+
function declFullName(decl) {
|
|
12369
|
+
switch (decl.type) {
|
|
12370
|
+
case "Literal":
|
|
12371
|
+
return decl.raw || decl.value?.toString() || "null";
|
|
12372
|
+
case "VariableDeclarator":
|
|
12373
|
+
return decl.fullName;
|
|
12374
|
+
default:
|
|
12375
|
+
throw new Error(`Unexpected EventDecl type: ${decl.type}`);
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
function declName(decl) {
|
|
12379
|
+
switch (decl.type) {
|
|
12380
|
+
case "Literal":
|
|
12381
|
+
return (decl.raw || decl.value?.toString() || "null").replace(/[^\w]/g, "_");
|
|
12382
|
+
case "VariableDeclarator":
|
|
12383
|
+
return decl.name;
|
|
12384
|
+
default:
|
|
12385
|
+
throw new Error(`Unexpected EventDecl type: ${decl.type}`);
|
|
12386
|
+
}
|
|
12387
|
+
}
|
|
12388
|
+
function sizeBasedPRE(state, func) {
|
|
12389
|
+
if (!func.node.body)
|
|
12390
|
+
return;
|
|
12391
|
+
if (!state.config ||
|
|
12392
|
+
!state.config.sizeBasedPRE ||
|
|
12393
|
+
(typeof state.config.sizeBasedPRE === "string" &&
|
|
12394
|
+
state.config.sizeBasedPRE !== func.fullName)) {
|
|
12395
|
+
return;
|
|
12396
|
+
}
|
|
12397
|
+
const { graph: head, identifiers } = buildPREGraph(state, func);
|
|
12398
|
+
const candidates = computeAttributes(head);
|
|
12399
|
+
if (candidates) {
|
|
12400
|
+
if (logging) {
|
|
12401
|
+
console.log(`Found ${candidates.size} candidates in ${func.fullName}`);
|
|
12402
|
+
candidates.forEach((s, decl) => {
|
|
12403
|
+
const defs = Array.from(s.ant).reduce((defs, event) => {
|
|
12404
|
+
if (event.type === "def")
|
|
12405
|
+
defs++;
|
|
12406
|
+
return defs;
|
|
12407
|
+
}, 0);
|
|
12408
|
+
console.log(` - ${declFullName(decl)}: ${candidateCost(s)} bytes, ${s.ant.size - defs} refs, ${defs} defs, ${s.live ? "" : "!"}live`);
|
|
12409
|
+
});
|
|
12410
|
+
}
|
|
12411
|
+
const nodeMap = new Map();
|
|
12412
|
+
const declMap = new Map();
|
|
12413
|
+
const variableDecl = withLoc({
|
|
12414
|
+
type: "VariableDeclaration",
|
|
12415
|
+
declarations: [],
|
|
12416
|
+
kind: "var",
|
|
12417
|
+
}, func.node.body);
|
|
12418
|
+
variableDecl.end = variableDecl.start;
|
|
12419
|
+
variableDecl.loc.end = variableDecl.loc.start;
|
|
12420
|
+
candidates.forEach((s, decl) => {
|
|
12421
|
+
let name;
|
|
12422
|
+
let i = 0;
|
|
12423
|
+
do {
|
|
12424
|
+
name = `pre_${declName(decl)}${i ? "_" + i : ""}`;
|
|
12425
|
+
if (!identifiers.has(name))
|
|
12426
|
+
break;
|
|
12427
|
+
i++;
|
|
12428
|
+
} while (true);
|
|
12429
|
+
declMap.set(decl, name);
|
|
12430
|
+
variableDecl.declarations.push(withLoc({
|
|
12431
|
+
type: "VariableDeclarator",
|
|
12432
|
+
id: withLoc({ type: "Identifier", name }, variableDecl),
|
|
12433
|
+
kind: "var",
|
|
12434
|
+
}, variableDecl));
|
|
12435
|
+
s.ant.forEach((event) => {
|
|
12436
|
+
const events = nodeMap.get(event.node);
|
|
12437
|
+
if (!events) {
|
|
12438
|
+
nodeMap.set(event.node, [event]);
|
|
12439
|
+
}
|
|
12440
|
+
else {
|
|
12441
|
+
events.push(event);
|
|
12442
|
+
}
|
|
12443
|
+
});
|
|
12444
|
+
});
|
|
12445
|
+
applyReplacements(func.node, nodeMap, declMap);
|
|
12446
|
+
func.node.body.body.unshift(variableDecl);
|
|
12447
|
+
}
|
|
12448
|
+
}
|
|
12449
|
+
function unhandledExpression(node) {
|
|
12450
|
+
throw new Error(`Unhandled expression type: ${node.type}`);
|
|
12451
|
+
}
|
|
12452
|
+
function buildPREGraph(state, func) {
|
|
12453
|
+
const findDecl = (node) => {
|
|
12454
|
+
if (node.type === "Identifier" ||
|
|
12455
|
+
(node.type === "MemberExpression" && !node.computed)) {
|
|
12456
|
+
const [, results] = state.lookup(node);
|
|
12457
|
+
if (results &&
|
|
12458
|
+
results.length === 1 &&
|
|
12459
|
+
results[0].parent?.type != "BlockStatement" &&
|
|
12460
|
+
results[0].results.length === 1 &&
|
|
12461
|
+
results[0].results[0].type === "VariableDeclarator") {
|
|
12462
|
+
return results[0].results[0];
|
|
12463
|
+
}
|
|
12464
|
+
}
|
|
12465
|
+
return null;
|
|
12466
|
+
};
|
|
12467
|
+
const literals = new Map();
|
|
12468
|
+
const identifiers = new Set();
|
|
12469
|
+
const liveDefs = new Map();
|
|
12470
|
+
const liveStmts = new Map();
|
|
12471
|
+
const liveDef = (def, stmt) => {
|
|
12472
|
+
let curNodes = liveDefs.get(def);
|
|
12473
|
+
if (!curNodes) {
|
|
12474
|
+
liveDefs.set(def, (curNodes = new Set()));
|
|
12475
|
+
}
|
|
12476
|
+
curNodes.add(stmt);
|
|
12477
|
+
let defs = liveStmts.get(stmt);
|
|
12478
|
+
if (!defs) {
|
|
12479
|
+
liveStmts.set(stmt, (defs = new Map()));
|
|
12480
|
+
}
|
|
12481
|
+
defs.set(def, (defs.get(def) || 0) + 1);
|
|
12482
|
+
};
|
|
12483
|
+
return {
|
|
12484
|
+
identifiers,
|
|
12485
|
+
graph: buildReducedGraph(state, func, (node, stmt, mayThrow) => {
|
|
12486
|
+
const defs = liveStmts.get(node);
|
|
12487
|
+
if (defs) {
|
|
12488
|
+
liveStmts.delete(node);
|
|
12489
|
+
defs.forEach((count, def) => {
|
|
12490
|
+
if (count > 1) {
|
|
12491
|
+
defs.set(def, count--);
|
|
12492
|
+
return;
|
|
12493
|
+
}
|
|
12494
|
+
const v = liveDefs.get(def);
|
|
12495
|
+
if (!v || !v.has(node)) {
|
|
12496
|
+
throw new Error(`No stmt in liveDef for ${def ? declFullName(def) : "null"}`);
|
|
12497
|
+
}
|
|
12498
|
+
v.delete(node);
|
|
12499
|
+
if (!v.size) {
|
|
12500
|
+
liveDefs.delete(def);
|
|
12501
|
+
}
|
|
12502
|
+
});
|
|
12503
|
+
}
|
|
12504
|
+
switch (node.type) {
|
|
12505
|
+
case "BinaryExpression":
|
|
12506
|
+
case "UnaryExpression":
|
|
12507
|
+
case "SizedArrayExpression":
|
|
12508
|
+
case "ArrayExpression":
|
|
12509
|
+
case "ObjectExpression":
|
|
12510
|
+
case "ThisExpression":
|
|
12511
|
+
case "LogicalExpression":
|
|
12512
|
+
case "ConditionalExpression":
|
|
12513
|
+
case "SequenceExpression":
|
|
12514
|
+
case "ParenthesizedExpression":
|
|
12515
|
+
break;
|
|
12516
|
+
case "Literal":
|
|
12517
|
+
if (!node.value && refCost(node) > LocalRefCost) {
|
|
12518
|
+
let decl = literals.get(node.value);
|
|
12519
|
+
if (!decl) {
|
|
12520
|
+
decl = node;
|
|
12521
|
+
literals.set(node.value, decl);
|
|
12522
|
+
}
|
|
12523
|
+
return {
|
|
12524
|
+
type: "ref",
|
|
12525
|
+
node,
|
|
12526
|
+
decl: decl,
|
|
12527
|
+
mayThrow,
|
|
12528
|
+
};
|
|
12529
|
+
}
|
|
12530
|
+
break;
|
|
12531
|
+
case "Identifier":
|
|
12532
|
+
identifiers.add(node.name);
|
|
12533
|
+
// fall through
|
|
12534
|
+
case "MemberExpression":
|
|
12535
|
+
{
|
|
12536
|
+
const decl = findDecl(node);
|
|
12537
|
+
if (decl && decl.type === "VariableDeclarator") {
|
|
12538
|
+
const defStmts = (decl.node.kind === "var" && liveDefs.get(null)) ||
|
|
12539
|
+
liveDefs.get(decl);
|
|
12540
|
+
if (defStmts) {
|
|
12541
|
+
break;
|
|
12542
|
+
/*
|
|
12543
|
+
// hold off on this for now. we need to communicate
|
|
12544
|
+
// which defs need to be fixed, which involves yet-another
|
|
12545
|
+
// table.
|
|
12546
|
+
|
|
12547
|
+
if (defStmts.size !== 1) break;
|
|
12548
|
+
const fixable = isFixableStmt([...defStmts][0]);
|
|
12549
|
+
if (fixable === false) break;
|
|
12550
|
+
cost += fixable;
|
|
12551
|
+
*/
|
|
12552
|
+
}
|
|
12553
|
+
return {
|
|
12554
|
+
type: "ref",
|
|
12555
|
+
node,
|
|
12556
|
+
decl,
|
|
12557
|
+
mayThrow,
|
|
12558
|
+
};
|
|
12559
|
+
}
|
|
12560
|
+
}
|
|
12561
|
+
break;
|
|
12562
|
+
case "VariableDeclarator": {
|
|
12563
|
+
const decl = findDecl(node.id.type === "BinaryExpression" ? node.id.left : node.id);
|
|
12564
|
+
if (decl) {
|
|
12565
|
+
liveDef(decl, stmt);
|
|
12566
|
+
return {
|
|
12567
|
+
type: "def",
|
|
12568
|
+
node,
|
|
12569
|
+
decl,
|
|
12570
|
+
mayThrow,
|
|
12571
|
+
};
|
|
12572
|
+
}
|
|
12573
|
+
break;
|
|
12574
|
+
}
|
|
12575
|
+
case "AssignmentExpression": {
|
|
12576
|
+
const decl = findDecl(node.left);
|
|
12577
|
+
if (decl) {
|
|
12578
|
+
liveDef(decl, stmt);
|
|
12579
|
+
return {
|
|
12580
|
+
type: "def",
|
|
12581
|
+
node,
|
|
12582
|
+
decl,
|
|
12583
|
+
mayThrow,
|
|
12584
|
+
};
|
|
12585
|
+
}
|
|
12586
|
+
break;
|
|
12587
|
+
}
|
|
12588
|
+
case "UpdateExpression": {
|
|
12589
|
+
const decl = findDecl(node.argument);
|
|
12590
|
+
if (decl) {
|
|
12591
|
+
liveDef(decl, stmt);
|
|
12592
|
+
return {
|
|
12593
|
+
type: "def",
|
|
12594
|
+
node,
|
|
12595
|
+
decl,
|
|
12596
|
+
mayThrow,
|
|
12597
|
+
};
|
|
12598
|
+
}
|
|
12599
|
+
break;
|
|
12600
|
+
}
|
|
12601
|
+
case "NewExpression":
|
|
12602
|
+
case "CallExpression":
|
|
12603
|
+
liveDef(null, stmt);
|
|
12604
|
+
return { type: "mod", node, mayThrow };
|
|
12605
|
+
default:
|
|
12606
|
+
if (!isExpression(node))
|
|
12607
|
+
break;
|
|
12608
|
+
unhandledExpression(node);
|
|
12609
|
+
}
|
|
12610
|
+
if (mayThrow) {
|
|
12611
|
+
return { type: "exn", node, mayThrow };
|
|
12612
|
+
}
|
|
12613
|
+
return null;
|
|
12614
|
+
}),
|
|
12615
|
+
};
|
|
12616
|
+
}
|
|
12617
|
+
function anticipatedDecls() {
|
|
12618
|
+
return new Map();
|
|
12619
|
+
}
|
|
12620
|
+
function cloneSet(ae) {
|
|
12621
|
+
return new Set(ae);
|
|
12622
|
+
}
|
|
12623
|
+
function mergeSet(a, b) {
|
|
12624
|
+
b.forEach((event) => a.add(event));
|
|
12625
|
+
}
|
|
12626
|
+
function equalSet(a, b) {
|
|
12627
|
+
if (a.size != b.size)
|
|
12628
|
+
return false;
|
|
12629
|
+
for (const item of a) {
|
|
12630
|
+
if (!b.has(item))
|
|
12631
|
+
return false;
|
|
12632
|
+
}
|
|
12633
|
+
return true;
|
|
12634
|
+
}
|
|
12635
|
+
function equalMap(a, b) {
|
|
12636
|
+
if (a.size != b.size)
|
|
12637
|
+
return false;
|
|
12638
|
+
for (const [item, value] of a) {
|
|
12639
|
+
if (b.get(item) !== value)
|
|
12640
|
+
return false;
|
|
12641
|
+
}
|
|
12642
|
+
return true;
|
|
12643
|
+
}
|
|
12644
|
+
function anticipatedState(node, events) {
|
|
12645
|
+
return { ant: events || new Set(), live: true, node, members: new Map() };
|
|
12646
|
+
}
|
|
12647
|
+
function cloneAnticipatedState(as) {
|
|
12648
|
+
return {
|
|
12649
|
+
ant: cloneSet(as.ant),
|
|
12650
|
+
live: as.live,
|
|
12651
|
+
node: as.node,
|
|
12652
|
+
members: new Map(as.members),
|
|
12653
|
+
};
|
|
12654
|
+
}
|
|
12655
|
+
function cloneAnticipatedDecls(ad) {
|
|
12656
|
+
const copy = anticipatedDecls();
|
|
12657
|
+
for (const [k, v] of ad) {
|
|
12658
|
+
copy.set(k, cloneAnticipatedState(v));
|
|
12659
|
+
}
|
|
12660
|
+
return copy;
|
|
12661
|
+
}
|
|
12662
|
+
function mergeAnticipatedDecls(a, b) {
|
|
12663
|
+
for (const [k, v] of b) {
|
|
12664
|
+
const ae = a.get(k);
|
|
12665
|
+
if (ae) {
|
|
12666
|
+
mergeSet(ae.ant, v.ant);
|
|
12667
|
+
v.members.forEach((live, block) => ae.members.set(block, live));
|
|
12668
|
+
if (v.live)
|
|
12669
|
+
ae.live = true;
|
|
12670
|
+
}
|
|
12671
|
+
else {
|
|
12672
|
+
a.set(k, cloneAnticipatedState(v));
|
|
12673
|
+
}
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
function equalStates(a, b) {
|
|
12677
|
+
if (a.size !== b.size)
|
|
12678
|
+
return false;
|
|
12679
|
+
for (const [k, ae] of a) {
|
|
12680
|
+
const be = b.get(k);
|
|
12681
|
+
if (!be ||
|
|
12682
|
+
be.live != ae.live ||
|
|
12683
|
+
!equalSet(ae.ant, be.ant) ||
|
|
12684
|
+
!equalMap(ae.members, be.members)) {
|
|
12685
|
+
return false;
|
|
12686
|
+
}
|
|
12687
|
+
}
|
|
12688
|
+
return true;
|
|
12689
|
+
}
|
|
12690
|
+
const LocalRefCost = 2;
|
|
12691
|
+
function refCost(node) {
|
|
12692
|
+
if (node.type === "Literal") {
|
|
12693
|
+
switch (typeof node.value) {
|
|
12694
|
+
case "string":
|
|
12695
|
+
return 5;
|
|
12696
|
+
case "number":
|
|
12697
|
+
return 5;
|
|
12698
|
+
case "boolean":
|
|
12699
|
+
return 2;
|
|
12700
|
+
default:
|
|
12701
|
+
if (node.value === null) {
|
|
12702
|
+
return 2;
|
|
12703
|
+
}
|
|
12704
|
+
return 0;
|
|
12705
|
+
}
|
|
12706
|
+
}
|
|
12707
|
+
// A read from a non-local identifier takes 8 bytes
|
|
12708
|
+
let cost = 8;
|
|
12709
|
+
if (node.type === "Identifier")
|
|
12710
|
+
return cost;
|
|
12711
|
+
while (true) {
|
|
12712
|
+
const next = node.object;
|
|
12713
|
+
if (next.type != "MemberExpression") {
|
|
12714
|
+
if (next.type != "ThisExpression") {
|
|
12715
|
+
cost += next.type === "Identifier" && next.name === "$" ? 4 : 6;
|
|
12716
|
+
}
|
|
12717
|
+
return cost;
|
|
12718
|
+
}
|
|
12719
|
+
node = next;
|
|
12720
|
+
cost += 6;
|
|
12721
|
+
}
|
|
12722
|
+
}
|
|
12723
|
+
function defCost(node) {
|
|
12724
|
+
return refCost(node) + 2;
|
|
12725
|
+
}
|
|
12726
|
+
function candidateBoundary(candState) {
|
|
12727
|
+
const boundary = new Set();
|
|
12728
|
+
candState.members.forEach((live, block) => {
|
|
12729
|
+
if (live && block !== candState.head) {
|
|
12730
|
+
if (block.preds) {
|
|
12731
|
+
block.preds.forEach((pred) => candState.members.has(pred) || boundary.add(pred));
|
|
12732
|
+
}
|
|
12733
|
+
}
|
|
12734
|
+
});
|
|
12735
|
+
if (candState.live) {
|
|
12736
|
+
if (!candState.head) {
|
|
12737
|
+
throw new Error(`Missing head`);
|
|
12738
|
+
}
|
|
12739
|
+
boundary.add(candState.head);
|
|
12740
|
+
}
|
|
12741
|
+
return boundary;
|
|
12742
|
+
}
|
|
12743
|
+
function candidateCost(candState) {
|
|
12744
|
+
let cost = 0;
|
|
12745
|
+
candState.ant.forEach((event) => {
|
|
12746
|
+
if (event.type === "ref") {
|
|
12747
|
+
cost -= refCost(candState.node) - LocalRefCost;
|
|
12748
|
+
}
|
|
12749
|
+
else {
|
|
12750
|
+
cost += defCost(candState.node);
|
|
12751
|
+
}
|
|
12752
|
+
});
|
|
12753
|
+
cost += defCost(candState.node) * candidateBoundary(candState).size;
|
|
12754
|
+
return cost;
|
|
12755
|
+
}
|
|
12756
|
+
function computeAttributes(head) {
|
|
12757
|
+
const order = getPostOrder(head);
|
|
12758
|
+
order.forEach((block, i) => {
|
|
12759
|
+
block.order = i;
|
|
12760
|
+
});
|
|
12761
|
+
if (logging) {
|
|
12762
|
+
order.forEach((block) => {
|
|
12763
|
+
console.log(block.order, `(${block.node ? block.node.loc?.start.line : "??"})`, `Preds: ${(block.preds || [])
|
|
12764
|
+
.map((block) => block.order)
|
|
12765
|
+
.join(", ")}`);
|
|
12766
|
+
if (block.events) {
|
|
12767
|
+
block.events.forEach((event) => event.type !== "exn" &&
|
|
12768
|
+
console.log(` ${event.type}: ${event.decl ? declFullName(event.decl) : "??"}`));
|
|
12769
|
+
}
|
|
12770
|
+
console.log(`Succs: ${(block.succs || [])
|
|
12771
|
+
.map((block) => block.order)
|
|
12772
|
+
.join(", ")} ExSucc: ${block.exsucc ? block.exsucc.order : ""}`);
|
|
12773
|
+
});
|
|
12774
|
+
}
|
|
12775
|
+
const enqueued = new Set();
|
|
12776
|
+
const queue = new priorityqueuejs((b, a) => (a.order || 0) - (b.order || 0));
|
|
12777
|
+
const enqueue = (block) => {
|
|
12778
|
+
if (!enqueued.has(block)) {
|
|
12779
|
+
enqueued.add(block);
|
|
12780
|
+
queue.enq(block);
|
|
12781
|
+
}
|
|
12782
|
+
};
|
|
12783
|
+
const dequeue = () => {
|
|
12784
|
+
const block = queue.deq();
|
|
12785
|
+
enqueued.delete(block);
|
|
12786
|
+
return block;
|
|
12787
|
+
};
|
|
12788
|
+
const blockStates = [];
|
|
12789
|
+
/*
|
|
12790
|
+
Algorithm
|
|
12791
|
+
=========
|
|
12792
|
+
|
|
12793
|
+
Process blocks in post-order, and the events in reverse
|
|
12794
|
+
order to collect the AnticipatedState at the start of each
|
|
12795
|
+
Block.
|
|
12796
|
+
|
|
12797
|
+
Then for each EventDecl find the best starting block.
|
|
12798
|
+
*/
|
|
12799
|
+
const modMap = new Map();
|
|
12800
|
+
const getMod = (event, decl, id) => {
|
|
12801
|
+
if (id.type !== "Identifier" && id.type !== "MemberExpression") {
|
|
12802
|
+
throw new Error("Trying to modify a non-variable");
|
|
12803
|
+
}
|
|
12804
|
+
let eventMap = modMap.get(event);
|
|
12805
|
+
if (!eventMap) {
|
|
12806
|
+
modMap.set(event, (eventMap = new Map()));
|
|
12807
|
+
}
|
|
12808
|
+
let result = eventMap.get(decl);
|
|
12809
|
+
if (!result) {
|
|
12810
|
+
result = {
|
|
12811
|
+
type: "mod",
|
|
12812
|
+
node: event.node,
|
|
12813
|
+
decl,
|
|
12814
|
+
id,
|
|
12815
|
+
mayThrow: event.mayThrow,
|
|
12816
|
+
};
|
|
12817
|
+
eventMap.set(decl, result);
|
|
12818
|
+
}
|
|
12819
|
+
return result;
|
|
12820
|
+
};
|
|
12821
|
+
order.forEach((block) => enqueue(block));
|
|
12822
|
+
while (queue.size()) {
|
|
12823
|
+
const top = dequeue();
|
|
12824
|
+
if (top.order === undefined) {
|
|
12825
|
+
throw new Error(`Unreachable block was visited!`);
|
|
12826
|
+
}
|
|
12827
|
+
const curState = (top.succs &&
|
|
12828
|
+
top.succs.reduce((blockState, succ) => {
|
|
12829
|
+
const succState = blockStates[succ.order];
|
|
12830
|
+
if (succState) {
|
|
12831
|
+
if (!blockState) {
|
|
12832
|
+
blockState = cloneAnticipatedDecls(succState);
|
|
12833
|
+
}
|
|
12834
|
+
else {
|
|
12835
|
+
mergeAnticipatedDecls(blockState, succState);
|
|
12836
|
+
}
|
|
12837
|
+
}
|
|
12838
|
+
return blockState;
|
|
12839
|
+
}, null)) ||
|
|
12840
|
+
anticipatedDecls();
|
|
12841
|
+
if (top.events) {
|
|
12842
|
+
for (let i = top.events.length; i--;) {
|
|
12843
|
+
const event = top.events[i];
|
|
12844
|
+
if (event.mayThrow && top.exsucc) {
|
|
12845
|
+
const succState = blockStates[top.exsucc.order];
|
|
12846
|
+
if (succState) {
|
|
12847
|
+
mergeAnticipatedDecls(curState, succState);
|
|
12848
|
+
}
|
|
12849
|
+
}
|
|
12850
|
+
switch (event.type) {
|
|
12851
|
+
case "ref": {
|
|
12852
|
+
let candidates = curState.get(event.decl);
|
|
12853
|
+
if (!candidates) {
|
|
12854
|
+
candidates = anticipatedState(event.node);
|
|
12855
|
+
curState.set(event.decl, candidates);
|
|
12856
|
+
}
|
|
12857
|
+
candidates.ant.add(event);
|
|
12858
|
+
candidates.live = true;
|
|
12859
|
+
break;
|
|
12860
|
+
}
|
|
12861
|
+
case "mod": {
|
|
12862
|
+
curState.forEach((candidates, decl) => {
|
|
12863
|
+
if (decl.type === "VariableDeclarator" &&
|
|
12864
|
+
decl.node.kind === "var" &&
|
|
12865
|
+
candidates.live) {
|
|
12866
|
+
candidates.ant.add(getMod(event, decl, candidates.node));
|
|
12867
|
+
candidates.live = false;
|
|
12868
|
+
}
|
|
12869
|
+
});
|
|
12870
|
+
break;
|
|
12871
|
+
}
|
|
12872
|
+
case "def": {
|
|
12873
|
+
let candidates = curState.get(event.decl);
|
|
12874
|
+
const isUpdate = event.node.type === "UpdateExpression" ||
|
|
12875
|
+
(event.node.type === "AssignmentExpression" &&
|
|
12876
|
+
event.node.operator !== "=");
|
|
12877
|
+
if (!candidates) {
|
|
12878
|
+
const target = event.node.type === "AssignmentExpression"
|
|
12879
|
+
? event.node.left
|
|
12880
|
+
: event.node.type === "UpdateExpression"
|
|
12881
|
+
? event.node.argument
|
|
12882
|
+
: event.node.id.type === "BinaryExpression"
|
|
12883
|
+
? event.node.id.left
|
|
12884
|
+
: event.node.id;
|
|
12885
|
+
candidates = anticipatedState(target);
|
|
12886
|
+
curState.set(event.decl, candidates);
|
|
12887
|
+
}
|
|
12888
|
+
if (isUpdate || candidates.live) {
|
|
12889
|
+
candidates.ant.add(event);
|
|
12890
|
+
}
|
|
12891
|
+
if (!isUpdate) {
|
|
12892
|
+
candidates.live = false;
|
|
12893
|
+
}
|
|
12894
|
+
break;
|
|
12895
|
+
}
|
|
12896
|
+
}
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
curState.forEach((antState) => {
|
|
12900
|
+
antState.head = top;
|
|
12901
|
+
antState.members.set(top, antState.live);
|
|
12902
|
+
});
|
|
12903
|
+
const oldState = blockStates[top.order];
|
|
12904
|
+
if (oldState && equalStates(oldState, curState)) {
|
|
12905
|
+
continue;
|
|
12906
|
+
}
|
|
12907
|
+
blockStates[top.order] = curState;
|
|
12908
|
+
if (top.preds) {
|
|
12909
|
+
top.preds.forEach((pred) => enqueue(pred));
|
|
12910
|
+
}
|
|
12911
|
+
}
|
|
12912
|
+
const candidateDecls = anticipatedDecls();
|
|
12913
|
+
blockStates.forEach((blockState, i) => {
|
|
12914
|
+
blockState &&
|
|
12915
|
+
blockState.forEach((events, decl) => {
|
|
12916
|
+
const cost = candidateCost(events);
|
|
12917
|
+
if (cost >= 0)
|
|
12918
|
+
return;
|
|
12919
|
+
const existing = candidateDecls.get(decl);
|
|
12920
|
+
if (!existing || candidateCost(existing) > cost) {
|
|
12921
|
+
const boundary = candidateBoundary(events);
|
|
12922
|
+
if (!Array.from(boundary).every((block) => {
|
|
12923
|
+
if (block !== events.head && block.events) {
|
|
12924
|
+
if (events.node.type === "Literal") {
|
|
12925
|
+
return false;
|
|
12926
|
+
}
|
|
12927
|
+
let i = block.events.length;
|
|
12928
|
+
while (i--) {
|
|
12929
|
+
const event = block.events[i];
|
|
12930
|
+
if (event.type === "def" || event.type === "mod") {
|
|
12931
|
+
events.ant.add({
|
|
12932
|
+
type: "mod",
|
|
12933
|
+
node: event.node,
|
|
12934
|
+
decl,
|
|
12935
|
+
id: events.node,
|
|
12936
|
+
mayThrow: false,
|
|
12937
|
+
});
|
|
12938
|
+
events.members.set(block, false);
|
|
12939
|
+
return true;
|
|
12940
|
+
}
|
|
12941
|
+
}
|
|
12942
|
+
}
|
|
12943
|
+
const node = block.node;
|
|
12944
|
+
if (!node)
|
|
12945
|
+
return false;
|
|
12946
|
+
events.ant.add({
|
|
12947
|
+
type: "mod",
|
|
12948
|
+
node: node.type === "FunctionDeclaration" ? node.body : node,
|
|
12949
|
+
before: true,
|
|
12950
|
+
decl,
|
|
12951
|
+
id: events.node,
|
|
12952
|
+
mayThrow: false,
|
|
12953
|
+
});
|
|
12954
|
+
events.members.set(block, false);
|
|
12955
|
+
return true;
|
|
12956
|
+
})) {
|
|
12957
|
+
return;
|
|
12958
|
+
}
|
|
12959
|
+
events.live = false;
|
|
12960
|
+
if (candidateCost(events) != cost) {
|
|
12961
|
+
throw new Error(`cost of block ${i} changed`);
|
|
12962
|
+
}
|
|
12963
|
+
candidateDecls.set(decl, events);
|
|
12964
|
+
}
|
|
12965
|
+
});
|
|
12966
|
+
});
|
|
12967
|
+
if (candidateDecls.size) {
|
|
12968
|
+
return candidateDecls;
|
|
12969
|
+
}
|
|
12970
|
+
return null;
|
|
12971
|
+
}
|
|
12972
|
+
/*
|
|
12973
|
+
* Determine the cost of fixing a def under a statement.
|
|
12974
|
+
*
|
|
12975
|
+
* eg:
|
|
12976
|
+
*
|
|
12977
|
+
* if (foo()) {
|
|
12978
|
+
* bar(X.y);
|
|
12979
|
+
* } else {
|
|
12980
|
+
* baz(X.y);
|
|
12981
|
+
* }
|
|
12982
|
+
*
|
|
12983
|
+
* Here, we could pull out X.y as a local, but if foo might modify
|
|
12984
|
+
* X.y, we have nowhere to insert the temporary. But we can rewrite
|
|
12985
|
+
* it as:
|
|
12986
|
+
*
|
|
12987
|
+
* var tmp = foo();
|
|
12988
|
+
* if (tmp) {
|
|
12989
|
+
* bar(X.y);
|
|
12990
|
+
* } else {
|
|
12991
|
+
* baz(X.y);
|
|
12992
|
+
* }
|
|
12993
|
+
*
|
|
12994
|
+
* and now we can insert a temporary before the if, but it costs
|
|
12995
|
+
* 4 bytes to do so.
|
|
12996
|
+
*
|
|
12997
|
+
* We can do the same for switch statements unless (ugh!)
|
|
12998
|
+
* the cases might modify the decl too.
|
|
12999
|
+
*
|
|
13000
|
+
* eg
|
|
13001
|
+
*
|
|
13002
|
+
* switch (foo()) {
|
|
13003
|
+
* case bar(): ...
|
|
13004
|
+
* }
|
|
13005
|
+
*
|
|
13006
|
+
*/
|
|
13007
|
+
function _isFixableStmt(node) {
|
|
13008
|
+
switch (node.type) {
|
|
13009
|
+
case "IfStatement":
|
|
13010
|
+
return 4;
|
|
13011
|
+
case "SwitchStatement":
|
|
13012
|
+
if (node.cases.every((c) => !c.test ||
|
|
13013
|
+
c.test.type === "Literal" ||
|
|
13014
|
+
c.test.type === "Identifier" ||
|
|
13015
|
+
c.test.type === "InstanceOfCase" ||
|
|
13016
|
+
(c.test.type === "UnaryExpression" && c.test.operator === ":"))) {
|
|
13017
|
+
return 4;
|
|
13018
|
+
}
|
|
13019
|
+
break;
|
|
13020
|
+
}
|
|
13021
|
+
return false;
|
|
13022
|
+
}
|
|
13023
|
+
function applyReplacements(func, nodeMap, declMap) {
|
|
13024
|
+
const ident = (name, node) => {
|
|
13025
|
+
return withLoc({ type: "Identifier", name }, node);
|
|
13026
|
+
};
|
|
13027
|
+
const pendingMap = new Map();
|
|
13028
|
+
const stmtStack = [func];
|
|
13029
|
+
traverseAst(func, (node) => {
|
|
13030
|
+
if (isStatement(node)) {
|
|
13031
|
+
stmtStack.push(node);
|
|
13032
|
+
}
|
|
13033
|
+
}, (node) => {
|
|
13034
|
+
const stmt = stmtStack[stmtStack.length - 1];
|
|
13035
|
+
if (stmt === node)
|
|
13036
|
+
stmtStack.pop();
|
|
13037
|
+
const events = nodeMap.get(node);
|
|
13038
|
+
if (events) {
|
|
13039
|
+
if (events.length === 1) {
|
|
13040
|
+
if (events[0].type === "ref") {
|
|
13041
|
+
if (node.type !== "Identifier" &&
|
|
13042
|
+
node.type !== "MemberExpression" &&
|
|
13043
|
+
node.type !== "Literal") {
|
|
13044
|
+
throw new Error(`Ref found, but wrong type of node: ${node.type}`);
|
|
13045
|
+
}
|
|
13046
|
+
const name = declMap.get(events[0].decl);
|
|
13047
|
+
if (!name) {
|
|
13048
|
+
throw new Error(`No replacement found for "${(0,external_api_cjs_namespaceObject.formatAst)(node)}"`);
|
|
13049
|
+
}
|
|
13050
|
+
return ident(name, node);
|
|
13051
|
+
}
|
|
13052
|
+
else if (events[0].type === "def") {
|
|
13053
|
+
if (node.type !== "AssignmentExpression" &&
|
|
13054
|
+
node.type !== "UpdateExpression") {
|
|
13055
|
+
throw new Error(`Def found, but wrong type of node: ${node.type}`);
|
|
13056
|
+
}
|
|
13057
|
+
const target = node.type === "AssignmentExpression"
|
|
13058
|
+
? node.left
|
|
13059
|
+
: node.argument;
|
|
13060
|
+
const name = declMap.get(events[0].decl);
|
|
13061
|
+
if (!name) {
|
|
13062
|
+
throw new Error(`No replacement found for "${(0,external_api_cjs_namespaceObject.formatAst)(target)}"`);
|
|
13063
|
+
}
|
|
13064
|
+
const id = ident(name, target);
|
|
13065
|
+
const assign = withLoc({
|
|
13066
|
+
type: "AssignmentExpression",
|
|
13067
|
+
left: target,
|
|
13068
|
+
right: { ...id },
|
|
13069
|
+
operator: "=",
|
|
13070
|
+
}, node);
|
|
13071
|
+
if (node.type === "AssignmentExpression") {
|
|
13072
|
+
node.left = id;
|
|
13073
|
+
}
|
|
13074
|
+
else {
|
|
13075
|
+
node.argument = id;
|
|
13076
|
+
}
|
|
13077
|
+
return withLoc({ type: "SequenceExpression", expressions: [node, assign] }, node);
|
|
13078
|
+
}
|
|
13079
|
+
}
|
|
13080
|
+
events.forEach((event) => {
|
|
13081
|
+
if (event.type !== "mod") {
|
|
13082
|
+
throw new Error(`Unexpected ${event.type} found amongst multiple events`);
|
|
13083
|
+
}
|
|
13084
|
+
if (!event.decl) {
|
|
13085
|
+
throw new Error(`Unexpected null decl on mod event`);
|
|
13086
|
+
}
|
|
13087
|
+
let pending = pendingMap.get(stmt);
|
|
13088
|
+
if (!pending) {
|
|
13089
|
+
pendingMap.set(stmt, (pending = new Set()));
|
|
13090
|
+
}
|
|
13091
|
+
pending.add(event);
|
|
13092
|
+
});
|
|
13093
|
+
}
|
|
13094
|
+
const pending = pendingMap.get(node);
|
|
13095
|
+
if (node.type === "SequenceExpression") {
|
|
13096
|
+
if (pending) {
|
|
13097
|
+
throw new Error(`Unexpected pending list at SequenceExpression`);
|
|
13098
|
+
}
|
|
13099
|
+
for (let i = node.expressions.length; i--;) {
|
|
13100
|
+
const ni = node.expressions[i];
|
|
13101
|
+
if (ni.type === "SequenceExpression") {
|
|
13102
|
+
node.expressions.splice(i, 1, ...ni.expressions);
|
|
13103
|
+
}
|
|
13104
|
+
}
|
|
13105
|
+
}
|
|
13106
|
+
const applyPending = (results, locNode) => {
|
|
13107
|
+
const target = results.length === 1 && results[0].type === "BlockStatement"
|
|
13108
|
+
? results[0]
|
|
13109
|
+
: null;
|
|
13110
|
+
pendingMap.delete(node);
|
|
13111
|
+
pending.forEach((event) => {
|
|
13112
|
+
const decl = event.decl;
|
|
13113
|
+
const name = declMap.get(decl);
|
|
13114
|
+
if (!name) {
|
|
13115
|
+
throw new Error(`No replacement found for "${declFullName(decl)}"`);
|
|
13116
|
+
}
|
|
13117
|
+
if (!event.id) {
|
|
13118
|
+
throw new Error(`Missing id for mod event for "${declFullName(decl)}"`);
|
|
13119
|
+
}
|
|
13120
|
+
const rhs = withLocDeep(event.id, locNode, locNode);
|
|
13121
|
+
rhs.end = rhs.start;
|
|
13122
|
+
if (rhs.loc) {
|
|
13123
|
+
rhs.loc.end = rhs.loc.start;
|
|
13124
|
+
}
|
|
13125
|
+
const insertion = withLoc({
|
|
13126
|
+
type: "ExpressionStatement",
|
|
13127
|
+
expression: withLoc({
|
|
13128
|
+
type: "AssignmentExpression",
|
|
13129
|
+
left: ident(name, rhs),
|
|
13130
|
+
right: rhs,
|
|
13131
|
+
operator: "=",
|
|
13132
|
+
}, rhs),
|
|
13133
|
+
}, rhs);
|
|
13134
|
+
if (event.type === "mod" && event.before) {
|
|
13135
|
+
if (target) {
|
|
13136
|
+
target.body.unshift(insertion);
|
|
13137
|
+
}
|
|
13138
|
+
else {
|
|
13139
|
+
results.unshift(insertion);
|
|
13140
|
+
}
|
|
13141
|
+
}
|
|
13142
|
+
else {
|
|
13143
|
+
results.push(insertion);
|
|
13144
|
+
}
|
|
13145
|
+
});
|
|
13146
|
+
return results.length === 1 ? null : results;
|
|
13147
|
+
};
|
|
13148
|
+
if (node.type === "ExpressionStatement" &&
|
|
13149
|
+
node.expression.type === "SequenceExpression") {
|
|
13150
|
+
const results = [];
|
|
13151
|
+
node.expression.expressions.forEach((expression) => {
|
|
13152
|
+
results.push({ ...node, expression });
|
|
13153
|
+
});
|
|
13154
|
+
if (!pending) {
|
|
13155
|
+
return results;
|
|
13156
|
+
}
|
|
13157
|
+
return applyPending(results, node);
|
|
13158
|
+
}
|
|
13159
|
+
if (pending) {
|
|
13160
|
+
return applyPending([node], node);
|
|
13161
|
+
}
|
|
13162
|
+
return null;
|
|
13163
|
+
});
|
|
13164
|
+
}
|
|
13165
|
+
|
|
13166
|
+
;// CONCATENATED MODULE: ./src/visitor.ts
|
|
13167
|
+
|
|
13168
|
+
function visitReferences(state, ast, name, defn, callback) {
|
|
13169
|
+
const checkResults = ([name, results], node) => {
|
|
13170
|
+
if (name && results) {
|
|
13171
|
+
if (!defn || (0,external_api_cjs_namespaceObject.sameLookupResult)(results, defn)) {
|
|
13172
|
+
if (callback(node, results, false) === false) {
|
|
13173
|
+
return [];
|
|
13174
|
+
}
|
|
13175
|
+
}
|
|
13176
|
+
}
|
|
13177
|
+
else if (defn === false) {
|
|
13178
|
+
if (callback(node, [], results === null) === false) {
|
|
13179
|
+
return [];
|
|
13180
|
+
}
|
|
13181
|
+
}
|
|
13182
|
+
return null;
|
|
13183
|
+
};
|
|
13184
|
+
state.pre = (node) => {
|
|
13185
|
+
switch (node.type) {
|
|
13186
|
+
case "AttributeList":
|
|
13187
|
+
return [];
|
|
13188
|
+
case "UnaryExpression":
|
|
13189
|
+
// a bare symbol isn't a reference
|
|
13190
|
+
if (node.operator === ":")
|
|
13191
|
+
return [];
|
|
13192
|
+
break;
|
|
13193
|
+
case "BinaryExpression":
|
|
13194
|
+
/*
|
|
13195
|
+
* `expr has :symbol` can be treated as a reference
|
|
13196
|
+
* to expr.symbol.
|
|
13197
|
+
*/
|
|
13198
|
+
if (node.operator === "has") {
|
|
13199
|
+
if (node.right.type === "UnaryExpression" &&
|
|
13200
|
+
node.right.operator === ":") {
|
|
13201
|
+
if (!name || node.right.argument.name === name) {
|
|
13202
|
+
return checkResults(state.lookup({
|
|
13203
|
+
type: "MemberExpression",
|
|
13204
|
+
object: node.left,
|
|
13205
|
+
property: node.right.argument,
|
|
13206
|
+
computed: false,
|
|
13207
|
+
}), node.right.argument);
|
|
13208
|
+
}
|
|
13209
|
+
}
|
|
13210
|
+
}
|
|
13211
|
+
break;
|
|
13212
|
+
case "CallExpression":
|
|
13213
|
+
// A call expression whose callee is an identifier is looked
|
|
13214
|
+
// up as a non-local. ie even if there's a same named local,
|
|
13215
|
+
// it will be ignored, and the lookup will start as if the
|
|
13216
|
+
// call had been written self.foo() rather than foo().
|
|
13217
|
+
if (node.callee.type === "Identifier") {
|
|
13218
|
+
if (!name || node.callee.name === name) {
|
|
13219
|
+
/* ignore return value */
|
|
13220
|
+
checkResults(state.lookupNonlocal(node.callee), node.callee);
|
|
13221
|
+
}
|
|
13222
|
+
return ["arguments"];
|
|
13223
|
+
}
|
|
13224
|
+
break;
|
|
13225
|
+
case "Identifier":
|
|
13226
|
+
if (!name || node.name === name) {
|
|
13227
|
+
return checkResults(state.lookup(node), node);
|
|
13228
|
+
}
|
|
13229
|
+
break;
|
|
13230
|
+
case "MemberExpression":
|
|
13231
|
+
if (!node.computed && node.property.type === "Identifier") {
|
|
13232
|
+
if (!name || node.property.name === name) {
|
|
13233
|
+
return checkResults(state.lookup(node), node) || ["object"];
|
|
13234
|
+
}
|
|
13235
|
+
return ["object"];
|
|
13236
|
+
}
|
|
13237
|
+
break;
|
|
13238
|
+
case "MethodDefinition": {
|
|
13239
|
+
if (!state.inType) {
|
|
13240
|
+
throw new Error("Method definition outside of type!");
|
|
13241
|
+
}
|
|
13242
|
+
if (node.params) {
|
|
13243
|
+
node.params.forEach((param) => {
|
|
13244
|
+
if (param.type == "BinaryExpression") {
|
|
13245
|
+
state.traverse(param.right);
|
|
13246
|
+
state.inType = true;
|
|
13247
|
+
}
|
|
13248
|
+
});
|
|
11612
13249
|
}
|
|
11613
13250
|
return ["returnType"];
|
|
11614
13251
|
}
|
|
@@ -11628,6 +13265,7 @@ function visitReferences(state, ast, name, defn, callback) {
|
|
|
11628
13265
|
|
|
11629
13266
|
|
|
11630
13267
|
|
|
13268
|
+
|
|
11631
13269
|
function collectClassInfo(state) {
|
|
11632
13270
|
const toybox = state.stack[0].decls["Toybox"][0];
|
|
11633
13271
|
const lang = toybox.decls["Lang"][0];
|
|
@@ -11928,7 +13566,50 @@ function getNodeValue(node) {
|
|
|
11928
13566
|
}
|
|
11929
13567
|
return [node, type];
|
|
11930
13568
|
}
|
|
11931
|
-
function
|
|
13569
|
+
function fullTypeName(state, tsp) {
|
|
13570
|
+
if (typeof tsp.name === "string") {
|
|
13571
|
+
return tsp.name;
|
|
13572
|
+
}
|
|
13573
|
+
const [, results] = state.lookupType(tsp.name);
|
|
13574
|
+
if (results && results.length === 1 && results[0].results.length === 1) {
|
|
13575
|
+
const result = results[0].results[0];
|
|
13576
|
+
if ((0,external_api_cjs_namespaceObject.isStateNode)(result)) {
|
|
13577
|
+
return result.fullName;
|
|
13578
|
+
}
|
|
13579
|
+
}
|
|
13580
|
+
return null;
|
|
13581
|
+
}
|
|
13582
|
+
function isBooleanExpression(state, node) {
|
|
13583
|
+
switch (node.type) {
|
|
13584
|
+
case "Literal":
|
|
13585
|
+
return typeof node.value === "boolean";
|
|
13586
|
+
case "BinaryExpression":
|
|
13587
|
+
switch (node.operator) {
|
|
13588
|
+
case "==":
|
|
13589
|
+
case "!=":
|
|
13590
|
+
case "<=":
|
|
13591
|
+
case ">=":
|
|
13592
|
+
case "<":
|
|
13593
|
+
case ">":
|
|
13594
|
+
return true;
|
|
13595
|
+
case "as":
|
|
13596
|
+
return node.right.ts.length === 1 &&
|
|
13597
|
+
node.right.ts[0].type === "TypeSpecPart" &&
|
|
13598
|
+
node.right.ts[0].name &&
|
|
13599
|
+
fullTypeName(state, node.right.ts[0]) === "$.Toybox.Lang.Boolean"
|
|
13600
|
+
? true
|
|
13601
|
+
: false;
|
|
13602
|
+
}
|
|
13603
|
+
return false;
|
|
13604
|
+
case "LogicalExpression":
|
|
13605
|
+
return (isBooleanExpression(state, node.left) &&
|
|
13606
|
+
isBooleanExpression(state, node.right));
|
|
13607
|
+
case "UnaryExpression":
|
|
13608
|
+
return node.operator === "!" && isBooleanExpression(state, node.argument);
|
|
13609
|
+
}
|
|
13610
|
+
return false;
|
|
13611
|
+
}
|
|
13612
|
+
function optimizeNode(state, node) {
|
|
11932
13613
|
switch (node.type) {
|
|
11933
13614
|
case "UnaryExpression": {
|
|
11934
13615
|
const [arg, type] = getNodeValue(node.argument);
|
|
@@ -11980,8 +13661,18 @@ function optimizeNode(node) {
|
|
|
11980
13661
|
"%": (left, right) => left % right,
|
|
11981
13662
|
"&": (left, right, type) => type === "Number" ? left & right : null,
|
|
11982
13663
|
"|": (left, right, type) => type === "Number" ? left | right : null,
|
|
13664
|
+
"^": (left, right, type) => type === "Number" ? left ^ right : null,
|
|
11983
13665
|
"<<": (left, right, type) => type === "Number" ? left << right : null,
|
|
11984
13666
|
">>": (left, right, type) => type === "Number" ? left >> right : null,
|
|
13667
|
+
"==": (left, right) => left == right,
|
|
13668
|
+
"!=": (left, right) => left != right,
|
|
13669
|
+
"<=": (left, right) => left <= right,
|
|
13670
|
+
">=": (left, right) => left >= right,
|
|
13671
|
+
"<": (left, right) => left < right,
|
|
13672
|
+
">": (left, right) => left > right,
|
|
13673
|
+
as: null,
|
|
13674
|
+
instanceof: null,
|
|
13675
|
+
has: null,
|
|
11985
13676
|
};
|
|
11986
13677
|
const op = operators[node.operator];
|
|
11987
13678
|
if (op) {
|
|
@@ -11989,11 +13680,17 @@ function optimizeNode(node) {
|
|
|
11989
13680
|
const [right, right_type] = getNodeValue(node.right);
|
|
11990
13681
|
if (!left || !right)
|
|
11991
13682
|
break;
|
|
13683
|
+
let value = null;
|
|
11992
13684
|
if (left_type != right_type ||
|
|
11993
13685
|
(left_type != "Number" && left_type != "Long")) {
|
|
11994
|
-
|
|
13686
|
+
if (node.operator !== "==" && node.operator !== "!=") {
|
|
13687
|
+
break;
|
|
13688
|
+
}
|
|
13689
|
+
value = operators[node.operator](left.value, right.value);
|
|
13690
|
+
}
|
|
13691
|
+
else {
|
|
13692
|
+
value = op(left.value, right.value, left_type);
|
|
11995
13693
|
}
|
|
11996
|
-
const value = op(left.value, right.value, left_type);
|
|
11997
13694
|
if (value === null)
|
|
11998
13695
|
break;
|
|
11999
13696
|
return {
|
|
@@ -12004,15 +13701,47 @@ function optimizeNode(node) {
|
|
|
12004
13701
|
}
|
|
12005
13702
|
break;
|
|
12006
13703
|
}
|
|
13704
|
+
case "LogicalExpression": {
|
|
13705
|
+
const [left, left_type] = getNodeValue(node.left);
|
|
13706
|
+
if (!left)
|
|
13707
|
+
break;
|
|
13708
|
+
const falsy = left.value === false ||
|
|
13709
|
+
left.value === null ||
|
|
13710
|
+
(left.value === 0 && (left_type === "Number" || left_type === "Long"));
|
|
13711
|
+
if (falsy === (node.operator === "&&")) {
|
|
13712
|
+
return left;
|
|
13713
|
+
}
|
|
13714
|
+
if (left_type !== "Boolean" &&
|
|
13715
|
+
left_type !== "Number" &&
|
|
13716
|
+
left_type !== "Long") {
|
|
13717
|
+
break;
|
|
13718
|
+
}
|
|
13719
|
+
const [right, right_type] = getNodeValue(node.right);
|
|
13720
|
+
if (right && right_type === left_type) {
|
|
13721
|
+
if (left_type === "Boolean" || node.operator === "||") {
|
|
13722
|
+
return right;
|
|
13723
|
+
}
|
|
13724
|
+
if (node.operator !== "&&") {
|
|
13725
|
+
throw new Error(`Unexpected operator "${node.operator}"`);
|
|
13726
|
+
}
|
|
13727
|
+
return { ...node, type: "BinaryExpression", operator: "&" };
|
|
13728
|
+
}
|
|
13729
|
+
if (left_type === "Boolean") {
|
|
13730
|
+
if (isBooleanExpression(state, node.right)) {
|
|
13731
|
+
return node.right;
|
|
13732
|
+
}
|
|
13733
|
+
}
|
|
13734
|
+
break;
|
|
13735
|
+
}
|
|
12007
13736
|
case "FunctionDeclaration":
|
|
12008
|
-
if (node.body && evaluateFunction(node, null) !== false) {
|
|
13737
|
+
if (node.body && evaluateFunction(state, node, null) !== false) {
|
|
12009
13738
|
node.optimizable = true;
|
|
12010
13739
|
}
|
|
12011
13740
|
break;
|
|
12012
13741
|
}
|
|
12013
13742
|
return null;
|
|
12014
13743
|
}
|
|
12015
|
-
function evaluateFunction(func, args) {
|
|
13744
|
+
function evaluateFunction(state, func, args) {
|
|
12016
13745
|
if (!func.body || (args && args.length != func.params.length)) {
|
|
12017
13746
|
return false;
|
|
12018
13747
|
}
|
|
@@ -12051,7 +13780,7 @@ function evaluateFunction(func, args) {
|
|
|
12051
13780
|
}
|
|
12052
13781
|
// fall through;
|
|
12053
13782
|
default: {
|
|
12054
|
-
const repl = optimizeNode(node);
|
|
13783
|
+
const repl = optimizeNode(state, node);
|
|
12055
13784
|
if (repl && repl.type === "Literal")
|
|
12056
13785
|
return repl;
|
|
12057
13786
|
throw new Error("Didn't optimize");
|
|
@@ -12111,10 +13840,19 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12111
13840
|
if (!objects) {
|
|
12112
13841
|
return false;
|
|
12113
13842
|
}
|
|
12114
|
-
|
|
13843
|
+
let obj = getLiteralFromDecls(objects);
|
|
12115
13844
|
if (!obj) {
|
|
12116
13845
|
return false;
|
|
12117
13846
|
}
|
|
13847
|
+
while (obj.type === "BinaryExpression") {
|
|
13848
|
+
if (obj.left.type === "BinaryExpression" && obj.left.operator === "as") {
|
|
13849
|
+
obj = { ...obj, left: obj.left.left };
|
|
13850
|
+
}
|
|
13851
|
+
else {
|
|
13852
|
+
obj = { ...obj, left: { ...obj.left } };
|
|
13853
|
+
break;
|
|
13854
|
+
}
|
|
13855
|
+
}
|
|
12118
13856
|
inPlaceReplacement(node, obj);
|
|
12119
13857
|
return true;
|
|
12120
13858
|
};
|
|
@@ -12332,14 +14070,20 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12332
14070
|
return null;
|
|
12333
14071
|
};
|
|
12334
14072
|
state.post = (node) => {
|
|
12335
|
-
|
|
14073
|
+
const locals = topLocals();
|
|
14074
|
+
if (locals.node === node) {
|
|
12336
14075
|
state.localsStack.pop();
|
|
12337
14076
|
}
|
|
12338
|
-
const opt = optimizeNode(node);
|
|
14077
|
+
const opt = optimizeNode(state, node);
|
|
12339
14078
|
if (opt) {
|
|
12340
14079
|
return replace(opt, node);
|
|
12341
14080
|
}
|
|
12342
14081
|
switch (node.type) {
|
|
14082
|
+
case "BlockStatement":
|
|
14083
|
+
if (node.body.length === 1 && node.body[0].type === "BlockStatement") {
|
|
14084
|
+
node.body.splice(0, 1, ...node.body[0].body);
|
|
14085
|
+
}
|
|
14086
|
+
break;
|
|
12343
14087
|
case "ConditionalExpression":
|
|
12344
14088
|
case "IfStatement":
|
|
12345
14089
|
if (node.test.type === "Literal" &&
|
|
@@ -12349,6 +14093,12 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12349
14093
|
return false;
|
|
12350
14094
|
return replace(rep, rep);
|
|
12351
14095
|
}
|
|
14096
|
+
else if (node.type === "IfStatement" &&
|
|
14097
|
+
node.alternate &&
|
|
14098
|
+
node.alternate.type === "BlockStatement" &&
|
|
14099
|
+
!node.alternate.body.length) {
|
|
14100
|
+
delete node.alternate;
|
|
14101
|
+
}
|
|
12352
14102
|
break;
|
|
12353
14103
|
case "WhileStatement":
|
|
12354
14104
|
if (node.test.type === "Literal" && node.test.value === false) {
|
|
@@ -12376,6 +14126,48 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12376
14126
|
return { type: "Literal", value: null, raw: "null" };
|
|
12377
14127
|
}
|
|
12378
14128
|
break;
|
|
14129
|
+
case "VariableDeclaration": {
|
|
14130
|
+
const locals = topLocals();
|
|
14131
|
+
if (locals.map &&
|
|
14132
|
+
locals.node &&
|
|
14133
|
+
locals.node.type === "BlockStatement") {
|
|
14134
|
+
let results;
|
|
14135
|
+
const declarations = node.declarations;
|
|
14136
|
+
let i = 0;
|
|
14137
|
+
let j = 0;
|
|
14138
|
+
while (i < node.declarations.length) {
|
|
14139
|
+
const decl = declarations[i++];
|
|
14140
|
+
if (decl.init && decl.init.type === "CallExpression") {
|
|
14141
|
+
const inlined = optimizeCall(state, decl.init, decl);
|
|
14142
|
+
if (!inlined)
|
|
14143
|
+
continue;
|
|
14144
|
+
if (inlined.type != "BlockStatement") {
|
|
14145
|
+
throw new Error("Unexpected inlined result");
|
|
14146
|
+
}
|
|
14147
|
+
if (!results) {
|
|
14148
|
+
results = [];
|
|
14149
|
+
}
|
|
14150
|
+
delete decl.init;
|
|
14151
|
+
results.push(withLoc({
|
|
14152
|
+
...node,
|
|
14153
|
+
declarations: declarations.slice(j, i),
|
|
14154
|
+
}, j ? declarations[j] : null, decl.id));
|
|
14155
|
+
results.push(inlined);
|
|
14156
|
+
j = i;
|
|
14157
|
+
}
|
|
14158
|
+
}
|
|
14159
|
+
if (results) {
|
|
14160
|
+
if (j < i) {
|
|
14161
|
+
results.push({
|
|
14162
|
+
...node,
|
|
14163
|
+
declarations: declarations.slice(j, i),
|
|
14164
|
+
});
|
|
14165
|
+
}
|
|
14166
|
+
return results;
|
|
14167
|
+
}
|
|
14168
|
+
}
|
|
14169
|
+
break;
|
|
14170
|
+
}
|
|
12379
14171
|
case "ExpressionStatement":
|
|
12380
14172
|
if (node.expression.type === "CallExpression") {
|
|
12381
14173
|
return replace(optimizeCall(state, node.expression, node), node.expression);
|
|
@@ -12420,6 +14212,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12420
14212
|
});
|
|
12421
14213
|
delete state.pre;
|
|
12422
14214
|
delete state.post;
|
|
14215
|
+
state.allFunctions.forEach((fn) => sizeBasedPRE(state, fn));
|
|
12423
14216
|
const cleanup = (node) => {
|
|
12424
14217
|
switch (node.type) {
|
|
12425
14218
|
case "ThisExpression":
|
|
@@ -12545,7 +14338,7 @@ function optimizeCall(state, node, context) {
|
|
|
12545
14338
|
callee.optimizable &&
|
|
12546
14339
|
!callee.hasOverride &&
|
|
12547
14340
|
node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
|
|
12548
|
-
const ret = evaluateFunction(callee, node.arguments);
|
|
14341
|
+
const ret = evaluateFunction(state, callee, node.arguments);
|
|
12549
14342
|
if (ret) {
|
|
12550
14343
|
return ret;
|
|
12551
14344
|
}
|
|
@@ -12606,7 +14399,7 @@ function pragmaChecker(ast, diagnostics) {
|
|
|
12606
14399
|
if (quote == '"') {
|
|
12607
14400
|
return haystack.includes(needle);
|
|
12608
14401
|
}
|
|
12609
|
-
const re = new RegExp(needle);
|
|
14402
|
+
const re = new RegExp(needle.replace(/@(\d+)/g, "(pre_)?$1(_\\d+)?"));
|
|
12610
14403
|
return re.test(haystack);
|
|
12611
14404
|
};
|
|
12612
14405
|
next();
|
|
@@ -12616,8 +14409,10 @@ function pragmaChecker(ast, diagnostics) {
|
|
|
12616
14409
|
if (node.start && node.start >= (comment.end || Infinity)) {
|
|
12617
14410
|
const { kind, quote, needle } = matchers.shift();
|
|
12618
14411
|
if (kind === "match") {
|
|
12619
|
-
|
|
12620
|
-
|
|
14412
|
+
const haystack = (0,external_api_cjs_namespaceObject.formatAst)(node).replace(/([\r\n]|\s)+/g, " ");
|
|
14413
|
+
if (!matcher(quote, needle, haystack)) {
|
|
14414
|
+
matcher(quote, needle, haystack);
|
|
14415
|
+
throw new Error(`Didn't find '${needle}' in '${haystack}' at ${comment.loc.source}:${comment.loc.start.line}`);
|
|
12621
14416
|
}
|
|
12622
14417
|
}
|
|
12623
14418
|
else if (kind === "expect") {
|
|
@@ -13058,6 +14853,7 @@ const configOptionsToCheck = [
|
|
|
13058
14853
|
"ignoredAnnotations",
|
|
13059
14854
|
"ignoredSourcePaths",
|
|
13060
14855
|
"checkInvalidSymbols",
|
|
14856
|
+
"sizeBasedPRE",
|
|
13061
14857
|
];
|
|
13062
14858
|
/**
|
|
13063
14859
|
* @param {BuildConfig} config
|
|
@@ -13109,7 +14905,7 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
|
|
|
13109
14905
|
// the oldest optimized file, we don't need to regenerate
|
|
13110
14906
|
const source_time = await (0,external_util_cjs_namespaceObject.last_modified)(Object.keys(fnMap).concat(dependencyFiles));
|
|
13111
14907
|
const opt_time = await (0,external_util_cjs_namespaceObject.first_modified)(Object.values(fnMap).map((v) => v.output));
|
|
13112
|
-
if (source_time < opt_time &&
|
|
14908
|
+
if (source_time < opt_time && 1656718632916 < opt_time) {
|
|
13113
14909
|
return { hasTests, diagnostics: prevDiagnostics };
|
|
13114
14910
|
}
|
|
13115
14911
|
}
|