@markw65/monkeyc-optimizer 1.0.26 → 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 +38 -0
- package/build/api.cjs +2048 -225
- package/build/optimizer.cjs +1970 -170
- 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
|
|
|
@@ -11187,6 +11429,7 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11187
11429
|
const post = state.post;
|
|
11188
11430
|
state.inlining = true;
|
|
11189
11431
|
let insertedVariableDecls = null;
|
|
11432
|
+
const replacements = new Set();
|
|
11190
11433
|
try {
|
|
11191
11434
|
state.pre = (node) => {
|
|
11192
11435
|
if (failed)
|
|
@@ -11194,7 +11437,7 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11194
11437
|
node.start = call.start;
|
|
11195
11438
|
node.end = call.end;
|
|
11196
11439
|
node.loc = call.loc;
|
|
11197
|
-
if (node
|
|
11440
|
+
if (replacements.has(node))
|
|
11198
11441
|
return false;
|
|
11199
11442
|
const result = pre(node, state);
|
|
11200
11443
|
if (!insertedVariableDecls && node.type === "BlockStatement") {
|
|
@@ -11222,6 +11465,7 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11222
11465
|
kind: "var",
|
|
11223
11466
|
};
|
|
11224
11467
|
node.body.unshift(insertedVariableDecls);
|
|
11468
|
+
replacements.add(insertedVariableDecls);
|
|
11225
11469
|
}
|
|
11226
11470
|
return result;
|
|
11227
11471
|
};
|
|
@@ -11236,7 +11480,9 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11236
11480
|
if ((0,external_api_cjs_namespaceObject.hasProperty)(params, node.name)) {
|
|
11237
11481
|
const ix = params[node.name];
|
|
11238
11482
|
if (ix >= 0) {
|
|
11239
|
-
replacement = call.arguments[ix];
|
|
11483
|
+
replacement = { ...call.arguments[ix] };
|
|
11484
|
+
replacements.add(replacement);
|
|
11485
|
+
return replacement;
|
|
11240
11486
|
}
|
|
11241
11487
|
break;
|
|
11242
11488
|
}
|
|
@@ -11273,6 +11519,10 @@ function processInlineBody(state, func, call, root, params) {
|
|
|
11273
11519
|
}
|
|
11274
11520
|
}
|
|
11275
11521
|
function unused(expression, top) {
|
|
11522
|
+
const estmt = (expression) => withLoc({
|
|
11523
|
+
type: "ExpressionStatement",
|
|
11524
|
+
expression,
|
|
11525
|
+
}, expression);
|
|
11276
11526
|
switch (expression.type) {
|
|
11277
11527
|
case "Literal":
|
|
11278
11528
|
return [];
|
|
@@ -11284,9 +11534,50 @@ function unused(expression, top) {
|
|
|
11284
11534
|
if (expression.operator === "as") {
|
|
11285
11535
|
return unused(expression.left);
|
|
11286
11536
|
}
|
|
11287
|
-
// fall through
|
|
11288
|
-
case "LogicalExpression":
|
|
11289
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
|
+
}
|
|
11290
11581
|
case "UnaryExpression":
|
|
11291
11582
|
return unused(expression.argument);
|
|
11292
11583
|
case "MemberExpression":
|
|
@@ -11301,17 +11592,7 @@ function unused(expression, top) {
|
|
|
11301
11592
|
.map((p) => unused(p.key).concat(unused(p.value)))
|
|
11302
11593
|
.flat(1);
|
|
11303
11594
|
}
|
|
11304
|
-
return top
|
|
11305
|
-
? null
|
|
11306
|
-
: [
|
|
11307
|
-
{
|
|
11308
|
-
type: "ExpressionStatement",
|
|
11309
|
-
expression,
|
|
11310
|
-
start: expression.start,
|
|
11311
|
-
end: expression.end,
|
|
11312
|
-
loc: expression.loc,
|
|
11313
|
-
},
|
|
11314
|
-
];
|
|
11595
|
+
return top ? null : [estmt(expression)];
|
|
11315
11596
|
}
|
|
11316
11597
|
function diagnostic(state, loc, message, type = "INFO") {
|
|
11317
11598
|
if (!loc || !loc.source)
|
|
@@ -11359,7 +11640,9 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11359
11640
|
if (retStmtCount > 1) {
|
|
11360
11641
|
inlineDiagnostic(state, func, call, "Function had more than one return statement");
|
|
11361
11642
|
}
|
|
11362
|
-
else if (context.type === "AssignmentExpression"
|
|
11643
|
+
else if ((context.type === "AssignmentExpression" ||
|
|
11644
|
+
context.type === "VariableDeclarator") &&
|
|
11645
|
+
retStmtCount !== 1) {
|
|
11363
11646
|
inlineDiagnostic(state, func, call, "Function did not have a return statement");
|
|
11364
11647
|
return null;
|
|
11365
11648
|
}
|
|
@@ -11367,7 +11650,9 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11367
11650
|
const last = func.node.body.body.slice(-1)[0];
|
|
11368
11651
|
if (!last ||
|
|
11369
11652
|
last.type !== "ReturnStatement" ||
|
|
11370
|
-
(context.type === "AssignmentExpression"
|
|
11653
|
+
((context.type === "AssignmentExpression" ||
|
|
11654
|
+
context.type === "VariableDeclarator") &&
|
|
11655
|
+
!last.argument)) {
|
|
11371
11656
|
inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
|
|
11372
11657
|
return null;
|
|
11373
11658
|
}
|
|
@@ -11391,16 +11676,32 @@ function inlineWithArgs(state, func, call, context) {
|
|
|
11391
11676
|
if (last.type != "ReturnStatement") {
|
|
11392
11677
|
throw new Error("ReturnStatement got lost!");
|
|
11393
11678
|
}
|
|
11394
|
-
if (
|
|
11395
|
-
context.
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
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
|
+
}
|
|
11404
11705
|
}
|
|
11405
11706
|
else {
|
|
11406
11707
|
--body.body.length;
|
|
@@ -11522,89 +11823,1429 @@ function fixNodeScope(state, lookupNode, nodeStack) {
|
|
|
11522
11823
|
return null;
|
|
11523
11824
|
}
|
|
11524
11825
|
|
|
11525
|
-
;// CONCATENATED MODULE: ./src/
|
|
11826
|
+
;// CONCATENATED MODULE: ./src/control-flow.ts
|
|
11526
11827
|
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
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);
|
|
11533
11884
|
}
|
|
11885
|
+
break;
|
|
11534
11886
|
}
|
|
11535
11887
|
}
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
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
|
+
}
|
|
11539
11899
|
}
|
|
11540
11900
|
}
|
|
11541
|
-
|
|
11542
|
-
}
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
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) {
|
|
11546
11914
|
return [];
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
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":
|
|
11550
11925
|
return [];
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
type: "MemberExpression",
|
|
11563
|
-
object: node.left,
|
|
11564
|
-
property: node.right.argument,
|
|
11565
|
-
computed: false,
|
|
11566
|
-
}), 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();
|
|
11567
11937
|
}
|
|
11938
|
+
else {
|
|
11939
|
+
defaultSeen = true;
|
|
11940
|
+
}
|
|
11941
|
+
});
|
|
11942
|
+
const endOfTests = localState.curBlock;
|
|
11943
|
+
if (!defaultSeen) {
|
|
11944
|
+
localState.addEdge(endOfTests, top.break);
|
|
11568
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 [];
|
|
11569
11958
|
}
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
if (
|
|
11578
|
-
|
|
11579
|
-
|
|
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);
|
|
11580
11970
|
}
|
|
11581
|
-
|
|
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 [];
|
|
11582
11983
|
}
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
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 [];
|
|
11587
12044
|
}
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
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 [];
|
|
11593
12049
|
}
|
|
11594
|
-
|
|
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 [];
|
|
11595
12069
|
}
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
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 [];
|
|
11600
12099
|
}
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
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();
|
|
11606
12116
|
}
|
|
11607
|
-
}
|
|
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
|
+
});
|
|
11608
13249
|
}
|
|
11609
13250
|
return ["returnType"];
|
|
11610
13251
|
}
|
|
@@ -11624,6 +13265,7 @@ function visitReferences(state, ast, name, defn, callback) {
|
|
|
11624
13265
|
|
|
11625
13266
|
|
|
11626
13267
|
|
|
13268
|
+
|
|
11627
13269
|
function collectClassInfo(state) {
|
|
11628
13270
|
const toybox = state.stack[0].decls["Toybox"][0];
|
|
11629
13271
|
const lang = toybox.decls["Lang"][0];
|
|
@@ -11924,7 +13566,50 @@ function getNodeValue(node) {
|
|
|
11924
13566
|
}
|
|
11925
13567
|
return [node, type];
|
|
11926
13568
|
}
|
|
11927
|
-
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) {
|
|
11928
13613
|
switch (node.type) {
|
|
11929
13614
|
case "UnaryExpression": {
|
|
11930
13615
|
const [arg, type] = getNodeValue(node.argument);
|
|
@@ -11976,8 +13661,18 @@ function optimizeNode(node) {
|
|
|
11976
13661
|
"%": (left, right) => left % right,
|
|
11977
13662
|
"&": (left, right, type) => type === "Number" ? left & right : null,
|
|
11978
13663
|
"|": (left, right, type) => type === "Number" ? left | right : null,
|
|
13664
|
+
"^": (left, right, type) => type === "Number" ? left ^ right : null,
|
|
11979
13665
|
"<<": (left, right, type) => type === "Number" ? left << right : null,
|
|
11980
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,
|
|
11981
13676
|
};
|
|
11982
13677
|
const op = operators[node.operator];
|
|
11983
13678
|
if (op) {
|
|
@@ -11985,11 +13680,17 @@ function optimizeNode(node) {
|
|
|
11985
13680
|
const [right, right_type] = getNodeValue(node.right);
|
|
11986
13681
|
if (!left || !right)
|
|
11987
13682
|
break;
|
|
13683
|
+
let value = null;
|
|
11988
13684
|
if (left_type != right_type ||
|
|
11989
13685
|
(left_type != "Number" && left_type != "Long")) {
|
|
11990
|
-
|
|
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);
|
|
11991
13693
|
}
|
|
11992
|
-
const value = op(left.value, right.value, left_type);
|
|
11993
13694
|
if (value === null)
|
|
11994
13695
|
break;
|
|
11995
13696
|
return {
|
|
@@ -12000,15 +13701,47 @@ function optimizeNode(node) {
|
|
|
12000
13701
|
}
|
|
12001
13702
|
break;
|
|
12002
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
|
+
}
|
|
12003
13736
|
case "FunctionDeclaration":
|
|
12004
|
-
if (node.body && evaluateFunction(node, null) !== false) {
|
|
13737
|
+
if (node.body && evaluateFunction(state, node, null) !== false) {
|
|
12005
13738
|
node.optimizable = true;
|
|
12006
13739
|
}
|
|
12007
13740
|
break;
|
|
12008
13741
|
}
|
|
12009
13742
|
return null;
|
|
12010
13743
|
}
|
|
12011
|
-
function evaluateFunction(func, args) {
|
|
13744
|
+
function evaluateFunction(state, func, args) {
|
|
12012
13745
|
if (!func.body || (args && args.length != func.params.length)) {
|
|
12013
13746
|
return false;
|
|
12014
13747
|
}
|
|
@@ -12047,7 +13780,7 @@ function evaluateFunction(func, args) {
|
|
|
12047
13780
|
}
|
|
12048
13781
|
// fall through;
|
|
12049
13782
|
default: {
|
|
12050
|
-
const repl = optimizeNode(node);
|
|
13783
|
+
const repl = optimizeNode(state, node);
|
|
12051
13784
|
if (repl && repl.type === "Literal")
|
|
12052
13785
|
return repl;
|
|
12053
13786
|
throw new Error("Didn't optimize");
|
|
@@ -12107,10 +13840,19 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12107
13840
|
if (!objects) {
|
|
12108
13841
|
return false;
|
|
12109
13842
|
}
|
|
12110
|
-
|
|
13843
|
+
let obj = getLiteralFromDecls(objects);
|
|
12111
13844
|
if (!obj) {
|
|
12112
13845
|
return false;
|
|
12113
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
|
+
}
|
|
12114
13856
|
inPlaceReplacement(node, obj);
|
|
12115
13857
|
return true;
|
|
12116
13858
|
};
|
|
@@ -12328,14 +14070,20 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12328
14070
|
return null;
|
|
12329
14071
|
};
|
|
12330
14072
|
state.post = (node) => {
|
|
12331
|
-
|
|
14073
|
+
const locals = topLocals();
|
|
14074
|
+
if (locals.node === node) {
|
|
12332
14075
|
state.localsStack.pop();
|
|
12333
14076
|
}
|
|
12334
|
-
const opt = optimizeNode(node);
|
|
14077
|
+
const opt = optimizeNode(state, node);
|
|
12335
14078
|
if (opt) {
|
|
12336
14079
|
return replace(opt, node);
|
|
12337
14080
|
}
|
|
12338
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;
|
|
12339
14087
|
case "ConditionalExpression":
|
|
12340
14088
|
case "IfStatement":
|
|
12341
14089
|
if (node.test.type === "Literal" &&
|
|
@@ -12345,6 +14093,12 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12345
14093
|
return false;
|
|
12346
14094
|
return replace(rep, rep);
|
|
12347
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
|
+
}
|
|
12348
14102
|
break;
|
|
12349
14103
|
case "WhileStatement":
|
|
12350
14104
|
if (node.test.type === "Literal" && node.test.value === false) {
|
|
@@ -12372,6 +14126,48 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12372
14126
|
return { type: "Literal", value: null, raw: "null" };
|
|
12373
14127
|
}
|
|
12374
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
|
+
}
|
|
12375
14171
|
case "ExpressionStatement":
|
|
12376
14172
|
if (node.expression.type === "CallExpression") {
|
|
12377
14173
|
return replace(optimizeCall(state, node.expression, node), node.expression);
|
|
@@ -12416,6 +14212,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
|
|
|
12416
14212
|
});
|
|
12417
14213
|
delete state.pre;
|
|
12418
14214
|
delete state.post;
|
|
14215
|
+
state.allFunctions.forEach((fn) => sizeBasedPRE(state, fn));
|
|
12419
14216
|
const cleanup = (node) => {
|
|
12420
14217
|
switch (node.type) {
|
|
12421
14218
|
case "ThisExpression":
|
|
@@ -12541,7 +14338,7 @@ function optimizeCall(state, node, context) {
|
|
|
12541
14338
|
callee.optimizable &&
|
|
12542
14339
|
!callee.hasOverride &&
|
|
12543
14340
|
node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
|
|
12544
|
-
const ret = evaluateFunction(callee, node.arguments);
|
|
14341
|
+
const ret = evaluateFunction(state, callee, node.arguments);
|
|
12545
14342
|
if (ret) {
|
|
12546
14343
|
return ret;
|
|
12547
14344
|
}
|
|
@@ -12602,7 +14399,7 @@ function pragmaChecker(ast, diagnostics) {
|
|
|
12602
14399
|
if (quote == '"') {
|
|
12603
14400
|
return haystack.includes(needle);
|
|
12604
14401
|
}
|
|
12605
|
-
const re = new RegExp(needle);
|
|
14402
|
+
const re = new RegExp(needle.replace(/@(\d+)/g, "(pre_)?$1(_\\d+)?"));
|
|
12606
14403
|
return re.test(haystack);
|
|
12607
14404
|
};
|
|
12608
14405
|
next();
|
|
@@ -12612,8 +14409,10 @@ function pragmaChecker(ast, diagnostics) {
|
|
|
12612
14409
|
if (node.start && node.start >= (comment.end || Infinity)) {
|
|
12613
14410
|
const { kind, quote, needle } = matchers.shift();
|
|
12614
14411
|
if (kind === "match") {
|
|
12615
|
-
|
|
12616
|
-
|
|
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}`);
|
|
12617
14416
|
}
|
|
12618
14417
|
}
|
|
12619
14418
|
else if (kind === "expect") {
|
|
@@ -13054,6 +14853,7 @@ const configOptionsToCheck = [
|
|
|
13054
14853
|
"ignoredAnnotations",
|
|
13055
14854
|
"ignoredSourcePaths",
|
|
13056
14855
|
"checkInvalidSymbols",
|
|
14856
|
+
"sizeBasedPRE",
|
|
13057
14857
|
];
|
|
13058
14858
|
/**
|
|
13059
14859
|
* @param {BuildConfig} config
|
|
@@ -13105,7 +14905,7 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
|
|
|
13105
14905
|
// the oldest optimized file, we don't need to regenerate
|
|
13106
14906
|
const source_time = await (0,external_util_cjs_namespaceObject.last_modified)(Object.keys(fnMap).concat(dependencyFiles));
|
|
13107
14907
|
const opt_time = await (0,external_util_cjs_namespaceObject.first_modified)(Object.values(fnMap).map((v) => v.output));
|
|
13108
|
-
if (source_time < opt_time &&
|
|
14908
|
+
if (source_time < opt_time && 1656718632916 < opt_time) {
|
|
13109
14909
|
return { hasTests, diagnostics: prevDiagnostics };
|
|
13110
14910
|
}
|
|
13111
14911
|
}
|