nuprl / Stopify

A JS-to-JS compiler that makes it easier to build Web IDEs and compile to JS.
https://zenodo.org/records/10408254
BSD 3-Clause "New" or "Revised" License
173 stars 12 forks source link

Program runs incorrectly in Stopify #481

Closed LeifAndersen closed 3 years ago

LeifAndersen commented 3 years ago

The following program runs incorrectly in stopify:

var i = 0;
while(i < 10){
  console.log(i);
  var G = i + 1;
  i = G;
}

It prints out 0, and terminates.

Oddly enough, this program works as expected:

var i = 0;
while(i < 10){
  console.log(i);
  var i = i + 1;
}

Which prints out 0 through 9 and then terminates.

This is how I am running stopify:

(let [program <the-program>
       runner (stopify.stopifyLocally program)]
  (set! runner.g #js {:console js/console})
  (.run runner #(js/console.log "DONE!")))

I'm using the latest version of stopify in npm: (@stopify/stopify v0.7.3).

Finally, I'm pretty sure stopify thinks the program is actually terminating, and not just timing out because DONE also gets printed to the console.

LeifAndersen commented 3 years ago

I should mention that the program does run properly on the stopify home page. So it might be that I'm running it incorrectly?

arjunguha commented 3 years ago

It is definitely a Stopify bug, which should behave correctly regardless of runtime/compiler options.

An equivalent program works correctly on Ocelot:

https://code.ocelot-ide.org/?gist=arjunguha/aefe0f874a10db181eaa610a7c740fad

However, Ocelot does a bunch of other transformations that may be masking the problem.

Looks like the default estimator in Stopify is "velocity", and that the version that you're using is the same version that ElementaryJS uses:

https://github.com/ocelot-ide/Stopify/blob/3ac8225e495bed2a783a696addb4852a69970e30/stopify/src/runtime/check-runtime-opts.ts#L36

arjunguha commented 3 years ago

Here is a variation of an example that Leif sent over email, which shows the same problem:

<script src="https://github.com/ocelot-ide/Stopify/releases/download/0.7.3/stopify-full.bundle.js"></script>
<script>
const prog = `
var i = 0;
debugger;
while(i < 10) {
    console.log(i);
    var G = i + 1;
    i = G;
};`;
let runner = stopify.stopifyLocally(prog);
runner.g = {console};
runner.run(() => {
    console.log(runner.g.G, runner.g.i);
    console.log("DONE!");
});
</script>

If you pretty-print the code around the debugger, you see that the loop body translates to:

if ($__R.mode) {
  G = $S.g.i + 1; // BUG HERE: the l-value should be $S.g.G, I believe
  $S.g.i = $S.g.G
}

The bug is almost certainly in this file, which introduces the global object:

https://github.com/ocelot-ide/Stopify/blob/master/hygiene/ts/useGlobalObject.ts

I believe the problem is masked in Ocelot, because it wraps the entire program in a thunk (IIRC, though I forget why).

arjunguha commented 3 years ago

My guess is that loop bodies are considered "scopes" in Babel (which they are for let- and const- bound variables). So, the condition here is probably wrong:

https://github.com/ocelot-ide/Stopify/blob/master/hygiene/ts/useGlobalObject.ts#L91