Chipmunk in ASM.JS

I'm not sold on emscripten. Its a cool idea, and its impressive that it works at all, but its output seems really stupid. For example, take this C function:

cpFloat  
cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset) {  
    return m*(0.5f*(r1*r1 + r2*r2) + cpvlengthsq(offset));
}

This is the asm.js code that emscripten generates using -O2:

function cpMomentForCircle(m, r1, r2, offset) {  
  // Type annotations
  m=+m;
  r1=+r1;
  r2=+r2;
  offset=offset|0;

  // Variable declarations
  var f=0, g=0, h=0.0;

  // Body
  f=STACKPTR;
  g=offset;
  offset=STACKPTR;
  STACKPTR=i+16|0;
  HEAP32[offset>>2] = HEAP32[g>>2];
  HEAP32[offset+4>>2] = HEAP32[g+4>>2];
  HEAP32[offset+8>>2] = HEAP32[g+8>>2];
  HEAP32[offset+12>>2] = HEAP32[g+12>>2];
  h=((r1*r1 + r2*r2)*0.5+ +cpvlengthsq(offset))*m;
  STACKPTR=f;
  return +h;
}

How did such a simple function become so complicated? It doesn't need to copy the offset vector onto the stack (in fact, it doesn't need to use the stack at all).

With code like that you can easily see how ChipmunkJS triples in size due to emscripten. You can also see how executables get so big...

So I was curious how hand written asmjs compares, so I ported cpVect to asmjs manually.

The original C:

static inline cpVect cpvclamp(const cpVect v, const cpFloat len)  
{
    return (cpvdot(v,v) > len*len) ? cpvmult(cpvnormalize(v), len) : v;
}

ChipmunkJS:

var vclamp = cp.v.clamp = function(v, len)  
{
    return (vdot(v,v) > len*len) ? vmult(vnormalize(v), len) : v;
};

And hand written ASMJS:

function vclamp(ret, v, len) {  
  ret = ret|0;
  v = v|0;
  len = +len;

  if (+vdot(v, v) > len*len) {
    vnormalize(ret, v);
    vmult(ret, ret, len);
  } else {
    cpv(ret, +f64[v>>3], +f64[v+8>>3]);
  }
}

(Most methods don't balloon out in complexity so much)

I don't know how my version compares to LLVM's version in terms of speed. I'd like to think that its faster - but I have no idea if thats actually true. I've been told that LLVM will generate better assembly than me almost always.

Its really annoying to write this code - especially dealing with the heap and doing type annotations everywhere. If I were going to convert the whole thing, I'd be better off using LLJS's asmjs branch. That said, lljs hasn't been touched in 3 months and doesn't seem to currently work. I think I would still be ahead time-wise after fixing LLJS though. I don't want to debug those bitshift operations.

The big win is in compiled output size. And here's the crazy part: The hand-written asmjs is smaller than ChipmunkJS!

$ uglifyjs -cm <cpVect.js  | wc -c
    2736

VS:

$ uglifyjs -m <asm.js  | wc -c
    2481

(-c doesn't work on asmjs modules - it breaks some of the type hints)

That asmjs module is at a disadvantage too - it includes a bunch of asmjs boilerplate that is only needed once!

The most interesting part is looking at the minified code. It looks totally different. And its super obvious which is which:

,vperp=cp.v.perp=function(t){return new Vect(-t.y,t.x)},
vpvrperp=cp.v.pvrperp=function(t){return new Vect(t.y,-t  
.x)},vproject=cp.v.project=function(t,n){return vmult(n,
vdot(t,n)/vlengthsq(n))};Vect.prototype.project=function  
(t){return this.mult(vdot(this,t)/vlengthsq(t)),this};va
r vrotate=cp.v.rotate=function(t,n){return new Vect(t.x*  
n.x-t.y*n.y,t.x*n.y+t.y*n.x)};Vect.prototype.rotate=func  
tion(t){return this.x=this.x*t.x-this.y*t.y,this.y=this.  

function I(n,r,t){n=n|0;r=r|0;t=t|0;m(n,+y[r>>3]-+y[t>>3  
],+y[r+8>>3]-+y[t+8>>3])}function U(n,r){n=n|0;r=r|0;y[n
>>3]=-+y[r>>3];y[n+8>>3]=-+y[r+8>>3]}function z(n,r,t){n
=n|0;r=r|0;t=+t;y[n>>3]=y[r>>3]*t;y[n+8>>3]=y[r+8>>3]*t}
function F(n,r){n=n|0;r=r|0;return+(+y[n>>3]*+y[r>>3]+ +  
y[n+8>>3]*+y[r+8>>3])}function _(n,r){n=n|0;r=r|0;return  
+(+y[n>>3]*+y[r+8>>3]-+y[n+8>>3]*+y[r>>3])}function b(n,
r){n=n|0;r=r|0;m(n,-+y[r+8>>3],+y[r>>3])}function j(n,r)  

I've never seen javascript look so mathsy. GWT, CoffeeScript and the closure compiler all look positively plain compared to that.

The entire cpVect asmjs module is here if you want to see a larger code sample.

In comparison, cpVect.js is here and the original cpVect.h is here.

The only downside is that its super awkward to call any of these methods from normal javascript. Because asm.js modules can't view or edit normal javascript objects, all the stateful data has to exist inside the module's own memory heap & stack. If I go this route, I'll need to wrap the entire chipmunk API in a plain JS API to actually make it usable by normal javascript programs. Its a serious downer. (Emscripten has exactly the same problem).