fasterthanlime / shin

:warning: (def shin (dissoc clojurescript :jvm :google_closure)) (deprecated)
MIT License
35 stars 1 forks source link

Smarter loop/recur #46

Closed fasterthanlime closed 9 years ago

fasterthanlime commented 9 years ago

See #45 for reference output vs ours

fasterthanlime commented 9 years ago

Original cljs code:

(loop [i 0
       j 0]
  (when (< i 10)
    (prn "i = " i)
    (recur (inc i) j)))

Recent commits made Shin's output go from:

(function () {
  var $$__i1 = 0;
  var $$__j2 = 0;
  var recur = null;
  var $$__loopret3 = null;
  while (true) {
    if ($s.call(null, $$__i1, 10)) {
      prn.call(null, 'i = ', $$__i1);
      $$__loopret3 = recur = [
  inc.call(null, $$__i1),
$$__j2
  ];
    } else {
    }
    if (recur) {
      $$__i1 = recur[0];
      $$__j2 = recur[1];
      recur = null;
      continue;
    } else {
    }
    break;
  }
  return $$__loopret3;
}());

to

{
  var $$__i1 = 0;
  var $$__j2 = 0;
  var recur = null;
  while (true) {
    if ($s.call(null, $$__i1, 10)) {
      prn.call(null, 'i = ', $$__i1);
      recur = [
        inc.call(null, $$__i1),
        $$__j2
          ];
    } else {
    }
    if (recur) {
      $$__i1 = recur[0];
      $$__j2 = recur[1];
      recur = null;
      continue;
    } else {
    }
    break;
  }
}

So we lost the (useless) outer anon-func, but we're still getting funky with the recur array so far.

fasterthanlime commented 9 years ago

Shin output (at the time of this writing)

{
  var $$__i1 = 0;
  var $$__j2 = 0;
  var $$__recur_sentinel3 = true;
  while ($$__recur_sentinel3) {
    $$__recur_sentinel3 = false;
    if ($s.call(null, $$__i1, 10)) {
      prn.call(null, 'i = ', $$__i1);
      {
        var $$__$$__G__45 = inc.call(null, $$__i1);
        var $$__$$__G__67 = $$__j2;
        $$__i1 = $$__$$__G__45;
        $$__j2 = $$__$$__G__67;
        $$__recur_sentinel3 = true;
      }
    }
  }
}

Mainline cljs output

var i_7994 = 0;
var j_7995 = 0;
while (true) {
    if ((i_7994 < 10)) {
        cljs.core.prn.call(null, "i = ", i_7994); {
            var G__7996 = (i_7994 + 1);
            var G__7997 = j_7995;
            i_7994 = G__7996;
            j_7995 = G__7997;
            continue;
        }
    } else {}
    break;
}

I'm keeping the BlockStatement around the loop. I just think it's nicer to read, and I don't believe they incur a significant performance difference.

fasterthanlime commented 9 years ago

With #47 closed, we now have:

{
  var $$__i1 = 0;
  var $$__j2 = 0;
  var $$__recur_sentinel3 = true;
  while ($$__recur_sentinel3) {
    $$__recur_sentinel3 = false;
    if ($$__i1 < 10) {
      prn.call(null, 'i = ', $$__i1);
      {
        var $$__$$__G__45 = inc.call(null, $$__i1);
        var $$__$$__G__67 = $$__j2;
        $$__i1 = $$__$$__G__45;
        $$__j2 = $$__$$__G__67;
        $$__recur_sentinel3 = true;
      }
    }
  }
}

Which is pretty much as good as it gets :) vs mainline cljs:

var i_7994 = 0;
var j_7995 = 0;
while (true) {
    if ((i_7994 < 10)) {
        cljs.core.prn.call(null, "i = ", i_7994);
        {
            var G__7996 = (i_7994 + 1);
            var G__7997 = j_7995;
            i_7994 = G__7996;
            j_7995 = G__7997;
            continue;
        }
    } else {}
    break;
}
fasterthanlime commented 9 years ago

Note: the reason we're using a sentinel to recur rather than writing out a continue directly is macros.

Sometimes, we'll have generated code like:

while(true) {
  when$_not.call(null, function() {
    // some block of code
    continue; // woops, there was a loop in the outer context
  });
}

And V8 will straight out refuse to load that code - not even refuse to execute it, just refuse to parse/load it. So we can't write out continues everywhere like that. But oh well, our solution is elegant too and there's no reason it should be slower than the mainline cljs one :)