@markw65/monkeyc-optimizer 1.1.13 → 1.1.15

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 CHANGED
@@ -715,3 +715,25 @@ Bug Fixes
715
715
  ### 1.1.13
716
716
 
717
717
  - Adds a new [Minimize Modules](https://github.com/markw65/monkeyc-optimizer/wiki/Optimizing-module-imports#minimize-modules) pass, which attempts to ensure that every module referenced by the program is imported.
718
+
719
+ ### 1.1.14
720
+
721
+ - Fixes a bug that could crash the optimizer if it tried to inline a function in a non-local variable's initializer.
722
+ - Adds a post build optimizer. This step takes the built .prg file, and optimizes the bytecode. Currently the optimizations are:
723
+ - Remove unreachable code
724
+ - simplify control flow by removing branches to branches
725
+ - Remove empty "finally" handlers (every try/catch gets an empty finally handler)
726
+ - Remove stores to dead locals
727
+ - Remove side-effect free code that produces an unused result
728
+ - Optimize shift left by constant to multiply, since the bytecode is smaller (this seems to be a bug in the garmin tools; they consider shift left and shift right to have an 8-bit argument, but its always zero, and the simulator and devices treat it as a one byte shift instruction, followed by a one byte nop).
729
+
730
+ ### 1.1.15
731
+
732
+ - Post build optimizer improvements
733
+
734
+ - Simplify LogicalExpressions. This generally saves 3 bytes per `&&` or `||`, and also makes them faster
735
+ - Adds a simple code sharing pass. If multiple code paths converge to the same point (or leave the function via return) and they end with the same sequence of bytecode, they're merged into one.
736
+ - Flips branch-true to branch-false or vice versa if the fall through block has multiple predecessors, and the target block has just one. This often leads to better control flow, reducing the number of "goto" bytecodes required.
737
+
738
+ - Source to Source Optimizer improvements
739
+ - Adds an `Iterate Optimizer` option that causes the optimizer to keep re-running until it finds nothing to remove. Defaults to false.
package/build/api.cjs CHANGED
@@ -2161,8 +2161,9 @@ function inlineRequested(state, func) {
2161
2161
  return false;
2162
2162
  }
2163
2163
  function shouldInline(state, func, call, context) {
2164
- if (state.inlining)
2164
+ if (state.inlining || (state.localsStack?.length ?? 0) <= 1) {
2165
2165
  return false;
2166
+ }
2166
2167
  let autoInline = false;
2167
2168
  let inlineAsExpression = false;
2168
2169
  const args = call.arguments;
@@ -3233,33 +3234,6 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3233
3234
  return result;
3234
3235
  };
3235
3236
  const topLocals = () => state.localsStack[state.localsStack.length - 1];
3236
- /*
3237
- * Might this function be called from somewhere, including
3238
- * callbacks from the api (eg getSettingsView, etc).
3239
- */
3240
- const maybeCalled = (func) => {
3241
- if (!func.body) {
3242
- // this is an api.mir function. It can be called
3243
- return true;
3244
- }
3245
- if (hasProperty(state.exposed, func.id.name))
3246
- return true;
3247
- if (func.attrs &&
3248
- func.attrs.attributes &&
3249
- func.attrs.attributes.elements.some((attr) => {
3250
- if (attr.type !== "UnaryExpression")
3251
- return false;
3252
- if (attr.argument.type !== "Identifier")
3253
- return false;
3254
- return attr.argument.name === "test";
3255
- })) {
3256
- return true;
3257
- }
3258
- if (hasProperty(state.calledFunctions, func.id.name)) {
3259
- return (state.calledFunctions[func.id.name].find((f) => f === func) !== null);
3260
- }
3261
- return false;
3262
- };
3263
3237
  /*
3264
3238
  * Does elm (a class) have a maybeCalled function called name,
3265
3239
  * anywhere in its superClass chain.
@@ -3269,7 +3243,7 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3269
3243
  elm.superClass.some((sc) => (hasProperty(sc.decls, name) &&
3270
3244
  sc.decls[name].some((f) => isStateNode(f) &&
3271
3245
  f.type === "FunctionDeclaration" &&
3272
- maybeCalled(f.node))) ||
3246
+ maybeCalled(state, f.node))) ||
3273
3247
  (sc.superClass && checkInherited(sc, name))));
3274
3248
  const renamer = (idnode) => {
3275
3249
  const ident = idnode.type === "Identifier" ? idnode : idnode.left;
@@ -3470,7 +3444,7 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3470
3444
  }
3471
3445
  istate = is;
3472
3446
  }
3473
- if (parent.type === "ClassDeclaration" && !maybeCalled(node)) {
3447
+ if (parent.type === "ClassDeclaration" && !maybeCalled(state, node)) {
3474
3448
  let used = false;
3475
3449
  if (node.id.name === "initialize") {
3476
3450
  used = true;
@@ -3672,15 +3646,31 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3672
3646
  Object.values(fnMap).forEach((f) => {
3673
3647
  collectNamespaces(f.ast, state);
3674
3648
  });
3675
- state.usedByName = {};
3676
- state.calledFunctions = {};
3677
- state.exposed = state.nextExposed;
3678
- state.nextExposed = {};
3679
- Object.values(fnMap).forEach((f) => {
3680
- collectNamespaces(f.ast, state);
3681
- });
3682
- state.exposed = state.nextExposed;
3683
- state.nextExposed = {};
3649
+ const cleanupAll = () => Object.values(fnMap).reduce((changes, f) => {
3650
+ traverseAst(f.ast, undefined, (node) => {
3651
+ const ret = cleanup(state, node, f.ast);
3652
+ if (ret === false) {
3653
+ changes = true;
3654
+ state.removeNodeComments(node, f.ast);
3655
+ }
3656
+ else if (ret) {
3657
+ changes = true;
3658
+ }
3659
+ return ret;
3660
+ });
3661
+ return changes;
3662
+ }, false);
3663
+ do {
3664
+ state.usedByName = {};
3665
+ state.calledFunctions = {};
3666
+ state.exposed = state.nextExposed;
3667
+ state.nextExposed = {};
3668
+ Object.values(fnMap).forEach((f) => {
3669
+ collectNamespaces(f.ast, state);
3670
+ });
3671
+ state.exposed = state.nextExposed;
3672
+ state.nextExposed = {};
3673
+ } while (cleanupAll() && state.config?.iterateOptimizer);
3684
3674
  delete state.pre;
3685
3675
  delete state.post;
3686
3676
  if (state.config?.minimizeModules ?? true) {
@@ -3689,108 +3679,7 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3689
3679
  });
3690
3680
  }
3691
3681
  Object.values(state.allFunctions).forEach((fns) => fns.forEach((fn) => sizeBasedPRE(state, fn)));
3692
- const cleanup = (node) => {
3693
- switch (node.type) {
3694
- case "ThisExpression":
3695
- node.text = "self";
3696
- break;
3697
- case "EnumStringBody":
3698
- if (node.members.every((m) => {
3699
- const name = "name" in m ? m.name : m.id.name;
3700
- return (hasProperty(state.index, name) &&
3701
- !hasProperty(state.exposed, name) &&
3702
- !hasProperty(state.usedByName, name));
3703
- })) {
3704
- node.enumType = [
3705
- ...new Set(node.members.map((m) => {
3706
- if (!("init" in m))
3707
- return "Number";
3708
- const [node, type] = getNodeValue(m.init);
3709
- if (!node) {
3710
- throw new Error("Failed to get type for eliminated enum");
3711
- }
3712
- return type;
3713
- })),
3714
- ].join(" or ");
3715
- node.members.splice(0);
3716
- }
3717
- break;
3718
- case "EnumDeclaration":
3719
- if (!node.body.members.length) {
3720
- if (!node.id)
3721
- return false;
3722
- if (!node.body.enumType) {
3723
- throw new Error("Missing enumType on optimized enum");
3724
- }
3725
- return {
3726
- type: "TypedefDeclaration",
3727
- id: node.id,
3728
- ts: {
3729
- type: "UnaryExpression",
3730
- argument: {
3731
- type: "TypeSpecList",
3732
- ts: [
3733
- node.body.enumType,
3734
- ],
3735
- },
3736
- prefix: true,
3737
- operator: " as",
3738
- },
3739
- };
3740
- }
3741
- break;
3742
- case "VariableDeclaration": {
3743
- node.declarations = node.declarations.filter((d) => {
3744
- const name = variableDeclarationName(d.id);
3745
- return (!hasProperty(state.index, name) ||
3746
- hasProperty(state.exposed, name) ||
3747
- hasProperty(state.usedByName, name));
3748
- });
3749
- if (!node.declarations.length) {
3750
- return false;
3751
- }
3752
- break;
3753
- }
3754
- case "ClassElement":
3755
- if (!node.item) {
3756
- return false;
3757
- }
3758
- break;
3759
- case "FunctionDeclaration":
3760
- if (!maybeCalled(node)) {
3761
- if (node.attrs &&
3762
- node.attrs.attributes &&
3763
- node.attrs.attributes.elements.some((attr) => attr.type === "UnaryExpression" && attr.argument.name === "keep")) {
3764
- break;
3765
- }
3766
- return false;
3767
- }
3768
- break;
3769
- case "ClassDeclaration":
3770
- case "ModuleDeclaration":
3771
- // none of the attributes means anything on classes and
3772
- // modules, and the new compiler complains about some
3773
- // of them. Just drop them all.
3774
- if (node.attrs && node.attrs.access) {
3775
- if (node.attrs.attributes) {
3776
- delete node.attrs.access;
3777
- }
3778
- else {
3779
- delete node.attrs;
3780
- }
3781
- }
3782
- }
3783
- return null;
3784
- };
3785
- Object.entries(fnMap).forEach(([, f]) => {
3786
- traverseAst(f.ast, undefined, (node) => {
3787
- const ret = cleanup(node);
3788
- if (ret === false) {
3789
- state.removeNodeComments(node, f.ast);
3790
- }
3791
- return ret;
3792
- });
3793
- });
3682
+ cleanupAll();
3794
3683
  config.checkCompilerLookupRules = checkLookupRules;
3795
3684
  reportMissingSymbols(state, config);
3796
3685
  if (state.inlineDiagnostics) {
@@ -3816,6 +3705,129 @@ async function optimizeMonkeyC(fnMap, resourcesMap, manifestXML, config) {
3816
3705
  });
3817
3706
  return state.diagnostics;
3818
3707
  }
3708
+ /*
3709
+ * Might this function be called from somewhere, including
3710
+ * callbacks from the api (eg getSettingsView, etc).
3711
+ */
3712
+ function maybeCalled(state, func) {
3713
+ if (!func.body) {
3714
+ // this is an api.mir function. It can be called
3715
+ return true;
3716
+ }
3717
+ if (hasProperty(state.exposed, func.id.name))
3718
+ return true;
3719
+ if (func.attrs &&
3720
+ func.attrs.attributes &&
3721
+ func.attrs.attributes.elements.some((attr) => {
3722
+ if (attr.type !== "UnaryExpression")
3723
+ return false;
3724
+ if (attr.argument.type !== "Identifier")
3725
+ return false;
3726
+ return attr.argument.name === "test";
3727
+ })) {
3728
+ return true;
3729
+ }
3730
+ if (hasProperty(state.calledFunctions, func.id.name)) {
3731
+ return state.calledFunctions[func.id.name].find((f) => f === func) !== null;
3732
+ }
3733
+ return false;
3734
+ }
3735
+ function cleanup(state, node, ast) {
3736
+ switch (node.type) {
3737
+ case "ThisExpression":
3738
+ node.text = "self";
3739
+ break;
3740
+ case "EnumStringBody":
3741
+ if (node.members.every((m) => {
3742
+ const name = "name" in m ? m.name : m.id.name;
3743
+ return (hasProperty(state.index, name) &&
3744
+ !hasProperty(state.exposed, name) &&
3745
+ !hasProperty(state.usedByName, name));
3746
+ })) {
3747
+ node.enumType = [
3748
+ ...new Set(node.members.map((m) => {
3749
+ if (!("init" in m))
3750
+ return "Number";
3751
+ const [node, type] = getNodeValue(m.init);
3752
+ if (!node) {
3753
+ throw new Error("Failed to get type for eliminated enum");
3754
+ }
3755
+ return type;
3756
+ })),
3757
+ ].join(" or ");
3758
+ node.members.splice(0);
3759
+ }
3760
+ break;
3761
+ case "EnumDeclaration":
3762
+ if (!node.body.members.length) {
3763
+ if (!node.id)
3764
+ return false;
3765
+ if (!node.body.enumType) {
3766
+ throw new Error("Missing enumType on optimized enum");
3767
+ }
3768
+ state.removeNodeComments(node, ast);
3769
+ return withLocDeep({
3770
+ type: "TypedefDeclaration",
3771
+ id: node.id,
3772
+ ts: {
3773
+ type: "UnaryExpression",
3774
+ argument: {
3775
+ type: "TypeSpecList",
3776
+ ts: [
3777
+ node.body.enumType,
3778
+ ],
3779
+ },
3780
+ prefix: true,
3781
+ operator: " as",
3782
+ },
3783
+ }, node, node);
3784
+ }
3785
+ break;
3786
+ case "VariableDeclarator": {
3787
+ const name = variableDeclarationName(node.id);
3788
+ return !hasProperty(state.index, name) ||
3789
+ hasProperty(state.exposed, name) ||
3790
+ hasProperty(state.usedByName, name)
3791
+ ? null
3792
+ : false;
3793
+ }
3794
+ case "VariableDeclaration": {
3795
+ if (!node.declarations.length) {
3796
+ return false;
3797
+ }
3798
+ break;
3799
+ }
3800
+ case "ClassElement":
3801
+ if (!node.item) {
3802
+ return false;
3803
+ }
3804
+ break;
3805
+ case "FunctionDeclaration":
3806
+ if (!maybeCalled(state, node)) {
3807
+ if (node.attrs &&
3808
+ node.attrs.attributes &&
3809
+ node.attrs.attributes.elements.some((attr) => attr.type === "UnaryExpression" && attr.argument.name === "keep")) {
3810
+ break;
3811
+ }
3812
+ return false;
3813
+ }
3814
+ break;
3815
+ case "ClassDeclaration":
3816
+ case "ModuleDeclaration":
3817
+ // none of the attributes means anything on classes and
3818
+ // modules, and the new compiler complains about some
3819
+ // of them. Just drop them all.
3820
+ if (node.attrs && node.attrs.access) {
3821
+ if (node.attrs.attributes) {
3822
+ delete node.attrs.access;
3823
+ }
3824
+ else {
3825
+ delete node.attrs;
3826
+ }
3827
+ }
3828
+ }
3829
+ return null;
3830
+ }
3819
3831
  function optimizeCall(istate, node, context) {
3820
3832
  const state = istate.state;
3821
3833
  const [name, results] = state.lookupNonlocal(node.callee);
@@ -12355,6 +12367,14 @@ module.exports = require("./api.cjs");
12355
12367
 
12356
12368
  /***/ }),
12357
12369
 
12370
+ /***/ 7168:
12371
+ /***/ ((module) => {
12372
+
12373
+ "use strict";
12374
+ module.exports = require("./sdk-util.cjs");
12375
+
12376
+ /***/ }),
12377
+
12358
12378
  /***/ 6906:
12359
12379
  /***/ ((module) => {
12360
12380
 
@@ -12514,8 +12534,8 @@ var negative_fixups = __webpack_require__(4499);
12514
12534
  var optimizer_types = __webpack_require__(9697);
12515
12535
  // EXTERNAL MODULE: external "./api.cjs"
12516
12536
  var external_api_cjs_ = __webpack_require__(6817);
12517
- ;// CONCATENATED MODULE: external "./sdk-util.cjs"
12518
- const external_sdk_util_cjs_namespaceObject = require("./sdk-util.cjs");
12537
+ // EXTERNAL MODULE: external "./sdk-util.cjs"
12538
+ var external_sdk_util_cjs_ = __webpack_require__(7168);
12519
12539
  ;// CONCATENATED MODULE: ./src/resources.ts
12520
12540
 
12521
12541
 
@@ -12567,7 +12587,7 @@ function visit_resources(elements, parent, v) {
12567
12587
  error(e);
12568
12588
  break;
12569
12589
  }
12570
- visit_resources(external_sdk_util_cjs_namespaceObject.xmlUtil.elementKids(e), e, visitor);
12590
+ visit_resources(external_sdk_util_cjs_.xmlUtil.elementKids(e), e, visitor);
12571
12591
  break;
12572
12592
  // These are the resources themselves. Some can occur at top level; most
12573
12593
  // are restricted to <resources> or one or more of the specific lists above
@@ -12712,7 +12732,7 @@ function add_resources_to_ast(state, ast, resources, manifestXML) {
12712
12732
  rez.body.body.push(hiddenRez);
12713
12733
  if (barrel === "" &&
12714
12734
  manifestXML &&
12715
- manifestXML.body instanceof external_sdk_util_cjs_namespaceObject.xmlUtil.Nodes) {
12735
+ manifestXML.body instanceof external_sdk_util_cjs_.xmlUtil.Nodes) {
12716
12736
  manifestXML.body
12717
12737
  .children("iq:application")
12718
12738
  .elements.forEach((e) => add_one_resource(state, manifestXML, rez, e));
@@ -12855,7 +12875,7 @@ function visit_resource_refs(state, doc, e) {
12855
12875
  return;
12856
12876
  }
12857
12877
  };
12858
- external_sdk_util_cjs_namespaceObject.xmlUtil.visit_xml([e], {
12878
+ external_sdk_util_cjs_.xmlUtil.visit_xml([e], {
12859
12879
  pre(node) {
12860
12880
  if (node.type !== "element")
12861
12881
  return false;
@@ -13300,7 +13320,7 @@ function checkCompilerVersion(version, sdkVer) {
13300
13320
  async function getApiMapping(state, resourcesMap, manifestXML) {
13301
13321
  // get the path to the currently active sdk
13302
13322
  const parser = (prettier_plugin_monkeyc_default()).parsers.monkeyc;
13303
- const sdk = await (0,external_sdk_util_cjs_namespaceObject.getSdkPath)();
13323
+ const sdk = await (0,external_sdk_util_cjs_.getSdkPath)();
13304
13324
  if (state) {
13305
13325
  state.sdk = sdk;
13306
13326
  const match = state.sdk?.match(/-(\d+\.\d+\.\d+)/);