From 99c6ebded380d6d0152fc1443094d788ace38767 Mon Sep 17 00:00:00 2001 From: Babz Date: Sun, 12 Sep 2021 20:34:21 +0200 Subject: [PATCH] add preemptive scheduler --- CMakeLists.txt | 23 ++++- src/job.c | 74 ++++++++++++++++ src/job.h | 19 +++++ src/main.c | 180 ++++++++++++++++++++++++++++----------- src/term.c | 13 +-- src/ui.c | 3 +- src/wren_utils.c | 38 +++++++++ src/wren_utils.h | 8 ++ wren/include/wren.h | 7 ++ wren/src/wren_common.h | 2 +- wren/src/wren_compiler.c | 4 +- wren/src/wren_debug.c | 3 +- wren/src/wren_value.c | 2 +- wren/src/wren_vm.c | 24 +++++- wren/src/wren_vm.h | 5 ++ 15 files changed, 335 insertions(+), 70 deletions(-) create mode 100644 src/job.c create mode 100644 src/job.h create mode 100644 src/wren_utils.c create mode 100644 src/wren_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e52bbf2..a21aeda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ set(SOURCES src/ui.c src/battery.c src/syscalls.S + src/job.c + src/wren_utils.c ) set(SOURCES_WREN wren/src/wren_compiler.c @@ -34,6 +36,9 @@ set(SOURCES_WREN wren/src/wren_opt_meta.c wren/src/wren_opt_random.c ) +set(SOURCES_PRINTF + src/printf.c +) # shared assets set(ASSETS @@ -79,23 +84,37 @@ set(FLAGS # -O2: good speed/size tradeoff # -O3: gotta go fast -Os + -m4a-nofpu + -flto -ffat-lto-objects ) set(FLAGS_WREN - -std=c99 + -std=c11 + -Os + -m4a-nofpu + -flto -ffat-lto-objects +) +set(FLAGS_PRINTF + -std=c11 -Ofast + -m4a-nofpu -flto -ffat-lto-objects ) set(CMAKE_ASM_FLAGS "-x assembler-with-cpp") +add_link_options(-flto) + +add_library(Printf ${SOURCES_PRINTF}) +target_compile_options(Printf PRIVATE ${FLAGS_PRINTF}) add_library(Wren ${SOURCES_WREN}) target_compile_options(Wren PRIVATE ${FLAGS_WREN}) +target_link_libraries(Wren Printf Gint::Gint) fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA) add_executable(Main ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}}) target_compile_options(Main PRIVATE ${FLAGS}) -target_link_libraries(Main Wren Gint::Gint) +target_link_libraries(Main Printf Wren Gint::Gint) if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) generate_g1a( diff --git a/src/job.c b/src/job.c new file mode 100644 index 0000000..b71c31c --- /dev/null +++ b/src/job.c @@ -0,0 +1,74 @@ +#include "job.h" + +#include + +#include "term.h" + +extern jmp_buf sched_ctxbuf; + +WrenVM *job_vms[UNS_MAX_JOBS] = {0}; +void *job_stacks[UNS_MAX_JOBS] = {0}; +int current_job_id = -1; + +static void *job_start_(const uint32_t entry_point) { + term_kprint("allocate new stack..."); + static const int job_stack_size = 64 * 1024; // 64 kiB stack + const uint32_t child_stack = (uint32_t)kmalloc(job_stack_size, NULL); + const uint32_t new_sp = ((child_stack + 3) >> 2 << 2) + ((job_stack_size + 3) >> 2 << 2); + term_kprintf("new_bp=0x%08x", child_stack); + term_kprintf("new_sp=0x%08x", new_sp); + + // get a copy of the current context + jmp_buf ctx; + setjmp(ctx); + + // transform it to the hidden representation so we can modify it + struct __jmp_buf *ctx_ptr = &ctx[0]; + + ctx_ptr->reg[7] = new_sp; // change stack pointer + ctx_ptr->pr = entry_point; // change pc backup + + const int ret = setjmp(sched_ctxbuf); + if (ret == 0) + longjmp(ctx, 1); // apply changes, run job until it yields a first time + else + return (void *)child_stack; // then return the stack descriptor (not "stack pointer"!) for later free +} + +int job_start(uint32_t entry_point) { + for (int i = 0; i < UNS_MAX_JOBS; i++) { + if (job_vms[i] == NULL) { + current_job_id = i; + job_stacks[i] = job_start_(entry_point); + return i; + } + } + + // no slot found + return -1; +} + +void job_resume(int job_id) { + wrenResume(job_vms[job_id]); // longjmp(vm->ctxbuf, 1); +} + +void job_free(int job_id) { + wrenFreeVM(job_vms[job_id]); + job_vms[job_id] = NULL; + + kfree(job_stacks[job_id]); + job_stacks[job_id] = NULL; +} + +void next_job(void) { + for (int i = 1; i < UNS_MAX_JOBS + 1; i++) { + const int candidate = (current_job_id + i) % UNS_MAX_JOBS; + + if (job_stacks[candidate] != NULL) { + current_job_id = candidate; + return; + } + } + + current_job_id = -1; +} \ No newline at end of file diff --git a/src/job.h b/src/job.h new file mode 100644 index 0000000..8acb1ed --- /dev/null +++ b/src/job.h @@ -0,0 +1,19 @@ +#ifndef UNS_JOB_H +#define UNS_JOB_H + +#include + +#include + +#define UNS_MAX_JOBS 128 +extern WrenVM *job_vms[UNS_MAX_JOBS]; +extern void *job_stacks[UNS_MAX_JOBS]; +extern int current_job_id; + +int job_start(uint32_t entry_point); +void job_resume(int job_id); +void job_free(int job_id); + +void next_job(void); + +#endif // #ifndef UNS_JOB_H \ No newline at end of file diff --git a/src/main.c b/src/main.c index e4a2183..0507fcc 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,6 @@ -#include +#include +#include +#include #include #include @@ -9,57 +11,33 @@ #include #include -#include - #include "battery.h" +#include "job.h" #include "term.h" #include "ui.h" - -static void wn_write(WrenVM __attribute__((unused)) * vm, const char *text) { term_print(text); } -static void wn_error(WrenVM __attribute__((unused)) * vm, WrenErrorType errorType, const char *module, const int line, - const char *msg) { - switch (errorType) { - case WREN_ERROR_COMPILE: - term_printf("[%s line %d] [Error] %s\n", module, line, msg); - break; - case WREN_ERROR_STACK_TRACE: - term_eprintf("[%s line %d] in %s\n", module, line, msg); - break; - case WREN_ERROR_RUNTIME: - term_eprintf("[Runtime Error] %s\n", msg); - break; - default: - break; - } -} - -static void *wn_reallocate(void *memory, size_t newSize, __attribute__((unused)) void *userData) { - if (memory == NULL && newSize == 0) - return NULL; - - if (memory == NULL) - return kmalloc(newSize, "_uram"); - - if (newSize == 0) { - kfree(memory); - return NULL; - } - - return krealloc(memory, newSize); -} +#include "wren_utils.h" static int tick_ctr = 0; +static int cycle_ctr = 0; static int shift_state = 0; static int alpha_state = 0; static int off_state = 0; -static int timer_redraw; static volatile int must_redraw; +static int timer_redraw; static int callback_redraw(void) { must_redraw = 1; return TIMER_CONTINUE; } +volatile int must_yield; +jmp_buf sched_ctxbuf; +static int timer_sched; +static int callback_sched(void) { + must_yield = 1; + return TIMER_CONTINUE; +} + static void check_keyevents(void) { static uint16_t backlight_save; @@ -118,33 +96,91 @@ static void check_keyevents(void) { } } -int main(__attribute__((unused)) int isappli, __attribute__((unused)) int optnum) { +__attribute__((noreturn)) static void run_vm_1(void) { WrenConfiguration config; - wrenInitConfiguration(&config); - config.reallocateFn = &wn_reallocate; - config.initialHeapSize = 1024 * 4; // 4kiB - config.minHeapSize = 1024; // 1kiB - config.heapGrowthPercent = 20; - config.writeFn = &wn_write; - config.errorFn = &wn_error; + init_wren_config(&config); + + term_kprint("create wren VM..."); + must_yield = 0; WrenVM *vm = wrenNewVM(&config); + + job_vms[current_job_id] = vm; + + term_kprint("start interpreter..."); + must_yield = 1; // yield ASAP wrenInterpret(vm, "my_module", - "System.print(\"I am running in a VM!\")\n" - "System.trigger_error_from_vm()"); - wrenFreeVM(vm); + "System.print(\"inner start\")\n" + "var i = 0\n" + "while (i < 30000) {\n" + " if ((i % 500) == 0) {\n" + " System.print(\"vm 1: i=%(i)\")\n" + " }\n" + " i = i + 1\n" + "}\n" + "System.print(\"inner end\")\n"); + term_kprint("interpreter returned"); + + // never return !!! + // longjmp to scheduler instead + // return value of 2 means job exit + longjmp(sched_ctxbuf, 2); +} + +__attribute__((noreturn)) static void run_vm_2(void) { + WrenConfiguration config; + init_wren_config(&config); + + term_kprint("create wren VM..."); + must_yield = 0; + WrenVM *vm = wrenNewVM(&config); + + job_vms[current_job_id] = vm; + + term_kprint("start interpreter..."); + must_yield = 1; // yield ASAP + wrenInterpret(vm, "my_module_2", + "System.print(\"inner start\")\n" + "var i = 0\n" + "while (i < 30000) {\n" + " if ((i % 500) == 0) {\n" + " System.print(\"vm 2: i=%(i)\")\n" + " }\n" + " i = i + 1\n" + "}\n" + "System.print(\"inner end\")\n"); + term_kprint("interpreter returned"); + + // never return !!! + // longjmp to scheduler instead + // return value of 2 means job exit + longjmp(sched_ctxbuf, 2); +} + +int main(int isappli, int optnum) { + term_kprintf("main(%d, %d)", isappli, optnum); + + timer_sched = timer_configure(TIMER_ANY, 10 * 1000, GINT_CALL(callback_sched)); // 100 Hz <=> 10 ms timer_redraw = timer_configure(TIMER_ANY, 31250, GINT_CALL(callback_redraw)); // 32 Hz timer_start(timer_redraw); + // start a few jobs + job_start((uint32_t)run_vm_1); + job_start((uint32_t)run_vm_2); + while (1) { + // system routine + check_keyevents(); if (off_state) { sleep(); continue; } + tick_ctr++; + if (must_redraw) { - set_statusbar(tick_ctr, shift_state, alpha_state, get_battery_voltage()); + set_statusbar(tick_ctr, cycle_ctr, shift_state, alpha_state, get_battery_voltage()); set_menubar(); dclear(C_BLACK); @@ -154,8 +190,52 @@ int main(__attribute__((unused)) int isappli, __attribute__((unused)) int optnum must_redraw = 0; } - tick_ctr++; + // scheduler routine + + next_job(); + if (current_job_id < 0) { + term_kprint("all jobs ended"); + break; + } + + // first, backup current scheduler context + const int ret = setjmp(sched_ctxbuf); + if (ret == 0) { + // pause timer during sheduler operations, we restart it before resuming a job + timer_pause(timer_sched); + cycle_ctr++; + + // resume job + timer_start(timer_sched); + must_yield = 0; + job_resume(current_job_id); + } else if (ret == 1) { + // term_kprintf("job(%d) yield", current_job_id); + continue; + } else if (ret == 2) { + term_kprintf("job(%d) exit", current_job_id); + job_free(current_job_id); + } else { + __builtin_unreachable(); + term_kprint("SHOULD NOT BE REACHED!!!"); + break; + } } + term_eprint("SCHEDULER EXIT"); + + set_statusbar(tick_ctr, cycle_ctr, shift_state, alpha_state, get_battery_voltage()); + set_menubar(); + + dclear(C_BLACK); + tgrid_display(); + dupdate(); + + while (1) { + getkey(); + } + + __builtin_unreachable(); + return 1; } \ No newline at end of file diff --git a/src/term.c b/src/term.c index 77e1c4a..51dba02 100644 --- a/src/term.c +++ b/src/term.c @@ -1,12 +1,11 @@ #include "term.h" -#include #include #include #include +#include #include -#include #include extern font_t uf5x7; @@ -148,7 +147,7 @@ int term_printf(const char *restrict format, ...) { va_start(argp, format); char buf[1024]; - const int n = vsprintf(buf, format, argp); + const int n = vsnprintf(buf, sizeof(buf), format, argp); term_print(buf); va_end(argp); @@ -161,7 +160,7 @@ int term_eprintf(const char *restrict format, ...) { va_start(argp, format); char buf[1024]; - const int n = vsprintf(buf, format, argp); + const int n = vsnprintf(buf, sizeof(buf), format, argp); term_eprint(buf); va_end(argp); @@ -169,8 +168,6 @@ int term_eprintf(const char *restrict format, ...) { } int term_kprint(const char *str) { - cpu_atomic_start(); - int t = rtc_ticks(); char kstr[256]; @@ -178,8 +175,6 @@ int term_kprint(const char *str) { const int ret = term_print_opt(kstr, C_RED | C_GREEN, C_BLACK); - cpu_atomic_end(); - return ret; } int term_kprintf(const char *restrict format, ...) { @@ -187,7 +182,7 @@ int term_kprintf(const char *restrict format, ...) { va_start(argp, format); char buf[1024]; - const int n = vsprintf(buf, format, argp); + const int n = vsnprintf(buf, sizeof(buf), format, argp); term_kprint(buf); va_end(argp); diff --git a/src/ui.c b/src/ui.c index 9d4d62b..6366948 100644 --- a/src/ui.c +++ b/src/ui.c @@ -27,7 +27,8 @@ void set_statusbar(int tick_ctr, int cycle_ctr, int shift_state, int alpha_state // then add actutal text char statusbar[UNS_TERM_COLS + 1]; - sprintf(statusbar, "%s%s t=%dk c=%d bat=%.2fV %s", shift_symbol, alpha_symbol, tick_ctr / 1000, cycle_ctr, (float)battery / 100, now); + sprintf(statusbar, "%s%s t=%dk c=%d bat=%.2fV %s", shift_symbol, alpha_symbol, tick_ctr / 1000, cycle_ctr, + (float)battery / 100, now); term_writeat(0, 0, C_BLACK, C_GREEN, statusbar); } diff --git a/src/wren_utils.c b/src/wren_utils.c new file mode 100644 index 0000000..d41be90 --- /dev/null +++ b/src/wren_utils.c @@ -0,0 +1,38 @@ +#include "wren_utils.h" + +#include + +#include "term.h" + +static void wn_write(WrenVM __attribute__((unused)) * vm, const char *text) { term_print(text); } +static void wn_error(WrenVM __attribute__((unused)) * vm, WrenErrorType errorType, const char *module, const int line, + const char *msg) { + switch (errorType) { + case WREN_ERROR_COMPILE: + term_printf("[%s line %d] [Error] %s\n", module, line, msg); + break; + case WREN_ERROR_STACK_TRACE: + term_eprintf("[%s line %d] in %s\n", module, line, msg); + break; + case WREN_ERROR_RUNTIME: + term_eprintf("[Runtime Error] %s\n", msg); + break; + default: + break; + } +} + +static void *wn_reallocate(void *memory, size_t newSize, __attribute__((unused)) void *userData) { + return krealloc(memory, newSize); +} + +void init_wren_config(WrenConfiguration *config) { + wrenInitConfiguration(config); + + config->reallocateFn = &wn_reallocate; + config->initialHeapSize = 16 * 1024; // 16 kiB + config->minHeapSize = 1024; // 1 kiB + config->heapGrowthPercent = 50; + config->writeFn = &wn_write; + config->errorFn = &wn_error; +} diff --git a/src/wren_utils.h b/src/wren_utils.h new file mode 100644 index 0000000..de886ef --- /dev/null +++ b/src/wren_utils.h @@ -0,0 +1,8 @@ +#ifndef UNS_WREN_UTILS_H +#define UNS_WREN_UTILS_H + +#include + +void init_wren_config(WrenConfiguration *config); + +#endif // #ifndef UNS_WREN_UTILS_H \ No newline at end of file diff --git a/wren/include/wren.h b/wren/include/wren.h index 03bd792..a26c9e4 100644 --- a/wren/include/wren.h +++ b/wren/include/wren.h @@ -5,6 +5,8 @@ #include #include +#include + // The Wren semantic version number components. #define WREN_VERSION_MAJOR 0 #define WREN_VERSION_MINOR 4 @@ -33,6 +35,10 @@ // here. typedef struct WrenVM WrenVM; +// restore execution after a yield +__attribute__((noreturn)) +void wrenResume(WrenVM* vm); + // A handle to a Wren object. // // This lets code outside of the VM hold a persistent reference to an object. @@ -274,6 +280,7 @@ typedef struct typedef enum { WREN_RESULT_SUCCESS, + WREN_RESULT_YIELD, WREN_RESULT_COMPILE_ERROR, WREN_RESULT_RUNTIME_ERROR } WrenInterpretResult; diff --git a/wren/src/wren_common.h b/wren/src/wren_common.h index 4309e6a..4167c6d 100644 --- a/wren/src/wren_common.h +++ b/wren/src/wren_common.h @@ -154,7 +154,7 @@ // Assertions add significant overhead, so are only enabled in debug builds. #ifdef DEBUG - #include + #include #define ASSERT(condition, message) \ do \ diff --git a/wren/src/wren_compiler.c b/wren/src/wren_compiler.c index 92b16ca..87894df 100644 --- a/wren/src/wren_compiler.c +++ b/wren/src/wren_compiler.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include "wren_common.h" @@ -429,7 +429,7 @@ static void printError(Parser* parser, int line, const char* label, // Format the label and message. char message[ERROR_MESSAGE_SIZE]; int length = sprintf(message, "%s: ", label); - length += vsprintf(message + length, format, args); + length += vsnprintf(message + length, ERROR_MESSAGE_SIZE - length, format, args); ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer."); ObjString* module = parser->module->name; diff --git a/wren/src/wren_debug.c b/wren/src/wren_debug.c index 6cc6f4f..a2d8aa5 100644 --- a/wren/src/wren_debug.c +++ b/wren/src/wren_debug.c @@ -1,5 +1,6 @@ -#include +#include #include +#undef printf #define printf(...) term_printf(__VA_ARGS__) #include "wren_debug.h" diff --git a/wren/src/wren_value.c b/wren/src/wren_value.c index c49a3b6..6adecb4 100644 --- a/wren/src/wren_value.c +++ b/wren/src/wren_value.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include "wren.h" diff --git a/wren/src/wren_vm.c b/wren/src/wren_vm.c index bb5c277..15cbe6a 100644 --- a/wren/src/wren_vm.c +++ b/wren/src/wren_vm.c @@ -9,6 +9,8 @@ #include "wren_primitive.h" #include "wren_vm.h" +#include + #if WREN_OPT_META #include "wren_opt_meta.h" #endif @@ -18,7 +20,7 @@ #if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC #include - #include + #include #endif // The behavior of realloc() when the size is 0 is implementation defined. It @@ -303,7 +305,7 @@ static void closeUpvalues(ObjFiber* fiber, Value* last) // Looks up a foreign method in [moduleName] on [className] with [signature]. // // This will try the host's foreign method binder first. If that fails, it -// falls back to handling the built-in modules. +// falls back to handling the built-in modules.instruction static WrenForeignMethodFn findForeignMethod(WrenVM* vm, const char* moduleName, const char* className, @@ -421,7 +423,7 @@ static void runtimeError(WrenVM* vm) return; } - // Otherwise, unhook the caller since we will never resume and return to it. + // Otherwise, unhook the caller since we will never re}sume and return to it. ObjFiber* caller = current->caller; current->caller = NULL; current = caller; @@ -820,6 +822,10 @@ inline static bool checkArity(WrenVM* vm, Value value, int numArgs) return false; } +extern volatile int must_yield; +extern jmp_buf sched_ctxbuf; +// restore execution after a yield +__attribute__((noreturn)) void wrenResume(WrenVM *vm) { longjmp(vm->ctxbuf, 1); } // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. @@ -887,6 +893,16 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) #endif + #define CHECK_SCHEDULER() \ + do \ + { \ + __builtin_expect(must_yield == 0, 1); \ + if (must_yield) { \ + if (!setjmp(vm->ctxbuf)) \ + longjmp(sched_ctxbuf, 1); \ + } \ + } while (false) + #if WREN_COMPUTED_GOTO static void* dispatchTable[] = { @@ -901,6 +917,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #define DISPATCH() \ do \ { \ + CHECK_SCHEDULER(); \ DEBUG_TRACE_INSTRUCTIONS(); \ goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ } while (false) @@ -909,6 +926,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #define INTERPRET_LOOP \ loop: \ + CHECK_SHEDULER(); \ DEBUG_TRACE_INSTRUCTIONS(); \ switch (instruction = (Code)READ_BYTE()) diff --git a/wren/src/wren_vm.h b/wren/src/wren_vm.h index 7ab74c9..2264e7b 100644 --- a/wren/src/wren_vm.h +++ b/wren/src/wren_vm.h @@ -6,6 +6,8 @@ #include "wren_value.h" #include "wren_utils.h" +#include + // The maximum number of temporary objects that can be made visible to the GC // at one time. #define WREN_MAX_TEMP_ROOTS 8 @@ -42,6 +44,9 @@ struct WrenVM ObjClass* rangeClass; ObjClass* stringClass; + // the interpreter saved context + jmp_buf ctxbuf; + // The fiber that is currently running. ObjFiber* fiber;