Created solution for testing output
This commit is contained in:
parent
5d9ea96abc
commit
b94fe6f5fb
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,11 +1,12 @@
|
|||
# VSCode file settings
|
||||
.vscode/settings.json
|
||||
|
||||
# Input elf files
|
||||
# Input elf and rom files
|
||||
*.elf
|
||||
*.z64
|
||||
|
||||
# Output C files
|
||||
out/
|
||||
test/funcs
|
||||
|
||||
# Linux build output
|
||||
build/
|
||||
|
|
31
test/RecompTest.sln
Normal file
31
test/RecompTest.sln
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.32929.386
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompTest", "RecompTest.vcxproj", "{73819ED8-8A5B-4554-B3F3-60257A43F296}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.Build.0 = Debug|x64
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.Build.0 = Debug|Win32
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.ActiveCfg = Release|x64
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.Build.0 = Release|x64
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.ActiveCfg = Release|Win32
|
||||
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DD740FE2-99F7-4C80-B124-06D14447F873}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
2028
test/RecompTest.vcxproj
Normal file
2028
test/RecompTest.vcxproj
Normal file
File diff suppressed because it is too large
Load diff
5718
test/RecompTest.vcxproj.filters
Normal file
5718
test/RecompTest.vcxproj.filters
Normal file
File diff suppressed because it is too large
Load diff
7
test/portultra/init.cpp
Normal file
7
test/portultra/init.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include "ultra64.h"
|
||||
#include "multilibultra.hpp"
|
||||
|
||||
extern "C" void osInitialize() {
|
||||
Multilibultra::init_scheduler();
|
||||
Multilibultra::native_init();
|
||||
}
|
83
test/portultra/main.c
Normal file
83
test/portultra/main.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
#if 0
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ultra64.h"
|
||||
|
||||
#define THREAD_STACK_SIZE 0x1000
|
||||
|
||||
u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
|
||||
OSThread idle_thread;
|
||||
OSThread main_thread;
|
||||
OSThread thread3;
|
||||
OSThread thread4;
|
||||
|
||||
OSMesgQueue queue;
|
||||
OSMesg buf[1];
|
||||
|
||||
void thread3_func(UNUSED void *arg) {
|
||||
OSMesg val;
|
||||
printf("Thread3 recv\n");
|
||||
fflush(stdout);
|
||||
osRecvMesg(&queue, &val, OS_MESG_BLOCK);
|
||||
printf("Thread3 complete: %d\n", (int)(intptr_t)val);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void thread4_func(void *arg) {
|
||||
printf("Thread4 send %d\n", (int)(intptr_t)arg);
|
||||
fflush(stdout);
|
||||
osSendMesg(&queue, arg, OS_MESG_BLOCK);
|
||||
printf("Thread4 complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void main_thread_func(UNUSED void* arg) {
|
||||
osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0]));
|
||||
|
||||
printf("main thread creating thread 3\n");
|
||||
osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14);
|
||||
printf("main thread starting thread 3\n");
|
||||
osStartThread(&thread3);
|
||||
|
||||
printf("main thread creating thread 4\n");
|
||||
osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13);
|
||||
printf("main thread starting thread 4\n");
|
||||
osStartThread(&thread4);
|
||||
|
||||
while (1) {
|
||||
printf("main thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void idle_thread_func(UNUSED void* arg) {
|
||||
printf("idle thread\n");
|
||||
printf("creating main thread\n");
|
||||
osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11);
|
||||
printf("starting main thread\n");
|
||||
osStartThread(&main_thread);
|
||||
|
||||
// Set this thread's priority to 0, making it the idle thread
|
||||
osSetThreadPri(NULL, 0);
|
||||
|
||||
// idle
|
||||
while (1) {
|
||||
printf("idle thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void bootproc(void) {
|
||||
osInitialize();
|
||||
|
||||
osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127);
|
||||
printf("Starting idle thread\n");
|
||||
osStartThread(&idle_thread);
|
||||
}
|
||||
|
||||
#endif
|
188
test/portultra/mesgqueue.cpp
Normal file
188
test/portultra/mesgqueue.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "multilibultra.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
mq->blocked_on_recv = NULLPTR;
|
||||
mq->blocked_on_send = NULLPTR;
|
||||
mq->msgCount = count;
|
||||
mq->msg = msg;
|
||||
mq->validCount = 0;
|
||||
mq->first = 0;
|
||||
}
|
||||
|
||||
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
|
||||
return mq->validCount;
|
||||
}
|
||||
|
||||
s32 MQ_IS_EMPTY(OSMesgQueue *mq) {
|
||||
return mq->validCount == 0;
|
||||
}
|
||||
|
||||
s32 MQ_IS_FULL(OSMesgQueue* mq) {
|
||||
return MQ_GET_COUNT(mq) >= mq->msgCount;
|
||||
}
|
||||
|
||||
void thread_queue_insert(RDRAM_ARG PTR(OSThread)* queue, PTR(OSThread) toadd_) {
|
||||
PTR(OSThread)* cur = queue;
|
||||
OSThread* toadd = TO_PTR(OSThread, toadd_);
|
||||
while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) {
|
||||
cur = &TO_PTR(OSThread, *cur)->next;
|
||||
}
|
||||
toadd->next = (*cur);
|
||||
*cur = toadd_;
|
||||
}
|
||||
|
||||
OSThread* thread_queue_pop(RDRAM_ARG PTR(OSThread)* queue) {
|
||||
PTR(OSThread) ret = *queue;
|
||||
*queue = TO_PTR(OSThread, ret)->next;
|
||||
return TO_PTR(OSThread, ret);
|
||||
}
|
||||
|
||||
bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) {
|
||||
return *queue == NULLPTR;
|
||||
}
|
||||
|
||||
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
Multilibultra::disable_preemption();
|
||||
|
||||
if (flags == OS_MESG_NOBLOCK) {
|
||||
// If non-blocking, fail if the queue is full
|
||||
if (MQ_IS_FULL(mq)) {
|
||||
Multilibultra::enable_preemption();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, yield this thread until the queue has room
|
||||
while (MQ_IS_FULL(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
|
||||
Multilibultra::enable_preemption();
|
||||
Multilibultra::pause_self(PASS_RDRAM1);
|
||||
Multilibultra::disable_preemption();
|
||||
}
|
||||
}
|
||||
|
||||
s32 last = (mq->first + mq->validCount) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[last] = msg;
|
||||
mq->validCount++;
|
||||
|
||||
OSThread* to_run = nullptr;
|
||||
|
||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
|
||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
|
||||
}
|
||||
|
||||
Multilibultra::enable_preemption();
|
||||
if (to_run) {
|
||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
||||
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||
if (to_run->priority > self->priority) {
|
||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
||||
} else {
|
||||
Multilibultra::schedule_running_thread(to_run);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
Multilibultra::disable_preemption();
|
||||
|
||||
if (flags == OS_MESG_NOBLOCK) {
|
||||
// If non-blocking, fail if the queue is full
|
||||
if (MQ_IS_FULL(mq)) {
|
||||
Multilibultra::enable_preemption();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
||||
while (MQ_IS_FULL(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
|
||||
Multilibultra::enable_preemption();
|
||||
Multilibultra::pause_self(PASS_RDRAM1);
|
||||
Multilibultra::disable_preemption();
|
||||
}
|
||||
}
|
||||
|
||||
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
|
||||
mq->validCount++;
|
||||
|
||||
OSThread *to_run = nullptr;
|
||||
|
||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
|
||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
|
||||
}
|
||||
|
||||
Multilibultra::enable_preemption();
|
||||
if (to_run) {
|
||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||
if (to_run->priority > self->priority) {
|
||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
||||
} else {
|
||||
Multilibultra::schedule_running_thread(to_run);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
OSMesg *msg = TO_PTR(OSMesg, msg_);
|
||||
Multilibultra::disable_preemption();
|
||||
|
||||
if (flags == OS_MESG_NOBLOCK) {
|
||||
// If non-blocking, fail if the queue is empty
|
||||
if (MQ_IS_EMPTY(mq)) {
|
||||
Multilibultra::enable_preemption();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
||||
while (MQ_IS_EMPTY(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
|
||||
thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread());
|
||||
Multilibultra::enable_preemption();
|
||||
Multilibultra::pause_self(PASS_RDRAM1);
|
||||
Multilibultra::disable_preemption();
|
||||
}
|
||||
}
|
||||
|
||||
if (msg != nullptr) {
|
||||
*msg = TO_PTR(OSMesg, mq->msg)[mq->first];
|
||||
}
|
||||
|
||||
mq->first = (mq->first + 1) % mq->msgCount;
|
||||
mq->validCount--;
|
||||
|
||||
OSThread *to_run = nullptr;
|
||||
|
||||
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) {
|
||||
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send);
|
||||
}
|
||||
|
||||
Multilibultra::enable_preemption();
|
||||
if (to_run) {
|
||||
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
|
||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||
if (to_run->priority > self->priority) {
|
||||
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
|
||||
} else {
|
||||
Multilibultra::schedule_running_thread(to_run);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg) {
|
||||
|
||||
}
|
53
test/portultra/multilibultra.hpp
Normal file
53
test/portultra/multilibultra.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef __MULTILIBULTRA_HPP__
|
||||
#define __MULTILIBULTRA_HPP__
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "platform_specific.h"
|
||||
|
||||
struct UltraThreadContext {
|
||||
std::thread host_thread;
|
||||
std::atomic_bool running;
|
||||
std::atomic_bool initialized;
|
||||
};
|
||||
|
||||
namespace Multilibultra {
|
||||
|
||||
void native_init();
|
||||
void init_scheduler();
|
||||
void native_thread_init(OSThread *t);
|
||||
void set_self_paused(RDRAM_ARG1);
|
||||
void wait_for_resumed(RDRAM_ARG1);
|
||||
void swap_to_thread(RDRAM_ARG OSThread *to);
|
||||
void pause_thread_impl(OSThread *t);
|
||||
void pause_thread_native_impl(OSThread *t);
|
||||
void resume_thread_impl(OSThread *t);
|
||||
void resume_thread_native_impl(OSThread *t);
|
||||
void schedule_running_thread(OSThread *t);
|
||||
void stop_thread(OSThread *t);
|
||||
void pause_self(RDRAM_ARG1);
|
||||
void cleanup_thread(OSThread *t);
|
||||
PTR(OSThread) this_thread();
|
||||
void disable_preemption();
|
||||
void enable_preemption();
|
||||
void notify_scheduler();
|
||||
void reprioritize_thread(OSThread *t, OSPri pri);
|
||||
void set_main_thread();
|
||||
|
||||
class preemption_guard {
|
||||
public:
|
||||
preemption_guard();
|
||||
~preemption_guard();
|
||||
private:
|
||||
std::lock_guard<std::mutex> lock;
|
||||
};
|
||||
|
||||
} // namespace Multilibultra
|
||||
|
||||
#define debug_printf(...) printf(__VA_ARGS__);
|
||||
//#define debug_printf(...)
|
||||
|
||||
#endif
|
32
test/portultra/platform_specific.h
Normal file
32
test/portultra/platform_specific.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef __PLATFORM_SPECIFIC_H__
|
||||
#define __PLATFORM_SPECIFIC_H__
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
//#include <pthread.h>
|
||||
//
|
||||
//typedef struct {
|
||||
// pthread_t t;
|
||||
// pthread_barrier_t pause_barrier;
|
||||
// pthread_mutex_t pause_mutex;
|
||||
// pthread_cond_t pause_cond;
|
||||
// void (*entrypoint)(void *);
|
||||
// void *arg;
|
||||
//} OSThreadNative;
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
//#include <pthread.h>
|
||||
//
|
||||
//typedef struct {
|
||||
// pthread_t t;
|
||||
// pthread_barrier_t pause_barrier;
|
||||
// pthread_mutex_t pause_mutex;
|
||||
// pthread_cond_t pause_cond;
|
||||
// void (*entrypoint)(void*);
|
||||
// void* arg;
|
||||
//} OSThreadNative;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
268
test/portultra/scheduler.cpp
Normal file
268
test/portultra/scheduler.cpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
#include <thread>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include "multilibultra.hpp"
|
||||
|
||||
class OSThreadComparator {
|
||||
public:
|
||||
bool operator() (OSThread *a, OSThread *b) const {
|
||||
return a->priority < b->priority;
|
||||
}
|
||||
};
|
||||
|
||||
class thread_queue_t : public std::priority_queue<OSThread*, std::vector<OSThread*>, OSThreadComparator> {
|
||||
public:
|
||||
// TODO comment this
|
||||
bool remove(const OSThread* value) {
|
||||
auto it = std::find(this->c.begin(), this->c.end(), value);
|
||||
|
||||
if (it == this->c.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (it == this->c.begin()) {
|
||||
// deque the top element
|
||||
this->pop();
|
||||
} else {
|
||||
// remove element and re-heap
|
||||
this->c.erase(it);
|
||||
std::make_heap(this->c.begin(), this->c.end(), this->comp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static struct {
|
||||
std::vector<OSThread*> to_schedule;
|
||||
std::vector<OSThread*> to_stop;
|
||||
std::vector<OSThread*> to_cleanup;
|
||||
std::vector<std::pair<OSThread*, OSPri>> to_reprioritize;
|
||||
std::mutex mutex;
|
||||
// OSThread* running_thread;
|
||||
std::atomic_int notify_count;
|
||||
std::atomic_int action_count;
|
||||
|
||||
bool can_preempt;
|
||||
std::mutex premption_mutex;
|
||||
} scheduler_context{};
|
||||
|
||||
void handle_thread_queueing(thread_queue_t& running_thread_queue) {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
|
||||
if (!scheduler_context.to_schedule.empty()) {
|
||||
OSThread* to_schedule = scheduler_context.to_schedule.back();
|
||||
scheduler_context.to_schedule.pop_back();
|
||||
scheduler_context.action_count.fetch_sub(1);
|
||||
debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id);
|
||||
running_thread_queue.push(to_schedule);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_thread_stopping(thread_queue_t& running_thread_queue) {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
|
||||
while (!scheduler_context.to_stop.empty()) {
|
||||
OSThread* to_stop = scheduler_context.to_stop.back();
|
||||
scheduler_context.to_stop.pop_back();
|
||||
scheduler_context.action_count.fetch_sub(1);
|
||||
debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id);
|
||||
running_thread_queue.remove(to_stop);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_thread_cleanup(thread_queue_t& running_thread_queue) {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
|
||||
while (!scheduler_context.to_cleanup.empty()) {
|
||||
OSThread* to_cleanup = scheduler_context.to_cleanup.back();
|
||||
scheduler_context.to_cleanup.pop_back();
|
||||
scheduler_context.action_count.fetch_sub(1);
|
||||
|
||||
debug_printf("[Scheduler] Destroying thread %d\n", to_cleanup->id);
|
||||
running_thread_queue.remove(to_cleanup);
|
||||
to_cleanup->context->host_thread.join();
|
||||
delete to_cleanup->context;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_thread_reprioritization(thread_queue_t& running_thread_queue) {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
|
||||
while (!scheduler_context.to_reprioritize.empty()) {
|
||||
const std::pair<OSThread*, OSPri> to_reprioritize = scheduler_context.to_reprioritize.back();
|
||||
scheduler_context.to_reprioritize.pop_back();
|
||||
scheduler_context.action_count.fetch_sub(1);
|
||||
|
||||
debug_printf("[Scheduler] Reprioritizing thread %d to %d\n", to_reprioritize.first->id, to_reprioritize.second);
|
||||
running_thread_queue.remove(to_reprioritize.first);
|
||||
to_reprioritize.first->priority = to_reprioritize.second;
|
||||
running_thread_queue.push(to_reprioritize.first);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_scheduler_notifications() {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
int32_t notify_count = scheduler_context.notify_count.exchange(0);
|
||||
if (notify_count) {
|
||||
debug_printf("Received %d notifications\n", notify_count);
|
||||
scheduler_context.action_count.fetch_sub(notify_count);
|
||||
}
|
||||
}
|
||||
|
||||
void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) {
|
||||
OSThread* new_running_thread = running_thread_queue.top();
|
||||
if (cur_running_thread != new_running_thread) {
|
||||
if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) {
|
||||
debug_printf("[Scheduler] Switching execution from thread %d (%d) to thread %d (%d)\n",
|
||||
cur_running_thread->id, cur_running_thread->priority,
|
||||
new_running_thread->id, new_running_thread->priority);
|
||||
Multilibultra::pause_thread_impl(cur_running_thread);
|
||||
} else {
|
||||
debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority);
|
||||
}
|
||||
Multilibultra::resume_thread_impl(new_running_thread);
|
||||
cur_running_thread = new_running_thread;
|
||||
} else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) {
|
||||
Multilibultra::resume_thread_impl(cur_running_thread);
|
||||
}
|
||||
}
|
||||
|
||||
void scheduler_func() {
|
||||
thread_queue_t running_thread_queue{};
|
||||
OSThread* cur_running_thread = nullptr;
|
||||
|
||||
while (true) {
|
||||
OSThread* old_running_thread = cur_running_thread;
|
||||
scheduler_context.action_count.wait(0);
|
||||
|
||||
std::lock_guard lock{scheduler_context.premption_mutex};
|
||||
|
||||
// Handle notifications
|
||||
handle_scheduler_notifications();
|
||||
|
||||
// Handle stopping threads
|
||||
handle_thread_stopping(running_thread_queue);
|
||||
|
||||
// Handle cleaning up threads
|
||||
handle_thread_cleanup(running_thread_queue);
|
||||
|
||||
// Handle queueing threads to run
|
||||
handle_thread_queueing(running_thread_queue);
|
||||
|
||||
// Handle threads that have changed priority
|
||||
handle_thread_reprioritization(running_thread_queue);
|
||||
|
||||
// Determine which thread to run, stopping the current running thread if necessary
|
||||
swap_running_thread(running_thread_queue, cur_running_thread);
|
||||
|
||||
std::this_thread::yield();
|
||||
if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) {
|
||||
debug_printf("[Scheduler] Swapped from Thread %d (%d) to Thread %d (%d)\n",
|
||||
old_running_thread->id, old_running_thread->priority, cur_running_thread->id, cur_running_thread->priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void do_yield() {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
namespace Multilibultra {
|
||||
|
||||
void init_scheduler() {
|
||||
scheduler_context.can_preempt = true;
|
||||
std::thread scheduler_thread{scheduler_func};
|
||||
scheduler_thread.detach();
|
||||
}
|
||||
|
||||
void schedule_running_thread(OSThread *t) {
|
||||
debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id);
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.to_schedule.push_back(t);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
|
||||
void swap_to_thread(RDRAM_ARG OSThread *to) {
|
||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||
debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id);
|
||||
{
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.to_schedule.push_back(to);
|
||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||
}
|
||||
|
||||
void reprioritize_thread(OSThread *t, OSPri pri) {
|
||||
debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri);
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.to_reprioritize.emplace_back(t, pri);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
|
||||
void pause_self(RDRAM_ARG1) {
|
||||
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
|
||||
debug_printf("[Scheduler] Thread %d pausing itself\n", self->id);
|
||||
{
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
||||
scheduler_context.to_stop.push_back(self);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||
}
|
||||
|
||||
void stop_thread(OSThread *t) {
|
||||
debug_printf("[Scheduler] Queuing Thread %d to be stopped\n", t->id);
|
||||
{
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.to_stop.push_back(t);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
Multilibultra::pause_thread_impl(t);
|
||||
}
|
||||
|
||||
void cleanup_thread(OSThread *t) {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.to_cleanup.push_back(t);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
|
||||
void disable_preemption() {
|
||||
scheduler_context.premption_mutex.lock();
|
||||
scheduler_context.can_preempt = false;
|
||||
}
|
||||
|
||||
void enable_preemption() {
|
||||
scheduler_context.can_preempt = true;
|
||||
scheduler_context.premption_mutex.unlock();
|
||||
}
|
||||
|
||||
// lock's constructor is called first, so can_preempt is set after locking
|
||||
preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} {
|
||||
scheduler_context.can_preempt = false;
|
||||
}
|
||||
|
||||
// lock's destructor is called last, so can_preempt is set before unlocking
|
||||
preemption_guard::~preemption_guard() {
|
||||
scheduler_context.can_preempt = true;
|
||||
}
|
||||
|
||||
void notify_scheduler() {
|
||||
std::lock_guard lock{scheduler_context.mutex};
|
||||
scheduler_context.notify_count.fetch_add(1);
|
||||
scheduler_context.action_count.fetch_add(1);
|
||||
scheduler_context.action_count.notify_all();
|
||||
}
|
||||
|
||||
}
|
60
test/portultra/task_pthreads.cpp
Normal file
60
test/portultra/task_pthreads.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef _WIN32
|
||||
|
||||
// #include <thread>
|
||||
// #include <stdexcept>
|
||||
// #include <atomic>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "multilibultra.hpp"
|
||||
|
||||
constexpr int pause_thread_signum = SIGUSR1;
|
||||
|
||||
// void cleanup_current_thread(OSThread *t) {
|
||||
// debug_printf("Thread cleanup %d\n", t->id);
|
||||
|
||||
// // delete t->context;
|
||||
// }
|
||||
|
||||
void sig_handler(int signum, siginfo_t *info, void *context) {
|
||||
if (signum == pause_thread_signum) {
|
||||
OSThread *t = Multilibultra::this_thread();
|
||||
|
||||
debug_printf("[Sig] Thread %d paused\n", t->id);
|
||||
|
||||
// Wait until the thread is marked as running again
|
||||
t->context->running.wait(false);
|
||||
|
||||
debug_printf("[Sig] Thread %d resumed\n", t->id);
|
||||
}
|
||||
}
|
||||
|
||||
void Multilibultra::native_init(void) {
|
||||
// Set up a signal handler to capture pause signals
|
||||
struct sigaction sigact;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
sigact.sa_sigaction = sig_handler;
|
||||
|
||||
sigaction(pause_thread_signum, &sigact, nullptr);
|
||||
}
|
||||
|
||||
void Multilibultra::native_thread_init(OSThread *t) {
|
||||
debug_printf("[Native] Init thread %d\n", t->id);
|
||||
}
|
||||
|
||||
void Multilibultra::pause_thread_native_impl(OSThread *t) {
|
||||
debug_printf("[Native] Pause thread %d\n", t->id);
|
||||
// Send a pause signal to the thread, which will trigger it to wait on its pause barrier in the signal handler
|
||||
pthread_kill(t->context->host_thread.native_handle(), pause_thread_signum);
|
||||
}
|
||||
|
||||
void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
|
||||
debug_printf("[Native] Resume thread %d\n", t->id);
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
#endif
|
32
test/portultra/task_win32.cpp
Normal file
32
test/portultra/task_win32.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include <Windows.h>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "multilibultra.hpp"
|
||||
|
||||
extern "C" unsigned int sleep(unsigned int seconds) {
|
||||
Sleep(seconds * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Multilibultra::native_init(void) {
|
||||
}
|
||||
|
||||
void Multilibultra::native_thread_init(OSThread *t) {
|
||||
debug_printf("[Native] Init thread %d\n", t->id);
|
||||
}
|
||||
|
||||
void Multilibultra::pause_thread_native_impl(OSThread *t) {
|
||||
debug_printf("[Native] Pause thread %d\n", t->id);
|
||||
// Pause the thread via the win32 API
|
||||
SuspendThread(t->context->host_thread.native_handle());
|
||||
// Perform a synchronous action to ensure that the thread is suspended
|
||||
// see: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743
|
||||
CONTEXT threadContext;
|
||||
GetThreadContext(t->context->host_thread.native_handle(), &threadContext);
|
||||
}
|
||||
|
||||
void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
|
||||
debug_printf("[Native] Resume thread %d\n", t->id);
|
||||
// Resume the thread
|
||||
ResumeThread(t->context->host_thread.native_handle());
|
||||
}
|
143
test/portultra/threads.cpp
Normal file
143
test/portultra/threads.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "multilibultra.hpp"
|
||||
|
||||
extern "C" void bootproc();
|
||||
|
||||
thread_local bool is_main_thread = false;
|
||||
thread_local PTR(OSThread) thread_self = NULLPTR;
|
||||
|
||||
void Multilibultra::set_main_thread() {
|
||||
is_main_thread = true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main(int argc, char** argv) {
|
||||
Multilibultra::set_main_thread();
|
||||
|
||||
bootproc();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
void run_thread_function(uint8_t* rdram, uint32_t addr, uint32_t sp, uint32_t arg);
|
||||
#else
|
||||
#define run_thread_function(func, sp, arg) func(arg)
|
||||
#endif
|
||||
|
||||
static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) {
|
||||
OSThread *self = TO_PTR(OSThread, self_);
|
||||
debug_printf("[Thread] Thread created: %d\n", self->id);
|
||||
thread_self = self_;
|
||||
|
||||
// Perform any necessary native thread initialization.
|
||||
Multilibultra::native_thread_init(self);
|
||||
|
||||
// Set initialized to false to indicate that this thread can be started.
|
||||
self->context->initialized.store(true);
|
||||
self->context->initialized.notify_all();
|
||||
|
||||
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
|
||||
|
||||
// Wait until the thread is marked as running.
|
||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||
|
||||
debug_printf("[Thread] Thread started: %d\n", self->id);
|
||||
|
||||
// Run the thread's function with the provided argument.
|
||||
run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
|
||||
|
||||
// Dispose of this thread after it completes.
|
||||
Multilibultra::cleanup_thread(self);
|
||||
}
|
||||
|
||||
extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t) {
|
||||
debug_printf("[os] Start Thread %d\n", TO_PTR(OSThread, t)->id);
|
||||
|
||||
// Wait until the thread is initialized to indicate that it's ready to be started.
|
||||
TO_PTR(OSThread, t)->context->initialized.wait(false);
|
||||
|
||||
debug_printf("[os] Thread %d is ready to be started\n", TO_PTR(OSThread, t)->id);
|
||||
|
||||
if (thread_self && (TO_PTR(OSThread, t)->priority > TO_PTR(OSThread, thread_self)->priority)) {
|
||||
Multilibultra::swap_to_thread(PASS_RDRAM TO_PTR(OSThread, t));
|
||||
} else {
|
||||
Multilibultra::schedule_running_thread(TO_PTR(OSThread, t));
|
||||
}
|
||||
|
||||
// The main thread "becomes" the first thread started, so join on it and exit after it completes.
|
||||
if (is_main_thread) {
|
||||
TO_PTR(OSThread, t)->context->host_thread.join();
|
||||
std::exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
|
||||
debug_printf("[os] Create Thread %d\n", id);
|
||||
OSThread *t = TO_PTR(OSThread, t_);
|
||||
|
||||
t->next = NULLPTR;
|
||||
t->priority = pri;
|
||||
t->id = id;
|
||||
t->state = OSThreadState::PAUSED;
|
||||
t->sp = sp;
|
||||
|
||||
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
||||
t->context = new UltraThreadContext{};
|
||||
t->context->initialized.store(false);
|
||||
t->context->running.store(false);
|
||||
|
||||
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
|
||||
if (t == NULLPTR) {
|
||||
t = thread_self;
|
||||
}
|
||||
bool pause_self = false;
|
||||
if (pri > TO_PTR(OSThread, thread_self)->priority) {
|
||||
pause_self = true;
|
||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
||||
} else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
|
||||
pause_self = true;
|
||||
Multilibultra::set_self_paused(PASS_RDRAM1);
|
||||
}
|
||||
Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri);
|
||||
if (pause_self) {
|
||||
Multilibultra::wait_for_resumed(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO yield thread, need a stable priority queue in the scheduler
|
||||
|
||||
void Multilibultra::set_self_paused(RDRAM_ARG1) {
|
||||
debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id);
|
||||
TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED;
|
||||
TO_PTR(OSThread, thread_self)->context->running.store(false);
|
||||
TO_PTR(OSThread, thread_self)->context->running.notify_all();
|
||||
}
|
||||
|
||||
void Multilibultra::wait_for_resumed(RDRAM_ARG1) {
|
||||
TO_PTR(OSThread, thread_self)->context->running.wait(false);
|
||||
}
|
||||
|
||||
void Multilibultra::pause_thread_impl(OSThread* t) {
|
||||
t->state = OSThreadState::PREEMPTED;
|
||||
t->context->running.store(false);
|
||||
t->context->running.notify_all();
|
||||
Multilibultra::pause_thread_native_impl(t);
|
||||
}
|
||||
|
||||
void Multilibultra::resume_thread_impl(OSThread *t) {
|
||||
t->state = OSThreadState::RUNNING;
|
||||
t->context->running.store(true);
|
||||
t->context->running.notify_all();
|
||||
Multilibultra::resume_thread_native_impl(t);
|
||||
}
|
||||
|
||||
PTR(OSThread) Multilibultra::this_thread() {
|
||||
return thread_self;
|
||||
}
|
133
test/portultra/ultra64.h
Normal file
133
test/portultra/ultra64.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
#ifndef __ULTRA64_MULTILIBULTRA_H__
|
||||
#define __ULTRA64_MULTILIBULTRA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "platform_specific.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <queue>
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED __attribute__((unused))
|
||||
#define ALIGNED(x) __attribute__((aligned(x)))
|
||||
#else
|
||||
#define UNUSED
|
||||
#define ALIGNED(x)
|
||||
#endif
|
||||
|
||||
typedef int64_t s64;
|
||||
typedef uint64_t u64;
|
||||
typedef int32_t s32;
|
||||
typedef uint32_t u32;
|
||||
typedef int16_t s16;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t s8;
|
||||
typedef uint8_t u8;
|
||||
|
||||
#define PTR(x) uint32_t
|
||||
#define RDRAM_ARG uint8_t *rdram,
|
||||
#define RDRAM_ARG1 uint8_t *rdram
|
||||
#define PASS_RDRAM rdram,
|
||||
#define PASS_RDRAM1 rdram
|
||||
#define TO_PTR(type, var) ((type*)(&rdram[var & 0x3FFFFFF]))
|
||||
#ifdef __cplusplus
|
||||
#define NULLPTR (PTR(void))0
|
||||
#endif
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL (PTR(void) 0)
|
||||
#endif
|
||||
|
||||
#define OS_MESG_NOBLOCK 0
|
||||
#define OS_MESG_BLOCK 1
|
||||
|
||||
typedef s32 OSPri;
|
||||
typedef s32 OSId;
|
||||
|
||||
/////////////
|
||||
// Structs //
|
||||
/////////////
|
||||
|
||||
typedef struct UltraThreadContext UltraThreadContext;
|
||||
|
||||
typedef enum {
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
PREEMPTED
|
||||
} OSThreadState;
|
||||
|
||||
typedef struct OSThread_t {
|
||||
PTR(struct OSThread_t) next; // Next thread in the given queue
|
||||
OSPri priority;
|
||||
uint32_t pad1;
|
||||
uint32_t pad2;
|
||||
uint16_t flags; // These two are swapped to reflect rdram byteswapping
|
||||
uint16_t state;
|
||||
OSId id;
|
||||
int32_t pad3;
|
||||
UltraThreadContext* context; // An actual pointer regardless of platform
|
||||
uint32_t sp;
|
||||
} OSThread;
|
||||
|
||||
typedef u32 OSEvent;
|
||||
typedef PTR(void) OSMesg;
|
||||
|
||||
// This union holds C++ members along with a padding array. Those members are guarded by an ifdef for C++
|
||||
// so that they don't cause compilation errors in C. The padding array reserves the necessary space to
|
||||
// hold the atomic members in C and a static assert is used to ensure that the union is large enough.
|
||||
// typedef union UltraQueueContext {
|
||||
// u64 pad[1];
|
||||
// #ifdef __cplusplus
|
||||
// struct {
|
||||
// } atomics;
|
||||
// // Construct pad instead of the atomics, which get constructed in-place in osCreateMesgQueue
|
||||
// UltraQueueContext() : pad{} {}
|
||||
// #endif
|
||||
// } UltraQueueContext;
|
||||
|
||||
// #ifdef __cplusplus
|
||||
// static_assert(sizeof(UltraQueueContext::pad) == sizeof(UltraQueueContext),
|
||||
// "UltraQueueContext does not have enough padding to hold C++ members!");
|
||||
// #endif
|
||||
|
||||
typedef struct OSMesgQueue {
|
||||
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
|
||||
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
|
||||
s32 validCount; /* Number of messages in the queue */
|
||||
s32 first; /* Index of the first message in the ring buffer */
|
||||
s32 msgCount; /* Size of message buffer */
|
||||
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
|
||||
} OSMesgQueue;
|
||||
|
||||
///////////////
|
||||
// Functions //
|
||||
///////////////
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
void osInitialize(void);
|
||||
|
||||
typedef void (thread_func_t)(PTR(void));
|
||||
|
||||
void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p);
|
||||
void osStartThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
|
||||
|
||||
s32 MQ_GET_COUNT(RDRAM_ARG PTR(OSMesgQueue));
|
||||
s32 MQ_IS_EMPTY(RDRAM_ARG PTR(OSMesgQueue));
|
||||
s32 MQ_IS_FULL(RDRAM_ARG PTR(OSMesgQueue));
|
||||
|
||||
void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
17
test/src/ai.cpp
Normal file
17
test/src/ai.cpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include "recomp.h"
|
||||
|
||||
extern "C" void osAiSetFrequency_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osAiSetNextBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osAiGetLength_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osAiGetStatus_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
25
test/src/cont.cpp
Normal file
25
test/src/cont.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "recomp.h"
|
||||
|
||||
extern "C" void osContInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osContStartReadData_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osContGetReadData_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osMotorInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osMotorStart_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osMotorStop_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
5
test/src/dp.cpp
Normal file
5
test/src/dp.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
#include "recomp.h"
|
||||
|
||||
extern "C" void osDpSetNextBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
21
test/src/eep.cpp
Normal file
21
test/src/eep.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "recomp.h"
|
||||
|
||||
extern "C" void osEepromProbe_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osEepromWrite_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongWrite_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osEepromRead_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongRead_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
65
test/src/misc_ultra.cpp
Normal file
65
test/src/misc_ultra.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include "recomp.h"
|
||||
|
||||
extern uint64_t start_time;
|
||||
|
||||
|
||||
extern "C" void osVirtualToPhysical_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
uint32_t virtual_addr = ctx->r4;
|
||||
// TODO handle TLB mappings
|
||||
ctx->r2 = virtual_addr - 0x80000000;
|
||||
}
|
||||
|
||||
extern "C" void osInvalDCache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osInvalICache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCacheAll_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
// Ticks per second
|
||||
constexpr uint32_t counter_rate = 46'875'000;
|
||||
|
||||
extern "C" void osGetCount_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
// TODO move this to a more appropriate place
|
||||
int32_t count = 0;
|
||||
#ifdef _WIN32
|
||||
SYSTEMTIME st;
|
||||
FILETIME ft;
|
||||
GetSystemTime(&st);
|
||||
SystemTimeToFileTime(&st, &ft);
|
||||
|
||||
uint64_t cur_time = ((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
|
||||
uint64_t delta_100ns = cur_time - start_time;
|
||||
|
||||
count = (delta_100ns * counter_rate) / (1'000'000'000 / 100);
|
||||
#endif
|
||||
|
||||
ctx->r2 = count;
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSetIntMask_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osDisableInt_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osRestoreInt_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
48
test/src/pi.cpp
Normal file
48
test/src/pi.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include <memory>
|
||||
#include "recomp.h"
|
||||
#include "../portultra/ultra64.h"
|
||||
|
||||
extern std::unique_ptr<uint8_t[]> rom;
|
||||
extern size_t rom_size;
|
||||
|
||||
extern "C" void osCartRomInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osCreatePiManager_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
constexpr uint32_t rom_base = 0xB0000000;
|
||||
|
||||
extern "C" void osPiStartDma_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
uint32_t mb = ctx->r4;
|
||||
uint32_t pri = ctx->r5;
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = ctx->r7;
|
||||
uint32_t dramAddr = MEM_W(0x10, ctx->r29);
|
||||
uint32_t size = MEM_W(0x14, ctx->r29);
|
||||
uint32_t mq_ = MEM_W(0x18, ctx->r29);
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
|
||||
printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
// TODO asynchronous transfer (will require preemption in the scheduler)
|
||||
// TODO this won't handle unaligned DMA
|
||||
memcpy(rdram + (dramAddr & 0x3FFFFFF), rom.get() + (devAddr | rom_base) - rom_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq_, 0, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
extern "C" void osEPiStartDma_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osPiGetStatus_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiRawStartDma_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
|
||||
;
|
||||
}
|
41
test/src/portultra_translation.cpp
Normal file
41
test/src/portultra_translation.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include "../portultra/ultra64.h"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osInitialize_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
//printf("Creating thread 0x%08X\n", (uint32_t)ctx->r4);
|
||||
osCreateThread(rdram, (uint32_t)ctx->r4, (OSId)ctx->r5, (uint32_t)ctx->r6, (uint32_t)ctx->r7,
|
||||
(uint32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStartThread_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
//printf("Starting thread 0x%08X\n", (uint32_t)ctx->r4);
|
||||
osStartThread(rdram, (uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
osSetThreadPri(rdram, (uint32_t)ctx->r4, (OSPri)ctx->r5);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
osCreateMesgQueue(rdram, (uint32_t)ctx->r4, (uint32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osRecvMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
ctx->r2 = osRecvMesg(rdram, (uint32_t)ctx->r4, (uint32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSendMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
ctx->r2 = osSendMesg(rdram, (uint32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osJamMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
ctx->r2 = osJamMesg(rdram, (uint32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSetEventMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
osSetEventMesg(rdram, (OSEvent)ctx->r4, (uint32_t)ctx->r5, (OSMesg)ctx->r6);
|
||||
}
|
169
test/src/recomp.cpp
Normal file
169
test/src/recomp.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
#include "recomp.h"
|
||||
#include "../portultra/multilibultra.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
}
|
||||
#else
|
||||
constexpr uint32_t byteswap(uint32_t val) {
|
||||
return __builtin_bswap32(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
void test_func(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
printf("in test_func\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
extern std::pair<uint32_t, recomp_func_t*> funcs[];
|
||||
extern const size_t num_funcs;
|
||||
|
||||
std::unordered_map<uint32_t, recomp_func_t*> func_map{};
|
||||
|
||||
extern "C" recomp_func_t* get_function(uint32_t addr) {
|
||||
auto func_find = func_map.find(addr);
|
||||
if (func_find == func_map.end()) {
|
||||
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
return func_find->second;
|
||||
}
|
||||
|
||||
extern "C" void bzero(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
uint32_t start_addr = ctx->r4;
|
||||
uint32_t size = ctx->r5;
|
||||
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
MEM_B(start_addr, i) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
extern "C" void do_break(uint32_t vram) {
|
||||
printf("Encountered break at original vram 0x%08X\n", vram);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void run_thread_function(uint8_t* rdram, uint32_t addr, uint32_t sp, uint32_t arg) {
|
||||
recomp_context ctx{};
|
||||
ctx.r29 = sp;
|
||||
recomp_func_t* func = get_function(addr);
|
||||
func(rdram, &ctx);
|
||||
}
|
||||
|
||||
extern "C" void game_init(uint8_t* restrict rdram, recomp_context* restrict ctx);
|
||||
|
||||
std::unique_ptr<uint8_t[]> rom;
|
||||
size_t rom_size;
|
||||
|
||||
uint64_t start_time;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) {
|
||||
printf("Usage: %s [baserom]\n", argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
{
|
||||
std::basic_ifstream<uint8_t> rom_file{ argv[1], std::ios::binary };
|
||||
|
||||
size_t iobuf_size = 0x100000;
|
||||
std::unique_ptr<uint8_t[]> iobuf = std::make_unique<uint8_t[]>(iobuf_size);
|
||||
rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size);
|
||||
|
||||
if (!rom_file) {
|
||||
fprintf(stderr, "Failed to open rom: %s\n", argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
rom_file.seekg(0, std::ios::end);
|
||||
rom_size = rom_file.tellg();
|
||||
rom_file.seekg(0, std::ios::beg);
|
||||
|
||||
rom = std::make_unique<uint8_t[]>(rom_size);
|
||||
|
||||
rom_file.read(rom.get(), rom_size);
|
||||
}
|
||||
|
||||
// Byteswap the rom
|
||||
for (size_t rom_addr = 0; rom_addr < rom_size; rom_addr += 4) {
|
||||
uint32_t word = *reinterpret_cast<uint32_t*>(rom.get() + rom_addr);
|
||||
word = byteswap(word);
|
||||
*reinterpret_cast<uint32_t*>(rom.get() + rom_addr) = word;
|
||||
}
|
||||
|
||||
// Get entrypoint from ROM
|
||||
// TODO fix this for other IPL3 versions
|
||||
uint32_t entrypoint = *reinterpret_cast<uint32_t*>(rom.get() + 0x8);
|
||||
|
||||
// Allocate rdram_buffer
|
||||
std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(8 * 1024 * 1024);
|
||||
std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024);
|
||||
recomp_context context{};
|
||||
|
||||
// Initial 1MB DMA
|
||||
std::copy_n(rom.get() + 0x1000, 0x100000, rdram_buffer.get() + entrypoint - 0x80000000);
|
||||
|
||||
// Initialize function address map
|
||||
for (size_t i = 0; i < num_funcs; i++) {
|
||||
func_map[funcs[i].first] = funcs[i].second;
|
||||
}
|
||||
|
||||
// TODO move this to a more appropriate place
|
||||
#ifdef _WIN32
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
FILETIME ft;
|
||||
GetSystemTime(&st);
|
||||
SystemTimeToFileTime(&st, &ft);
|
||||
|
||||
start_time = ((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set up stack pointer
|
||||
context.r29 = 0x803FFFF0u;
|
||||
|
||||
// Initialize variables normally set by IPL3
|
||||
constexpr uint32_t osTvType = 0x80000300;
|
||||
constexpr uint32_t osRomType = 0x80000304;
|
||||
constexpr uint32_t osRomBase = 0x80000308;
|
||||
constexpr uint32_t osResetType = 0x8000030c;
|
||||
constexpr uint32_t osCicId = 0x80000310;
|
||||
constexpr uint32_t osVersion = 0x80000314;
|
||||
constexpr uint32_t osMemSize = 0x80000318;
|
||||
constexpr uint32_t osAppNMIBuffer = 0x8000031c;
|
||||
uint8_t *rdram = rdram_buffer.get();
|
||||
MEM_W(osTvType, 0) = 1; // NTSC
|
||||
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
||||
MEM_W(osResetType, 0) = 0; // cold reset
|
||||
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
||||
|
||||
// Clear bss
|
||||
// TODO run the entrypoint instead
|
||||
memset(rdram_buffer.get() + 0XAF860, 0, 0xC00A0u - 0XAF860);
|
||||
|
||||
printf("[Recomp] Starting\n");
|
||||
|
||||
Multilibultra::set_main_thread();
|
||||
|
||||
game_init(rdram_buffer.get(), &context);
|
||||
|
||||
printf("[Recomp] Quitting\n");
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
22
test/src/sp.cpp
Normal file
22
test/src/sp.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include <cstdio>
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osSpTaskLoad_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskStartGo_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYield_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYielded_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osSpSetPc_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
33
test/src/vi.cpp
Normal file
33
test/src/vi.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "recomp.h"
|
||||
|
||||
extern "C" void osCreateViManager_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViBlack_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViSetSpecialFeatures_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViGetNextFramebuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
|
||||
;
|
||||
}
|
Loading…
Reference in a new issue