   dup2_x1: {compile: (b) -> [v1,v2,v3]=[b.pop(),b.pop(),b.pop()];b.push(v2,v1,v3,v2,v1)}
   dup2_x2: {compile: (b) -> [v1,v2,v3,v4]=[b.pop(),b.pop(),b.pop(),b.pop()];b.push(v2,v1,v4,v3,v2,v1)}
   swap: {compile: (b) -> v2=b.pop(); v1=b.pop(); b.push(v2,v1)}
-  iadd: { compile: (b) -> b.push "util.wrap_int(#{b.pop()}+#{b.pop()})" }
-  ladd: { compile: (b) -> b.push2 "#{b.pop2()}.add(#{b.pop2()})" }
-  fadd: { compile: (b) -> b.push "util.wrap_float(#{b.pop()}+#{b.pop()})" }
-  dadd: { compile: (b) -> b.push2 "#{b.pop()}+#{b.pop()}" }
-  isub: { compile: (b) -> b.push "util.wrap_int(-#{b.pop()}+#{b.pop()})" }
-  lsub: { compile: (b) -> b.push2 "#{b.pop2()}.add(#{b.pop2()})" }
-  fsub: { compile: (b) -> b.push "util.wrap_float(-#{b.pop()}+#{b.pop()})" }
-  dsub: { compile: (b) -> b.push2 "-#{b.pop()}+#{b.pop()}" }
+  iadd: { compile: (b) -> b.push new Expr "util.wrap_int($0+$1)",b.pop(),b.pop() }
+  ladd: { compile: (b) -> b.push2 new Expr "$0.add($1)",b.pop2(),b.pop2() }
+  fadd: { compile: (b) -> b.push new Expr "util.wrap_float($0+$1)",b.pop(),b.pop() }
+  dadd: { compile: (b) -> b.push2 new Expr "$0+$1",b.pop(),b.pop() }
+  isub: { compile: (b) -> b.push new Expr "util.wrap_int($1-$0)",b.pop(),b.pop() }
+  lsub: { compile: (b) -> b.push2 new Expr "$1.subtract($0)",b.pop2(),b.pop2() }
+  fsub: { compile: (b) -> b.push new Expr "util.wrap_float($1-$0)",b.pop(),b.pop() }
+  dsub: { compile: (b) -> b.push2 new Expr "$1-$0",b.pop2(),b.pop2() }
   imul: { compile: (b) -> b.push "gLong.fromInt(#{b.pop()}).multiply(gLong.fromInt(#{b.pop()})).toInt()" }
   lmul: { compile: (b) -> b.push2 "#{b.pop2()}.multiply(#{b.pop2()})" }
   fmul: { compile: (b) -> b.push "util.wrap_float(#{b.pop()}*#{b.pop()})" }

        # java bytecode verification ensures that the stack height and stack /
        # local table types match up across blocks
        next_block.compile @stack[..], @locals[..]

cmpMap =
  eq: '=='

        b.push temp

compile_obj_handlers = {
  aconst_null: { compile: (b) -> b.push new Primitive "null"; }
  iconst_m1: { compile: (b) -> b.push new Primitive "-1"; }
  iconst_0: { compile: (b) -> b.push new Primitive "0"; }
  iconst_1: { compile: (b) -> b.push new Primitive "1"; }
  iconst_2: { compile: (b) -> b.push new Primitive "2"; }
  iconst_3: { compile: (b) -> b.push new Primitive "3"; }
  iconst_4: { compile: (b) -> b.push new Primitive "4"; }
  iconst_5: { compile: (b) -> b.push new Primitive "5"; }
  lconst_0: { compile: (b) -> b.push2 new Primitive "gLong.ZERO"; }
  lconst_1: { compile: (b) -> b.push2 new Primitive "gLong.ONE"; }
  fconst_0: { compile: (b) -> b.push new Primitive "0"; }
  fconst_1: { compile: (b) -> b.push new Primitive "1"; }
  fconst_2: { compile: (b) -> b.push new Primitive "2"; }
  dconst_0: { compile: (b) -> b.push2 new Primitive "0"; }
  dconst_1: { compile: (b) -> b.push2 new Primitive "1"; }
  # the *astore commands don't work yet...
  iastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  lastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
  fastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  dastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
  aastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  bastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  castore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  sastore: {compile: (b) -> b.add_line "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
  pop: {compile: (b) -> b.pop()}
  pop2: {compile: (b) -> b.pop2()}
  # TODO: avoid duplicating non-primitive expressions so as to save on computation
