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]

  • New version v2.0.82 is out.

    This should handle the new .prg file format, and new bytecodes generated by sdk-7.x for system 5 devices.

    It also allows exclude annotations on typedefs, and arbitrary initializers for enum values.

  • I'll just note that the new bytecodes really help with size. 

    With sdk-6.4.2, when built for edge1040, one of my projects ends up at 10952 bytes before the post build optimizer, and 10126 after.

    With sdk-7.1.0, the same project ends up at 8845 bytes before the post build optimizer, and 8415 after

    The win from the post build optimizer is much smaller - in part because there's less opportunity because of the better bytecodes, but also in part because although the bytecode optimizer understands the new bytecodes, it doesn't yet take advantage of them; and in a few cases it will actually make things worse.

    Note that the same project for fenix5xplus (which doesn't get to use the new instructions) is 10907 with sdk-6.4.2, and 10862 with sdk-7.1.0. So the new compiler is a bit better at optimizing, but the huge wins come from the new instruction set. 

    Just to note some of the highlights. In the old bytecode, materializing any literal Number took 5 bytes - 1 byte for the bytecode, and 4 for the value. Now there are 4 new bytecodes:

    • one specifically for the value 0, so you can materialize a zero with a single byte
    • one for values in the range -128 to 127, which takes 2 bytes
    • one for values in the range -32768 to 32767 which takes 3 bytes
    • one for values in the range -8388608 to 8388607, which takes 4 bytes

    There are also single byte bytecodes to materialize 0L, 0.0F and 0.0D.

    To properly take advantage of this, pre will need to be modified to understand the cost of Numbers of different sizes.

    Other big improvements are literal arrays, byte arrays and dictionaries, which can now be materialized with a single bytecode (of course, the values are stored in the data section - but it still saves all the instructions that would have been used to initialize the array).

    There's also a bunch of common patterns that have been turned into single instructions. eg looking up a non-local symbol used to take 2 bytes to push self, 5 bytes to push the symbol and one byte to do the lookup. Now it can be done by a single 5 byte instruction, saving 3 bytes and 2 instructions.

  • So until now when I used 0 in a function at least 3 times it was better to add a variable for it. Now this is still the case for old devices but if I understand what you wrote it probably is no longer the case for system7 devices, because there it's better to use 0 in each and every place?

  • indeed useful. Any chance you can add the human readable device name as well somewhere in the line?

  • Correct. And for byte sized integers, the push instruction is the same size as a push of a local, so it's also going to be better to stick with the literals. For numbers outside the range -128 to 127, it will be worth putting them in locals if there are enough uses.

    Currently, neither the source to source PRE, nor the post build PRE is aware of the new instructions - but once I fix that the best thing to do is to write the code naturally, and let the optimizer take care of which constants should go in locals.

    Quite apart from the fact that the goalposts can change (as they just did), there are a lot of hidden constants in MC code. eg 

    var x = [7,8,9];

    gets translated to (something very similar to)

    var x = new [3];
    x[0] = 7;
    x[1] = 8;
    x[2] = 9
    
    

    So there was a hidden 0, 1 and 2 in the code that you wouldn't have seen. So trying to put all the zeros in a single local just doesn't work.

    Another one is that -x gets translated to 0-x, so there's another hidden zero.

  • '@' is a valid character in the strings xml. For example this is valid:

    <string id="TEST">@ABCD</string>
    But the optimizer doesn't think so:
    ERROR> fr955: strings.xml:3:23: Undefined symbol ABCD
  • Hi, I've found several issues.

    See comments in code with problem description:

    import Toybox.Lang;
    
    class Test {
        enum TestEnum {
            VALUE,
        }
    
        typedef TestType as Number;
    
        function initialize() {
            test1(0);
            test2();
            test3(0);
            test4();
            test5();
        }
    
        function test1(value as Object) as Boolean {
            // Undefined symbol VALUE.equals [pmc-analysis]
            return VALUE.equals(value);
        }
    
        function test2() as Boolean {
            // Dictionary<Number, Array<Number>>
            // after code formatting becomes
            // Dictionary<Number, Array<Number> >
            // with extra space ---------------^
            var dict = ({ 0 => [0] as Array<Number> }) as Dictionary<Number, Array<Number>>;
    
            var dictValues = dict.values();
    
            for (var i = 0; i < dictValues.size(); i++) {
                var arr = dictValues[i];
    
                for (var j = 0; j < arr.size(); j++) {
                    var arrElem = arr[j];
    
                    // Argument 1 to $.Test.test3 expected to be Number but got Array<Number> [pmc-analysis]
                    if (test3(arrElem)) {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        function test3(value as Number) as Boolean {
            try {
                value++;
            } catch (e instanceof Lang.Exception) {
                return false;
            }
            // The line below disappears after code formatting
    
            return true;
        }
    
        static function test4() as Number {
            // Unable to resolve type TestType [pmc-analysis]
            var value = 0 as TestType;
    
            return value as Number;
        }
    
        function test5() as Void {
            var dict = ({ 0 => [0] as Array<Number> }) as Dictionary<Number, Array<Number>>;
    
            // Build fails with
            // no viable alternative at input '(dict.get(pre_0)asArray<Number>)[pre_0]='
            test6(dict, 1);
        }
    
        (:inline)
        function test6(dict as Dictionary<Number, Array<Number>>, value as Number) as Void {
            var arr = dict.get(0) as Array<Number>;
            arr[0] = value;
        }
    }

    <drawable
        id="Background"
        class="WatchUi.Bitmap">
        <!-- Undefined symbol center [pmc-analysis] -->
        <param name="locX">center</param>
        <param name="locY">center</param>
    </drawable>
    <!-- Expected ..., identifier, number, string, or whitespace but ")" found. [pmc-analysis] -->
    <label
        text="test"
        x="33.3%"
        y="33.3%"/>
    

    Cound you, please, fix them?

  • please, fix them?

    I will do my best. Would you mind filing issues at https://github.com/markw65/monkeyc-optimizer/issues? Preferably separate issues for each problem.

    Some comments though: The extra space in Dictionary<Number, Array<Number> > is because when I first wrote the formatter, Garmin's parser didn't work without the space - and those compilers could still be in use today. Here's the output when you omit the space:

    ERROR: <device>: bug.mc:17,14: mismatched input '<' expecting {',', ';'}

    I don't want to break working code - but this is one instance where a formatting option might be appropriate.

    "Disappearing lines" - that's a prettier thing. Not something I'm going to fix.

    The rest seem reasonable, and I'll get to them as soon as I can.

  • no viable alternative at input '(dict.get(pre_0)asArray<Number>)[pre_0]='

    This one is pretty clearly a bug in Garmin's parser:

    foo()[0] = 0; // ok

    (foo() as Array<Number>)[0] = 0; // fails

    (foo())[0] = 0; // also fails

    I'll file a bug report. Meanwhile I'll try to prevent creating these... 

  • Version 2.0.83 is out.

    It fixes a number of recently reported issues.

    - Fix return types of Dictionary.values() and Dictionary.keys()
    - Fix issue with single-copy-prop creating stores that the Garmin compiler can't parse
    - Handle percentage strings in resources
    - Handle special strings in <param> nodes
    - Don't treat '@Dotted.Name' as special in strings and jsonData
    Also makes some small improvements to the bytecode optimizer for sdk-7 and ciq-5 devices.
    - Include ipush2 and ipush3 in pre