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]

  • Here are some stats:

    sdk 4.1.7: code: 14865 data: 6336

    prettier 2.0.40 no propagate, no trusted: code: 13733 data: 5266
    prettier 2.0.40 no propagate, trusted: code: 13730 data: 5266
    prettier 2.0.40 propagate, no trusted: code: 13637 data: 5290
    prettier 2.0.40 propagate, trusted: code: 13627 data: 5290

    prettier 2.0.41 no propagate, no trusted: code: 13733 data: 5266
    prettier 2.0.41 no propagate, trusted: code: 13730 data: 5266
    prettier 2.0.41 propagate, no trusted: code: 13598 data: 5290
    prettier 2.0.41 propagate, trusted: code: 13575 data: 5291

  • There WAS some other strange issue: some strange bug in 2.0.40. I don't know how to send you a minimal version to reproduce this but there was a serious problem with 2.0.40: it created code that causes stack overflow error. This is 100% reproducible with my project, but not sure I'll be able to shrink it down and still have it. And also it happens only with the older version, let me know if it still is interesting to you.

    happens: when the PRE is enabled (in all 4 variations of propagate and trust)

  • Here are some stats:

    That looks pretty good? I thought you said that the results were bad when you finally got it to compile?

  • Yes it's very good! When I tried it for the 1st time with the new version there were some optimization problems but after I fixed them it produced much better code.

  • it created code that causes stack overflow error

    Hmm. Assuming it's not a bug causing a recursive call of some kind, I suppose that means that I really should try to share variables to keep the stack frames small. I must admit Ive been assuming that stack space wasn't much of an issue.

    I would definitely like to investigate, but there's probably no way to do so without the actual code. When you get the stack overflow does it print a backtrace?

    If so, maybe you could see which functions were active, and what the optimizer had done to them (in terms of adding new variables) compared to what it does in the new version.

    One improvement in the new version is that if you already have a value in a local, it should use that instead of creating a pre-variable. In the following

      var x = some_computation();
      mMemberVariable = x;
      ...
      mMemberVariable.foo();
      if (mMemberVariable.bar == 42) { whatever(); }
      

    it should use x for the last two mMemberVariable references instead of creating a new pre_mMemberVariable local and using that. So maybe something like that pulled it back down to a safe level

  • So it looks as I wrongly assumed: 100/3 is Number

    Yes, 100/3 is Number, and my type checker definitely knows that.

    But if its the issue I mentioned above where the typechecer is running on code that it thinks has had type propagation run, but it actually hasn't, then this could happen because <unknown-type> / <unknown-type> is going to produce Number or Float or Long or Double (or its going to crash; but there's no need for the optimizer to worry about that possibility, as long as it doesn't cause it to happen when it wouldn't have happened in the first place).

    There's also one other edge case (besides inlining) that might cause something like this. If there's a complex conditional guarding a block of code (eg if (!(a && b) || c && !d) { whatever }), and type propagation manages to deduce that the code is unreachable, it won't propagate types into the unreachable block.

    But if the optimizer fails to realize that the code is unreachable (which, obviously, is a bug, but there are subtle differences between the environment that type propagation runs in, and the environment that the optimizer runs in) then again, its likely to report errors because everything in the block has unknown types.

  • There are if-s around the 2 calls that appear in the errors but both are reachable and I know that at least the 1st one was reached because that's the code that calculates my new feature.

  • So I can understand why the optimizer thinks that foo() can also returns Null

    Can you explain it? I've tried reading that code a few times, and it looks to me like r is assigned a non-null value on every path through the function. Am I missing something?

  • OOOPS! I managed to cause this, so your assumption that the stack is not counted into the DF's memory limit might be true, but it does have another (smaller) limit, so even though my peak memory usage is less than 32kB out of 252bK on the fr255 it crashes.

    I replaced this code (where the stackoverflow happened): 

    var value = getConfig(key);

    with:

    function stack(i as Number, key as PropertyKeyType) as PropertyValueType or Null {
      var a=1,b=2,c=3,d=4,e2=5,f=6,g=7,h=8,i2=9,j=10;
      if (i <= 0) {
        return getConfig(key);
      }
      return stack(i - 1, key);
    }

    var
    value = stack(10, key);
    And now it happens even without prettier optimizer:

    Error: Stack Overflow Error
    Details: Failed invoking <symbol>
    Stack:
     - stack() at source/Config.mc:63 0x1000007e
     - stack() at source/Config.mc:68 0x100000fa
     - stack() at source/Config.mc:68 0x100000fa
     - stack() at source/Config.mc:68 0x100000fa
     - stack() at source/Config.mc:68 0x100000fa
     - getConfigStr() at source/Config.mc:79 0x10001178
    ... 7 more lines, the last one is the initialize() of the app
    And when I try it with prettier (2.0.41) then it almost works. If I change the number of recursion to 13 then it crashes. So it looks like 2.0.40 used just a little bit more local variables and it pushed it over the edge. And it LOOKS that with 2.0.41 it's just but ok. But it's obviously getting to be the new problem: even though I have now 90+kB I could use for code the stack issue will very soon become the new bottleneck.
    When I try with the original code (without the stack()) and 2.0.40 then I see 2 things:

    Error: Stack Overflow Error
    Details: Failed invoking <symbol>
    Stack:
    - getConfig() at bin/optimized/group002-debug/source/source/Config.mc:11 0x10001437
    - getConfig() at bin/optimized/group002-debug/source/source/Config.mc:11 0x10001437
    - getConfigStr() at bin/optimized/group002-debug/source/source/Config.mc:55 0x10000053
    - searchVal2OtherVal() at bin/optimized/group002-debug/source/source/ExtHRMField.mc:1232 0x10001519
    - loadHeartRateAlert() at bin/optimized/group002-debug/source/source/ExtHRMField.mc:795 0x10001da0
    - setHeartRateZones() at bin/optimized/group002-debug/source/source/ExtHRMField.mc:772 0x100025e2
    - setAntName() at bin/optimized/group002-debug/source/source/ExtHRMField.mc:1373 0x1000238b
    - compute() at bin/optimized/group002-debug/source/source/ExtHRMField.mc:1021 0x10001b24
    1. it does happen from the compute() (not the initialize of the app)
    2. that it has 2 identical lines at the beginning. This might probably be a bug in the simulator, but not sure, this is the generated code for getConfig, so there can't be a recursion unless Properties.getValue() calls getConfig...:

    (:no_inline)
    function getConfig(key as PropertyKeyType) as PropertyValueType? {
      var val;
      try {
        val = Properties.getValue(key);
      } catch (e) {
        logRelease("" + e);
        val = null;
      }
      return val;
    }
    
  • I am pretty sure that in the reality it's never null, otherwise I would've see it in ERA. And even according to the snippet I sent it should not be null, however there is something that I considered as a possibility to "sneak in" a null:

    getString() that I sent above is a simplification of the code in another class. And even though I know it will never return null because I initialized it with ["a","b","c"] but in THEORY:

    private var mArr as Array<String>;
    ...
    return mArr[mI];

    Can't this return null?

    I mean I can do:
    initialize() {
      mArr = new[3];
    }

    and then there are 3 nulls in mArr. I haven't try this, so maybe it would fail somewhere else (especially because I run with -l 3) but IMHO it would work. And if so then maybe this introduces a possible String or Null.

    Ah and here is the real deal: in theory the following could be IMHO:
    mS = null; mT = -1; cond1 = false; so this is what we do:
    var s = mS;
    var t = mT;
    // cond1's else:
      if (t == 0) {}
      if (t < 5) {
        r = s as String; // this is a lie, because not s is null
      }
    }
    return r;

    IMHO this is what the optimizer saw (especially because of the type propagation, and I guess it's more clever than I am... it doesn't believe that s is String there.