add preemptive scheduler

This commit is contained in:
Babz 2021-09-12 20:34:21 +02:00
parent a1d8e52a1a
commit 99c6ebded3
15 changed files with 335 additions and 70 deletions

View File

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

74
src/job.c Normal file
View File

@ -0,0 +1,74 @@
#include "job.h"
#include <gint/kmalloc.h>
#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;
}

19
src/job.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef UNS_JOB_H
#define UNS_JOB_H
#include <stdint.h>
#include <wren.h>
#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

View File

@ -1,4 +1,6 @@
#include <stdio.h>
#include <printf.h>
#include <setjmp.h>
#include <unistd.h>
#include <gint/cpu.h>
#include <gint/display.h>
@ -9,57 +11,33 @@
#include <gint/mpu/power.h>
#include <gint/timer.h>
#include <wren.h>
#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;
}

View File

@ -1,12 +1,11 @@
#include "term.h"
#include <gint/cpu.h>
#include <gint/display.h>
#include <gint/rtc.h>
#include <ctype.h>
#include <printf.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
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);

View File

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

38
src/wren_utils.c Normal file
View File

@ -0,0 +1,38 @@
#include "wren_utils.h"
#include <gint/kmalloc.h>
#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;
}

8
src/wren_utils.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef UNS_WREN_UTILS_H
#define UNS_WREN_UTILS_H
#include <wren.h>
void init_wren_config(WrenConfiguration *config);
#endif // #ifndef UNS_WREN_UTILS_H

View File

@ -5,6 +5,8 @@
#include <stdlib.h>
#include <stdbool.h>
#include <setjmp.h>
// 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;

View File

@ -154,7 +154,7 @@
// Assertions add significant overhead, so are only enabled in debug builds.
#ifdef DEBUG
#include <stdio.h>
#include <printf.h>
#define ASSERT(condition, message) \
do \

View File

@ -1,6 +1,6 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <printf.h>
#include <string.h>
#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;

View File

@ -1,5 +1,6 @@
#include <stdio.h>
#include <printf.h>
#include <term.h>
#undef printf
#define printf(...) term_printf(__VA_ARGS__)
#include "wren_debug.h"

View File

@ -1,6 +1,6 @@
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <printf.h>
#include <string.h>
#include "wren.h"

View File

@ -9,6 +9,8 @@
#include "wren_primitive.h"
#include "wren_vm.h"
#include <term.h>
#if WREN_OPT_META
#include "wren_opt_meta.h"
#endif
@ -18,7 +20,7 @@
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
#include <time.h>
#include <stdio.h>
#include <printf.h>
#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())

View File

@ -6,6 +6,8 @@
#include "wren_value.h"
#include "wren_utils.h"
#include <setjmp.h>
// 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;