Files
scriptc/packages/compiler/native/host.c
T
Chris Tate 257d4dc5cb Declare flock on macOS in the native compiler host
host.c defines _POSIX_C_SOURCE, under which Darwin's <sys/file.h> hides
flock and LOCK_EX/LOCK_UN, so the native compiler build failed on macOS
with an implicit declaration error. Define _DARWIN_C_SOURCE on Apple
targets before the system headers. Linux preprocessing is unchanged.
2026-10-23 00:11:30 -05:00

138 lines
5.2 KiB
C

#ifndef _WIN32
#define _POSIX_C_SOURCE 200809L
#endif
/* flock and LOCK_EX/LOCK_UN, which Darwin hides under _POSIX_C_SOURCE. */
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
#define _DARWIN_C_SOURCE 1
#endif
#include <stdint.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
/* Accept only directories owned by this user with private POSIX access.
* Windows inherits the user's cache ACL; reject reparse points so an
* installed cache directory cannot redirect compiler payloads elsewhere. */
uint8_t scriptc_native_private_directory(const uint8_t *bytes, size_t size, uint8_t harden) {
if (size == 0 || memchr(bytes, 0, size) != NULL) return 0;
#ifdef _WIN32
(void)harden;
if (size > INT_MAX) return 0;
int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)bytes, (int)size, NULL, 0);
if (length <= 0) return 0;
wchar_t *path = malloc(((size_t)length + 1) * sizeof(wchar_t));
if (!path) return 0;
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)bytes, (int)size, path, length) != length) { free(path); return 0; }
path[length] = 0;
DWORD attrs = GetFileAttributesW(path);
free(path);
return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
#else
char *path = malloc(size + 1);
if (!path) return 0;
memcpy(path, bytes, size);
path[size] = 0;
struct stat info;
int ok = lstat(path, &info) == 0 && S_ISDIR(info.st_mode) && info.st_uid == getuid();
if (ok && harden && (info.st_mode & 077) != 0) ok = chmod(path, 0700) == 0;
if (ok) ok = lstat(path, &info) == 0 && S_ISDIR(info.st_mode) && info.st_uid == getuid() && (info.st_mode & 077) == 0;
free(path);
return ok;
#endif
}
/* The runtime's full sweep after a phase's data died (scr_rc.h). */
void scr_collect_cycles_phase(void);
/* Reclaims the cycles a finished compiler phase leaves behind. The
* frontend's syntax trees and program state refer to each other, so
* reference counting frees almost none of them when the frontend is
* disposed, and the collector's schedule alone may not run a full pass
* before the build ends. */
void scriptc_native_collect_cycles(void) {
scr_collect_cycles_phase();
}
/* Forked lowering workers (frontend/process-fork.ts). A worker shares the
* parent's heap copy-on-write, writes its results to a file and leaves with
* _exit, so no buffered output or exit handler of the parent runs twice.
* Checker requests from workers hold an exclusive flock on a file each
* worker opens after the fork, so every worker has its own open file
* description and the lock excludes the other workers. */
#ifdef _WIN32
int32_t scriptc_native_fork(void) { return -1; }
void scriptc_native_exit(int32_t code) { ExitProcess((UINT)code); }
int32_t scriptc_native_wait(int32_t pid) { (void)pid; return -1; }
uint8_t scriptc_native_lock_open(const uint8_t *bytes, size_t size) { (void)bytes; (void)size; return 0; }
void scriptc_native_lock(void) {}
void scriptc_native_unlock(void) {}
#else
#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/wait.h>
int32_t scriptc_native_fork(void) { return (int32_t)fork(); }
void scriptc_native_exit(int32_t code) { _exit(code); }
/* The worker's exit status, 128 plus the signal for a worker a signal
* ended, or -1 when waitpid fails. */
int32_t scriptc_native_wait(int32_t pid) {
int status = 0;
while (waitpid((pid_t)pid, &status, 0) < 0) {
if (errno != EINTR) return -1;
}
if (WIFEXITED(status)) return WEXITSTATUS(status);
if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
return -1;
}
static int scriptc_native_lock_fd = -1;
uint8_t scriptc_native_lock_open(const uint8_t *bytes, size_t size) {
if (size == 0 || memchr(bytes, 0, size) != NULL) return 0;
char *path = malloc(size + 1);
if (!path) return 0;
memcpy(path, bytes, size);
path[size] = 0;
if (scriptc_native_lock_fd >= 0) close(scriptc_native_lock_fd);
scriptc_native_lock_fd = open(path, O_CREAT | O_RDWR | O_CLOEXEC, 0600);
free(path);
return scriptc_native_lock_fd >= 0;
}
void scriptc_native_lock(void) {
if (scriptc_native_lock_fd < 0) return;
while (flock(scriptc_native_lock_fd, LOCK_EX) < 0 && errno == EINTR) {
}
}
void scriptc_native_unlock(void) {
if (scriptc_native_lock_fd >= 0) flock(scriptc_native_lock_fd, LOCK_UN);
}
#endif
/* The runtime's growth cap for full cycle passes (scr_rc.h). */
void scr_cyc_set_growth_cap(long cap);
/* Bounds the cyclic garbage a build can hold to twice its live heap.
* Lowering a large program leaves hundreds of megabytes of cyclic garbage,
* and whether a scheduled full pass reclaims it before the build's peak
* depends on where the growth rule fires: with the default cap of 16, small
* changes to the compiler moved the peak of one build by 390 MB. With a cap
* of 2 a full pass runs once the heap has grown to three times its size
* after the last one; a cap of 1 bounds the garbage further but runs more
* passes over the live heap. SCR_CYCLE_GROWTH_CAP still overrides it. */
void scriptc_native_bound_cycle_growth(void) {
scr_cyc_set_growth_cap(2);
}