/*
 * Decrement s's reference counter
 * free the string if the ref count gets to 0.
 */
void string_decref(char *s) {
  string_t *h;
  h = string_header(s);
  assert(h->ref > 0);
  h->ref --;
  if (h->ref == 0) free(h);
}
Exemple #2
0
// TODO turn into macro, also use it for new_str opcode
static heap_address new_empty_string(size_t length) {

  size_t adjusted_length = length + 1; // allow space for trailing '\0'
  int num_chunks = adjusted_length / char_per_string_chunk;
  if( (adjusted_length % char_per_string_chunk) != 0 ) {
    num_chunks += char_per_string_chunk - (adjusted_length % char_per_string_chunk);
  }

  size_t total_size = string_header_size + num_chunks;
  heap_address string_address = heap_alloc(total_size);
  vm_value *str_pointer = heap_get_pointer(string_address);

  memset(str_pointer, 0, total_size * sizeof(vm_value));
  *str_pointer = string_header(length, num_chunks);

  return string_address;
}
Exemple #3
0
vm_value vm_execute(vm_instruction *program, int program_length, vm_value *ctable, int ctable_length) {
  ++invocation;

  vm_state state0;
  vm_state *state = &state0;
  reset(state);

  state->const_table = ctable;
  state->const_table_length = ctable_length;


  bool is_running = true;


restart:
  while(is_running && state->program_pointer < program_length) {

    is_running = true;
    vm_value instr = program[state->program_pointer];
    vm_opcode opcode = get_opcode(instr);
    ++state->program_pointer;

    switch (opcode) {

      case OP_LOAD_i: {
        int reg0 = get_arg_r0(instr);
        int val = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = val;
      }
      break;


      case OP_LOAD_ps: {
        int reg0 = get_arg_r0(instr);
        int value = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = make_tagged_val(value, vm_tag_plain_symbol);
      }
      break;


      case OP_LOAD_cs: {
        int reg0 = get_arg_r0(instr);
        int value = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = make_tagged_val(value, vm_tag_compound_symbol);
      }
      break;

      case OP_LOAD_os: {
        int reg0 = get_arg_r0(instr);
        int value = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = make_tagged_val(value, vm_tag_opaque_symbol);
      }
      break;

      case OP_LOAD_f: {
        int reg0 = get_arg_r0(instr);
        int value = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = make_tagged_val(value, vm_tag_function);
      }
      break;


      case OP_LOAD_str: {
        int reg0 = get_arg_r0(instr);
        int value = get_arg_i(instr);
        check_reg(reg0);
        get_reg(reg0) = make_tagged_val(value, vm_tag_string);
      }
      break;


      case OP_ADD: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        int reg0 = get_arg_r0(instr);
        if(get_tag(arg1) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(arg1));
        }

        else if(get_tag(arg2) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(arg2));
        }

        check_reg(reg0);
        int result = ((arg1 - int_bias) + (arg2 - int_bias)) + int_bias;
        if(result < min_biased_int || result > max_biased_int) {
          fail("Int overflow");
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_SUB: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        int reg0 = get_arg_r0(instr);
        if(get_tag(arg1) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(arg1));
        }

        if(get_tag(arg2) != vm_tag_number) {
          fail("Expected a number, but got %s ", value_to_type_string(arg2));
        }

        check_reg(reg0);
        int result = ((arg1 - int_bias) - (arg2 - int_bias)) + int_bias;
        if(result < min_biased_int || result > max_biased_int) {
          fail("Int overflow");
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_MUL: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        int reg0 = get_arg_r0(instr);
        if(get_tag(arg1) != vm_tag_number) {
          fail("Expected a number, but got %s ", value_to_type_string(arg1) );
        }

        if(get_tag(arg2) != vm_tag_number) {
          fail("Expected a number, but got %s ", value_to_type_string(arg2) );
        }

        check_reg(reg0);
        int result = ((arg1 - int_bias) * (arg2 - int_bias)) + int_bias;
        if(result < min_biased_int || result > max_biased_int) {
          panic_stop_vm_m("Int overflow");
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_DIV: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        if(arg2 == 0) {
          fail("Division by 0");
        }

        if(get_tag(arg1) != vm_tag_number) {
          fail("Expected a number, but got %s ", value_to_type_string(arg1) );
        }

        if(get_tag(arg2) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(arg2) );
        }


        int reg0 = get_arg_r0(instr);
        check_reg(reg0);
        int result = ((arg1 - int_bias) / (arg2 - int_bias)) + int_bias;
        if(result < min_biased_int || result > max_biased_int) {
          fail("Int overflow");
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_MOVE: {
        int reg0 = get_arg_r0(instr);
        int reg1 = get_arg_r1(instr);
        check_reg(reg0);
        check_reg(reg1);
        get_reg(reg0) = get_reg(reg1);
      }
      break;


      case OP_AP: {
        if (state->stack_pointer + 1 == stack_size) {
          fail("(call)!");
        }

        // this macro will create `return_pointer`
        do_call((&next_frame), get_arg_r1(instr), instr);
        if (call_failed) {
          fail("call failed"); //TODO give a better error description
        }

        next_frame.return_address = return_pointer;
        next_frame.result_register = get_arg_r0(instr);
        ++state->stack_pointer;

      }
      break;


      case OP_TAIL_AP: {
        do_call((&current_frame), get_arg_r1(instr), instr);
        if (call_failed) {
          fail("call failed"); //TODO give a better error description
        }

      }
      break;


      case OP_GEN_AP: {
        if (state->stack_pointer + 1 == stack_size) {
          printf("Stack overflow (call cl)!");
          panic_stop_vm();
        }

        int return_pointer = do_gen_ap(state, (&next_frame), instr, program);

        if (return_pointer != -1) {
          next_frame.return_address = return_pointer;
          next_frame.result_register = get_arg_r0(instr);
          ++state->stack_pointer;
        }

      }
      break;


      // TODO It's not entirely clear yet what happens when this returns a new PAP
      case OP_TAIL_GEN_AP: {
        do_gen_ap(state, &current_frame, instr, program);
      }
      break;


op_ret:
      case OP_RET: {
        int return_val_reg = get_arg_r0(instr);
        if (state->stack_pointer == 0) {
          //We simply copy the result value to register 0, so that the runtime can find it
          current_frame.reg[0] = current_frame.reg[return_val_reg];
          is_running = false;
          break;
        }
        --state->stack_pointer;
        current_frame.reg[next_frame.result_register] = next_frame.reg[return_val_reg];
        state->program_pointer = next_frame.return_address;
      }
      break;


      case OP_JMP: {
        int offset = get_arg_i(instr) - int_bias;
        state->program_pointer += offset;
        if(state->program_pointer < 0 || state->program_pointer > program_length) {
          panic_stop_vm_m("Illegal address!");
        }
      }
      break;

      case OP_JMP_TRUE: {
        check_reg(get_arg_r0(instr));
        vm_value bool_value = get_reg(get_arg_r0(instr));

        if( is_equal(state, bool_value, make_tagged_val(symbol_id_true, vm_tag_plain_symbol) )) {
          int offset = get_arg_i(instr) - int_bias;
          state->program_pointer += offset;
          if(state->program_pointer < 0 || state->program_pointer > program_length) {
            panic_stop_vm_m("Illegal address: %i", state->program_pointer);
          }
        }
        // else: do nothing

      }
      break;

      case OP_MATCH: {
        check_reg(get_arg_r0(instr));
        int subject = get_reg(get_arg_r0(instr));
        check_reg(get_arg_r1(instr));
        int patterns_addr = get_reg(get_arg_r1(instr));
        int capture_reg = get_arg_r2(instr);
        check_reg(capture_reg);

        check_ctable_index(patterns_addr)
        vm_value match_header = state->const_table[patterns_addr];
        int number_of_patterns = from_match_value(match_header);
        int i = 0;

        for(i=0; i<number_of_patterns; ++i) {
          int rel_pat_addr = patterns_addr + 1 + i;

          check_ctable_index(rel_pat_addr)
          vm_value pat = state->const_table[rel_pat_addr];
          if(does_value_match(state, pat, subject, capture_reg)) {
            break;
          }
          else {
            continue;
          }
        }

        if(i == number_of_patterns) {
          throw("Pattern match failed!");
        }

        state->program_pointer += i;
      }
      break;


      case OP_SET_ARG: {
        int target_arg = get_arg_r0(instr);
        int source_reg = get_arg_r1(instr);
        int extra_amount = get_arg_r2(instr);
        memcpy(&next_frame.reg[target_arg], &current_frame.reg[source_reg], (1 + extra_amount) * sizeof(vm_value));
      }
      break;


      case OP_SET_CL_VAL: {
        int cl_reg = get_arg_r0(instr);
        check_reg(cl_reg);
        vm_value closure = get_reg(cl_reg);

        if( get_tag(closure) != vm_tag_pap ) {
          fail("Expected a closure, but got %s", value_to_type_string(closure));
        }

        heap_address cl_address = get_val(closure);
        check_reg(get_arg_r1(instr));
        vm_value new_value = get_reg(get_arg_r1(instr));
        int arg_index = get_arg_r2(instr);

        vm_value *cl_pointer = heap_get_pointer(cl_address);
        int header = *cl_pointer;
        int num_env_args = pap_var_count(header);
        if(arg_index >= num_env_args) {
          panic_stop_vm_m("Illegal closure modification (index: %i, num env vars: %i)", arg_index, num_env_args);
        }
        cl_pointer[pap_header_size + arg_index] = new_value;

      }
      break;


      case OP_PART_AP: {
        int reg0 = get_arg_r0(instr);
        int fun_reg = get_arg_r1(instr);
        check_reg(fun_reg);
        int func = get_reg(fun_reg);

        if( get_tag(func) != vm_tag_function ) {
          fail("Expected a function, but got %s", value_to_type_string(func));
        }

        int fun_address = get_val(func);
        int num_args = get_arg_r2(instr);

        vm_value fun_header = program[fun_address];
        //TODO check that it's actually a function
        int arity = get_arg_i(fun_header);

        // TODO this was >= earlier, which apparently gave false positives. Find out why, and find out if > is the correct choice
        if(num_args > arity) {
          panic_stop_vm_m("Illegal partial application (num args: %i, arity: %i)", num_args, arity);
        }

        int pap_arity = arity - num_args;

        build_pap(num_args, pap_arity, 0, num_args, fun_address);
        check_reg(reg0);
        get_reg(reg0) = pap_value;
      }
      break;


      case OP_EQ: {
        check_reg(get_arg_r1(instr));
        check_reg(get_arg_r2(instr));
        vm_value l = get_reg(get_arg_r1(instr));
        vm_value r = get_reg(get_arg_r2(instr));
        int result_reg = get_arg_r0(instr);
        check_reg(result_reg);

        if( is_equal(state, l, r)) {
          get_reg(result_reg) = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        }
        else {
          get_reg(result_reg) = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        }
      }
      break;

      case OP_LT: {
        check_reg(get_arg_r1(instr));
        check_reg(get_arg_r2(instr));
        vm_value l = get_reg(get_arg_r1(instr));
        vm_value r = get_reg(get_arg_r2(instr));
        int result_reg = get_arg_r0(instr);
        check_reg(result_reg);

        if(get_tag(l) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(l));
        }
        else if(get_tag(r) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(r));
        }

        if( l < r) {
          get_reg(result_reg) = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        }
        else {
          get_reg(result_reg) = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        }
      }
      break;

      case OP_GT: {
        check_reg(get_arg_r1(instr));
        check_reg(get_arg_r2(instr));
        vm_value l = get_reg(get_arg_r1(instr));
        vm_value r = get_reg(get_arg_r2(instr));
        int result_reg = get_arg_r0(instr);
        check_reg(result_reg);

        if(get_tag(l) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(l));
        }
        else if(get_tag(r) != vm_tag_number) {
          fail("Expected a number, but got %s", value_to_type_string(r));
        }

        if( l > r) {
          get_reg(result_reg) = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        }
        else {
          get_reg(result_reg) = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        }
      }
      break;


      case OP_COPY_SYM: {
        check_reg(get_arg_r0(instr));
        check_reg(get_arg_r1(instr));
        vm_value const_symbol = get_reg(get_arg_r1(instr));

        if ( get_tag(const_symbol) != vm_tag_compound_symbol ) {
          panic_stop_vm_m("Expected a const symbol, but got %s", value_to_type_string(const_symbol));
        }

        int c_addr = get_val(const_symbol);
        vm_value c_sym_header = state->const_table[c_addr];

        int count = compound_symbol_count(c_sym_header);

        size_t total_size = compound_symbol_header_size + count;
        heap_address dyn_sym_address = heap_alloc(total_size);
        vm_value *sym_pointer = heap_get_pointer(dyn_sym_address);  \
        memcpy(sym_pointer, &(state->const_table[c_addr]), total_size * sizeof(vm_value));

        get_reg(get_arg_r0(instr)) = make_tagged_val(dyn_sym_address, vm_tag_dynamic_compound_symbol);

      }
      break;

      case OP_SET_SYM_FIELD: {
        check_reg(get_arg_r0(instr));
        check_reg(get_arg_r1(instr));
        vm_value heap_symbol = get_reg(get_arg_r0(instr));

        if ( get_tag(heap_symbol) != vm_tag_dynamic_compound_symbol ) {
          panic_stop_vm_m("Expected a dynamic symbol, but got %s", value_to_type_string(heap_symbol));
        }

        int h_addr = get_val(heap_symbol);
        vm_value *p = heap_get_pointer(h_addr);
        vm_value h_sym_header = *p;

        int count = compound_symbol_count(h_sym_header);

        int index = get_arg_r2(instr);
        if(index < 0 || index >= count) {
          panic_stop_vm_m("Illegal index while setting symbol field: %d", index);
        }

        p[compound_symbol_header_size + index] = get_reg(get_arg_r1(instr));
      }
      break;


      case OP_STR_LEN: {
        check_reg(get_arg_r0(instr));
        check_reg(get_arg_r1(instr));

        vm_value str = get_reg(get_arg_r1(instr));
        int tag = get_tag(str);
        if (tag != vm_tag_string && tag != vm_tag_dynamic_string ) {
          fail("Expected a string, but got %s", value_to_type_string(str));
        }

        int str_addr = get_val(str);
        vm_value *str_pointer;

        if(tag == vm_tag_string) {
          str_pointer = state->const_table + str_addr;
        }
        else {
          str_pointer = heap_get_pointer(str_addr);
        }

        vm_value str_header = *str_pointer;

        int count = string_length(str_header);
        get_reg(get_arg_r0(instr)) = make_tagged_val(count + int_bias, vm_tag_number);
      }
      break;


      case OP_NEW_STR: {
        int result_reg = get_arg_r0(instr);
        check_reg(result_reg);
        check_reg(get_arg_r1(instr));

        vm_value length_value = get_reg(get_arg_r1(instr));
        if(get_tag(length_value) != vm_tag_number) {
          panic_stop_vm_m("Expected a number, but got %s", value_to_type_string(length_value));
        }

        int length = length_value - int_bias;
        if(length < 0) {
          panic_stop_vm_m("Negative length for new string, got: %d", length);
        }

        int adjusted_length = length + 1; // allow space for trailing '\0'
        int num_chunks = adjusted_length / char_per_string_chunk;
        if( (adjusted_length % char_per_string_chunk) != 0 ) {
          num_chunks += char_per_string_chunk - (adjusted_length % char_per_string_chunk);
        }

        size_t total_size = string_header_size + num_chunks;
        heap_address string_address = heap_alloc(total_size);
        vm_value *str_pointer = heap_get_pointer(string_address);

        memset(str_pointer, 0, total_size * sizeof(vm_value));
        *str_pointer = string_header(length, num_chunks);

        get_reg(result_reg) = make_tagged_val(string_address, vm_tag_dynamic_string);

      }
      break;


      case OP_GET_CHAR: {
        int result_reg = get_arg_r0(instr);
        check_reg(result_reg);
        check_reg(get_arg_r1(instr));
        check_reg(get_arg_r2(instr));

        vm_value str = get_reg(get_arg_r1(instr));
        vm_value str_tag = get_tag(str);
        if(str_tag != vm_tag_string && str_tag != vm_tag_dynamic_string) {
          panic_stop_vm_m("Expected a string, but got %s", value_to_type_string(str));
        }

        int str_addr = get_val(str);
        vm_value *str_pointer;

        if(str_tag == vm_tag_string) {
          str_pointer = state->const_table + str_addr;
        }
        else {
          str_pointer = heap_get_pointer(str_addr);
        }

        vm_value str_header = *str_pointer;

        int index = get_reg(get_arg_r2(instr)) - int_bias;
        int str_length = string_length(str_header);
        if(index < 0 || index > str_length) {
          panic_stop_vm_m("Illegal string index: %d", index);
        }

        char *char_pointer = (char *) (str_pointer + string_header_size);
        int character = char_pointer[index];

        get_reg(result_reg) = make_tagged_val(character, vm_tag_number);

      }
      break;


      case OP_PUT_CHAR: {
        check_reg(get_arg_r0(instr));
        check_reg(get_arg_r1(instr));
        check_reg(get_arg_r2(instr));

        vm_value str = get_reg(get_arg_r1(instr));
        vm_value str_tag = get_tag(str);
        if(str_tag != vm_tag_dynamic_string) {
          panic_stop_vm_m("Expected a dynamic string, but got %s", value_to_type_string(str));
        }

        int character = get_reg(get_arg_r0(instr));
        if(get_tag(character) != vm_tag_number) {
          panic_stop_vm_m("Expected a number, but got %s", value_to_type_string(character));
        }

        int str_addr = get_val(str);
        vm_value *str_pointer = heap_get_pointer(str_addr);

        vm_value str_header = *str_pointer;

        int index = get_reg(get_arg_r2(instr)) - int_bias;
        int str_length = string_length(str_header);
        if(index < 0 || index > str_length) {
          panic_stop_vm_m("Illegal string index: %d", index);
        }

        char *char_pointer = (char *) (str_pointer + string_header_size);
        char_pointer[index] = (char) character;
      }
      break;


      case OP_OR: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        int reg0 = get_arg_r0(instr);
        check_reg(reg0);

        vm_value result = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        vm_value true_sym = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        if(arg1 == true_sym || arg2 == true_sym) {
          result = true_sym;
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_AND: {
        int reg1 = get_arg_r1(instr);
        int reg2 = get_arg_r2(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        check_reg(reg2);
        int arg2 = get_reg(reg2);
        int reg0 = get_arg_r0(instr);
        check_reg(reg0);

        vm_value result = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        vm_value true_sym = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        if(arg1 == true_sym && arg2 == true_sym) {
          result = true_sym;
        }
        get_reg(reg0) = result;
      }
      break;


      case OP_NOT: {
        int reg1 = get_arg_r1(instr);
        check_reg(reg1);
        int arg1 = get_reg(reg1);
        int reg0 = get_arg_r0(instr);
        check_reg(reg0);

        vm_value result = make_tagged_val(symbol_id_false, vm_tag_plain_symbol);
        vm_value true_sym = make_tagged_val(symbol_id_true, vm_tag_plain_symbol);
        if(arg1 != true_sym) {
          result = true_sym;
        }
        //else: result is already set to false
        get_reg(reg0) = result;
      }
      break;


      case OP_GET_FIELD: {
        int result_reg = get_arg_r0(instr);
        int obj_reg = get_arg_r1(instr);
        int sym_reg = get_arg_r2(instr);
        check_reg(result_reg);
        check_reg(obj_reg);
        check_reg(sym_reg);

        vm_value obj_ref = get_reg(obj_reg);
        vm_value requested_name = get_reg(sym_reg);

        if(get_tag(requested_name) != vm_tag_plain_symbol) {
          panic_stop_vm_m("Malformed lookup!");
        }

        vm_value obj_header = 0;
        vm_value *obj_fields = 0;

        if(get_tag(obj_ref) == vm_tag_opaque_symbol) {

          int obj_addr = get_val(obj_ref);
          vm_value *obj_pointer = state->const_table + obj_addr;

          vm_value obj_owner = obj_pointer[1];
          if(obj_owner != make_tagged_val(0, vm_tag_plain_symbol)) {
            panic_stop_vm_m("Malformed module!");
          }

          obj_header = obj_pointer[0];
          obj_fields = obj_pointer + 2;


        }
        // TODO this branch is untested, as records are apparently always dynamic in this case (?!)
        else if(get_tag(obj_ref) == vm_tag_compound_symbol) {

          int obj_addr = get_val(obj_ref);
          vm_value *obj_pointer = state->const_table + obj_addr;

          obj_header = obj_pointer[0];
          if(compound_symbol_id(obj_header) != symbol_id_record) {
            fail("Expected a module or record, got %s", value_to_type_string(obj_ref));
          }

          obj_fields = obj_pointer + 1;

        }
        else if(get_tag(obj_ref) == vm_tag_dynamic_compound_symbol) {


          int obj_addr = get_val(obj_ref);
          vm_value *obj_pointer = heap_get_pointer(obj_addr);

          //vm_value *obj_pointer = state->const_table + obj_addr;

          obj_header = obj_pointer[0];
          obj_fields = obj_pointer + 1;

          if(compound_symbol_id(obj_header) != symbol_id_record) {
            fail("Expected a module or record, got %s", value_to_type_string(obj_ref));
          }


        }

        else {
          // exits subroutine here
          fail("Expected a module or record, got %s", value_to_type_string(obj_ref));
        }


        int num_symbol_fields = compound_symbol_count(obj_header);

        bool found = false;
        for(int i=0; i < num_symbol_fields; i += 2) {
          vm_value name = obj_fields[i];

          if(name == requested_name) {
            get_reg(result_reg) = obj_fields[i+1];
            found = true;
            break;
          }
        }

        if(found == false) {
          //TODO change this to built-in nil type
          get_reg(result_reg) = make_tagged_val(symbol_id_nil, vm_tag_plain_symbol);
        }

      }
      break;


      case OP_CONVERT: {
        int result_reg = get_arg_r0(instr);
        int source_reg = get_arg_r1(instr);
        int type_reg = get_arg_r2(instr);
        check_reg(result_reg);
        check_reg(source_reg);
        check_reg(type_reg);

        vm_value source = get_reg(source_reg);
        vm_value target_type = get_reg(type_reg);
        if(get_tag(target_type) != vm_tag_plain_symbol) {
          fail("Expected a symbol, got %s", value_to_type_string(target_type));
        }
        vm_value target_type_id = get_val(target_type);
        vm_value result;

        switch(target_type_id) {

          case symbol_id_number:
            result = convert_to_number(state, source);
            break;

          case symbol_id_string:
            result = convert_to_string(state, source);
            break;

          default:
            result = make_str_error("Unable to convert value");

        }

        get_reg(result_reg) = result;
      }
      break;

      default:
        panic_stop_vm_m("UNKNOWN OPCODE: %04x", opcode);
    }


  }


  vm_value result = state->stack[state->stack_pointer].reg[0];


  vm_value io_result_value = 0;
  vm_value next_action;
  io_action_result action_result = check_io_action(state, result, program, &io_result_value, &next_action);

  switch(action_result) {
    case no_io_action:
      // do nothing
      break;

    case intermediary_io_action: {
        state->stack_pointer = 0;

        current_frame.reg[0] = next_action;
        next_frame.reg[0] = io_result_value; // argument for next_action
        vm_instruction instr = op_gen_ap(0, 0, 1);
        int return_pointer = do_gen_ap(state, &current_frame, instr, program);
        if (return_pointer != -1) {
          current_frame.return_address = return_pointer;
          current_frame.result_register = 0;
        }
        else {
          // TODO is this malformed?
          panic_stop_vm_m("malformed bound lambda in io action");
        }
        is_running = true;
      }
      goto restart;

    case final_io_action:
      result = io_result_value;
      break;

    default:
      panic_stop_vm_m("Unknown result of io action: %d", action_result);
  }


  return result;
}