Files
Yukihiro "Matz" Matsumoto be6413f0d8 mruby-task: migrate HAL to ports/ directories
Move hal-posix-task and hal-win-task into
mruby-task/ports/posix/ and mruby-task/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake. Update
cosmopolitan.rb to use conf.ports :posix instead of explicit
hal-* gem references.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00

235 lines
5.3 KiB
C

/*
** task_hal.c - POSIX HAL implementation for mruby-task
**
** See Copyright Notice in mruby.h
**
** POSIX implementation using SIGALRM and setitimer() for timer,
** and sigprocmask() for interrupt protection.
**
** Supported platforms: Linux, macOS, BSD, Unix
**
** Note: When compiled for Emscripten/WASM, the SIGALRM timer is disabled
** because JavaScript handles tick calls via setInterval. Using both would
** cause tick_ to increment twice as fast, making sleep wake up early.
*/
#include <mruby.h>
#include "task_hal.h"
#ifndef __EMSCRIPTEN__
#include <signal.h>
#include <sys/time.h>
#endif
#include <time.h>
#include <unistd.h>
#include <stdint.h>
/* Time conversion constants */
#define NSEC_PER_MSEC 1000000ULL
#define NSEC_PER_SEC 1000000000ULL
#define USEC_PER_MSEC 1000ULL
#ifndef __EMSCRIPTEN__
/* Multi-VM support */
static mrb_state *vm_list[MRB_TASK_MAX_VMS];
static volatile sig_atomic_t vm_count = 0;
static sigset_t alarm_mask;
/* SIGALRM signal handler - ticks all registered VMs */
static void
sigalrm_handler(int sig)
{
int i;
(void)sig;
/* Tick all registered VMs */
for (i = 0; i < vm_count; i++) {
if (vm_list[i]) {
mrb_tick(vm_list[i]);
}
}
}
#endif /* __EMSCRIPTEN__ */
/*
* HAL Interface Implementation
*/
void
mrb_hal_task_init(mrb_state *mrb)
{
int i;
/* Initialize task state */
for (i = 0; i < 4; i++) {
mrb->task.queues[i] = NULL;
}
mrb->task.tick = 0;
mrb->task.wakeup_tick = UINT32_MAX;
mrb->task.switching = FALSE;
#ifndef __EMSCRIPTEN__
/* POSIX: Set up SIGALRM timer for tick handling */
struct sigaction sa;
struct itimerval timer;
int vm_index = -1;
/* Block SIGALRM during registration to avoid race */
sigemptyset(&alarm_mask);
sigaddset(&alarm_mask, SIGALRM);
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Check if this VM is already registered */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
vm_index = i;
break;
}
}
/* Register new VM if not already present */
if (vm_index < 0) {
if (vm_count >= MRB_TASK_MAX_VMS) {
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
mrb_raisef(mrb, E_RUNTIME_ERROR,
"too many mrb_states with task scheduler (max: %d)",
MRB_TASK_MAX_VMS);
}
vm_list[vm_count] = mrb;
vm_count++;
}
/* Set up signal handler and timer only for first VM */
if (vm_count == 1) {
/* Set up signal handler - SA_RESTART to avoid breaking IO operations */
sa.sa_handler = sigalrm_handler;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, NULL);
/* Start timer */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = MRB_TICK_UNIT * 1000;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000;
setitimer(ITIMER_REAL, &timer, NULL);
}
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#endif
}
void
mrb_task_enable_irq(void)
{
#ifndef __EMSCRIPTEN__
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#endif
}
void
mrb_task_disable_irq(void)
{
#ifndef __EMSCRIPTEN__
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
#endif
}
void
mrb_hal_task_idle_cpu(mrb_state *mrb)
{
(void)mrb;
/* On POSIX, just pause briefly */
usleep(MRB_TICK_UNIT * 1000);
}
void
mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec)
{
struct timespec start, now, sleep_time;
int ret;
(void)mrb;
/* Validate input to prevent overflow */
if (usec < 0) {
return;
}
ret = clock_gettime(CLOCK_MONOTONIC, &start);
if (ret != 0) {
/* Fallback to simple usleep if clock_gettime fails */
usleep(usec);
return;
}
uint64_t target_ns = (uint64_t)usec * USEC_PER_MSEC;
/* Loop until enough real time has elapsed */
while (1) {
ret = clock_gettime(CLOCK_MONOTONIC, &now);
if (ret != 0) {
break; /* Clock failure - exit loop */
}
uint64_t elapsed_ns = (uint64_t)(now.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(uint64_t)(now.tv_nsec - start.tv_nsec);
if (elapsed_ns >= target_ns) {
break;
}
/* Sleep for remaining time, but at least 1ms to allow timer interrupts */
uint64_t remaining_ns = target_ns - elapsed_ns;
if (remaining_ns > NSEC_PER_MSEC) {
sleep_time.tv_sec = remaining_ns / NSEC_PER_SEC;
sleep_time.tv_nsec = remaining_ns % NSEC_PER_SEC;
}
else {
sleep_time.tv_sec = 0;
sleep_time.tv_nsec = NSEC_PER_MSEC;
}
nanosleep(&sleep_time, NULL); /* Interrupted by signals - that's OK */
}
}
void
mrb_hal_task_final(mrb_state *mrb)
{
#ifndef __EMSCRIPTEN__
struct itimerval timer;
int i, j;
/* Block SIGALRM during unregistration */
sigprocmask(SIG_BLOCK, &alarm_mask, NULL);
/* Find and remove this VM from the list */
for (i = 0; i < vm_count; i++) {
if (vm_list[i] == mrb) {
/* Shift remaining VMs down */
for (j = i; j < vm_count - 1; j++) {
vm_list[j] = vm_list[j + 1];
}
vm_list[vm_count - 1] = NULL;
vm_count--;
break;
}
}
/* Stop timer if last VM */
if (vm_count == 0) {
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);
}
/* Unblock SIGALRM */
sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL);
#else
/* WASM: No timer cleanup needed */
(void)mrb;
#endif
}