From e00501346b71c341f9269dd47d381d82d0b4dc7d Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Wed, 8 Jan 2020 19:00:04 -0800 Subject: [PATCH] Fix LI_* instructions The 'lint' type in system 4 is NOT a 64-bit integer type. It is actually a signed 32-bit integer type that is clamped between 0 and INT32_MAX. Negative numbers can be assigned to a lint using SH_LOCALASSIGN (or at least, the unofficial compiler emits such code) but LI_* instructions always produce a positive result. I'm guessing System40.exe converts lints to 64-bit integers for doing arithmetic, and then clamps the result to the 32-bit integer range before writing back to a variable. This fixes a bug in Sengoku Rance where units would become invincible in commander battles after their HP fell below zero. --- include/vm.h | 7 ---- src/debug-ffi.c | 17 +-------- src/debug-ffi.stub | 1 - src/resume.c | 7 ++-- src/savedata.c | 10 +---- src/vm.c | 91 +++++++++++++++++++++++++++++++--------------- 6 files changed, 68 insertions(+), 65 deletions(-) diff --git a/include/vm.h b/include/vm.h index 2ab0bd1..37dd0b2 100644 --- a/include/vm.h +++ b/include/vm.h @@ -29,7 +29,6 @@ struct string; // Non-heap values. Stored in pages and on the stack. union vm_value { int32_t i; - int64_t li; float f; void *ref; // for casting HLL return value }; @@ -59,11 +58,6 @@ static inline union vm_value vm_int(int32_t v) return (union vm_value) { .i = v }; } -static inline union vm_value vm_long(int64_t v) -{ - return (union vm_value) { .li = v }; -} - static inline union vm_value vm_bool(bool b) { return (union vm_value) { .i = !!b }; @@ -77,7 +71,6 @@ static inline union vm_value vm_float(float v) #define vm_value_cast(v) _Generic((v), \ union vm_value: _vm_id, \ int32_t: vm_int, \ - int64_t: vm_long, \ bool: vm_bool, \ float: vm_float)(v) diff --git a/src/debug-ffi.c b/src/debug-ffi.c index 6b04286..95b0081 100644 --- a/src/debug-ffi.c +++ b/src/debug-ffi.c @@ -114,12 +114,6 @@ sexp sexp_vm_value_get_i (sexp ctx, sexp self, sexp_sint_t n, sexp x) { return sexp_make_integer(ctx, ((union vm_value*)sexp_cpointer_value(x))->i); } -sexp sexp_vm_value_get_li (sexp ctx, sexp self, sexp_sint_t n, sexp x) { - if (! (sexp_pointerp(x) && (sexp_pointer_tag(x) == sexp_unbox_fixnum(sexp_opcode_arg1_type(self))))) - return sexp_type_exception(ctx, self, sexp_unbox_fixnum(sexp_opcode_arg1_type(self)), x); - return sexp_make_integer(ctx, ((union vm_value*)sexp_cpointer_value(x))->li); -} - sexp sexp_vm_value_get_f (sexp ctx, sexp self, sexp_sint_t n, sexp x) { if (! (sexp_pointerp(x) && (sexp_pointer_tag(x) == sexp_unbox_fixnum(sexp_opcode_arg1_type(self))))) return sexp_type_exception(ctx, self, sexp_unbox_fixnum(sexp_opcode_arg1_type(self)), x); @@ -267,10 +261,9 @@ sexp sexp_init_library (sexp ctx, sexp self, sexp_sint_t n, sexp env, const char sexp_env_define(ctx, env, tmp, sexp_vm_value_type_obj); sexp_type_slots(sexp_vm_value_type_obj) = SEXP_NULL; sexp_push(ctx, sexp_type_slots(sexp_vm_value_type_obj), sexp_intern(ctx, "f", -1)); - sexp_push(ctx, sexp_type_slots(sexp_vm_value_type_obj), sexp_intern(ctx, "li", -1)); sexp_push(ctx, sexp_type_slots(sexp_vm_value_type_obj), sexp_intern(ctx, "i", -1)); - sexp_type_getters(sexp_vm_value_type_obj) = sexp_make_vector(ctx, SEXP_THREE, SEXP_FALSE); - sexp_type_setters(sexp_vm_value_type_obj) = sexp_make_vector(ctx, SEXP_THREE, SEXP_FALSE); + sexp_type_getters(sexp_vm_value_type_obj) = sexp_make_vector(ctx, SEXP_TWO, SEXP_FALSE); + sexp_type_setters(sexp_vm_value_type_obj) = sexp_make_vector(ctx, SEXP_TWO, SEXP_FALSE); tmp = sexp_make_type_predicate(ctx, name, sexp_vm_value_type_obj); name = sexp_intern(ctx, "vm-value?", 9); sexp_env_define(ctx, env, name, tmp); @@ -331,12 +324,6 @@ sexp sexp_init_library (sexp ctx, sexp self, sexp_sint_t n, sexp env, const char sexp_opcode_return_type(op) = sexp_make_fixnum(SEXP_FLONUM); sexp_opcode_arg1_type(op) = sexp_make_fixnum(sexp_type_tag(sexp_vm_value_type_obj)); } - if (sexp_vectorp(sexp_type_getters(sexp_vm_value_type_obj))) sexp_vector_set(sexp_type_getters(sexp_vm_value_type_obj), SEXP_TWO, op); - op = sexp_define_foreign(ctx, env, "vm-value-long", 1, sexp_vm_value_get_li); - if (sexp_opcodep(op)) { - sexp_opcode_return_type(op) = sexp_make_fixnum(SEXP_FIXNUM); - sexp_opcode_arg1_type(op) = sexp_make_fixnum(sexp_type_tag(sexp_vm_value_type_obj)); - } if (sexp_vectorp(sexp_type_getters(sexp_vm_value_type_obj))) sexp_vector_set(sexp_type_getters(sexp_vm_value_type_obj), SEXP_ONE, op); op = sexp_define_foreign(ctx, env, "vm-value-int", 1, sexp_vm_value_get_i); if (sexp_opcodep(op)) { diff --git a/src/debug-ffi.stub b/src/debug-ffi.stub index 5bb362e..f1c44ed 100644 --- a/src/debug-ffi.stub +++ b/src/debug-ffi.stub @@ -69,7 +69,6 @@ (define-c-union vm_value predicate: vm-value? (int i vm-value-int) - (long li vm-value-long) (float f vm-value-float)) (define-c (free string) (vm-value-string vm_value_string) (vm_value)) diff --git a/src/resume.c b/src/resume.c index 7cd1f9e..d3c6f1c 100644 --- a/src/resume.c +++ b/src/resume.c @@ -50,8 +50,7 @@ static enum page_type string_to_page_type(const char *str) static cJSON *value_to_json(union vm_value v) { - // XXX: type punning through int64_t - return cJSON_CreateNumber(v.li); + return cJSON_CreateNumber(v.i); } static cJSON *resume_page_to_json(struct page *page) @@ -204,7 +203,7 @@ static void load_page(int slot, cJSON *json) int i = 0; cJSON *item; cJSON_ArrayForEach(item, values) { - page->values[i].li = item->valuedouble; // XXX: using valueint would truncate longs + page->values[i].i = item->valueint; i++; } @@ -309,7 +308,7 @@ static void load_stack(cJSON *json) cJSON *item; cJSON_ArrayForEach(item, json) { type_check(cJSON_Number, item); - stack_push_value(vm_long(item->valuedouble)); + stack_push_value(vm_int(item->valueint)); } } diff --git a/src/savedata.c b/src/savedata.c index 8b9a43b..43a4142 100644 --- a/src/savedata.c +++ b/src/savedata.c @@ -56,11 +56,10 @@ cJSON *vm_value_to_json(enum ain_data_type type, union vm_value val) case AIN_BOOL: case AIN_FUNC_TYPE: case AIN_DELEGATE: + case AIN_LONG_INT: return cJSON_CreateNumber(val.i); case AIN_FLOAT: return cJSON_CreateNumber(val.f); - case AIN_LONG_INT: - return cJSON_CreateNumber(val.li); case AIN_STRING: return cJSON_CreateString(heap[val.i].s->text); case AIN_STRUCT: @@ -206,6 +205,7 @@ union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type stru switch (type) { case AIN_INT: case AIN_BOOL: + case AIN_LONG_INT: if (!cJSON_IsNumber(json)) { invalid_save_data("Not a number", json); return vm_int(0); @@ -217,12 +217,6 @@ union vm_value json_to_vm_value(enum ain_data_type type, enum ain_data_type stru return vm_float(0); } return vm_float(json->valuedouble); - case AIN_LONG_INT: - if (!cJSON_IsNumber(json)) { - invalid_save_data("Not a number", json); - return vm_long(0); - } - return vm_long(json->valueint); case AIN_STRING: slot = heap_alloc_slot(VM_STRING); if (!cJSON_IsString(json)) { diff --git a/src/vm.c b/src/vm.c index 15ddd95..46505ad 100644 --- a/src/vm.c +++ b/src/vm.c @@ -35,6 +35,15 @@ #include "vm/heap.h" #include "vm/page.h" +static inline int32_t lint_clamp(int64_t n) +{ + if (n < 0) + return 0; + if (n > INT32_MAX) + return INT32_MAX; + return (int32_t)n; +} + #define INITIAL_STACK_SIZE 4096 // When the IP is set to VM_RETURN, the VM halts @@ -935,95 +944,117 @@ static void execute_instruction(enum opcode opcode) // --- 64-bit integers --- // case ITOLI: { - stack_set(0, (int64_t)stack_peek(0).i); + stack_set(0, lint_clamp(stack_peek(0).i)); break; } case LI_ADD: { - stack[stack_ptr-2].li += stack[stack_ptr-1].li; + int64_t a = stack[stack_ptr-2].i; + int64_t b = stack[stack_ptr-1].i; + stack[stack_ptr-2].i = lint_clamp(a + b); stack_ptr--; break; } case LI_SUB: { - stack[stack_ptr-2].li -= stack[stack_ptr-1].li; + int64_t a = stack[stack_ptr-2].i; + int64_t b = stack[stack_ptr-1].i; + stack[stack_ptr-2].i = lint_clamp(a - b); stack_ptr--; break; } case LI_MUL: { - stack[stack_ptr-2].li *= stack[stack_ptr-1].li; + int64_t a = stack[stack_ptr-2].i; + int64_t b = stack[stack_ptr-1].i; + stack[stack_ptr-2].i = lint_clamp(a * b); stack_ptr--; break; } case LI_DIV: { - stack[stack_ptr-2].li /= stack[stack_ptr-1].li; + int64_t a = stack[stack_ptr-2].i; + int64_t b = stack[stack_ptr-1].i; + stack[stack_ptr-2].i = lint_clamp(a / b); stack_ptr--; break; } case LI_MOD: { - stack[stack_ptr-2].li %= stack[stack_ptr-1].li; + int64_t a = stack[stack_ptr-2].i; + int64_t b = stack[stack_ptr-1].i; + stack[stack_ptr-2].i = lint_clamp(a % b); stack_ptr--; break; } case LI_ASSIGN: { - int64_t v = stack_pop().li; - stack_push(stack_pop_var()->li = v); + int64_t v = stack_pop().i; + stack_push(stack_pop_var()->i = lint_clamp(v)); break; } case LI_PLUSA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li += n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i + n)); break; } case LI_MINUSA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li -= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i - n)); break; } case LI_MULA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li *= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i * n)); break; } case LI_DIVA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li /= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i / n)); break; } case LI_MODA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li %= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i % n)); break; } case LI_ANDA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li &= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i & n)); break; } case LI_ORA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li |= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i | n)); break; } case LI_XORA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li ^= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i ^ n)); break; } case LI_LSHIFTA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li <<= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i << n)); break; } case LI_RSHIFTA: { - int64_t n = stack_pop().li; - stack_push(stack_pop_var()->li >>= n); + int64_t n = stack_pop().i; + union vm_value *v = stack_pop_var(); + stack_push(v->i = lint_clamp((int64_t)v->i >> n)); break; } case LI_INC: { - stack_pop_var()->li++; + union vm_value *v = stack_pop_var(); + v->i = lint_clamp((int64_t)v->i + (int64_t)1); break; } case LI_DEC: { - stack_pop_var()->li--; + union vm_value *v = stack_pop_var(); + v->i = lint_clamp((int64_t)v->i - (int64_t)1); break; } //