Clean up asm for format (#164)

* clean up asm for format

* fix windows

* remove rpc-call hack

* revert cmake version change

* clang format

* asm fix for windows
This commit is contained in:
water111 2020-12-23 11:15:41 -05:00 committed by GitHub
parent 3355df809a
commit 5cb6368b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 142 additions and 128 deletions

View File

@ -111,4 +111,4 @@ add_subdirectory("third-party/zydis")
# windows memory management lib
IF (WIN32)
add_subdirectory(third-party/mman)
ENDIF ()
ENDIF ()

View File

@ -3,3 +3,7 @@ add_library(common_util
FileUtil.cpp
DgoWriter.cpp
Timer.cpp)
IF(UNIX)
target_link_libraries(common_util stdc++fs)
ENDIF()

View File

@ -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:

View File

@ -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 <typename T>
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<s32>(0);
auto fno = args->get_as<u32>(1);
auto async = args->get_as<u32>(2);
auto send_buff = args->get_as<u64>(3);
auto send_size = args->get_as<s32>(4);
auto recv_buff = args->get_as<u64>(5);
auto recv_size = args->get_as<s32>(6);
return sceSifCallRpc(&cd[rpcChannel], fno, async, Ptr<u8>(send_buff).c(), send_size,
Ptr<u8>(recv_buff).c(), recv_size, nullptr, nullptr);
}

View File

@ -19,16 +19,7 @@ void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> 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

View File

@ -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<Function> make_function_from_c_linux(void* func) {
// allocate a function object on the global heap
auto mem = Ptr<u8>(
@ -393,16 +397,59 @@ ret
return mem.cast<Function>();
}
extern "C" {
void _stack_call_linux();
void _stack_call_win32();
}
Ptr<Function> make_stack_arg_function_from_c_linux(void* func) {
// allocate a function object on the global heap
auto mem = Ptr<u8>(
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<Function>();
}
/*!
* 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<Function> make_function_for_format_from_c_win32(void* func) {
Ptr<Function> make_stack_arg_function_from_c_win32(void* func) {
// allocate a function object on the global heap
auto mem = Ptr<u8>(
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<Function> 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<Function> make_function_from_c(void* func) {
#endif
}
Ptr<Function> 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<Function> 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<Function> make_function_symbol_from_c(const char* name, void* f) {
auto sym = intern_from_c(name);
@ -480,12 +544,12 @@ Ptr<Function> 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<Function> make_format_function_symbol_from_c_win32(const char* name, void* f) {
Ptr<Function> 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);

View File

@ -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<Function> make_function_symbol_from_c(const char* name, void* f);
Ptr<Function> 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> type, u32 method_id, u32 a1, u32 a2);
Ptr<Type> alloc_and_init_type(Ptr<Symbol> sym, u32 method_count);

View File

@ -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);
}

View File

@ -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))

View File

@ -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))