@markw65/monkeyc-optimizer 1.0.18 → 1.0.19
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 +22 -0
- package/build/api.cjs +208 -86
- package/build/optimizer.cjs +247 -97
- package/build/src/inliner.d.ts +3 -2
- package/build/src/mc-rewrite.d.ts +8 -1
- package/build/src/optimizer.d.ts +26 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -198,3 +198,25 @@ More fixes found via open source projects.
|
|
|
198
198
|
|
|
199
199
|
- Bug Fixes
|
|
200
200
|
- The new inliner was too agressive at constant propagating literal parameters to their point of use.
|
|
201
|
+
|
|
202
|
+
### 1.0.19
|
|
203
|
+
|
|
204
|
+
- Upgrade to @markw65/prettier-plugin-monkeyc@1.0.22
|
|
205
|
+
|
|
206
|
+
- fixes some minor typing issues for mctree
|
|
207
|
+
- special handling for certain parenthesized expressions.
|
|
208
|
+
|
|
209
|
+
- Optimizer
|
|
210
|
+
|
|
211
|
+
- Handle more unused expressions, add tests, and prettify the OptimizerTests project
|
|
212
|
+
- Allow statement-style inlining in assignent and return contexts
|
|
213
|
+
- Add diagnostics for failure to inline
|
|
214
|
+
|
|
215
|
+
- Tests
|
|
216
|
+
|
|
217
|
+
- More tweaks to pragma-checker
|
|
218
|
+
- Add launch and task configs for building/running tests
|
|
219
|
+
|
|
220
|
+
- Code cleanup
|
|
221
|
+
- Properly type the results of JSON.parse
|
|
222
|
+
- Switch over to using ParenthesizedExpression for formatAst (depends on @markw65/prettier-plugin-monkeyc@1.0.22)
|
package/build/api.cjs
CHANGED
|
@@ -256,15 +256,30 @@ function inliningLooksUseful(func, node) {
|
|
|
256
256
|
}
|
|
257
257
|
return false;
|
|
258
258
|
}
|
|
259
|
-
var
|
|
259
|
+
var InlineStatus;
|
|
260
260
|
(function (InlineStatus) {
|
|
261
261
|
InlineStatus[InlineStatus["Never"] = 0] = "Never";
|
|
262
262
|
InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
|
|
263
263
|
InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
|
|
264
|
-
})(
|
|
265
|
-
function
|
|
264
|
+
})(InlineStatus || (InlineStatus = {}));
|
|
265
|
+
function inlineRequested(state, func) {
|
|
266
|
+
const excludeAnnotations = (func.node.loc?.source &&
|
|
267
|
+
state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
|
|
268
|
+
{};
|
|
269
|
+
if (func.node.attrs &&
|
|
270
|
+
func.node.attrs.attrs &&
|
|
271
|
+
func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
|
|
272
|
+
(attr.argument.name === "inline" ||
|
|
273
|
+
(attr.argument.name.startsWith("inline_") &&
|
|
274
|
+
hasProperty(excludeAnnotations, attr.argument.name.substring(7)))))) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
function inliner_shouldInline(state, func, call, context) {
|
|
266
280
|
let autoInline = false;
|
|
267
281
|
let inlineAsExpression = false;
|
|
282
|
+
const args = call.arguments;
|
|
268
283
|
if (func.node.body &&
|
|
269
284
|
func.node.body.body.length === 1 &&
|
|
270
285
|
func.node.body.body[0].type === "ReturnStatement" &&
|
|
@@ -274,39 +289,30 @@ function inliner_shouldInline(state, func, args) {
|
|
|
274
289
|
autoInline = inliningLooksUseful(func.node, func.node.body.body[0].argument);
|
|
275
290
|
}
|
|
276
291
|
if (autoInline === 1) {
|
|
277
|
-
return
|
|
292
|
+
return true;
|
|
278
293
|
}
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
{
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return
|
|
290
|
-
? inliner_InlineStatus.AsExpression
|
|
291
|
-
: inliner_InlineStatus.AsStatement;
|
|
294
|
+
const requested = inlineRequested(state, func);
|
|
295
|
+
if (autoInline || requested) {
|
|
296
|
+
if (inlineAsExpression) {
|
|
297
|
+
if (canInline(state, func, args)) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!context && requested) {
|
|
302
|
+
inlineDiagnostic(state, func, call, "This function can only be inlined in statement, assignment, or return contexts");
|
|
303
|
+
}
|
|
304
|
+
return context != null;
|
|
292
305
|
}
|
|
293
|
-
return
|
|
306
|
+
return false;
|
|
294
307
|
}
|
|
295
308
|
function processInlineBody(state, func, call, root, insertedVariableDecls, params) {
|
|
296
|
-
|
|
297
|
-
const safeArgs = getArgSafety(state, func, call.arguments, false);
|
|
298
|
-
params = Object.fromEntries(func.node.params.map((param, i) => {
|
|
299
|
-
const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
|
|
300
|
-
? i
|
|
301
|
-
: -1;
|
|
302
|
-
const name = variableDeclarationName(param);
|
|
303
|
-
return [name, argnum];
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
309
|
+
let failed = false;
|
|
306
310
|
const pre = state.pre;
|
|
307
311
|
const post = state.post;
|
|
308
312
|
try {
|
|
309
313
|
state.pre = (node) => {
|
|
314
|
+
if (failed)
|
|
315
|
+
return [];
|
|
310
316
|
node.start = call.start;
|
|
311
317
|
node.end = call.end;
|
|
312
318
|
node.loc = call.loc;
|
|
@@ -318,6 +324,11 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
318
324
|
const { map } = locals;
|
|
319
325
|
if (!map)
|
|
320
326
|
throw new Error("No local variable map!");
|
|
327
|
+
// We still need to keep track of every local name that was
|
|
328
|
+
// already in use, but we don't want to use any of its renames.
|
|
329
|
+
// We also want to know whether a local is from the function being
|
|
330
|
+
// inlined, or the calling function, so set every element to false.
|
|
331
|
+
Object.keys(map).forEach((key) => (map[key] = false));
|
|
321
332
|
const declarations = func.node.params
|
|
322
333
|
.map((param, i) => {
|
|
323
334
|
const paramName = variableDeclarationName(param);
|
|
@@ -342,6 +353,8 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
342
353
|
return result;
|
|
343
354
|
};
|
|
344
355
|
state.post = (node) => {
|
|
356
|
+
if (failed)
|
|
357
|
+
return null;
|
|
345
358
|
let replacement = null;
|
|
346
359
|
switch (node.type) {
|
|
347
360
|
case "Identifier": {
|
|
@@ -356,22 +369,28 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
356
369
|
}
|
|
357
370
|
replacement = fixNodeScope(state, node, func.stack);
|
|
358
371
|
if (!replacement) {
|
|
359
|
-
|
|
372
|
+
failed = true;
|
|
373
|
+
inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
|
|
374
|
+
return null;
|
|
360
375
|
}
|
|
361
376
|
break;
|
|
362
377
|
}
|
|
363
378
|
}
|
|
364
379
|
return post(replacement || node, state) || replacement;
|
|
365
380
|
};
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
381
|
+
let ret = state.traverse(root);
|
|
382
|
+
if (failed) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (ret === null) {
|
|
386
|
+
ret = root;
|
|
387
|
+
}
|
|
388
|
+
if (!ret) {
|
|
389
|
+
inlineDiagnostic(state, func, call, `Internal error`);
|
|
390
|
+
return null;
|
|
373
391
|
}
|
|
374
|
-
|
|
392
|
+
inlineDiagnostic(state, func, call, null);
|
|
393
|
+
return ret;
|
|
375
394
|
}
|
|
376
395
|
finally {
|
|
377
396
|
state.pre = pre;
|
|
@@ -394,7 +413,16 @@ function inliner_unused(expression, top) {
|
|
|
394
413
|
case "UnaryExpression":
|
|
395
414
|
return inliner_unused(expression.argument);
|
|
396
415
|
case "MemberExpression":
|
|
397
|
-
|
|
416
|
+
if (expression.computed) {
|
|
417
|
+
return inliner_unused(expression.object).concat(inliner_unused(expression.property));
|
|
418
|
+
}
|
|
419
|
+
return inliner_unused(expression.object);
|
|
420
|
+
case "ArrayExpression":
|
|
421
|
+
return expression.elements.map((e) => inliner_unused(e)).flat(1);
|
|
422
|
+
case "ObjectExpression":
|
|
423
|
+
return expression.properties
|
|
424
|
+
.map((p) => inliner_unused(p.key).concat(inliner_unused(p.value)))
|
|
425
|
+
.flat(1);
|
|
398
426
|
}
|
|
399
427
|
return top
|
|
400
428
|
? null
|
|
@@ -402,30 +430,98 @@ function inliner_unused(expression, top) {
|
|
|
402
430
|
{
|
|
403
431
|
type: "ExpressionStatement",
|
|
404
432
|
expression,
|
|
433
|
+
start: expression.start,
|
|
434
|
+
end: expression.end,
|
|
435
|
+
loc: expression.loc,
|
|
405
436
|
},
|
|
406
437
|
];
|
|
407
438
|
}
|
|
408
|
-
function
|
|
439
|
+
function diagnostic(state, loc, message) {
|
|
440
|
+
if (!loc || !loc.source)
|
|
441
|
+
return;
|
|
442
|
+
const source = loc.source;
|
|
443
|
+
if (!state.diagnostics)
|
|
444
|
+
state.diagnostics = {};
|
|
445
|
+
if (!hasProperty(state.diagnostics, source)) {
|
|
446
|
+
if (!message)
|
|
447
|
+
return;
|
|
448
|
+
state.diagnostics[source] = [];
|
|
449
|
+
}
|
|
450
|
+
const diags = state.diagnostics[source];
|
|
451
|
+
let index = diags.findIndex((item) => item.loc === loc);
|
|
452
|
+
if (message) {
|
|
453
|
+
if (index < 0)
|
|
454
|
+
index = diags.length;
|
|
455
|
+
diags[index] = { type: "INFO", loc, message };
|
|
456
|
+
}
|
|
457
|
+
else if (index >= 0) {
|
|
458
|
+
diags.splice(index, 1);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function inlineDiagnostic(state, func, call, message) {
|
|
462
|
+
if (inlineRequested(state, func)) {
|
|
463
|
+
diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function inlineWithArgs(state, func, call, context) {
|
|
409
467
|
if (!func.node || !func.node.body) {
|
|
410
468
|
return null;
|
|
411
469
|
}
|
|
412
470
|
let retStmtCount = 0;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
471
|
+
if (context.type === "ReturnStatement") {
|
|
472
|
+
const last = func.node.body.body.slice(-1)[0];
|
|
473
|
+
if (!last || last.type !== "ReturnStatement") {
|
|
474
|
+
inlineDiagnostic(state, func, call, "Function didn't end with a return statement");
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
traverseAst(func.node.body, (node) => {
|
|
480
|
+
node.type === "ReturnStatement" && retStmtCount++;
|
|
481
|
+
});
|
|
482
|
+
if (retStmtCount > 1) {
|
|
483
|
+
inlineDiagnostic(state, func, call, "Function had more than one return statement");
|
|
484
|
+
}
|
|
485
|
+
else if (context.type === "AssignmentExpression" && retStmtCount !== 1) {
|
|
486
|
+
inlineDiagnostic(state, func, call, "Function did not have a return statement");
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
if (retStmtCount === 1) {
|
|
490
|
+
const last = func.node.body.body.slice(-1)[0];
|
|
491
|
+
if (!last ||
|
|
492
|
+
last.type !== "ReturnStatement" ||
|
|
493
|
+
(context.type === "AssignmentExpression" && !last.argument)) {
|
|
494
|
+
inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
420
498
|
}
|
|
421
499
|
const body = JSON.parse(JSON.stringify(func.node.body));
|
|
422
|
-
|
|
423
|
-
|
|
500
|
+
const safeArgs = getArgSafety(state, func, call.arguments, false);
|
|
501
|
+
const params = Object.fromEntries(func.node.params.map((param, i) => {
|
|
502
|
+
const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
|
|
503
|
+
? i
|
|
504
|
+
: -1;
|
|
505
|
+
const name = variableDeclarationName(param);
|
|
506
|
+
return [name, argnum];
|
|
507
|
+
}));
|
|
508
|
+
if (!processInlineBody(state, func, call, body, func.node.params.length ? false : true, params)) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
diagnostic(state, call.loc, null);
|
|
512
|
+
if (context.type !== "ReturnStatement" && retStmtCount) {
|
|
424
513
|
const last = body.body[body.body.length - 1];
|
|
425
514
|
if (last.type != "ReturnStatement") {
|
|
426
515
|
throw new Error("ReturnStatement got lost!");
|
|
427
516
|
}
|
|
428
|
-
if (
|
|
517
|
+
if (context.type === "AssignmentExpression") {
|
|
518
|
+
context.right = last.argument;
|
|
519
|
+
body.body[body.body.length - 1] = {
|
|
520
|
+
type: "ExpressionStatement",
|
|
521
|
+
expression: context,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
else if (last.argument) {
|
|
429
525
|
const side_exprs = inliner_unused(last.argument);
|
|
430
526
|
body.body.splice(body.body.length - 1, 1, ...side_exprs);
|
|
431
527
|
}
|
|
@@ -435,13 +531,13 @@ function inlineWithArgs(state, func, call) {
|
|
|
435
531
|
}
|
|
436
532
|
return body;
|
|
437
533
|
}
|
|
438
|
-
function inliner_inlineFunction(state, func, call,
|
|
439
|
-
if (
|
|
440
|
-
return inlineWithArgs(state, func, call);
|
|
534
|
+
function inliner_inlineFunction(state, func, call, context) {
|
|
535
|
+
if (context) {
|
|
536
|
+
return inlineWithArgs(state, func, call, context);
|
|
441
537
|
}
|
|
442
538
|
const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
|
|
443
539
|
const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
|
|
444
|
-
return processInlineBody(state, func, call, retArg, true, params)
|
|
540
|
+
return processInlineBody(state, func, call, retArg, true, params);
|
|
445
541
|
}
|
|
446
542
|
function applyTypeIfNeeded(node) {
|
|
447
543
|
if ("enumType" in node && node.enumType) {
|
|
@@ -456,13 +552,14 @@ function applyTypeIfNeeded(node) {
|
|
|
456
552
|
}
|
|
457
553
|
function fixNodeScope(state, lookupNode, nodeStack) {
|
|
458
554
|
if (lookupNode.type === "Identifier") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
555
|
+
const locals = state.localsStack[state.localsStack.length - 1];
|
|
556
|
+
const { map } = locals;
|
|
557
|
+
if (!map)
|
|
558
|
+
throw new Error("No local variable map!");
|
|
559
|
+
if (hasProperty(map, lookupNode.name) && map[lookupNode.name] !== false) {
|
|
560
|
+
// map[name] !== false means its an entry that was created during inlining
|
|
561
|
+
// so its definitely one of our locals.
|
|
562
|
+
return lookupNode;
|
|
466
563
|
}
|
|
467
564
|
}
|
|
468
565
|
const [, original] = state.lookup(lookupNode, null, nodeStack);
|
|
@@ -938,13 +1035,15 @@ function optimizeNode(node) {
|
|
|
938
1035
|
return null;
|
|
939
1036
|
}
|
|
940
1037
|
function evaluateFunction(func, args) {
|
|
941
|
-
if (args && args.length != func.params.length) {
|
|
1038
|
+
if (!func.body || (args && args.length != func.params.length)) {
|
|
942
1039
|
return false;
|
|
943
1040
|
}
|
|
944
1041
|
const paramValues = args &&
|
|
945
1042
|
Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
|
|
946
1043
|
let ret = null;
|
|
947
|
-
const body = args
|
|
1044
|
+
const body = args
|
|
1045
|
+
? JSON.parse(JSON.stringify(func.body))
|
|
1046
|
+
: func.body;
|
|
948
1047
|
try {
|
|
949
1048
|
traverseAst(body, (node) => {
|
|
950
1049
|
switch (node.type) {
|
|
@@ -1145,7 +1244,7 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
1145
1244
|
if (map) {
|
|
1146
1245
|
if (hasProperty(map, node.name)) {
|
|
1147
1246
|
const name = map[node.name];
|
|
1148
|
-
if (name
|
|
1247
|
+
if (typeof name === "string") {
|
|
1149
1248
|
node.name = name;
|
|
1150
1249
|
}
|
|
1151
1250
|
}
|
|
@@ -1236,21 +1335,49 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
1236
1335
|
return node.body;
|
|
1237
1336
|
}
|
|
1238
1337
|
break;
|
|
1338
|
+
case "ReturnStatement":
|
|
1339
|
+
if (node.argument && node.argument.type === "CallExpression") {
|
|
1340
|
+
return optimizeCall(state, node.argument, node);
|
|
1341
|
+
}
|
|
1342
|
+
break;
|
|
1239
1343
|
case "CallExpression": {
|
|
1240
|
-
const ret = optimizeCall(state, node,
|
|
1344
|
+
const ret = optimizeCall(state, node, null);
|
|
1241
1345
|
if (ret) {
|
|
1242
1346
|
replace(node, ret);
|
|
1243
1347
|
}
|
|
1244
1348
|
break;
|
|
1245
1349
|
}
|
|
1350
|
+
case "AssignmentExpression":
|
|
1351
|
+
if (node.operator === "=" &&
|
|
1352
|
+
node.left.type === "Identifier" &&
|
|
1353
|
+
node.right.type === "Identifier" &&
|
|
1354
|
+
node.left.name === node.right.name) {
|
|
1355
|
+
return { type: "Literal", value: null, raw: "null" };
|
|
1356
|
+
}
|
|
1357
|
+
break;
|
|
1246
1358
|
case "ExpressionStatement":
|
|
1247
1359
|
if (node.expression.type === "CallExpression") {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1360
|
+
return optimizeCall(state, node.expression, node);
|
|
1361
|
+
}
|
|
1362
|
+
else if (node.expression.type === "AssignmentExpression") {
|
|
1363
|
+
if (node.expression.right.type === "CallExpression") {
|
|
1364
|
+
let ok = false;
|
|
1365
|
+
if (node.expression.left.type === "Identifier") {
|
|
1366
|
+
if (hasProperty(topLocals().map, node.expression.left.type)) {
|
|
1367
|
+
ok = true;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (!ok && node.expression.operator == "=") {
|
|
1371
|
+
const [, result] = state.lookup(node.expression.left);
|
|
1372
|
+
ok = result != null;
|
|
1373
|
+
}
|
|
1374
|
+
if (ok) {
|
|
1375
|
+
const ret = optimizeCall(state, node.expression.right, node.expression);
|
|
1376
|
+
if (ret && ret.type === "BlockStatement") {
|
|
1377
|
+
const r2 = state.traverse(ret);
|
|
1378
|
+
return r2 === false || r2 ? r2 : ret;
|
|
1379
|
+
}
|
|
1252
1380
|
}
|
|
1253
|
-
node.expression = ret;
|
|
1254
1381
|
}
|
|
1255
1382
|
}
|
|
1256
1383
|
else {
|
|
@@ -1341,8 +1468,9 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
1341
1468
|
return ret;
|
|
1342
1469
|
});
|
|
1343
1470
|
});
|
|
1471
|
+
return state.diagnostics;
|
|
1344
1472
|
}
|
|
1345
|
-
function optimizeCall(state, node,
|
|
1473
|
+
function optimizeCall(state, node, context) {
|
|
1346
1474
|
const [name, callees] = state.lookup(node.callee);
|
|
1347
1475
|
if (!callees || !callees.length) {
|
|
1348
1476
|
const n = name ||
|
|
@@ -1362,7 +1490,8 @@ function optimizeCall(state, node, asStatement) {
|
|
|
1362
1490
|
}
|
|
1363
1491
|
if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
|
|
1364
1492
|
const callee = callees[0].node;
|
|
1365
|
-
if (
|
|
1493
|
+
if (!context &&
|
|
1494
|
+
callee.optimizable &&
|
|
1366
1495
|
!callee.hasOverride &&
|
|
1367
1496
|
node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
|
|
1368
1497
|
const ret = evaluateFunction(callee, node.arguments);
|
|
@@ -1370,10 +1499,8 @@ function optimizeCall(state, node, asStatement) {
|
|
|
1370
1499
|
return ret;
|
|
1371
1500
|
}
|
|
1372
1501
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
(asStatement && inlineStatus === InlineStatus.AsStatement)) {
|
|
1376
|
-
const ret = inlineFunction(state, callees[0], node, inlineStatus);
|
|
1502
|
+
if (shouldInline(state, callees[0], node, context)) {
|
|
1503
|
+
const ret = inlineFunction(state, callees[0], node, context);
|
|
1377
1504
|
if (ret) {
|
|
1378
1505
|
return ret;
|
|
1379
1506
|
}
|
|
@@ -1382,7 +1509,7 @@ function optimizeCall(state, node, asStatement) {
|
|
|
1382
1509
|
if (!hasProperty(state.calledFunctions, name)) {
|
|
1383
1510
|
state.calledFunctions[name] = [];
|
|
1384
1511
|
}
|
|
1385
|
-
callees.forEach((c) => isStateNode(c) && state.calledFunctions[name]
|
|
1512
|
+
callees.forEach((c) => isStateNode(c) && pushUnique(state.calledFunctions[name], c.node));
|
|
1386
1513
|
return null;
|
|
1387
1514
|
}
|
|
1388
1515
|
|
|
@@ -1927,10 +2054,10 @@ function formatAst(node, monkeyCSource = null) {
|
|
|
1927
2054
|
* The estree printer sometimes looks at the parent node without
|
|
1928
2055
|
* checking that there *is* a parent node (eg it assumes all
|
|
1929
2056
|
* BinaryExpressions have a parent node, and crashes if they don't).
|
|
1930
|
-
* To avoid issues, wrap nodes in an
|
|
1931
|
-
*
|
|
2057
|
+
* To avoid issues, wrap nodes in an ParenthesizedExpression.
|
|
2058
|
+
* The printer knows that parentless ParenthesizedExpressions
|
|
2059
|
+
* should be ignored.
|
|
1932
2060
|
*/
|
|
1933
|
-
let unwrap = false;
|
|
1934
2061
|
switch (node.type) {
|
|
1935
2062
|
case "Program":
|
|
1936
2063
|
case "BlockStatement":
|
|
@@ -1938,11 +2065,10 @@ function formatAst(node, monkeyCSource = null) {
|
|
|
1938
2065
|
break;
|
|
1939
2066
|
default: {
|
|
1940
2067
|
const e = {
|
|
1941
|
-
type: "
|
|
2068
|
+
type: "ParenthesizedExpression",
|
|
1942
2069
|
expression: node,
|
|
1943
2070
|
};
|
|
1944
2071
|
node = e;
|
|
1945
|
-
unwrap = true;
|
|
1946
2072
|
}
|
|
1947
2073
|
}
|
|
1948
2074
|
// If we *do* have the original source, pass that in ahead of the
|
|
@@ -1950,15 +2076,11 @@ function formatAst(node, monkeyCSource = null) {
|
|
|
1950
2076
|
// as the ast itself, and the printers will find what they're
|
|
1951
2077
|
// looking for in the source.
|
|
1952
2078
|
const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
|
|
1953
|
-
|
|
2079
|
+
return external_prettier_namespaceObject.format(source, {
|
|
1954
2080
|
parser: "monkeyc-json",
|
|
1955
2081
|
plugins: [(prettier_plugin_monkeyc_default())],
|
|
1956
2082
|
endOfLine: "lf",
|
|
1957
2083
|
});
|
|
1958
|
-
if (unwrap) {
|
|
1959
|
-
return result.replace(/;$/, "");
|
|
1960
|
-
}
|
|
1961
|
-
return result;
|
|
1962
2084
|
}
|
|
1963
2085
|
function handleException(state, node, exception) {
|
|
1964
2086
|
try {
|
package/build/optimizer.cjs
CHANGED
|
@@ -10983,9 +10983,24 @@ var InlineStatus;
|
|
|
10983
10983
|
InlineStatus[InlineStatus["AsExpression"] = 1] = "AsExpression";
|
|
10984
10984
|
InlineStatus[InlineStatus["AsStatement"] = 2] = "AsStatement";
|
|
10985
10985
|
})(InlineStatus || (InlineStatus = {}));
|
|
10986
|
-
function
|
|
10986
|
+
function inlineRequested(state, func) {
|
|
10987
|
+
const excludeAnnotations = (func.node.loc?.source &&
|
|
10988
|
+
state.fnMap[func.node.loc?.source]?.excludeAnnotations) ||
|
|
10989
|
+
{};
|
|
10990
|
+
if (func.node.attrs &&
|
|
10991
|
+
func.node.attrs.attrs &&
|
|
10992
|
+
func.node.attrs.attrs.some((attr) => attr.type === "UnaryExpression" &&
|
|
10993
|
+
(attr.argument.name === "inline" ||
|
|
10994
|
+
(attr.argument.name.startsWith("inline_") &&
|
|
10995
|
+
(0,external_api_cjs_namespaceObject.hasProperty)(excludeAnnotations, attr.argument.name.substring(7)))))) {
|
|
10996
|
+
return true;
|
|
10997
|
+
}
|
|
10998
|
+
return false;
|
|
10999
|
+
}
|
|
11000
|
+
function shouldInline(state, func, call, context) {
|
|
10987
11001
|
let autoInline = false;
|
|
10988
11002
|
let inlineAsExpression = false;
|
|
11003
|
+
const args = call.arguments;
|
|
10989
11004
|
if (func.node.body &&
|
|
10990
11005
|
func.node.body.body.length === 1 &&
|
|
10991
11006
|
func.node.body.body[0].type === "ReturnStatement" &&
|
|
@@ -10995,39 +11010,30 @@ function shouldInline(state, func, args) {
|
|
|
10995
11010
|
autoInline = inliningLooksUseful(func.node, func.node.body.body[0].argument);
|
|
10996
11011
|
}
|
|
10997
11012
|
if (autoInline === 1) {
|
|
10998
|
-
return
|
|
11013
|
+
return true;
|
|
10999
11014
|
}
|
|
11000
|
-
const
|
|
11001
|
-
|
|
11002
|
-
{
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
return
|
|
11011
|
-
? InlineStatus.AsExpression
|
|
11012
|
-
: InlineStatus.AsStatement;
|
|
11015
|
+
const requested = inlineRequested(state, func);
|
|
11016
|
+
if (autoInline || requested) {
|
|
11017
|
+
if (inlineAsExpression) {
|
|
11018
|
+
if (canInline(state, func, args)) {
|
|
11019
|
+
return true;
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
if (!context && requested) {
|
|
11023
|
+
inlineDiagnostic(state, func, call, "This function can only be inlined in statement, assignment, or return contexts");
|
|
11024
|
+
}
|
|
11025
|
+
return context != null;
|
|
11013
11026
|
}
|
|
11014
|
-
return
|
|
11027
|
+
return false;
|
|
11015
11028
|
}
|
|
11016
11029
|
function processInlineBody(state, func, call, root, insertedVariableDecls, params) {
|
|
11017
|
-
|
|
11018
|
-
const safeArgs = getArgSafety(state, func, call.arguments, false);
|
|
11019
|
-
params = Object.fromEntries(func.node.params.map((param, i) => {
|
|
11020
|
-
const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
|
|
11021
|
-
? i
|
|
11022
|
-
: -1;
|
|
11023
|
-
const name = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
|
|
11024
|
-
return [name, argnum];
|
|
11025
|
-
}));
|
|
11026
|
-
}
|
|
11030
|
+
let failed = false;
|
|
11027
11031
|
const pre = state.pre;
|
|
11028
11032
|
const post = state.post;
|
|
11029
11033
|
try {
|
|
11030
11034
|
state.pre = (node) => {
|
|
11035
|
+
if (failed)
|
|
11036
|
+
return [];
|
|
11031
11037
|
node.start = call.start;
|
|
11032
11038
|
node.end = call.end;
|
|
11033
11039
|
node.loc = call.loc;
|
|
@@ -11039,6 +11045,11 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
11039
11045
|
const { map } = locals;
|
|
11040
11046
|
if (!map)
|
|
11041
11047
|
throw new Error("No local variable map!");
|
|
11048
|
+
// We still need to keep track of every local name that was
|
|
11049
|
+
// already in use, but we don't want to use any of its renames.
|
|
11050
|
+
// We also want to know whether a local is from the function being
|
|
11051
|
+
// inlined, or the calling function, so set every element to false.
|
|
11052
|
+
Object.keys(map).forEach((key) => (map[key] = false));
|
|
11042
11053
|
const declarations = func.node.params
|
|
11043
11054
|
.map((param, i) => {
|
|
11044
11055
|
const paramName = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
|
|
@@ -11063,6 +11074,8 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
11063
11074
|
return result;
|
|
11064
11075
|
};
|
|
11065
11076
|
state.post = (node) => {
|
|
11077
|
+
if (failed)
|
|
11078
|
+
return null;
|
|
11066
11079
|
let replacement = null;
|
|
11067
11080
|
switch (node.type) {
|
|
11068
11081
|
case "Identifier": {
|
|
@@ -11077,22 +11090,28 @@ function processInlineBody(state, func, call, root, insertedVariableDecls, param
|
|
|
11077
11090
|
}
|
|
11078
11091
|
replacement = fixNodeScope(state, node, func.stack);
|
|
11079
11092
|
if (!replacement) {
|
|
11080
|
-
|
|
11093
|
+
failed = true;
|
|
11094
|
+
inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
|
|
11095
|
+
return null;
|
|
11081
11096
|
}
|
|
11082
11097
|
break;
|
|
11083
11098
|
}
|
|
11084
11099
|
}
|
|
11085
11100
|
return post(replacement || node, state) || replacement;
|
|
11086
11101
|
};
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
}
|
|
11102
|
+
let ret = state.traverse(root);
|
|
11103
|
+
if (failed) {
|
|
11104
|
+
return null;
|
|
11105
|
+
}
|
|
11106
|
+
if (ret === null) {
|
|
11107
|
+
ret = root;
|
|
11094
11108
|
}
|
|
11095
|
-
|
|
11109
|
+
if (!ret) {
|
|
11110
|
+
inlineDiagnostic(state, func, call, `Internal error`);
|
|
11111
|
+
return null;
|
|
11112
|
+
}
|
|
11113
|
+
inlineDiagnostic(state, func, call, null);
|
|
11114
|
+
return ret;
|
|
11096
11115
|
}
|
|
11097
11116
|
finally {
|
|
11098
11117
|
state.pre = pre;
|
|
@@ -11115,7 +11134,16 @@ function unused(expression, top) {
|
|
|
11115
11134
|
case "UnaryExpression":
|
|
11116
11135
|
return unused(expression.argument);
|
|
11117
11136
|
case "MemberExpression":
|
|
11118
|
-
|
|
11137
|
+
if (expression.computed) {
|
|
11138
|
+
return unused(expression.object).concat(unused(expression.property));
|
|
11139
|
+
}
|
|
11140
|
+
return unused(expression.object);
|
|
11141
|
+
case "ArrayExpression":
|
|
11142
|
+
return expression.elements.map((e) => unused(e)).flat(1);
|
|
11143
|
+
case "ObjectExpression":
|
|
11144
|
+
return expression.properties
|
|
11145
|
+
.map((p) => unused(p.key).concat(unused(p.value)))
|
|
11146
|
+
.flat(1);
|
|
11119
11147
|
}
|
|
11120
11148
|
return top
|
|
11121
11149
|
? null
|
|
@@ -11123,30 +11151,98 @@ function unused(expression, top) {
|
|
|
11123
11151
|
{
|
|
11124
11152
|
type: "ExpressionStatement",
|
|
11125
11153
|
expression,
|
|
11154
|
+
start: expression.start,
|
|
11155
|
+
end: expression.end,
|
|
11156
|
+
loc: expression.loc,
|
|
11126
11157
|
},
|
|
11127
11158
|
];
|
|
11128
11159
|
}
|
|
11129
|
-
function
|
|
11160
|
+
function diagnostic(state, loc, message) {
|
|
11161
|
+
if (!loc || !loc.source)
|
|
11162
|
+
return;
|
|
11163
|
+
const source = loc.source;
|
|
11164
|
+
if (!state.diagnostics)
|
|
11165
|
+
state.diagnostics = {};
|
|
11166
|
+
if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.diagnostics, source)) {
|
|
11167
|
+
if (!message)
|
|
11168
|
+
return;
|
|
11169
|
+
state.diagnostics[source] = [];
|
|
11170
|
+
}
|
|
11171
|
+
const diags = state.diagnostics[source];
|
|
11172
|
+
let index = diags.findIndex((item) => item.loc === loc);
|
|
11173
|
+
if (message) {
|
|
11174
|
+
if (index < 0)
|
|
11175
|
+
index = diags.length;
|
|
11176
|
+
diags[index] = { type: "INFO", loc, message };
|
|
11177
|
+
}
|
|
11178
|
+
else if (index >= 0) {
|
|
11179
|
+
diags.splice(index, 1);
|
|
11180
|
+
}
|
|
11181
|
+
}
|
|
11182
|
+
function inlineDiagnostic(state, func, call, message) {
|
|
11183
|
+
if (inlineRequested(state, func)) {
|
|
11184
|
+
diagnostic(state, call.loc, message && `While inlining ${func.node.id.name}: ${message}`);
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
function inlineWithArgs(state, func, call, context) {
|
|
11130
11188
|
if (!func.node || !func.node.body) {
|
|
11131
11189
|
return null;
|
|
11132
11190
|
}
|
|
11133
11191
|
let retStmtCount = 0;
|
|
11134
|
-
(
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11192
|
+
if (context.type === "ReturnStatement") {
|
|
11193
|
+
const last = func.node.body.body.slice(-1)[0];
|
|
11194
|
+
if (!last || last.type !== "ReturnStatement") {
|
|
11195
|
+
inlineDiagnostic(state, func, call, "Function didn't end with a return statement");
|
|
11196
|
+
return null;
|
|
11197
|
+
}
|
|
11198
|
+
}
|
|
11199
|
+
else {
|
|
11200
|
+
(0,external_api_cjs_namespaceObject.traverseAst)(func.node.body, (node) => {
|
|
11201
|
+
node.type === "ReturnStatement" && retStmtCount++;
|
|
11202
|
+
});
|
|
11203
|
+
if (retStmtCount > 1) {
|
|
11204
|
+
inlineDiagnostic(state, func, call, "Function had more than one return statement");
|
|
11205
|
+
}
|
|
11206
|
+
else if (context.type === "AssignmentExpression" && retStmtCount !== 1) {
|
|
11207
|
+
inlineDiagnostic(state, func, call, "Function did not have a return statement");
|
|
11208
|
+
return null;
|
|
11209
|
+
}
|
|
11210
|
+
if (retStmtCount === 1) {
|
|
11211
|
+
const last = func.node.body.body.slice(-1)[0];
|
|
11212
|
+
if (!last ||
|
|
11213
|
+
last.type !== "ReturnStatement" ||
|
|
11214
|
+
(context.type === "AssignmentExpression" && !last.argument)) {
|
|
11215
|
+
inlineDiagnostic(state, func, call, "There was a return statement, but not at the end of the function");
|
|
11216
|
+
return null;
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11141
11219
|
}
|
|
11142
11220
|
const body = JSON.parse(JSON.stringify(func.node.body));
|
|
11143
|
-
|
|
11144
|
-
|
|
11221
|
+
const safeArgs = getArgSafety(state, func, call.arguments, false);
|
|
11222
|
+
const params = Object.fromEntries(func.node.params.map((param, i) => {
|
|
11223
|
+
const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
|
|
11224
|
+
? i
|
|
11225
|
+
: -1;
|
|
11226
|
+
const name = (0,external_api_cjs_namespaceObject.variableDeclarationName)(param);
|
|
11227
|
+
return [name, argnum];
|
|
11228
|
+
}));
|
|
11229
|
+
if (!processInlineBody(state, func, call, body, func.node.params.length ? false : true, params)) {
|
|
11230
|
+
return null;
|
|
11231
|
+
}
|
|
11232
|
+
diagnostic(state, call.loc, null);
|
|
11233
|
+
if (context.type !== "ReturnStatement" && retStmtCount) {
|
|
11145
11234
|
const last = body.body[body.body.length - 1];
|
|
11146
11235
|
if (last.type != "ReturnStatement") {
|
|
11147
11236
|
throw new Error("ReturnStatement got lost!");
|
|
11148
11237
|
}
|
|
11149
|
-
if (
|
|
11238
|
+
if (context.type === "AssignmentExpression") {
|
|
11239
|
+
context.right = last.argument;
|
|
11240
|
+
body.body[body.body.length - 1] = {
|
|
11241
|
+
type: "ExpressionStatement",
|
|
11242
|
+
expression: context,
|
|
11243
|
+
};
|
|
11244
|
+
}
|
|
11245
|
+
else if (last.argument) {
|
|
11150
11246
|
const side_exprs = unused(last.argument);
|
|
11151
11247
|
body.body.splice(body.body.length - 1, 1, ...side_exprs);
|
|
11152
11248
|
}
|
|
@@ -11156,13 +11252,13 @@ function inlineWithArgs(state, func, call) {
|
|
|
11156
11252
|
}
|
|
11157
11253
|
return body;
|
|
11158
11254
|
}
|
|
11159
|
-
function inlineFunction(state, func, call,
|
|
11160
|
-
if (
|
|
11161
|
-
return inlineWithArgs(state, func, call);
|
|
11255
|
+
function inlineFunction(state, func, call, context) {
|
|
11256
|
+
if (context) {
|
|
11257
|
+
return inlineWithArgs(state, func, call, context);
|
|
11162
11258
|
}
|
|
11163
11259
|
const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
|
|
11164
11260
|
const params = Object.fromEntries(func.node.params.map((param, i) => [(0,external_api_cjs_namespaceObject.variableDeclarationName)(param), i]));
|
|
11165
|
-
return processInlineBody(state, func, call, retArg, true, params)
|
|
11261
|
+
return processInlineBody(state, func, call, retArg, true, params);
|
|
11166
11262
|
}
|
|
11167
11263
|
function applyTypeIfNeeded(node) {
|
|
11168
11264
|
if ("enumType" in node && node.enumType) {
|
|
@@ -11177,13 +11273,14 @@ function applyTypeIfNeeded(node) {
|
|
|
11177
11273
|
}
|
|
11178
11274
|
function fixNodeScope(state, lookupNode, nodeStack) {
|
|
11179
11275
|
if (lookupNode.type === "Identifier") {
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11276
|
+
const locals = state.localsStack[state.localsStack.length - 1];
|
|
11277
|
+
const { map } = locals;
|
|
11278
|
+
if (!map)
|
|
11279
|
+
throw new Error("No local variable map!");
|
|
11280
|
+
if ((0,external_api_cjs_namespaceObject.hasProperty)(map, lookupNode.name) && map[lookupNode.name] !== false) {
|
|
11281
|
+
// map[name] !== false means its an entry that was created during inlining
|
|
11282
|
+
// so its definitely one of our locals.
|
|
11283
|
+
return lookupNode;
|
|
11187
11284
|
}
|
|
11188
11285
|
}
|
|
11189
11286
|
const [, original] = state.lookup(lookupNode, null, nodeStack);
|
|
@@ -11656,13 +11753,15 @@ function optimizeNode(node) {
|
|
|
11656
11753
|
return null;
|
|
11657
11754
|
}
|
|
11658
11755
|
function evaluateFunction(func, args) {
|
|
11659
|
-
if (args && args.length != func.params.length) {
|
|
11756
|
+
if (!func.body || (args && args.length != func.params.length)) {
|
|
11660
11757
|
return false;
|
|
11661
11758
|
}
|
|
11662
11759
|
const paramValues = args &&
|
|
11663
11760
|
Object.fromEntries(func.params.map((p, i) => [(0,external_api_cjs_namespaceObject.variableDeclarationName)(p), args[i]]));
|
|
11664
11761
|
let ret = null;
|
|
11665
|
-
const body = args
|
|
11762
|
+
const body = args
|
|
11763
|
+
? JSON.parse(JSON.stringify(func.body))
|
|
11764
|
+
: func.body;
|
|
11666
11765
|
try {
|
|
11667
11766
|
(0,external_api_cjs_namespaceObject.traverseAst)(body, (node) => {
|
|
11668
11767
|
switch (node.type) {
|
|
@@ -11863,7 +11962,7 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
11863
11962
|
if (map) {
|
|
11864
11963
|
if ((0,external_api_cjs_namespaceObject.hasProperty)(map, node.name)) {
|
|
11865
11964
|
const name = map[node.name];
|
|
11866
|
-
if (name
|
|
11965
|
+
if (typeof name === "string") {
|
|
11867
11966
|
node.name = name;
|
|
11868
11967
|
}
|
|
11869
11968
|
}
|
|
@@ -11954,21 +12053,49 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
11954
12053
|
return node.body;
|
|
11955
12054
|
}
|
|
11956
12055
|
break;
|
|
12056
|
+
case "ReturnStatement":
|
|
12057
|
+
if (node.argument && node.argument.type === "CallExpression") {
|
|
12058
|
+
return optimizeCall(state, node.argument, node);
|
|
12059
|
+
}
|
|
12060
|
+
break;
|
|
11957
12061
|
case "CallExpression": {
|
|
11958
|
-
const ret = optimizeCall(state, node,
|
|
12062
|
+
const ret = optimizeCall(state, node, null);
|
|
11959
12063
|
if (ret) {
|
|
11960
12064
|
replace(node, ret);
|
|
11961
12065
|
}
|
|
11962
12066
|
break;
|
|
11963
12067
|
}
|
|
12068
|
+
case "AssignmentExpression":
|
|
12069
|
+
if (node.operator === "=" &&
|
|
12070
|
+
node.left.type === "Identifier" &&
|
|
12071
|
+
node.right.type === "Identifier" &&
|
|
12072
|
+
node.left.name === node.right.name) {
|
|
12073
|
+
return { type: "Literal", value: null, raw: "null" };
|
|
12074
|
+
}
|
|
12075
|
+
break;
|
|
11964
12076
|
case "ExpressionStatement":
|
|
11965
12077
|
if (node.expression.type === "CallExpression") {
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
12078
|
+
return optimizeCall(state, node.expression, node);
|
|
12079
|
+
}
|
|
12080
|
+
else if (node.expression.type === "AssignmentExpression") {
|
|
12081
|
+
if (node.expression.right.type === "CallExpression") {
|
|
12082
|
+
let ok = false;
|
|
12083
|
+
if (node.expression.left.type === "Identifier") {
|
|
12084
|
+
if ((0,external_api_cjs_namespaceObject.hasProperty)(topLocals().map, node.expression.left.type)) {
|
|
12085
|
+
ok = true;
|
|
12086
|
+
}
|
|
12087
|
+
}
|
|
12088
|
+
if (!ok && node.expression.operator == "=") {
|
|
12089
|
+
const [, result] = state.lookup(node.expression.left);
|
|
12090
|
+
ok = result != null;
|
|
12091
|
+
}
|
|
12092
|
+
if (ok) {
|
|
12093
|
+
const ret = optimizeCall(state, node.expression.right, node.expression);
|
|
12094
|
+
if (ret && ret.type === "BlockStatement") {
|
|
12095
|
+
const r2 = state.traverse(ret);
|
|
12096
|
+
return r2 === false || r2 ? r2 : ret;
|
|
12097
|
+
}
|
|
11970
12098
|
}
|
|
11971
|
-
node.expression = ret;
|
|
11972
12099
|
}
|
|
11973
12100
|
}
|
|
11974
12101
|
else {
|
|
@@ -12059,8 +12186,9 @@ async function optimizeMonkeyC(fnMap) {
|
|
|
12059
12186
|
return ret;
|
|
12060
12187
|
});
|
|
12061
12188
|
});
|
|
12189
|
+
return state.diagnostics;
|
|
12062
12190
|
}
|
|
12063
|
-
function optimizeCall(state, node,
|
|
12191
|
+
function optimizeCall(state, node, context) {
|
|
12064
12192
|
const [name, callees] = state.lookup(node.callee);
|
|
12065
12193
|
if (!callees || !callees.length) {
|
|
12066
12194
|
const n = name ||
|
|
@@ -12080,7 +12208,8 @@ function optimizeCall(state, node, asStatement) {
|
|
|
12080
12208
|
}
|
|
12081
12209
|
if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
|
|
12082
12210
|
const callee = callees[0].node;
|
|
12083
|
-
if (
|
|
12211
|
+
if (!context &&
|
|
12212
|
+
callee.optimizable &&
|
|
12084
12213
|
!callee.hasOverride &&
|
|
12085
12214
|
node.arguments.every((n) => getNodeValue(n)[0] !== null)) {
|
|
12086
12215
|
const ret = evaluateFunction(callee, node.arguments);
|
|
@@ -12088,10 +12217,8 @@ function optimizeCall(state, node, asStatement) {
|
|
|
12088
12217
|
return ret;
|
|
12089
12218
|
}
|
|
12090
12219
|
}
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
(asStatement && inlineStatus === InlineStatus.AsStatement)) {
|
|
12094
|
-
const ret = inlineFunction(state, callees[0], node, inlineStatus);
|
|
12220
|
+
if (shouldInline(state, callees[0], node, context)) {
|
|
12221
|
+
const ret = inlineFunction(state, callees[0], node, context);
|
|
12095
12222
|
if (ret) {
|
|
12096
12223
|
return ret;
|
|
12097
12224
|
}
|
|
@@ -12100,7 +12227,7 @@ function optimizeCall(state, node, asStatement) {
|
|
|
12100
12227
|
if (!(0,external_api_cjs_namespaceObject.hasProperty)(state.calledFunctions, name)) {
|
|
12101
12228
|
state.calledFunctions[name] = [];
|
|
12102
12229
|
}
|
|
12103
|
-
callees.forEach((c) => (0,external_api_cjs_namespaceObject.isStateNode)(c) && state.calledFunctions[name]
|
|
12230
|
+
callees.forEach((c) => (0,external_api_cjs_namespaceObject.isStateNode)(c) && (0,external_util_cjs_namespaceObject.pushUnique)(state.calledFunctions[name], c.node));
|
|
12104
12231
|
return null;
|
|
12105
12232
|
}
|
|
12106
12233
|
|
|
@@ -12112,12 +12239,31 @@ function pragmaChecker(ast) {
|
|
|
12112
12239
|
return;
|
|
12113
12240
|
let index = -1;
|
|
12114
12241
|
let comment;
|
|
12242
|
+
let matchers;
|
|
12115
12243
|
const next = () => {
|
|
12116
12244
|
while (++index < comments.length) {
|
|
12117
12245
|
comment = comments[index];
|
|
12118
|
-
|
|
12246
|
+
let match = comment.value.match(/^\s*@match\s+(.+)/);
|
|
12247
|
+
if (!match)
|
|
12248
|
+
continue;
|
|
12249
|
+
let str = match[1];
|
|
12250
|
+
matchers = [];
|
|
12251
|
+
while ((match = str.match(/^([/%&#@"])(.+?(?<!\\)(?:\\{2})*)\1(\s+|$)/))) {
|
|
12252
|
+
matchers.push({ quote: match[1], needle: match[2] });
|
|
12253
|
+
str = str.substring(match[0].length);
|
|
12254
|
+
if (!str.length)
|
|
12255
|
+
break;
|
|
12256
|
+
}
|
|
12257
|
+
if (!str.length)
|
|
12119
12258
|
break;
|
|
12259
|
+
if (!matchers.length) {
|
|
12260
|
+
match = str.match(/^(\S+)\s+$/);
|
|
12261
|
+
if (match) {
|
|
12262
|
+
matchers.push({ quote: '"', needle: match[1] });
|
|
12263
|
+
break;
|
|
12264
|
+
}
|
|
12120
12265
|
}
|
|
12266
|
+
throw new Error(`Build pragma '${comment.value}' is invalid. In ${comment.loc.source}:${comment.loc.start.line}`);
|
|
12121
12267
|
}
|
|
12122
12268
|
};
|
|
12123
12269
|
next();
|
|
@@ -12125,29 +12271,23 @@ function pragmaChecker(ast) {
|
|
|
12125
12271
|
if (index >= comments.length)
|
|
12126
12272
|
return false;
|
|
12127
12273
|
if (node.start && node.start >= (comment.end || Infinity)) {
|
|
12128
|
-
|
|
12129
|
-
if (!match) {
|
|
12130
|
-
throw new Error(`Build pragma '${comment.value}' is invalid. In ${comment.loc.source}:${comment.loc.start.line}`);
|
|
12131
|
-
}
|
|
12274
|
+
const { quote, needle } = matchers.shift();
|
|
12132
12275
|
const haystack = (0,external_api_cjs_namespaceObject.formatAst)(node).replace(/[\r\n]/g, " ");
|
|
12133
12276
|
let found = false;
|
|
12134
|
-
|
|
12135
|
-
if (match.length == 2) {
|
|
12277
|
+
if (quote == '"') {
|
|
12136
12278
|
found = haystack.includes(needle);
|
|
12137
12279
|
}
|
|
12138
12280
|
else {
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
}
|
|
12142
|
-
else {
|
|
12143
|
-
const re = new RegExp((needle = match[2]));
|
|
12144
|
-
found = re.test(haystack);
|
|
12145
|
-
}
|
|
12281
|
+
const re = new RegExp(needle);
|
|
12282
|
+
found = re.test(haystack);
|
|
12146
12283
|
}
|
|
12147
12284
|
if (!found) {
|
|
12148
|
-
throw new Error(`Didn't find '${needle}'
|
|
12285
|
+
throw new Error(`Didn't find '${needle}' at ${comment.loc.source}:${comment.loc.start.line}`);
|
|
12286
|
+
}
|
|
12287
|
+
if (!matchers.length) {
|
|
12288
|
+
next();
|
|
12149
12289
|
}
|
|
12150
|
-
|
|
12290
|
+
return false;
|
|
12151
12291
|
}
|
|
12152
12292
|
return null;
|
|
12153
12293
|
});
|
|
@@ -12236,7 +12376,7 @@ async function buildOptimizedProject(product, options) {
|
|
|
12236
12376
|
config.releaseBuild = true;
|
|
12237
12377
|
}
|
|
12238
12378
|
}
|
|
12239
|
-
const { jungleFiles, program, hasTests } = await generateOptimizedProject(config);
|
|
12379
|
+
const { jungleFiles, program, hasTests, diagnostics } = await generateOptimizedProject(config);
|
|
12240
12380
|
config.jungleFiles = jungleFiles;
|
|
12241
12381
|
let bin = config.buildDir || "bin";
|
|
12242
12382
|
let name = `optimized-${program}.prg`;
|
|
@@ -12255,6 +12395,7 @@ async function buildOptimizedProject(product, options) {
|
|
|
12255
12395
|
delete config.testBuild;
|
|
12256
12396
|
return build_project(product, config).then((result) => ({
|
|
12257
12397
|
hasTests,
|
|
12398
|
+
diagnostics,
|
|
12258
12399
|
...result,
|
|
12259
12400
|
}));
|
|
12260
12401
|
}
|
|
@@ -12393,6 +12534,7 @@ async function generateOptimizedProject(options) {
|
|
|
12393
12534
|
(await checkManifest(xml, targets.map((t) => t.product)))) &&
|
|
12394
12535
|
!dropBarrels;
|
|
12395
12536
|
let hasTests = false;
|
|
12537
|
+
let diagnostics = {};
|
|
12396
12538
|
const promises = Object.keys(buildConfigs)
|
|
12397
12539
|
.sort()
|
|
12398
12540
|
.map((key) => {
|
|
@@ -12410,7 +12552,13 @@ async function generateOptimizedProject(options) {
|
|
|
12410
12552
|
e.products = products[key];
|
|
12411
12553
|
throw e;
|
|
12412
12554
|
})
|
|
12413
|
-
.then((t) =>
|
|
12555
|
+
.then((t) => {
|
|
12556
|
+
if (t.hasTests)
|
|
12557
|
+
hasTests = true;
|
|
12558
|
+
if (t.diagnostics) {
|
|
12559
|
+
diagnostics = { ...diagnostics, ...t.diagnostics };
|
|
12560
|
+
}
|
|
12561
|
+
})
|
|
12414
12562
|
: promises_namespaceObject.rm(external_path_.resolve(workspace, outputPath), {
|
|
12415
12563
|
recursive: true,
|
|
12416
12564
|
force: true,
|
|
@@ -12481,6 +12629,7 @@ async function generateOptimizedProject(options) {
|
|
|
12481
12629
|
xml,
|
|
12482
12630
|
program: external_path_.basename(external_path_.dirname(manifest)),
|
|
12483
12631
|
hasTests,
|
|
12632
|
+
diagnostics,
|
|
12484
12633
|
};
|
|
12485
12634
|
}
|
|
12486
12635
|
async function fileInfoFromConfig(workspace, output, buildConfig, extraExcludes) {
|
|
@@ -12559,7 +12708,7 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
|
|
|
12559
12708
|
const actualOptimizedFiles = (await (0,external_util_cjs_namespaceObject.globa)(external_path_.join(output, "**", "*.mc"), { mark: true }))
|
|
12560
12709
|
.filter((file) => !file.endsWith("/"))
|
|
12561
12710
|
.sort();
|
|
12562
|
-
const { hasTests, ...prevOptions } = JSON.parse(await promises_namespaceObject.readFile(external_path_.join(output, "build-info.json"), "utf-8")
|
|
12711
|
+
const { hasTests, diagnostics: prevDiagnostics, ...prevOptions } = JSON.parse(await promises_namespaceObject.readFile(external_path_.join(output, "build-info.json"), "utf-8")
|
|
12563
12712
|
.catch(() => "{}"));
|
|
12564
12713
|
// check that the set of files thats actually there is the same as the
|
|
12565
12714
|
// set of files we're going to generate (in case eg a jungle file change
|
|
@@ -12577,13 +12726,13 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
|
|
|
12577
12726
|
// the oldest optimized file, we don't need to regenerate
|
|
12578
12727
|
const source_time = await (0,external_util_cjs_namespaceObject.last_modified)(Object.keys(fnMap).concat(dependencyFiles));
|
|
12579
12728
|
const opt_time = await (0,external_util_cjs_namespaceObject.first_modified)(Object.values(fnMap).map((v) => v.output));
|
|
12580
|
-
if (source_time < opt_time &&
|
|
12581
|
-
return hasTests;
|
|
12729
|
+
if (source_time < opt_time && 1654554780017 < opt_time) {
|
|
12730
|
+
return { hasTests, diagnostics: prevDiagnostics };
|
|
12582
12731
|
}
|
|
12583
12732
|
}
|
|
12584
12733
|
await promises_namespaceObject.rm(output, { recursive: true, force: true });
|
|
12585
12734
|
await promises_namespaceObject.mkdir(output, { recursive: true });
|
|
12586
|
-
await optimizeMonkeyC(fnMap);
|
|
12735
|
+
const diagnostics = await optimizeMonkeyC(fnMap);
|
|
12587
12736
|
return Promise.all(Object.values(fnMap).map(async (info) => {
|
|
12588
12737
|
const name = info.output;
|
|
12589
12738
|
const dir = external_path_.dirname(name);
|
|
@@ -12598,9 +12747,10 @@ async function generateOneConfig(buildConfig, dependencyFiles, config) {
|
|
|
12598
12747
|
const hasTests = results.some((v) => v);
|
|
12599
12748
|
return promises_namespaceObject.writeFile(external_path_.join(output, "build-info.json"), JSON.stringify({
|
|
12600
12749
|
hasTests,
|
|
12750
|
+
diagnostics,
|
|
12601
12751
|
...Object.fromEntries(configOptionsToCheck.map((option) => [option, config[option]])),
|
|
12602
12752
|
}))
|
|
12603
|
-
.then(() => hasTests);
|
|
12753
|
+
.then(() => ({ hasTests, diagnostics }));
|
|
12604
12754
|
});
|
|
12605
12755
|
}
|
|
12606
12756
|
async function getProjectAnalysis(targets, analysis, options) {
|
package/build/src/inliner.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ export declare enum InlineStatus {
|
|
|
4
4
|
AsExpression = 1,
|
|
5
5
|
AsStatement = 2
|
|
6
6
|
}
|
|
7
|
-
export declare function shouldInline(state: ProgramStateAnalysis, func: FunctionStateNode,
|
|
7
|
+
export declare function shouldInline(state: ProgramStateAnalysis, func: FunctionStateNode, call: mctree.CallExpression, context: InlineContext | null): boolean;
|
|
8
8
|
declare type InlineBody = mctree.BlockStatement | mctree.ExpressionStatement["expression"];
|
|
9
9
|
export declare function unused(expression: mctree.ExpressionStatement["expression"]): mctree.ExpressionStatement[];
|
|
10
10
|
export declare function unused(expression: mctree.ExpressionStatement["expression"], top: true): mctree.ExpressionStatement[] | null;
|
|
11
|
-
export declare
|
|
11
|
+
export declare type InlineContext = mctree.ReturnStatement | mctree.AssignmentExpression | mctree.ExpressionStatement;
|
|
12
|
+
export declare function inlineFunction(state: ProgramStateAnalysis, func: FunctionStateNode, call: mctree.CallExpression, context: InlineContext | null): InlineBody | null;
|
|
12
13
|
export declare function applyTypeIfNeeded(node: mctree.Node): mctree.Node;
|
|
13
14
|
export {};
|
|
@@ -4,4 +4,11 @@ export declare function getFileASTs(fnMap: FilesToOptimizeMap): Promise<boolean>
|
|
|
4
4
|
export declare function analyze(fnMap: FilesToOptimizeMap): Promise<ProgramStateAnalysis>;
|
|
5
5
|
export declare function getLiteralFromDecls(decls: StateNodeDecl[]): null;
|
|
6
6
|
export declare function getLiteralNode(node: mctree.Node | null | undefined): null | mctree.Literal | mctree.AsExpression;
|
|
7
|
-
export declare function optimizeMonkeyC(fnMap: FilesToOptimizeMap): Promise<
|
|
7
|
+
export declare function optimizeMonkeyC(fnMap: FilesToOptimizeMap): Promise<Record<string, {
|
|
8
|
+
type: "ERROR" | "WARNING" | "INFO";
|
|
9
|
+
loc: {
|
|
10
|
+
start: mctree.Position;
|
|
11
|
+
end: mctree.Position;
|
|
12
|
+
};
|
|
13
|
+
message: string;
|
|
14
|
+
}[]> | undefined>;
|
package/build/src/optimizer.d.ts
CHANGED
|
@@ -126,7 +126,7 @@ declare global {
|
|
|
126
126
|
localsStack?: {
|
|
127
127
|
node?: mctree.Node;
|
|
128
128
|
map?: {
|
|
129
|
-
[key: string]:
|
|
129
|
+
[key: string]: boolean | string;
|
|
130
130
|
};
|
|
131
131
|
inners?: {
|
|
132
132
|
[key: string]: true;
|
|
@@ -138,6 +138,14 @@ declare global {
|
|
|
138
138
|
constants?: {
|
|
139
139
|
[key: string]: mctree.Literal;
|
|
140
140
|
};
|
|
141
|
+
diagnostics?: Record<string, {
|
|
142
|
+
type: "ERROR" | "WARNING" | "INFO";
|
|
143
|
+
loc: {
|
|
144
|
+
start: mctree.Position;
|
|
145
|
+
end: mctree.Position;
|
|
146
|
+
};
|
|
147
|
+
message: string;
|
|
148
|
+
}[]>;
|
|
141
149
|
};
|
|
142
150
|
type Finalized<T, Keys extends keyof T> = T & {
|
|
143
151
|
[key in Keys]-?: NonNullable<T[key]>;
|
|
@@ -172,17 +180,34 @@ export declare function buildOptimizedProject(product: string | null, options: B
|
|
|
172
180
|
program: string;
|
|
173
181
|
product: string | null;
|
|
174
182
|
hasTests: boolean | undefined;
|
|
183
|
+
diagnostics: Record<string, {
|
|
184
|
+
type: "ERROR" | "WARNING" | "INFO";
|
|
185
|
+
loc: {
|
|
186
|
+
start: mctree.Position;
|
|
187
|
+
end: mctree.Position;
|
|
188
|
+
};
|
|
189
|
+
message: string;
|
|
190
|
+
}[]> | undefined;
|
|
175
191
|
}>;
|
|
176
192
|
export declare function generateOptimizedProject(options: BuildConfig): Promise<{
|
|
177
193
|
jungleFiles: string | undefined;
|
|
178
194
|
program: string;
|
|
179
195
|
xml?: undefined;
|
|
180
196
|
hasTests?: undefined;
|
|
197
|
+
diagnostics?: undefined;
|
|
181
198
|
} | {
|
|
182
199
|
jungleFiles: string;
|
|
183
200
|
xml: import("./manifest").ManifestXML;
|
|
184
201
|
program: string;
|
|
185
202
|
hasTests: boolean;
|
|
203
|
+
diagnostics: Record<string, {
|
|
204
|
+
type: "ERROR" | "WARNING" | "INFO";
|
|
205
|
+
loc: {
|
|
206
|
+
start: mctree.Position;
|
|
207
|
+
end: mctree.Position;
|
|
208
|
+
};
|
|
209
|
+
message: string;
|
|
210
|
+
}[]>;
|
|
186
211
|
}>;
|
|
187
212
|
export declare type PreAnalysis = {
|
|
188
213
|
fnMap: FilesToOptimizeMap;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markw65/monkeyc-optimizer",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.19",
|
|
5
5
|
"description": "Source to source optimizer for Garmin Monkey C code",
|
|
6
6
|
"main": "build/optimizer.cjs",
|
|
7
7
|
"types": "build/src/optimizer.d.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"author": "markw65",
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@markw65/prettier-plugin-monkeyc": "^1.0.
|
|
37
|
+
"@markw65/prettier-plugin-monkeyc": "^1.0.22"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/glob": "^7.2.0",
|