Big update to prettier-extension-monkeyc

I've posted about prettier-extension-monkeyc before, but I've added a bunch of new features that developers will probably like (well, I've been missing them, so maybe you have too).

The new features it implements for VSCode include:

  • Goto Definition. Point at a symbol, Ctrl/Cmd click, and it will take you to the definition. Or F12
  • Goto References. Right click on a symbol and select "Goto References". It will show you all the references. Or Shift-F12
  • Peek Definition/Peek References. Same as above, but in a popup window so you don't lose your place in the original document.
  • Rename Symbol. Right click on a local, function, class or module name, and select "Rename Symbol". It will rename all the references. It doesn't yet work for class members/methods.
  • Goto Symbol. Type Ctrl/Cmd-Shift-O and pick a symbol from the drop down (which has a hierarchical view of all symbols in the current file). This also appears as an outline across the top of the file.
  • Open Symbol By Name. Type Ctrl/Cmd-T, then start typing letters from a symbol name. A drop down will be populated with all matching symbols from anywhere in your project.

Older features include a prettier based formatter for monkeyc, and a monkeyc optimizer that will build/run/export an optimized version of your project.

[edit: My last couple of replies seem to have just disappeared, and the whole conversation seems to be in a jumbled order, so tldr: there's a new test-release at https://github.com/markw65/prettier-extension-monkeyc/releases/tag/v2.0.9 which seems to work for me on linux. I'll do more verification tomorrow, and push a proper update to the vscode store once I'm sure everything is working]

  • By orphan I mean devices that don't support background wouldn't be supported. I ran the optimizer, it does recover a lot of code memory. From looking at the optimized source it seems that most of the gains were made by using a local variable for 0, 0.0, 0.0d and 1 and replacing constants. Does a local variable for each function use less memory than one variable for the class? I have noticed that the $ and self keyword increases code size so I got rid of those. I can't tell that there was a performance penalty by making the VM hunt for the symbols. Following this it seems that declaring const values is counter productive and variables should be used. Could a simple 0 be used where a 0.0 or 0.0d would be appropriate and the VM cast the 0 as necessary? I have noticed that having just one double in an equation of floats will make the result double so it seems that casting/promoting will happen.

  • By orphan I mean devices that don't support background wouldn't be supported

    Ok... but if you're writing background code, it's not going to run on devices that don't support background anyway?

    If you're writing an app that has a background service on devices that support it, but not on devices that don't, you could have two sets of resource files, selected by the .jungle, and only add the background scope to the set that support background apps...

    From looking at the optimized source it seems that most of the gains were made by using a local variable for 0, 0.0, 0.0d and 1

    That's certainly one of the most significant optimizations.

    and replacing constants

    This used to be a big deal - and was basically what prompted me to write the optimizer in the first place - but since sdk-4.1.6 Garmin's optimizer should also replace constants with their values. I still do that optimization because eg you could have both a use of a literal 3, and a constant THREE in the same function, and I want to put them both into the same local variable, rather than using a separate variable for each. Also, if you had 2*THREE, I want to replace it with 6 (and then possibly put that in a local variable).

    Does a local variable for each function use less memory than one variable for the class

    Pushing a literal Number or Float takes 5 code bytes, Long and Double take 9, and a const or var from the current class takes 8 (two bytes to push self, 5 bytes to push the symbol, and one byte to do the lookup). Storing or loading a local takes 2 bytes.

    So... putting a literal in a class const/var won't save code size (unless its a Long or Double; but even then, its unlikely to be the best way to go because of the overhead of initializing the variable, and the extra storage associated with the variable).

    But if a 5-byte literal is used 3 times, or a 9-byte literal is used twice in a single function, then putting it into a local is a code size win.

    I have noticed that the $ and self keyword increases code size

    $ does increase code size (but my optimizer should automatically fix that for you). But self shouldn't make any difference. If foo is a non-local variable, then foo and self.foo will generate exactly the same code (and of course, if foo is a local, then foo refers to the local, while self.foo refers to a non local foo assuming there is one in the current lookup scope).

    If you're in a global function, dropping the '$' will actually have less runtime cost than keeping it, because self will be equal to '$', and is already in a local. If you're in a class/module, then dropping the '$' will cause various other scopes to be searched, which will add some runtime overhead - but its unlikely to be significant (imo).

    Following this it seems that declaring const values is counter productive

    If the final binary still contains the const, then yes, it's counter productive. But as I said, both my optimizer, and Garmin's in 4.1.6 and later will handle consts properly; so there is essentially no cost to using named constants (and obviously huge benefits for readability).

    Could a simple 0 be used where a 0.0 or 0.0d would be appropriate and the VM cast the 0 as necessary

    Mostly, yes. But it depends. If a function expects a Number, and you pass a Float (or vice versa), you could get a runtime error (or a compile time error if you have the type checker enabled), or a subtle change in behavior. For example in a conditional, a Number will be true if it's non-zero, and false if it's zero. But a Float will always be true, regardless of its value:

    (:typecheck(false))
    function foo() {
      var x = 0;
      if (x) {
        // doesn't execute
      }
      var y = 0.0;
      if (y) {
        // does execute
      }
    }

    Note that Garmin's type checker doesn't like this code - but the runtime accepts it.

    I have looked into extending my optimization to try to do exactly what you suggest - but getting it right is complicated.

  • One thing I noticed looking at the optimized code is reusing variables, which makes sense, but the names are the first ones in the code. The variables in the code have names related to their purpose and are useful for debugging. It's a little disconcerting to use a name for another purpose. Maybe when creating variables for reuse use names like pre_reuse_1 or pre_stack_1 and so on or something similar?

  • This is something that was bothering me as well. I haven't look to the generated code lately, but i remember that Mark added comments next to them that shows it's meaning. Probably it's not 100% as you or I would like it , and if I remember correctly things get worse when you start to use inline functions (which can spare lots of code and even make it slightly faster) and still keep the original code pretty much readable.

  • The variables in the code have names related to their purpose and are useful for debugging. It's a little disconcerting to use a name for another purpose

    If you're using the post build optimizer (and if you want the best results, you probably should), it has its own variable sharing code. That actually works a little better, because things like self, and any catch variables are available for re-use.

    So if you turn on prettierMonkeyC.postBuildOptimizer, and you turn off prettierMonkeyC.minimizeLocals (which only affects the source-to-source optimizer), you should get essentially the same final results, but with all the original variable names in the source.

    But generally, I would recommend debugging using the unoptimized code anyway - unless, of course, you're trying to debug why the optimized version isn't working!

  • Are constants like 0, 0.0, 1, 1.0, etc better left as is or should a variable be used for these? If it's a variable, where is the best place to define it? Locally in the function, at the class level, or globally? Does public or private matter?

    A side question: how can I quote in the forum? I don't see that option anywhere.

  • A side question: how can I quote in the forum? I don't see that option anywhere.

    This may not work on phones, but it definitely works on desktop (PC/Mac) and on an iPad. To quote forum text with a nice link to the original post/comment, press Reply (*), select the text you want to quote, and press the Quote button that appears. If the post or comment is in a different thread or page, you can make a fake reply for the purposes of quoting and copy/paste the quoted text into your real reply.

    (*) You can skip this step - if you do so, then a reply will be created when you press Quote.

    You can also style any text you want with the same "quoted" style (but obviously without a link to the original text) using Format > Formats > Blocks > Blockquote. Sometimes this doesn't look great if you're copying and pasting formatted text (e.g. garmin docs or some random website), since it changes everything to italics and may break certain formatting like bulleted lists / tables or change the size of certain text. In this case sometimes I prefer to use Format > Indent (but sometimes that fails with certain formatted text). Ofc, sometimes a screenshot works better.

    random quoted text

    NOTE: you can't quote emojis or the forum software will get super mad at you and block your post.

  • Are constants like 0, 0.0, 1, 1.0, etc better left as is or should a variable be used for these?

    It depends. If there are three uses of a Number or Float, then it's generally better to put it in a local. But the post build optimizer can sometimes do even better. eg "foo(42, 42, 42)". You can reduce the code size by changing it to "var x=42; foo(x, x, x);". The original code takes 15 bytes to put 3 copies of 42 onto the stack; the new code takes 7 bytes to initialize x, and then 2 bytes to put each copy of x on the stack, saving 2 bytes. But the post build optimizer can do better. It changes it to "foo(42, <dup>, <dup>)" where <dup> is a 2-byte bytecode that pushes a copy of a value thats already on the stack, saving 6 bytes (there's no way to write this at the source code level, of course).

    There are other issues too. eg "x = [2,2]". Here there are only 2 uses of the literal 2 in the source code, but in the generated bytecode there's an additional 2 (the size of the array), and again, thats something the post build optimizer will fix for you - putting all 3 uses in the same variable.

    Similarly, values can be hidden in api constants (or your own constants) - "foo(WatchUi.BehaviorDelegate.BEHAVIOR_NEXT_PAGE, Ant.DeviceConfig.DEFAULT_DEVICE_TYPE, Lang.ENDIAN_BIG)" ends up as "foo(1,1,1)" - so again, putting 1 into a local would help; but you really don't want to do that in code you maintain - which is where my optimizer comes in.

    where is the best place to define it

    To be of any use, it has to be a local; accessing a local takes two bytes, while accessing a class or module variable takes at least 8 (thats assuming you can access it via a simple name; if you have to qualify the name its an extra 6 bytes for each qualifier).

    But again, the real point is that generally doing this in your code should be a last resort; it tends to obfuscate the code, and makes it difficult to maintain. And my optimizer (especially with the post build optimizer enabled) will generally do at least as well as you can do (on this particular issue) without having to compromise the code.

  • This is great information. What I'm gathering from this is that using constants is the way to go, it makes the optimizer's job easier. I do have some named constants for readability, like:

    public const PIx2d as Double = 6.28318530717958648d; // 2pi
    public const iPIx2d as Double = 0.159154943091895336d; // 1/2pi

    How will the optimizer handle these? Where should these be defined?

  • How will the optimizer handle these

    The first thing it will do is replace all uses of them with their literal values (as if you'd used the literal values in the first place). This allows it to remove the definitions from your code (which reduces the size of the data section; and since these are doubles, there's also some code to initialize them, so it saves code size too). Then it will do any constant folding it can at the point of use (ie "var x = PIx2d * 5;" becomes "var x = 6.28318530717958648d * 5;" becomes "var x = 31.41592653589793d"). And then it will look for multiple uses of the same constant in each function, and replace them with locals where it's a code size win. Since this is a double, it only needs to find 2 uses for it to be a win to replace it with a local.

    Where should these be defined?

    Since the compiler is going to remove the const definitions from your code, it doesn't really matter where you define them. So its whatever makes most sense for maintainability.