Skip to content
Open
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
66 changes: 66 additions & 0 deletions .github/workflows/msvc-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: MSVC POSIX Shims

on:
push:
branches: [fix/msvc-posix-shims]
pull_request:
branches: [fix/msvc-posix-shims]

jobs:
msvc_compile:
name: verify MSVC compilation
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Compile runtime with MSVC clang
shell: pwsh
run: |
# Verify clang is MSVC-targeted (not mingw)
clang --version
# Compile a simple program — this is the exact scenario from #25
node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend c
- name: Compile Map + sort program (the bare.ts pattern)
shell: pwsh
run: |
$ts = @'
const m = new Map<number, number>();
m.set(1, 10);
m.set(2, 5);
m.set(3, 20);
const sorted = [...m.entries()].sort((a, b) => a[1] - b[1]);
console.log(sorted[0][0], sorted[0][1]);
'@
$ts | Out-File -Encoding utf8 test-msvc.ts
node packages/cli/dist/main.js run test-msvc.ts --backend c

linux_regression:
name: verify no Linux regressions
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
SCRIPTC_NO_CACHE: "1"
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Corpus smoke tests
run: >-
SCRIPTC_TEST_WORKERS=4 pnpm exec vitest run tests/harness/differential.test.ts
-t "001-hello|518-array-sort|520-map-basics|path"
18 changes: 17 additions & 1 deletion packages/runtime/src/scr_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@
#include "scr_runtime.h"

#include <ctype.h>
#include <dirent.h>
#ifndef _MSC_VER
#include <dirent.h> /* mingw-w64 ships one; MSVC: see scr_win.c shims */
#else
/* MSVC dirent shim — declared here, defined in scr_win.c. */
enum { DT_REG = 8, DT_DIR = 4 };
struct dirent { char d_name[260]; unsigned char d_type; };
typedef struct { void *_hFind; int _first; struct dirent _ent; } DIR;
DIR *opendir(const char *path);
struct dirent *readdir(DIR *d);
int closedir(DIR *d);
#endif
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
Expand All @@ -44,7 +54,13 @@
#include <direct.h> /* _mkdir */
#include <io.h> /* _isatty, _access, open/read/write/close */
#include <process.h> /* getpid */
#ifndef _MSC_VER
#include <unistd.h> /* mingw-w64 ships one: getcwd, access, isatty, ... */
#else
#define getcwd _getcwd
#define access _access
#define isatty _isatty
#endif
#include <winsock2.h> /* BEFORE windows.h (which pulls winsock 1 otherwise) */
#include <ws2tcpip.h> /* inet_ntop, sockaddr_in6 */
#include <iphlpapi.h> /* GetAdaptersAddresses (os.networkInterfaces) */
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/src/scr_path.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <direct.h> /* _getcwd */
#define getcwd _getcwd
#endif

/* ── a tiny growable byte buffer ─────────────────────────────────────── */

Expand Down
43 changes: 43 additions & 0 deletions packages/runtime/src/scr_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h> /* memcpy in the inline slot accessors */
#ifdef _MSC_VER
#include <BaseTsd.h> /* SSIZE_T on MSVC */
typedef SSIZE_T ssize_t;
#else
#include <sys/types.h> /* ssize_t in the transport ops table */
#endif

/* ── win32 libc shims (scr_win.c; see the windows portability inventory) ──
* The POSIX/BSD functions the runtime calls that mingw-w64's CRT does not
Expand All @@ -31,6 +36,44 @@ char *stpcpy(char *dst, const char *src);
void arc4random_buf(void *buf, size_t n);
struct tm *gmtime_r(const time_t *t, struct tm *out);
char *strcasestr(const char *hay, const char *needle);
#ifdef _MSC_VER
#include <stdlib.h> /* _MAX_PATH */
#include <sys/stat.h> /* _S_IFMT, _S_IFDIR, _S_IFREG */
#ifndef PATH_MAX
#define PATH_MAX _MAX_PATH
#endif
#ifndef F_OK
#define F_OK 0
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
#endif
#ifndef S_ISLNK
#define S_ISLNK(m) (0)
#endif
#ifndef S_ISFIFO
#define S_ISFIFO(m) (0)
#endif
#ifndef S_ISSOCK
#define S_ISSOCK(m) (0)
#endif
#ifndef S_ISBLK
#define S_ISBLK(m) (0)
#endif
#ifndef S_ISCHR
#define S_ISCHR(m) (0)
#endif
#ifndef _mode_t_defined
typedef unsigned int mode_t;
#endif
#define CLOCK_REALTIME 0
#define CLOCK_MONOTONIC 1
int clock_gettime(int clk_id, struct timespec *ts);
int nanosleep(const struct timespec *req, struct timespec *rem);
#endif /* _MSC_VER */
#endif

/* ── process ──────────────────────────────────────────────────────────── */
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime/src/scr_url.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif

ScrUrl *scr_url_retain(ScrUrl *u) {
if (u->rc != SIZE_MAX) u->rc++;
Expand Down
110 changes: 110 additions & 0 deletions packages/runtime/src/scr_win.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,116 @@
#include <time.h>
#include <windows.h>

/* ── MSVC POSIX shims ────────────────────────────────────────────────
* mingw-w64 provides POSIX headers/functions (unistd.h, dirent.h,
* clock_gettime, nanosleep). MSVC's CRT does not — these shims
* bridge the gap so the runtime compiles under both toolchains. */
#ifdef _MSC_VER

#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#endif
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 1
#endif

int clock_gettime(int clk_id, struct timespec *ts) {
(void)clk_id;
/* QueryPerformanceCounter is the only high-res monotonic clock on
* Windows; its epoch is arbitrary but monotonic — sufficient for
* elapsed-time measurements. For CLOCK_REALTIME we use
* GetSystemTimeAsFileTime which is UTC since 1601. */
if (clk_id == CLOCK_REALTIME) {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER li;
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
/* FILETIME is 100-ns intervals since 1601-01-01.
* Unix epoch offset: 11644473600 seconds = 116444736000000000 * 100ns. */
li.QuadPart -= 116444736000000000ULL;
ts->tv_sec = (time_t)(li.QuadPart / 10000000ULL);
ts->tv_nsec = (long)((li.QuadPart % 10000000ULL) * 100);
return 0;
}
/* CLOCK_MONOTONIC — QueryPerformanceCounter. */
static LARGE_INTEGER freq = {0};
if (freq.QuadPart == 0) QueryPerformanceFrequency(&freq);
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
ts->tv_sec = (time_t)(now.QuadPart / freq.QuadPart);
ts->tv_nsec = (long)((now.QuadPart % freq.QuadPart) * 1000000000LL / freq.QuadPart);
return 0;
}

int nanosleep(const struct timespec *req, struct timespec *rem) {
if (rem) { rem->tv_sec = 0; rem->tv_nsec = 0; }
/* Sleep takes milliseconds; ceil to avoid sleeping too short. */
DWORD ms = (DWORD)(req->tv_sec * 1000 + (req->tv_nsec + 999999) / 1000000);
if (ms == 0) ms = 1; /* Sleep(0) yields the timeslice */
Sleep(ms);
return 0;
}

/* Minimal <dirent.h> shim for MSVC — provides opendir/readdir/closedir
* and the d_type constants over FindFirstFileW/FindNextFileW. Enough
* for scr_lib.c's readdir loops; not a full POSIX emulation. */
#include <wchar.h>

struct dirent {
char d_name[260];
unsigned char d_type;
};

enum { DT_REG = 8, DT_DIR = 4 };

typedef struct {
HANDLE hFind;
WIN32_FIND_DATAW fdata;
struct dirent entry;
int first;
} DIR;

DIR *opendir(const char *path) {
DIR *d = (DIR *)malloc(sizeof *d);
if (!d) return NULL;
/* Build wildcard path: "path\*" */
wchar_t wpath[MAX_PATH * 2];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
wcscat(wpath, L"\\*");
d->hFind = FindFirstFileW(wpath, &d->fdata);
d->first = 1;
if (d->hFind == INVALID_HANDLE_VALUE) { free(d); return NULL; }
return d;
}

struct dirent *readdir(DIR *d) {
for (;;) {
if (d->first) { d->first = 0; }
else if (!FindNextFileW(d->hFind, &d->fdata)) { return NULL; }
/* Skip . and .. */
if (d->fdata.cFileName[0] == L'.' &&
(d->fdata.cFileName[1] == L'\0' ||
(d->fdata.cFileName[1] == L'.' && d->fdata.cFileName[2] == L'\0')))
continue;
WideCharToMultiByte(CP_UTF8, 0, d->fdata.cFileName, -1,
Comment thread
swadhinbiswas marked this conversation as resolved.
d->entry.d_name, sizeof d->entry.d_name, NULL, NULL);
d->entry.d_name[sizeof d->entry.d_name - 1] = '\0';
d->entry.d_type = (d->fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
? DT_DIR : DT_REG;
return &d->entry;
}
}

int closedir(DIR *d) {
if (!d) return -1;
if (d->hFind != INVALID_HANDLE_VALUE) FindClose(d->hFind);
free(d);
return 0;
}

#endif /* _MSC_VER */

/* 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
Loading