diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 816918ff0f..614544af5b 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -342,7 +342,8 @@ Ptr make_function_from_c_linux(void* func) { * Create a GOAL function from a C function. This doesn't export it as a global function, it just * creates a function object on the global heap. * - * The implementation is to create a simple trampoline function which jumps to the C function. + * This creates a simple trampoline function which jumps to the C function and reorders the + * arguments to be correct for Windows. */ Ptr make_function_from_c_win32(void* func) { // allocate a function object on the global heap @@ -386,6 +387,38 @@ Ptr make_function_from_c_win32(void* func) { return mem.cast(); } +/*! + * Create a GOAL function from a C function. This calls a windows function, but doesn't scramble + * the argument order. It's supposed to be used with _format_win32 which assumes GOAL order. + */ +Ptr make_function_for_format_from_c_win32(void* func) { + // allocate a function object on the global heap + auto mem = Ptr( + alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x80)); + auto f = (uint64_t)func; + auto fp = (u8*)&f; + + int i = 0; + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = fp[j]; + } + + /* + * sub rsp, 40 + * call rax + * add rsp, 40 + * ret + */ + for (auto x : {0x48, 0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3}) { + mem.c()[i++] = x; + } + + return mem.cast(); +} + /*! * Create a GOAL function from a C function. This doesn't export it as a global function, it just * creates a function object on the global heap. @@ -440,6 +473,17 @@ Ptr make_function_symbol_from_c(const char* name, void* f) { return func; } +/*! + * Given a C function and a name, create a GOAL function and store it in the symbol with the given + * name. This is designed for _format_win32, which is special because it takes 8 arguments. + */ +Ptr make_format_function_symbol_from_c_win32(const char* name, void* f) { + auto sym = intern_from_c(name); + auto func = make_function_for_format_from_c_win32(f); + sym->value = func.offset; + return func; +} + /*! * Set the named symbol to the value. This isn't specific to functions. */ @@ -1778,7 +1822,7 @@ s32 InitHeapAndSymbol() { #ifdef __linux__ make_function_symbol_from_c("_format", (void*)_format_linux); #elif _WIN32 - make_function_symbol_from_c("_format", (void*)_format_win32); + make_format_function_symbol_from_c_win32("_format", (void*)_format_win32); #endif // allocations diff --git a/goal_src/test/test-format-reg-order.gc b/goal_src/test/test-format-reg-order.gc new file mode 100644 index 0000000000..4339feca42 --- /dev/null +++ b/goal_src/test/test-format-reg-order.gc @@ -0,0 +1,5 @@ +(define-extern _format function) +(define format _format) + +(format #t "test ~D ~D ~D ~D ~D ~D~%" 1 2 3 4 5 6) +0 \ No newline at end of file diff --git a/test/test_compiler_and_runtime.cpp b/test/test_compiler_and_runtime.cpp index f04c1364d2..9e40bf42a5 100644 --- a/test/test_compiler_and_runtime.cpp +++ b/test/test_compiler_and_runtime.cpp @@ -253,6 +253,8 @@ TEST(CompilerAndRuntime, CompilerTests) { runner.run_test("test-factorial-loop.gc", {"3628800\n"}); runner.run_test("test-protect.gc", {"33\n"}); + runner.run_test("test-format-reg-order.gc", {"test 1 2 3 4 5 6\n0\n"}); + // expected = // "test newline\nnewline\ntest tilde ~ \ntest A print boxed-string: \"boxed string!\"\ntest // A " "print symbol: a-symbol\ntest A make boxed object longer: \"srt\"!\ntest A