Fixing tail calls.

This commit is contained in:
Ben Vanik 2014-01-26 11:45:58 -08:00
parent aadf92e4ea
commit bbf3b4bdab
4 changed files with 57 additions and 23 deletions

View File

@ -10,6 +10,7 @@
#include <alloy/backend/x64/lowering/lowering_sequences.h>
#include <alloy/backend/x64/x64_emitter.h>
#include <alloy/backend/x64/x64_function.h>
#include <alloy/backend/x64/lowering/lowering_table.h>
#include <alloy/runtime/symbol_info.h>
#include <alloy/runtime/runtime.h>
@ -35,7 +36,7 @@ void Dummy() {
//
}
void PrintString(void* raw_context, uint8_t* membase, const char* str) {
void PrintString(void* raw_context, const char* str) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *((ThreadState**)raw_context);
fprintf(stdout, "XE[t] :%d: %s\n", thread_state->GetThreadID(), str);
@ -43,48 +44,74 @@ void PrintString(void* raw_context, uint8_t* membase, const char* str) {
}
// TODO(benvanik): fancy stuff.
void CallThunk(void* raw_context, uint8_t* membase,
FunctionInfo* symbol_info) {
void* ResolveFunctionSymbol(void* raw_context, FunctionInfo* symbol_info) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *((ThreadState**)raw_context);
Function* fn = NULL;
thread_state->runtime()->ResolveFunction(symbol_info->address(), &fn);
XEASSERTNOTNULL(fn);
fn->Call(thread_state);
XEASSERT(fn->type() == Function::USER_FUNCTION);
auto x64_fn = (X64Function*)fn;
return x64_fn->machine_code();
}
void* ResolveFunctionAddress(void* raw_context, uint64_t target_address) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *((ThreadState**)raw_context);
Function* fn = NULL;
thread_state->runtime()->ResolveFunction(target_address, &fn);
XEASSERTNOTNULL(fn);
XEASSERTALWAYS();
//fn->Call(thread_state);
return 0;
}
void IssueCall(X64Emitter& e, FunctionInfo* symbol_info, uint32_t flags) {
e.mov(e.r8, (uint64_t)symbol_info);
e.mov(e.rax, (uint64_t)CallThunk);
// If we are an extern function, we can directly insert a call.
auto fn = symbol_info->function();
if (fn && fn->type() == Function::EXTERN_FUNCTION) {
auto extern_fn = (ExternFunction*)fn;
e.mov(e.rdx, (uint64_t)extern_fn->arg0());
e.mov(e.r8, (uint64_t)extern_fn->arg1());
e.mov(e.rax, (uint64_t)extern_fn->handler());
} else {
// Generic call, resolve address.
// TODO(benvanik): caching/etc. For now this makes debugging easier.
e.mov(e.rdx, (uint64_t)symbol_info);
e.mov(e.rax, (uint64_t)ResolveFunctionSymbol);
e.call(e.rax);
e.mov(e.rcx, e.qword[e.rsp + 0]);
e.mov(e.rdx, e.qword[e.rcx + 8]); // membase
}
if (flags & CALL_TAIL) {
// TODO(benvanik): adjust stack?
e.add(e.rsp, 0x40);
e.jmp(e.rax);
} else {
e.call(e.rax);
e.mov(e.rdx, e.qword[e.rsp + 8]);
e.mov(e.rcx, e.qword[e.rsp + 0]);
e.mov(e.rdx, e.qword[e.rcx + 8]); // membase
}
}
void IndirectCallThunk(void* raw_context, uint8_t* membase,
uint64_t target_address) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *((ThreadState**)raw_context);
XEASSERTALWAYS();
}
void IssueCallIndirect(X64Emitter& e, Value* target, uint32_t flags) {
Reg64 r;
e.BeginOp(target, r, 0);
if (r != e.r8) {
e.mov(e.r8, r);
if (r != e.rdx) {
e.mov(e.rdx, r);
}
e.EndOp(r);
e.mov(e.rax, (uint64_t)IndirectCallThunk);
e.mov(e.rax, (uint64_t)ResolveFunctionAddress);
e.call(e.rax);
e.mov(e.rcx, e.qword[e.rsp + 0]);
e.mov(e.rdx, e.qword[e.rcx + 8]); // membase
if (flags & CALL_TAIL) {
// TODO(benvanik): adjust stack?
e.add(e.rsp, 0x40);
e.jmp(e.rax);
} else {
e.sub(e.rsp, 0x20);
e.call(e.rax);
e.add(e.rsp, 0x20);
e.mov(e.rcx, e.qword[e.rsp + 0]);
e.mov(e.rdx, e.qword[e.rcx + 8]); // membase
}
}
@ -514,11 +541,11 @@ void alloy::backend::x64::lowering::RegisterSequences(LoweringTable* table) {
// TODO(benvanik): pass through.
auto str = (const char*)i->src1.offset;
auto str_copy = xestrdupa(str);
e.mov(e.r8, (uint64_t)str_copy);
e.mov(e.rdx, (uint64_t)str_copy);
e.mov(e.rax, (uint64_t)PrintString);
e.call(e.rax);
e.mov(e.rdx, e.qword[e.rsp + 8]);
e.mov(e.rcx, e.qword[e.rsp + 0]);
e.mov(e.rdx, e.qword[e.rcx + 8]); // membase
i = e.Advance(i);
return true;
});

View File

@ -128,7 +128,6 @@ int X64Emitter::Emit(HIRBuilder* builder) {
const bool emit_prolog = true;
const size_t stack_size = 64;
if (emit_prolog) {
mov(qword[rsp + 16], rdx);
mov(qword[rsp + 8], rcx);
sub(rsp, stack_size);
mov(qword[rsp + 8 * 0], rbx);
@ -138,6 +137,10 @@ int X64Emitter::Emit(HIRBuilder* builder) {
mov(qword[rsp + 8 * 4], r15);
}
// membase stays in rdx. If we evict it (like on function calls) we
// must put it back.
mov(rdx, qword[rcx + 8]);
auto lowering_table = backend_->lowering_table();
// Body.

View File

@ -25,6 +25,9 @@ public:
X64Function(runtime::FunctionInfo* symbol_info);
virtual ~X64Function();
void* machine_code() const { return machine_code_; }
size_t code_size() const { return code_size_; }
void Setup(void* machine_code, size_t code_size);
protected:

View File

@ -67,6 +67,8 @@ typedef struct XECACHEALIGN64 PPCContext_s {
// Must be stored at 0x0 for now.
// TODO(benvanik): find a nice way to describe this to the JIT.
runtime::ThreadState* thread_state;
// TODO(benvanik): this is getting nasty. Must be here.
uint8_t* membase;
// Most frequently used registers first.
uint64_t r[32]; // General purpose registers
@ -196,7 +198,6 @@ typedef struct XECACHEALIGN64 PPCContext_s {
// Runtime-specific data pointer. Used on callbacks to get access to the
// current runtime and its data.
uint8_t* membase;
runtime::Runtime* runtime;
volatile int suspend_flag;