Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions packages/runtime/src/scr_win.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,161 @@
#include "scr_runtime.h"

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
#endif

#define SCR_WIN_FILETIME_UNIX_EPOCH UINT64_C(116444736000000000)
#define SCR_WIN_TICKS_PER_SECOND UINT64_C(10000000)

static int scr_win_error(DWORD error) {
switch (error) {
case ERROR_INVALID_PARAMETER:
errno = EINVAL;
break;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
errno = ENOMEM;
break;
default:
errno = EIO;
break;
}
return -1;
}

static int scr_win_clock_gettime(clockid_t clock_id, int64_t *seconds,
long *nanoseconds) {
if (clock_id == CLOCK_REALTIME || clock_id == CLOCK_REALTIME_COARSE) {
FILETIME file_time;
ULARGE_INTEGER ticks;
GetSystemTimePreciseAsFileTime(&file_time);
ticks.LowPart = file_time.dwLowDateTime;
ticks.HighPart = file_time.dwHighDateTime;
if (ticks.QuadPart < SCR_WIN_FILETIME_UNIX_EPOCH) {
errno = EOVERFLOW;
return -1;
}
ticks.QuadPart -= SCR_WIN_FILETIME_UNIX_EPOCH;
*seconds = (int64_t)(ticks.QuadPart / SCR_WIN_TICKS_PER_SECOND);
*nanoseconds = (long)((ticks.QuadPart % SCR_WIN_TICKS_PER_SECOND) * 100);
return 0;
}
if (clock_id == CLOCK_MONOTONIC) {
LARGE_INTEGER counter;
LARGE_INTEGER frequency;
if (!QueryPerformanceFrequency(&frequency) || frequency.QuadPart <= 0 ||
!QueryPerformanceCounter(&counter) || counter.QuadPart < 0) {
return scr_win_error(GetLastError());
}
*seconds = counter.QuadPart / frequency.QuadPart;
*nanoseconds = (long)(((uint64_t)(counter.QuadPart % frequency.QuadPart) *
UINT64_C(1000000000)) /
(uint64_t)frequency.QuadPart);
return 0;
}
errno = EINVAL;
return -1;
}

static int scr_win_nanosleep(int64_t seconds, long nanoseconds,
int64_t *remaining_seconds,
long *remaining_nanoseconds) {
if (seconds < 0 || nanoseconds < 0 || nanoseconds >= 1000000000L) {
errno = EINVAL;
return -1;
}
uint64_t subsecond_ticks = ((uint64_t)nanoseconds + 99) / 100;
if ((uint64_t)seconds >
((uint64_t)INT64_MAX - subsecond_ticks) / SCR_WIN_TICKS_PER_SECOND) {
errno = EINVAL;
return -1;
}
uint64_t ticks =
(uint64_t)seconds * SCR_WIN_TICKS_PER_SECOND + subsecond_ticks;
if (ticks == 0) {
Sleep(0);
} else {
HANDLE timer = CreateWaitableTimerExW(
NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
TIMER_MODIFY_STATE | SYNCHRONIZE);
if (timer == NULL) timer = CreateWaitableTimerW(NULL, FALSE, NULL);
if (timer == NULL) return scr_win_error(GetLastError());
LARGE_INTEGER due;
due.QuadPart = -(LONGLONG)ticks;
if (!SetWaitableTimer(timer, &due, 0, NULL, NULL, FALSE)) {
DWORD error = GetLastError();
CloseHandle(timer);
return scr_win_error(error);
}
DWORD wait = WaitForSingleObject(timer, INFINITE);
DWORD error = wait == WAIT_OBJECT_0 ? ERROR_SUCCESS : GetLastError();
CloseHandle(timer);
if (wait != WAIT_OBJECT_0) return scr_win_error(error);
}
if (remaining_seconds != NULL) *remaining_seconds = 0;
if (remaining_nanoseconds != NULL) *remaining_nanoseconds = 0;
return 0;
}

int clock_gettime32(clockid_t clock_id, struct _timespec32 *tp) {
int64_t seconds;
long nanoseconds;
if (scr_win_clock_gettime(clock_id, &seconds, &nanoseconds) != 0) return -1;
if (seconds < INT32_MIN || seconds > INT32_MAX) {
errno = EOVERFLOW;
return -1;
}
tp->tv_sec = (__time32_t)seconds;
tp->tv_nsec = nanoseconds;
return 0;
}

int clock_gettime64(clockid_t clock_id, struct _timespec64 *tp) {
int64_t seconds;
long nanoseconds;
if (scr_win_clock_gettime(clock_id, &seconds, &nanoseconds) != 0) return -1;
tp->tv_sec = (__time64_t)seconds;
tp->tv_nsec = nanoseconds;
return 0;
}

int nanosleep32(const struct _timespec32 *request, struct _timespec32 *remain) {
int64_t remaining_seconds;
long remaining_nanoseconds;
int result = scr_win_nanosleep(
request->tv_sec, request->tv_nsec,
remain == NULL ? NULL : &remaining_seconds,
remain == NULL ? NULL : &remaining_nanoseconds);
if (result == 0 && remain != NULL) {
remain->tv_sec = (__time32_t)remaining_seconds;
remain->tv_nsec = remaining_nanoseconds;
}
return result;
}

int nanosleep64(const struct _timespec64 *request, struct _timespec64 *remain) {
int64_t remaining_seconds;
long remaining_nanoseconds;
int result = scr_win_nanosleep(
request->tv_sec, request->tv_nsec,
remain == NULL ? NULL : &remaining_seconds,
remain == NULL ? NULL : &remaining_nanoseconds);
if (result == 0 && remain != NULL) {
remain->tv_sec = (__time64_t)remaining_seconds;
remain->tv_nsec = remaining_nanoseconds;
}
return result;
}

/* POSIX.1-2008 stpcpy: strcpy returning the END of the copy — scr_number.c
* (untouchable by project rule; ryu-adjacent) builds "e+"/"e-" exponent
* tails with it. */
Expand Down
50 changes: 50 additions & 0 deletions packages/runtime/test/test_win_time.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "scr_runtime.h"

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>

static int fail(const char *message) {
fprintf(stderr, "%s\n", message);
return 1;
}

static int64_t elapsed_ns(const struct timespec *before,
const struct timespec *after) {
return (int64_t)(after->tv_sec - before->tv_sec) * INT64_C(1000000000) +
(after->tv_nsec - before->tv_nsec);
}

int main(void) {
struct timespec realtime;
if (clock_gettime(CLOCK_REALTIME, &realtime) != 0)
return fail("CLOCK_REALTIME failed");
if (realtime.tv_sec < 1577836800)
return fail("CLOCK_REALTIME predates 2020");

struct timespec before;
struct timespec after;
if (clock_gettime(CLOCK_MONOTONIC, &before) != 0)
return fail("CLOCK_MONOTONIC failed");
struct timespec request = {0, 2000000};
struct timespec remain = {-1, -1};
if (nanosleep(&request, &remain) != 0) return fail("nanosleep failed");
if (remain.tv_sec != 0 || remain.tv_nsec != 0)
return fail("nanosleep left a remainder");
if (clock_gettime(CLOCK_MONOTONIC, &after) != 0)
return fail("second CLOCK_MONOTONIC failed");
if (elapsed_ns(&before, &after) < 1000000)
return fail("nanosleep returned early");

errno = 0;
if (clock_gettime((clockid_t)-1, &after) != -1 || errno != EINVAL)
return fail("invalid clock was accepted");
struct timespec invalid = {0, 1000000000};
errno = 0;
if (nanosleep(&invalid, NULL) != -1 || errno != EINVAL)
return fail("invalid sleep was accepted");

puts("win32 time shims ok");
return 0;
}
31 changes: 31 additions & 0 deletions packages/runtime/test/win-time.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { execFile } from "node:child_process";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";

const execFileAsync = promisify(execFile);
const platformTest = process.platform === "win32" ? test : test.skip;

platformTest("win32 timing shims link and preserve clock contracts", async () => {
const testDir = import.meta.dirname;
const srcDir = join(testDir, "../src");
const buildDir = join(testDir, "build");
const bin = join(buildDir, "test_win_time.exe");
const configuredCompiler = process.env["SCRIPTC_CC"];
const compiler = configuredCompiler === "zigcc" ? "zig" : configuredCompiler ?? "clang";
const compilerArgs = configuredCompiler === "zigcc" ? ["cc"] : [];
await mkdir(buildDir, { recursive: true });
await execFileAsync(compiler, [
...compilerArgs,
"-std=c11", "-O2", "-Wall", "-Wextra",
"-I", srcDir,
"-o", bin,
join(testDir, "test_win_time.c"),
join(srcDir, "scr_win.c"),
"-ladvapi32",
]);
const { stdout, stderr } = await execFileAsync(bin);
expect(stdout).toBe("win32 time shims ok\r\n");
expect(stderr).toBe("");
});
Loading