   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()}
+  iastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  lastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  fastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  dastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop2()}
+  aastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  bastore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  castore: {compile: (b) -> b.add_stmt "b.check_null($2).array[$1]=$0",b.pop(),b.pop(),b.pop()}
+  sastore: {compile: (b) -> b.add_stmt "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

    @stack = []
    @locals = []
    @next = []
    @body = ""
    @visited = false

  push: (values...) -> @stack.push.apply @stack, values

  pop: -> @stack.pop()
  pop2: -> @stack.pop(); @stack.pop()
  put_cl: (idx, v) ->
    @locals[idx] = v
  put_cl2: (idx, v) ->
    @locals[idx] = v
    @locals[idx+1] = null
  cl: (idx) -> @locals[idx]
  add_stmt: (stmt) -> @stmts.push stmt
  new_temp: -> @block_chain.new_temp()

  compile_epilogue: ->
    # copy our stack / local values into appropriately-named vars so that they
    # can be accessed from other blocks
    rv = []
    for blk_id in @next
      block = @block_chain.get_block_from_instr blk_id
      for s, i in @stack when s?
        unless /^\$\d+$/.test s
          temp = @new_temp()
          rv.push new Move temp, s
          @stack[i] = temp
        rv.push new Move block.in_stack[i], @stack[i] if block.in_stack[i]?

      for l, i in @locals when l?
        unless /^\$\d+$/.test l
          temp = @new_temp()
          rv.push new Move temp, l
          @locals[i] = temp
        rv.push new Move block.in_locals[i], @locals[i] if block.in_locals[i]?
    rv

  compile: (prev_stack, prev_locals) ->
    return if @visited

    @visited = true

    # the first block has its local vars set from the function params
    unless @start_idx == 0
      @stack =
        for s in prev_stack
          if s? then @new_temp() else null
      @locals =
        for l in prev_locals
          if l? then @new_temp() else null
      # dup the stack / locals so the next pass can retrieve our input variables
      @in_stack = @stack[..]
      @in_locals = @locals[..]

    instr_idx = @start_idx
    for op in @opcodes
