mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
188 lines
7.2 KiB
C
188 lines
7.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. The sweep then returns the freed pages to the
|
|
* system (scr_heap_trim), so code generation does not grow on top of the
|
|
* frontend's heap. */
|
|
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; }
|
|
int32_t scriptc_native_exited(void) { 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 <signal.h>
|
|
#include <sys/file.h>
|
|
#include <sys/wait.h>
|
|
|
|
#if defined(__linux__)
|
|
/* LLVM's profile runtime, linked into instrumented builds of the compiler
|
|
* (SCRIPTC_NATIVE_PROFILE_GENERATE); other builds leave these null. A worker
|
|
* starts with zeroed counters and writes its profile before _exit, which
|
|
* skips the runtime's atexit writer, so the profile counts the worker's work
|
|
* once. */
|
|
extern void __llvm_profile_reset_counters(void) __attribute__((weak));
|
|
extern int __llvm_profile_write_file(void) __attribute__((weak));
|
|
#endif
|
|
|
|
int32_t scriptc_native_fork(void) {
|
|
pid_t pid = fork();
|
|
#if defined(__linux__)
|
|
if (pid == 0 && __llvm_profile_reset_counters != NULL) __llvm_profile_reset_counters();
|
|
#endif
|
|
return (int32_t)pid;
|
|
}
|
|
|
|
void scriptc_native_exit(int32_t code) {
|
|
#if defined(__linux__)
|
|
if (__llvm_profile_write_file != NULL) (void)__llvm_profile_write_file();
|
|
#endif
|
|
_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;
|
|
}
|
|
|
|
/* The pid of a child that has ended, waiting for one if none has, or -1
|
|
* when waitid fails. The child is left to scriptc_native_wait: a child
|
|
* another part of the process waits for is not reaped here. */
|
|
int32_t scriptc_native_exited(void) {
|
|
siginfo_t info;
|
|
memset(&info, 0, sizeof info);
|
|
while (waitid(P_ALL, 0, &info, WEXITED | WNOWAIT) < 0) {
|
|
if (errno != EINTR) return -1;
|
|
}
|
|
return info.si_pid > 0 ? (int32_t)info.si_pid : -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);
|
|
}
|
|
|
|
/* The runtime's nursery trigger (scr_rc.h). */
|
|
void scr_cyc_set_nursery_threshold(long threshold);
|
|
|
|
/* Runs nursery passes every 1024 new candidates instead of every 256. Much
|
|
* of a build's cyclic garbage outlives 256 releases: in the shorter window
|
|
* it survives a nursery pass, is promoted, and only full passes over the
|
|
* whole live heap find it. A constructor sets it before the compiler's
|
|
* modules initialize, so every pass of a build uses it.
|
|
* SCR_CYCLE_THRESHOLD still overrides it. */
|
|
__attribute__((constructor)) static void scriptc_native_widen_cycle_nursery(void) {
|
|
scr_cyc_set_nursery_threshold(1024);
|
|
}
|