/* * 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); }
// 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; }
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((¤t_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, ¤t_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], ¤t_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, ¤t_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; }