feat: async/await/spawn + fix else-if lowering bug
Self-hosted compiler (buxc2):
- Add async/await/spawn tokens, parsing, HIR, lowering, C emission
- Fix pointer type emission (*T -> T*) in C backend
- Fix sizeof(Type) parsing with parentheses
- Fix import ::{...} infinite loop guard
- Fix int64 emission by avoiding else-if chain workaround
- Add debug-free cli and hir_lower
Bootstrap compiler (Nim):
- Fix critical else-if lowering bug in hir_lower.nim:
when both elseIfs and else block exist, elseIfs were dropped
causing all else-if chains to collapse to if+else only
Runtime:
- Fix bux_async_await memory corruption (don't free in run)
- Add bux_remove_from_ready for safe cleanup
- Fix forward declaration and deduplicate bux_now_ms
Docs & examples:
- Update async.bux example with proper int64 params
- Update README, LanguageRef, Stdlib, PLAN for async features
- Mark Phase 7.10 bootstrap loop as completed
All 27 examples pass. buxc2 check/build work on all examples.
This commit is contained in:
+58
-13
@@ -8,6 +8,7 @@
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <ucontext.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Command-line argument storage */
|
||||
int g_argc = 0;
|
||||
@@ -809,6 +810,7 @@ typedef struct bux_async_task {
|
||||
int state; /* 0 = ready, 1 = running, 2 = done */
|
||||
void (*entry)(void);
|
||||
void* result; /* pointer to heap-allocated result */
|
||||
int64_t sleep_until_ms;
|
||||
struct bux_async_task* next;
|
||||
} bux_async_task_t;
|
||||
|
||||
@@ -837,6 +839,26 @@ static bux_async_task_t* bux_dequeue_ready(void) {
|
||||
return task;
|
||||
}
|
||||
|
||||
static void bux_remove_from_ready(bux_async_task_t* target) {
|
||||
bux_async_task_t* prev = NULL;
|
||||
bux_async_task_t* curr = bux_ready_head;
|
||||
while (curr) {
|
||||
if (curr == target) {
|
||||
if (prev) {
|
||||
prev->next = curr->next;
|
||||
} else {
|
||||
bux_ready_head = curr->next;
|
||||
}
|
||||
if (bux_ready_tail == curr) {
|
||||
bux_ready_tail = prev;
|
||||
}
|
||||
return;
|
||||
}
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void bux_coro_trampoline(void) {
|
||||
bux_async_task_t* self = bux_current_task;
|
||||
if (self != NULL && self->entry != NULL) {
|
||||
@@ -864,6 +886,7 @@ void* bux_async_spawn(void (*func)(void)) {
|
||||
task->next = NULL;
|
||||
task->caller_ctx = NULL;
|
||||
task->entry = func;
|
||||
task->sleep_until_ms = 0;
|
||||
|
||||
getcontext(&task->ctx);
|
||||
task->ctx.uc_stack.ss_sp = task->stack;
|
||||
@@ -884,21 +907,31 @@ void bux_async_yield(void) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bux_async_run(void);
|
||||
|
||||
void* bux_async_await(void* handle) {
|
||||
if (!handle) return NULL;
|
||||
bux_async_task_t* target = (bux_async_task_t*)handle;
|
||||
while (target->state != 2) {
|
||||
if (bux_current_task != NULL) {
|
||||
/* Inside a coroutine: yield and let scheduler run */
|
||||
bux_async_yield();
|
||||
} else {
|
||||
/* Main thread: run scheduler until target is done */
|
||||
if (!bux_scheduler_running) {
|
||||
bux_async_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
return target->result;
|
||||
void* result = target->result;
|
||||
bux_remove_from_ready(target);
|
||||
free(target->stack);
|
||||
free(target);
|
||||
return result;
|
||||
}
|
||||
static int64_t bux_now_ms(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
void bux_async_run(void) {
|
||||
@@ -909,27 +942,39 @@ void bux_async_run(void) {
|
||||
bux_async_task_t* task = bux_dequeue_ready();
|
||||
if (!task) break;
|
||||
if (task->state == 2) {
|
||||
free(task->stack);
|
||||
free(task);
|
||||
/* Leave completed tasks in queue for await to clean up */
|
||||
bux_enqueue_ready(task);
|
||||
continue;
|
||||
}
|
||||
/* Check if task is still sleeping */
|
||||
if (task->sleep_until_ms > 0) {
|
||||
int64_t now = bux_now_ms();
|
||||
if (now < task->sleep_until_ms) {
|
||||
/* Re-queue and check next task */
|
||||
bux_enqueue_ready(task);
|
||||
/* If all tasks are sleeping, sleep the thread */
|
||||
if (bux_ready_head == task && bux_ready_tail == task) {
|
||||
int64_t delay = task->sleep_until_ms - now;
|
||||
struct timespec ts;
|
||||
ts.tv_sec = delay / 1000;
|
||||
ts.tv_nsec = (delay % 1000) * 1000000;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
task->sleep_until_ms = 0;
|
||||
}
|
||||
task->state = 1;
|
||||
bux_current_task = task;
|
||||
swapcontext(&bux_scheduler_ctx, &task->ctx);
|
||||
bux_current_task = NULL;
|
||||
if (task->state == 2) {
|
||||
free(task->stack);
|
||||
free(task);
|
||||
}
|
||||
}
|
||||
bux_scheduler_running = 0;
|
||||
}
|
||||
|
||||
void bux_async_sleep(int64_t ms) {
|
||||
/* Naive sleep: block this coroutine via yield */
|
||||
if (ms > 0) {
|
||||
/* TODO: proper timer-based scheduling */
|
||||
/* For now, just yield once */
|
||||
if (ms > 0 && bux_current_task != NULL) {
|
||||
bux_current_task->sleep_until_ms = bux_now_ms() + ms;
|
||||
bux_async_yield();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user