diff --git a/CMakeLists.txt b/CMakeLists.txt index 135009f9df..203df86406 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,4 +111,4 @@ add_subdirectory("third-party/zydis") # windows memory management lib IF (WIN32) add_subdirectory(third-party/mman) -ENDIF () \ No newline at end of file +ENDIF () diff --git a/common/util/CMakeLists.txt b/common/util/CMakeLists.txt index caf551f79c..b2b4e92f32 100644 --- a/common/util/CMakeLists.txt +++ b/common/util/CMakeLists.txt @@ -3,3 +3,7 @@ add_library(common_util FileUtil.cpp DgoWriter.cpp Timer.cpp) + +IF(UNIX) +target_link_libraries(common_util stdc++fs) +ENDIF() diff --git a/game/kernel/asm_funcs.asm b/game/kernel/asm_funcs.asm index b08834a50c..9d38f60837 100644 --- a/game/kernel/asm_funcs.asm +++ b/game/kernel/asm_funcs.asm @@ -4,20 +4,47 @@ ;; GOAL Runtime assembly functions. These exist only in the x86 version of GOAL. -;; declaration of the extern "C" function format_impl -extern format_impl - SECTION .TEXT -;; This _format function which will be exported to the GOAL symbol table at runtime start as "_format" -;; This function accepts 8 GOAL arguments and puts them on the stack, then calls format_impl and passes -;; a pointer to this array of GOAL arguments as the argument. The reason for this is that GOAL and -;; the standard System V ABI used in Linux are different for 8 argument function calls. - -global _format_win32 -_format_win32: - ; GOAL will call with regs RDI, RSI, RDX, RCX, R8, R9, R10, R11 +;; this function pushes all 8 OpenGOAL registers into a stack array. +;; then it calls the function pointed to by rax with a pointer to this array. +;; it returns the return value of the called function. +global _stack_call_linux +_stack_call_linux: + pop rax + ; align stack + sub rsp, 8 + ; create stack array of arguments + push r11 + push r10 + push r9 + push r8 + push rcx + push rdx + push rsi + push rdi + ; set first argument + mov rdi, rsp + ; call function + call rax + ; restore arguments (probably don't need to really do this...) + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 + pop r10 + pop r11 + ; restore stack + add rsp, 8 + ; return! + ret +;; windows implementation of stack_call +global _stack_call_win32 +_stack_call_win32: + pop rax ; to make sure the stack frame is aligned sub rsp, 8 @@ -36,7 +63,7 @@ _format_win32: sub rsp, 32 ; call C function to do format, result will go in RAX - call format_impl + call rax add rsp, 32 ; restore @@ -52,47 +79,6 @@ _format_win32: add rsp, 8 ret -global _format_linux -_format_linux: - ; GOAL will call with regs RDI, RSI, RDX, RCX, R8, R9, R10, R11 - - ; to make sure the stack frame is aligned - sub rsp, 8 - - ; push all registers and create the register array on the stack - push r11 - push r10 - push r9 - push r8 - push rcx - push rdx - push rsi - push rdi - - ; set the first argument register to the stack argument array - mov rdi, rsp - - ; call C function to do format, result will go in RAX - call format_impl - - ; restore - ; (note - this could probably just be add rsp 72, we don't care about the value of these register) - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 - pop r10 - pop r11 - add rsp, 8 - ret -;; NOTE: calling format has a _lot_ of indirection... -;; symbol table lookup to find the GOAL "format" symbol value -;; run the GOAL-to-C trampoline (on GOAL heap) to jump to this _format -;; run this wrapper to call the real format_impl - - ;; The _call_goal_asm function is used to call a GOAL function from C. ;; It supports up to 3 arguments and a return value. ;; This should be called with the arguments: diff --git a/game/kernel/kdgo.cpp b/game/kernel/kdgo.cpp index edd9200e6c..9cde408e3b 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/kdgo.cpp @@ -50,38 +50,31 @@ s32 RpcCall(s32 rpcChannel, nullptr); } -// Terrible hack! Remove soon! - namespace { -struct RpcCallArgCache { - s32 rpcChannel; - u32 fno; - u32 async; -} rpc_arg_cache; +struct GoalStackArgs { + u64 args[8]; + template + T get_as(int i) { + static_assert(sizeof(T) <= 8, "arg size"); + T result; + memcpy(&result, args + i, sizeof(T)); + return result; + } +}; } // namespace -void RpcCall_wrapper_part1(s32 rpcChannel, u32 fno, u32 async) { - rpc_arg_cache.rpcChannel = rpcChannel; - rpc_arg_cache.fno = fno; - rpc_arg_cache.async = async; -} - -u64 RpcCall_wrapper_part2(u64 send_buff, s32 send_size, u64 recv_buff, s32 recv_size) { - return RpcCall_wrapper(rpc_arg_cache.rpcChannel, rpc_arg_cache.fno, rpc_arg_cache.async, - send_buff, send_size, recv_buff, recv_size); -} - /*! * GOAL Wrapper for RpcCall. */ -u64 RpcCall_wrapper(s32 rpcChannel, - u32 fno, - u32 async, - u64 send_buff, - s32 send_size, - u64 recv_buff, - s32 recv_size) { - fprintf(stderr, "size in c is %d\n", recv_size); +u64 RpcCall_wrapper(void* _args) { + GoalStackArgs* args = (GoalStackArgs*)_args; + auto rpcChannel = args->get_as(0); + auto fno = args->get_as(1); + auto async = args->get_as(2); + auto send_buff = args->get_as(3); + auto send_size = args->get_as(4); + auto recv_buff = args->get_as(5); + auto recv_size = args->get_as(6); return sceSifCallRpc(&cd[rpcChannel], fno, async, Ptr(send_buff).c(), send_size, Ptr(recv_buff).c(), recv_size, nullptr, nullptr); } diff --git a/game/kernel/kdgo.h b/game/kernel/kdgo.h index a717d80254..7dbc3a5ee0 100644 --- a/game/kernel/kdgo.h +++ b/game/kernel/kdgo.h @@ -19,16 +19,7 @@ void load_and_link_dgo_from_c(const char* name, Ptr heap, u32 linkFla void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); void StopIOP(); -u64 RpcCall_wrapper(s32 rpcChannel, - u32 fno, - u32 async, - u64 send_buff, - s32 send_size, - u64 recv_buff, - s32 recv_size); +u64 RpcCall_wrapper(void* args); u32 RpcBusy(s32 channel); void LoadDGOTest(); - -void RpcCall_wrapper_part1(s32 rpcChannel, u32 fno, u32 async); -u64 RpcCall_wrapper_part2(u64 send_buff, s32 send_size, u64 recv_buff, s32 recv_size); #endif // JAK_V2_KDGO_H diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index 50bac9ef9f..ebe3164fdc 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -315,6 +315,10 @@ u64 make_string_from_c(const char* c_str) { return mem; } +/*! + * This creates an OpenGOAL function from a C++ function. Only 6 arguments can be accepted. + * But calling this function is very fast and doesn't use the stack. + */ Ptr make_function_from_c_linux(void* func) { // allocate a function object on the global heap auto mem = Ptr( @@ -393,16 +397,59 @@ ret return mem.cast(); } +extern "C" { +void _stack_call_linux(); +void _stack_call_win32(); +} + +Ptr make_stack_arg_function_from_c_linux(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), 0x40)); + auto f = (uint64_t)func; + auto target_function = (u8*)&f; + auto trampoline_function_addr = _stack_call_linux; + auto trampoline = (u8*)&trampoline_function_addr; + + // movabs rax, target_function + int offset = 0; + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = target_function[i]; + } + + // push rax + mem.c()[offset++] = 0x50; + + // movabs rax, trampoline + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = trampoline[i]; + } + + // jmp rax + mem.c()[offset++] = 0xff; + mem.c()[offset++] = 0xe0; + + // CacheFlush(mem, 0x34); + + 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) { +Ptr make_stack_arg_function_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; + auto trampoline_function_addr = _stack_call_win32; + auto trampoline = (u8*)&trampoline_function_addr; int i = 0; // we will put the function address in RAX with a movabs rax, imm8 @@ -412,13 +459,20 @@ Ptr make_function_for_format_from_c_win32(void* func) { mem.c()[i++] = fp[j]; } + // push rax + mem.c()[i++] = 0x50; + + // 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++] = trampoline[j]; + } + /* - * sub rsp, 40 - * call rax - * add rsp, 40 - * ret + * jmp rax */ - for (auto x : {0x48, 0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xC3}) { + for (auto x : {0xFF, 0xE0}) { mem.c()[i++] = x; } @@ -439,6 +493,14 @@ Ptr make_function_from_c(void* func) { #endif } +Ptr make_stack_arg_function_from_c(void* func) { +#ifdef __linux__ + return make_stack_arg_function_from_c_linux(func); +#elif _WIN32 + return make_stack_arg_function_from_c_win32(func); +#endif +} + /*! * Create a GOAL function which does nothing and immediately returns. */ @@ -471,6 +533,8 @@ Ptr make_zero_func() { * Given a C function and a name, create a GOAL function and store it in the symbol with the given * name. This effectively creates a global GOAL function with the given name which calls the given C * function. + * + * This work on both Linux and Windows, but only supports up to 6 arguments. */ Ptr make_function_symbol_from_c(const char* name, void* f) { auto sym = intern_from_c(name); @@ -480,12 +544,12 @@ Ptr make_function_symbol_from_c(const char* name, void* f) { } /*! - * 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. + * Like make_function_symbol_from_c, but all 8 GOAL arguments are put into an array on the stack. + * The address of this array is passed as the first and only argument to f. */ -Ptr make_format_function_symbol_from_c_win32(const char* name, void* f) { +Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f) { auto sym = intern_from_c(name); - auto func = make_function_for_format_from_c_win32(f); + auto func = make_stack_arg_function_from_c(f); sym->value = func.offset; return func; } @@ -1587,15 +1651,6 @@ s32 test_function(s32 arg0, s32 arg1, s32 arg2, s32 arg3) { return arg0 + 2 * arg1 + 3 * arg2 + 4 * arg3; } -extern "C" { -// defined in asm_funcs. It calls format_impl and sets up arguments correctly. -#ifdef __linux__ -void _format_linux(); -#elif _WIN32 -void _format_win32(); -#endif -} - /*! * Initializes the GOAL Heap, GOAL Symbol Table, GOAL Funcdamental Types, loads the GOAL kernel, * exports Machine functions, loads the game engine, and calls "play" to initialize the engine. @@ -1845,11 +1900,7 @@ s32 InitHeapAndSymbol() { make_function_symbol_from_c("load", (void*)load); make_function_symbol_from_c("loado", (void*)loado); make_function_symbol_from_c("unload", (void*)unload); -#ifdef __linux__ - make_function_symbol_from_c("_format", (void*)_format_linux); -#elif _WIN32 - make_format_function_symbol_from_c_win32("_format", (void*)_format_win32); -#endif + make_stack_arg_function_symbol_from_c("_format", (void*)format_impl); // allocations make_function_symbol_from_c("malloc", (void*)alloc_heap_memory); diff --git a/game/kernel/kscheme.h b/game/kernel/kscheme.h index 11002924d4..316ee684bc 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/kscheme.h @@ -101,6 +101,7 @@ u64 load(u32 file_name_in, u32 heap_in); u64 loado(u32 file_name_in, u32 heap_in); u64 unload(u32 name); Ptr make_function_symbol_from_c(const char* name, void* f); +Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f); u64 call_goal_function_by_name(const char* name); u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2); Ptr alloc_and_init_type(Ptr sym, u32 method_count); diff --git a/game/kernel/ksound.cpp b/game/kernel/ksound.cpp index 0f917bc499..7bbc056416 100644 --- a/game/kernel/ksound.cpp +++ b/game/kernel/ksound.cpp @@ -25,8 +25,5 @@ void InitSoundScheme() { make_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper); make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy); make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest); - - // terrible hack! - make_function_symbol_from_c("rpc-call-p1", (void*)RpcCall_wrapper_part1); - make_function_symbol_from_c("rpc-call-p2", (void*)RpcCall_wrapper_part2); + make_stack_arg_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper); } \ No newline at end of file diff --git a/goal_src/engine/ps2/rpc-h.gc b/goal_src/engine/ps2/rpc-h.gc index 1f630667c9..7ec2c383fa 100644 --- a/goal_src/engine/ps2/rpc-h.gc +++ b/goal_src/engine/ps2/rpc-h.gc @@ -130,12 +130,6 @@ '#f ) -(defmacro hack-rpc-call (a0 a1 a2 a3 a4 a5 a6) - `(begin - (rpc-call-p1 ,a0 ,a1 ,a2) - (rpc-call-p2 ,a3 ,a4 ,a5 ,a6) - ) - ) ;; method 9 (defmethod call rpc-buffer-pair ((obj rpc-buffer-pair) (fno uint) (recv-buff pointer) (recv-size uint)) @@ -170,7 +164,7 @@ (let ((current-buffer (-> obj current))) ;; rpc_channel, fno, async, send_buff, send_size, recv_buff, recv_size (format 0 "recv-size is ~D~%" recv-size) - (hack-rpc-call (-> obj rpc-port) + (rpc-call (-> obj rpc-port) fno (the uint 1) (the uint (-> current-buffer base)) diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index f5a102e49d..e709f374a2 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -154,6 +154,3 @@ (define-extern rpc-call (function int uint uint uint int uint int uint)) (define-extern rpc-busy? (function int uint)) -;; hack! remove! -(define-extern rpc-call-p1 (function int uint uint none)) -(define-extern rpc-call-p2 (function uint int uint int uint)) \ No newline at end of file