Expand LLVM native output targets (#280)

* Expand LLVM native output targets

- Add owned helper and runtime-pack contracts for macOS x64, Linux glibc/musl, Windows x64, and WASI.
- Generalize LLVM target selection, package validation, linker plans, and release/CI matrices.
- Document linker boundaries and add focused native-output and runtime-pack coverage.

* Pin Windows LLVM helper CI to VS 2022

The official LLVM archive needs the Visual Studio 2022 CMake generator, which is not guaranteed by windows-latest.

* Align bootstrap cache with LLVM runtime-pack builds

Use the same target-specific helper and linker identity before and after compiler loading so routed executable cache hits remain lightweight.

* Install Windows LLVM CI tools with Chocolatey

windows-2022 includes Visual Studio but not winget; use its available Chocolatey bootstrap for CMake and Ninja.

* Prevent Zig version probes from leaving runtime-pack objects

Run runtime-pack compiler version probes in a private temporary directory and remove the accidental tracked WASI a.o file.

* Use 7-Zip for Windows LLVM setup

Extract the official LLVM development archive with two-stage 7-Zip instead of Windows tar.exe, which timed out while materializing the toolchain tree.

* Define ssize_t for the MSVC runtime pack

Clang's MSVC target does not expose POSIX ssize_t through sys/types.h; define the pointer-sized runtime type without affecting MinGW.

* Build the Windows runtime pack with Zig

Use the MinGW-compatible Windows sysroot required by the runtime while retaining COFF helper output and installing Zig in the Windows native CI lane.

* Give macOS LLVM differential shard time to finish

Shard 1 passed its setup and focused contracts but was cancelled during its cold LLVM differential slice at the 20-minute job limit.
This commit is contained in:
Chris Tate
2026-09-01 13:41:34 -05:00
committed by GitHub
parent 1071b87d77
commit c41f1a8be9
102 changed files with 1909 additions and 224 deletions
+144 -2
View File
@@ -111,10 +111,61 @@ jobs:
pnpm test packages/compiler/test/cc-driver.test.ts
--testNamePattern "host-native clang static build"
llvm_artifacts_linux_x64:
name: test (Linux x64 LLVM artifacts, no clang)
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- name: Install pinned LLVM development distribution
run: |
sudo apt-get update
sudo apt-get install --yes libzstd-dev
curl -fL --retry 3 -o "$RUNNER_TEMP/llvm.tar.xz" https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/LLVM-22.1.8-Linux-X64.tar.xz
tar -xJf "$RUNNER_TEMP/llvm.tar.xz" -C "$RUNNER_TEMP"
mv "$RUNNER_TEMP/LLVM-22.1.8-Linux-X64" "$RUNNER_TEMP/llvm-22.1.8"
echo "LLVM_DIR=$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH"
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @scriptc/llvm-linux-x64-gnu build:native
- run: CC=clang AR=llvm-ar pnpm --filter @scriptc/runtime-linux-x64-gnu build:native
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- run: pnpm --filter @scriptc/runtime-wasm32-wasi build:native
- run: pnpm build
- name: Linux helper object format and no-clang output contract
run: |
PATH="$RUNNER_TEMP/traps:$PATH"
mkdir -p "$RUNNER_TEMP/traps"
for tool in clang cc gcc ar ld; do printf '#!/bin/sh\nexit 97\n' > "$RUNNER_TEMP/traps/$tool"; chmod +x "$RUNNER_TEMP/traps/$tool"; done
node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$RUNNER_TEMP/hello.o"
file "$RUNNER_TEMP/hello.o" | grep 'ELF 64-bit LSB relocatable, x86-64'
- name: Linux helper/runtime-pack executable differential smoke
env:
SCRIPTC_NO_CACHE: "1"
SCRIPTC_LEGACY_C_PIPELINE: "0"
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o "$RUNNER_TEMP/hello"
test "$("$RUNNER_TEMP/hello")" = 'hello world'
- name: WASI helper object/runtime-pack smoke
run: pnpm test packages/cli/test/wasi-runtime-pack.test.ts
llvm_artifacts_macos_arm64:
name: test (macOS arm64 LLVM artifacts, no clang, ${{ matrix.shard }}/3)
runs-on: macos-15
timeout-minutes: 20
# Shard 1 additionally runs package/install and link-parity contracts
# before its LLVM differential slice. The slice itself can take ~10
# minutes on a cold hosted runner, so 20 minutes cancels an otherwise
# healthy job before the differential completes.
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
@@ -191,6 +242,38 @@ jobs:
"$RUNNER_TEMP/installed-link.json" "$RUNNER_TEMP/installed-program"
test "$("$RUNNER_TEMP/installed-program")" = 'hello world'
llvm_artifacts_macos_x64:
name: test (macOS x64 LLVM artifacts, no clang)
runs-on: macos-15-intel
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- run: brew install llvm@22
- run: pnpm install --frozen-lockfile
- run: LLVM_DIR=/usr/local/opt/llvm@22/lib/cmake/llvm pnpm --filter @scriptc/llvm-darwin-x64 build:native
- run: pnpm --filter @scriptc/runtime-darwin-x64 build:native
- run: pnpm build
- name: Intel Mach-O no-clang object contract
run: |
mkdir -p "$RUNNER_TEMP/traps"
for tool in clang cc gcc ar ld; do printf '#!/bin/sh\nexit 97\n' > "$RUNNER_TEMP/traps/$tool"; chmod +x "$RUNNER_TEMP/traps/$tool"; done
PATH="$RUNNER_TEMP/traps:$PATH" node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$RUNNER_TEMP/hello.o"
file "$RUNNER_TEMP/hello.o" | grep 'Mach-O 64-bit object x86_64'
- name: Intel helper/runtime-pack executable smoke
env:
SCRIPTC_NO_CACHE: "1"
SCRIPTC_LEGACY_C_PIPELINE: "0"
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts -o "$RUNNER_TEMP/hello"
test "$($RUNNER_TEMP/hello)" = 'hello world'
# Exercises the supported Windows GNU target and the built CLI end to end:
# TS7 must open its synthetic project, ambient files must resolve across
# slash styles, and the default executable must use the .exe suffix.
@@ -262,11 +345,67 @@ jobs:
pnpm exec vitest run packages/compiler/test/cc-driver.test.ts
--testNamePattern "Windows CA roots honor"
llvm_artifacts_windows_x64:
name: test (Windows x64 MSVC LLVM artifacts)
# The official LLVM 22 Windows archives are MSVC-built and the helper
# selects CMake's VS 2022 generator. `windows-latest` can move to an
# image without that generator, so retain the hosted VS 2022 contract.
runs-on: windows-2022
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: pnpm
- name: Install pinned LLVM development distribution
shell: pwsh
run: |
# windows-2022 already provides CMake, Ninja, VS 2022, and 7-Zip.
# Use the complete archive because it supplies LLVM's exported CMake
# targets and static libraries. Windows tar.exe takes 25+ minutes to
# materialize it; two-stage 7-Zip extraction takes seconds.
$archive = "$env:RUNNER_TEMP\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar.xz"
$llvm = "$env:RUNNER_TEMP\llvm-22.1.8"
$expanded = "$env:RUNNER_TEMP\llvm-expanded"
$sevenZip = "$env:ProgramFiles\7-Zip\7z.exe"
if (-not (Test-Path $sevenZip)) {
choco install 7zip --no-progress -y
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
if (-not (Test-Path $sevenZip)) { throw "7-Zip is unavailable" }
curl.exe -fL --retry 3 -o $archive https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/clang%2Bllvm-22.1.8-x86_64-pc-windows-msvc.tar.xz
& $sevenZip x -y "-o$expanded" $archive
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& $sevenZip x -y "-o$expanded" "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm
echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV
echo "$llvm\bin" >> $env:GITHUB_PATH
- run: pnpm install --frozen-lockfile
- name: Build Windows helper
run: pnpm --filter @scriptc/llvm-win32-x64-msvc build:native
- uses: vercel-labs/setup-zig@v1
with:
version: 0.16.0
- name: Build Windows runtime pack
run: pnpm --filter @scriptc/runtime-win32-x64-msvc build:native
- run: pnpm build
- name: Windows COFF no-clang object contract
shell: pwsh
run: |
node packages/cli/dist/main.js build tests/corpus/001-hello.ts --emit=obj -o "$env:RUNNER_TEMP\hello.obj"
$bytes = [System.IO.File]::ReadAllBytes("$env:RUNNER_TEMP\hello.obj")
if ($bytes[0] -ne 0x64 -or $bytes[1] -ne 0x86) { throw "expected AMD64 COFF object" }
# Aggregate gate with the pre-matrix job's name, so anything keyed on the
# single "test" check (branch protection, badges) keeps resolving. Fails
# unless every matrix shard and both platform integration jobs succeeded.
test:
needs: [tests, linux_host_clang, llvm_artifacts_macos_arm64, windows_cli]
needs: [tests, linux_host_clang, llvm_artifacts_linux_x64, llvm_artifacts_macos_arm64, llvm_artifacts_macos_x64, windows_cli, llvm_artifacts_windows_x64]
if: always()
runs-on: ubuntu-latest
steps:
@@ -274,5 +413,8 @@ jobs:
run: |
test "${{ needs.tests.result }}" = success
test "${{ needs.linux_host_clang.result }}" = success
test "${{ needs.llvm_artifacts_linux_x64.result }}" = success
test "${{ needs.llvm_artifacts_macos_arm64.result }}" = success
test "${{ needs.llvm_artifacts_macos_x64.result }}" = success
test "${{ needs.windows_cli.result }}" = success
test "${{ needs.llvm_artifacts_windows_x64.result }}" = success
+147 -10
View File
@@ -49,9 +49,133 @@ jobs:
fi
echo "version=$LOCAL_VERSION" >> "$GITHUB_OUTPUT"
build-native-packages:
name: Build native package (${{ matrix.platform }})
needs: check-release
if: needs.check-release.outputs.should_release == 'true'
strategy:
fail-fast: false
matrix:
include:
- platform: darwin-arm64
runner: macos-15
helper: llvm-darwin-arm64
runtime: runtime-darwin-arm64
- platform: darwin-x64
runner: macos-15-intel
helper: llvm-darwin-x64
runtime: runtime-darwin-x64
- platform: linux-x64
runner: ubuntu-24.04
helper: llvm-linux-x64-gnu
runtime: runtime-linux-x64-gnu
llvm_asset: LLVM-22.1.8-Linux-X64
- platform: linux-arm64
runner: ubuntu-24.04-arm
helper: llvm-linux-arm64-gnu
runtime: runtime-linux-arm64-gnu
llvm_asset: LLVM-22.1.8-Linux-ARM64
- platform: windows-x64
runner: windows-2022
helper: llvm-win32-x64-msvc
runtime: runtime-win32-x64-msvc
use_zig: true
- platform: linux-x64-musl
runner: ubuntu-24.04
helper: llvm-linux-x64-musl
runtime: runtime-linux-x64-musl
llvm_asset: LLVM-22.1.8-Linux-X64
use_zig: true
- platform: linux-arm64-musl
runner: ubuntu-24.04-arm
helper: llvm-linux-arm64-musl
runtime: runtime-linux-arm64-musl
llvm_asset: LLVM-22.1.8-Linux-ARM64
use_zig: true
- platform: wasm32-wasi
runner: ubuntu-24.04
helper: llvm-linux-x64-gnu
runtime: runtime-wasm32-wasi
llvm_asset: LLVM-22.1.8-Linux-X64
use_zig: true
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 11.1.3
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: pnpm
- name: Install toolchain (macOS)
if: startsWith(matrix.platform, 'darwin')
run: brew install llvm@22
- name: Install pinned LLVM development distribution (Linux)
if: startsWith(matrix.platform, 'linux') || matrix.platform == 'wasm32-wasi'
run: |
sudo apt-get update
sudo apt-get install --yes libzstd-dev
curl -fL --retry 3 -o "$RUNNER_TEMP/llvm.tar.xz" "https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/${{ matrix.llvm_asset }}.tar.xz"
tar -xJf "$RUNNER_TEMP/llvm.tar.xz" -C "$RUNNER_TEMP"
mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8"
echo "LLVM_DIR=$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH"
- uses: vercel-labs/setup-zig@v1
if: matrix.use_zig == true
with:
version: 0.16.0
- name: Install pinned LLVM development distribution (Windows)
if: matrix.platform == 'windows-x64'
shell: pwsh
run: |
# The complete archive supplies LLVM's exported CMake targets and
# static libraries. windows-2022 includes CMake, Ninja, VS 2022,
# and 7-Zip; unlike tar.exe, 7-Zip expands this archive quickly.
$archive = "$env:RUNNER_TEMP\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar.xz"
$llvm = "$env:RUNNER_TEMP\llvm-22.1.8"
$expanded = "$env:RUNNER_TEMP\llvm-expanded"
$sevenZip = "$env:ProgramFiles\7-Zip\7z.exe"
if (-not (Test-Path $sevenZip)) {
choco install 7zip --no-progress -y
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
if (-not (Test-Path $sevenZip)) { throw "7-Zip is unavailable" }
curl.exe -fL --retry 3 -o $archive https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8/clang%2Bllvm-22.1.8-x86_64-pc-windows-msvc.tar.xz
& $sevenZip x -y "-o$expanded" $archive
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& $sevenZip x -y "-o$expanded" "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc.tar"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm
echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV
echo "$llvm\bin" >> $env:GITHUB_PATH
- run: pnpm install --frozen-lockfile
- name: Build native packages (POSIX)
if: matrix.platform != 'windows-x64'
run: |
pnpm --filter @scriptc/${{ matrix.helper }} build:native
if [ "${{ matrix.use_zig }}" = true ]; then
CC=zig AR=zig pnpm --filter @scriptc/${{ matrix.runtime }} build:native
else
CC=clang AR=llvm-ar pnpm --filter @scriptc/${{ matrix.runtime }} build:native
fi
- name: Build native packages (Windows)
if: matrix.platform == 'windows-x64'
run: |
pnpm --filter @scriptc/${{ matrix.helper }} build:native
pnpm --filter @scriptc/${{ matrix.runtime }} build:native
- uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.platform }}
path: |
packages/${{ matrix.helper }}
packages/${{ matrix.runtime }}
retention-days: 1
publish:
name: Publish to npm
needs: check-release
needs: [check-release, build-native-packages]
if: needs.check-release.outputs.should_release == 'true'
runs-on: macos-15
timeout-minutes: 15
@@ -63,6 +187,13 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download native packages
uses: actions/download-artifact@v4
with:
pattern: native-*
path: native-artifacts
merge-multiple: true
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
@@ -74,14 +205,10 @@ jobs:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Install pinned LLVM
run: brew install llvm@22
# Publishing uses npm trusted publishing (OIDC): the job's id-token
# permission lets npm mint short-lived credentials, so no npm token
# secret exists anywhere in this repo. All five packages —
# @scriptc/runtime, @scriptc/runtime-darwin-arm64,
# @scriptc/llvm-darwin-arm64, @scriptc/compiler, and scriptc — must each be
# secret exists anywhere in this repo. The runtime, every shipped
# platform runtime/helper package, compiler, and CLI must each be
# configured on npmjs.com with a GitHub Actions trusted publisher
# pointing at repository vercel-labs/scriptc, workflow release.yml,
# environment Release. A package missing that configuration fails
@@ -91,14 +218,13 @@ jobs:
- name: Install and build
run: |
pnpm install --frozen-lockfile
pnpm --filter @scriptc/llvm-darwin-arm64 build:native
pnpm --filter @scriptc/runtime-darwin-arm64 build:native
cp -R native-artifacts/. packages/
pnpm -r build
- name: Check version sync
run: |
VERSION="${{ needs.check-release.outputs.version }}"
for pkg in packages/runtime packages/runtime-darwin-arm64 packages/llvm-darwin-arm64 packages/compiler packages/cli; do
for pkg in packages/runtime packages/runtime-darwin-arm64 packages/llvm-darwin-arm64 packages/runtime-linux-x64-gnu packages/llvm-linux-x64-gnu packages/runtime-linux-arm64-gnu packages/llvm-linux-arm64-gnu packages/runtime-linux-x64-musl packages/llvm-linux-x64-musl packages/runtime-linux-arm64-musl packages/llvm-linux-arm64-musl packages/runtime-wasm32-wasi packages/runtime-win32-x64-msvc packages/llvm-win32-x64-msvc packages/compiler packages/cli; do
V=$(node -p "require('./$pkg/package.json').version")
if [ "$V" != "$VERSION" ]; then
echo "Version mismatch: $pkg is $V, expected $VERSION"
@@ -152,6 +278,17 @@ jobs:
publish_dir packages/runtime
publish_dir packages/runtime-darwin-arm64
publish_dir packages/llvm-darwin-arm64 "$HELPER_TARBALL"
publish_dir packages/runtime-linux-x64-gnu
publish_dir packages/llvm-linux-x64-gnu
publish_dir packages/runtime-linux-arm64-gnu
publish_dir packages/llvm-linux-arm64-gnu
publish_dir packages/runtime-linux-x64-musl
publish_dir packages/llvm-linux-x64-musl
publish_dir packages/runtime-linux-arm64-musl
publish_dir packages/llvm-linux-arm64-musl
publish_dir packages/runtime-wasm32-wasi
publish_dir packages/runtime-win32-x64-msvc
publish_dir packages/llvm-win32-x64-msvc
publish_dir packages/compiler
publish_dir packages/cli
env:
+4
View File
@@ -18,6 +18,10 @@ dist/
/packages/runtime-darwin-arm64/artifacts/
/packages/runtime-darwin-arm64/runtime-pack.json
/packages/runtime-darwin-arm64/.runtime-pack-*
/packages/llvm-*/bin/
/packages/runtime-*/artifacts/
/packages/runtime-*/runtime-pack.json
/packages/runtime-*/.runtime-pack-*
!tests/fixtures/fetch/node_modules/eventsource-parser/dist/
!tests/fixtures/npm/node_modules/*/dist/
!tests/fixtures/npm/workspace/*/dist/
+5 -5
View File
@@ -9,12 +9,12 @@ pnpm install && pnpm -r build # build the workspace
pnpm test:sandbox # full gate: ~4m custom image, ~9m cold managed fallback
```
The ordinary workspace build does not rebuild packaged macOS native artifacts.
The ordinary workspace build does not rebuild packaged native artifacts.
When changing native assembly/object emission or runtime-pack selection,
install CMake, Ninja, and Homebrew `llvm@22`, then run
`pnpm --filter @scriptc/llvm-darwin-arm64 build:native` and
`pnpm --filter @scriptc/runtime-darwin-arm64 build:native` explicitly. The
macOS full test suite also needs those generated artifacts.
install CMake, Ninja, and the pinned LLVM 22 development package, then run the
matching `@scriptc/llvm-<platform>` and `@scriptc/runtime-<platform>`
`build:native` scripts explicitly. The macOS full test suite also needs its
generated artifacts.
Use focused local tests while iterating, then use `pnpm test:sandbox` whenever a
full validation gate is required. It loads Sandbox configuration from the
+6 -7
View File
@@ -8,7 +8,7 @@ scriptc is experimental and targets macOS, Linux, Windows, and WebAssembly via W
## Installation
The compiler requires Node.js 24 or newer. `--emit=ir|c|llvm` needs only Node. On macOS 15+ arm64, `--emit=asm|obj` additionally uses the optional platform helper installed with scriptc, but needs no compiler, archiver, linker, or SDK. Ordinary LLVM-tier executable builds need a platform linker driver and SDK, but use the bundled helper plus precompiled runtime pack rather than compiling generated or runtime C. Set `SCRIPTC_LINKER` to choose that driver. Explicit C builds, LLVM fallbacks, `--sanitize`, and the deprecated `SCRIPTC_CC=clang|zigcc` compatibility route additionally need a C compiler. The executables it produces do not require Node.
The compiler requires Node.js 24 or newer. `--emit=ir|c|llvm` needs only Node. `--emit=asm|obj` uses the matching optional platform helper installed with scriptc on supported macOS, Linux, and Windows hosts (and for WASI), but needs no compiler, archiver, linker, or SDK. Ordinary LLVM-tier executable builds need a platform linker driver and SDK/sysroot, but use the bundled helper plus precompiled runtime pack rather than compiling generated or runtime C. Set `SCRIPTC_LINKER` to choose that driver. Explicit C builds, LLVM fallbacks, `--sanitize`, and the deprecated `SCRIPTC_CC=clang|zigcc` compatibility route additionally need a C compiler. The executables it produces do not require Node.
```console
$ npm install -g scriptc
@@ -153,12 +153,11 @@ $ vercel link && vercel env pull # writes a project-scoped VERCEL_OIDC_TOKEN
$ pnpm test:sandbox
```
The normal workspace build needs no local LLVM installation. To rebuild the
optional macOS arm64 native artifacts, install CMake, Ninja, and Homebrew
`llvm@22`, then run
`pnpm --filter @scriptc/llvm-darwin-arm64 build:native` and
`pnpm --filter @scriptc/runtime-darwin-arm64 build:native`. The macOS full test
suite also uses those generated artifacts.
The normal workspace build needs no local LLVM installation. To rebuild a
native helper/runtime pack, install CMake, Ninja, and the pinned LLVM 22
development package on that target host, then run the matching
`@scriptc/llvm-<platform>` and `@scriptc/runtime-<platform>` `build:native`
scripts. The macOS full test suite also uses those generated artifacts.
`pnpm test:sandbox` loads `.env.local`, preflights Vercel authentication and
project access, and uses the managed `vercel/sandbox/universal` image by
+6 -6
View File
@@ -1,21 +1,21 @@
# Releasing
Releases are manual, single-commit affairs. The maintainer controls the changelog voice and format. The five npm packages — `@scriptc/runtime`, `@scriptc/runtime-darwin-arm64`, `@scriptc/llvm-darwin-arm64`, `@scriptc/compiler`, and `scriptc` — always publish together at the same version.
Releases are manual, single-commit affairs. The maintainer controls the changelog voice and format. `@scriptc/runtime`, every platform helper/runtime pack, `@scriptc/compiler`, and `scriptc` always publish together at the same version.
To prepare a release:
1. Bump the version in `packages/cli/package.json`
2. Run `node scripts/sync-versions.mjs` to stamp the same version into `packages/runtime`, `packages/runtime-darwin-arm64`, `packages/llvm-darwin-arm64`, and `packages/compiler`, then `pnpm manifest` to restamp `packages/compiler/surface-manifest.json` with the new version, and commit both (the test suite's staleness guard fails on a version drift)
2. Run `node scripts/sync-versions.mjs` to stamp the same version into the runtime, helper, and compiler packages, then `pnpm manifest` to restamp `packages/compiler/surface-manifest.json` with the new version, and commit both (the test suite's staleness guard fails on a version drift)
3. Fold the `## Unreleased` section of `CHANGELOG.md` into a new `## <version>` entry (newest first, below `## Unreleased`), and leave `## Unreleased` empty for the next cycle
4. Wrap the new entry in `<!-- release:start -->` and `<!-- release:end -->` markers; this marked block is also the GitHub release body
5. Remove the `<!-- release:start -->` and `<!-- release:end -->` markers from the previous release entry; only the latest release should have markers
6. With Zig on `PATH`, run `SCRIPTC_CROSS=1 pnpm exec vitest run tests/harness/library-cross.test.ts` and require the cross-target library conformance lane to pass
7. Commit to `main`
CI (`.github/workflows/release.yml`) compares the version in `packages/cli/package.json` to what `scriptc` has on npm. If it differs, it builds the workspace, verifies all five package versions match (a mismatch fails with a hint to run `scripts/sync-versions.mjs`), and publishes to npm in dependency order — `@scriptc/runtime`, `@scriptc/runtime-darwin-arm64`, `@scriptc/llvm-darwin-arm64`, `@scriptc/compiler`, then `scriptc` — so each package's dependencies are resolvable the moment it lands. After the publish succeeds, a separate job creates the git tag `v<version>` and the GitHub release with the marked changelog entry as its body, and attaches `surface-manifest.json` — the machine-readable listing of the surface the static tier compiles at that version (stable per-entry ids, so two releases diff mechanically; see `packages/compiler/src/coverage/surface-manifest.ts` for the schema). The job regenerates the manifest from the tree and fails on any byte difference from the committed file before attaching, so the asset is always the manifest of the code being released. The same file ships inside the `@scriptc/compiler` package as `@scriptc/compiler/surface-manifest.json`.
CI (`.github/workflows/release.yml`) compares the version in `packages/cli/package.json` to what `scriptc` has on npm. If it differs, it builds platform packages on matching macOS, Linux, and Windows runners, verifies the version spine, and publishes the runtime, helper, compiler, and CLI packages in dependency order. After the publish succeeds, a separate job creates the git tag `v<version>` and the GitHub release with the marked changelog entry as its body, and attaches `surface-manifest.json` — the machine-readable listing of the surface the static tier compiles at that version (stable per-entry ids, so two releases diff mechanically; see `packages/compiler/src/coverage/surface-manifest.ts` for the schema). The job regenerates the manifest from the tree and fails on any byte difference from the committed file before attaching, so the asset is always the manifest of the code being released. The same file ships inside the `@scriptc/compiler` package as `@scriptc/compiler/surface-manifest.json`.
The release job runs on macOS arm64, builds and strips the pinned LLVM helper,
then builds the matching precompiled runtime pack before publishing both
The release job builds and strips each pinned LLVM helper on its matching host,
then builds the matching precompiled runtime pack before publishing the
constrained platform packages ahead of `@scriptc/compiler`. Ordinary LLVM-tier
executables use the helper for the program object and the platform pack for
runtime objects; the user's toolchain performs only the final platform link.
@@ -25,7 +25,7 @@ platform pack is available. The GitHub release remains a tag, release notes, and
manifest asset; the npm publish never waits on the GitHub release.
Publishing uses npm trusted publishing (OIDC) — there is no npm token secret.
Each of the five packages must have a GitHub Actions trusted publisher for
Each published package must have a GitHub Actions trusted publisher for
`release.yml` and the `Release` environment. A missing configuration fails
before upload. Re-runs skip package versions already present on npm, so a
partially published release can be resumed safely.
+3 -3
View File
@@ -90,7 +90,7 @@ Prebuilds release runtime objects and native TLS/dynamic-engine archives for tar
<dd>Primary artifact path. An explicit path is exact. Defaults are <code>.scriptc/&lt;name&gt;.ir.json</code>, <code>.c</code>, <code>.ll</code>, <code>.s</code>, <code>.o</code>, or the platform executable name.</dd>
<dt><code>--emit &lt;ir|c|llvm|asm|obj|exe&gt;</code></dt>
<dd>Select the invocation's one primary artifact. <code>ir</code>, <code>c</code>, and <code>llvm</code> need only Node. <code>asm</code> and <code>obj</code> use the bundled LLVM helper on macOS 15+ arm64 and emit artifacts targeting macOS 14.0. <code>exe</code> is the default.</dd>
<dd>Select the invocation's one primary artifact. <code>ir</code>, <code>c</code>, and <code>llvm</code> need only Node. <code>asm</code> and <code>obj</code> use a matching bundled LLVM helper on supported macOS, Linux, and Windows hosts and for WASI; they need no installed C compiler. <code>exe</code> is the default.</dd>
<dt><code>--print &lt;native-link-info&gt;</code></dt>
<dd>Build an object (equivalent to <code>--emit=obj</code>) and print its machine-readable external link recipe as JSON instead of printing the artifact path. The document names the exact installed source runtime pack and all link inputs, but does not invoke a linker.</dd>
@@ -102,7 +102,7 @@ Prebuilds release runtime objects and native TLS/dynamic-engine archives for tar
<dd>Bind signature-only TypeScript declarations to native C ABI symbols and link the manifest's archive, object, and system-library inputs. See <a href="/ffi">Native FFI</a>.</dd>
<dt><code>--backend &lt;c|llvm&gt;</code></dt>
<dd>Code generator: <code>llvm</code> (default) or <code>c</code> (the readable debugging backend). On macOS arm64, LLVM-tier executable code generation uses the bundled helper and precompiled runtime pack before the platform link. Unset, a native build can fall back to C when the program is outside the LLVM tier. The production <code>wasm32-wasi</code> target never falls back: a missing LLVM lowering is <code>SC3001</code>.</dd>
<dd>Code generator: <code>llvm</code> (default) or <code>c</code> (the readable debugging backend). On supported targets, LLVM-tier executable code generation uses the bundled helper and precompiled runtime pack before the platform link. Unset, a native build can fall back to C when the program is outside the LLVM tier. The production <code>wasm32-wasi</code> target never falls back: a missing LLVM lowering is <code>SC3001</code>.</dd>
<dt><code>--npm-static &lt;pkg[,pkg…]|auto&gt;</code></dt>
<dd>EXPERIMENTAL. Compile the named npm packages' shipped JS statically as program modules instead of embedding them for the engine (repeatable; <code>auto</code> opts in every eligible direct import). A package the preflight refuses falls back to the island with a coverage-report note. See <a href="/dependencies">npm Dependencies</a> for maturity notes.</dd>
@@ -159,7 +159,7 @@ fib-linux: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically
## Backends
The default backend emits textual LLVM IR. On macOS arm64 it is lowered to an object by the bundled helper and linked with the precompiled runtime pack; <code>SCRIPTC_LINKER</code> controls the platform linker driver, separately from C compilation. Other executable targets retain their existing toolchain path. A program outside the LLVM tier is never miscompiled—the native build falls back to the C backend transparently and says so in one stderr line. The production <code>wasm32-wasi</code> target uses LLVM's 32-bit ABI path and never falls back; an LLVM coverage gap is a build diagnostic. Dynamic npm embedding is LLVM surface on every target.
The default backend emits textual LLVM IR. On supported targets it is lowered to an object by the bundled helper and linked with the matching precompiled runtime pack; <code>SCRIPTC_LINKER</code> controls the platform linker driver, separately from C compilation. A program outside the LLVM tier is never miscompiled—the native build falls back to the C backend transparently and says so in one stderr line. The production <code>wasm32-wasi</code> target uses LLVM's 32-bit ABI path and never falls back; an LLVM coverage gap is a build diagnostic. Dynamic npm embedding is LLVM surface on every target.
The C backend is a debugging aid: deliberately readable, source-line-annotated output with differential tests against LLVM wherever the two overlap. Pin it when you want to inspect what your program became:
+2 -2
View File
@@ -11,8 +11,8 @@ TypeScript ──tsc: parse + typecheck──▶ lowering ──▶ typed IR ─
1. **Frontend** — the real TypeScript compiler parses and type-checks your program against `es2025` (plus `@types/node` when your project has it), honoring your `tsconfig.json` for checker strictness. The frontend then lowers the checked AST into a typed intermediate representation, using tsc's own type and narrowing answers to drive every decision. A construct with no lowering is a precise diagnostic at this stage — never a miscompile later.
2. **Typed IR** — the only interface between the ends: a validated, serializable representation (`--emit=ir` writes it as JSON and stops). Types are concrete here; generics have been monomorphized, unions are tagged values, closures have explicit captures.
3. **Backends** — `--emit=c` writes readable C and stops; `--emit=llvm` writes textual LLVM IR and stops. Neither source-output command discovers or invokes a native toolchain. On macOS 15+ arm64, `--emit=asm|obj` sends LLVM IR to a version-matched out-of-process helper linked to LLVM 22; it needs no clang or linker and emits macOS 14-targeted artifacts. Executable builds default to LLVM and can fall back to C on a native program outside the LLVM tier (one stderr note; `--backend llvm` pins it and fails with a diagnostic instead). The production <code>wasm32-wasi</code> target never falls back.
4. **Link** — on macOS arm64 the release packages contain one precompiled object per runtime feature unit plus QuickJS, libregexp, zlib, and mbedTLS archives. A hashed manifest maps IR feature gates to an ordered typed link plan, so binaries still pay only for what they use. The user needs a platform linker and SDK, but ordinary LLVM-tier builds compile no C. AddressSanitizer, explicit C builds, and LLVM refusals keep the external C-toolchain path.
3. **Backends** — `--emit=c` writes readable C and stops; `--emit=llvm` writes textual LLVM IR and stops. Neither source-output command discovers or invokes a native toolchain. On supported macOS, Linux, and Windows hosts (and for WASI), `--emit=asm|obj` sends LLVM IR to a version-matched out-of-process LLVM 22 helper; it needs no clang or linker. Executable builds default to LLVM and can fall back to C on a native program outside the LLVM tier (one stderr note; `--backend llvm` pins it and fails with a diagnostic instead). The production <code>wasm32-wasi</code> target never falls back.
4. **Link** — release packages contain one precompiled object per runtime feature unit plus QuickJS, libregexp, zlib, and mbedTLS archives for every supported target. A hashed manifest maps IR feature gates to an ordered typed link plan, so binaries still pay only for what they use. The user needs the target platform linker and SDK/sysroot, but ordinary LLVM-tier builds compile no C. AddressSanitizer, explicit C builds, and LLVM refusals keep the external C-toolchain path.
Program objects define <code>main</code> and leave their selected
<code>scr_*</code> runtime functions undefined. The
+8 -5
View File
@@ -1,7 +1,7 @@
# Native Program Objects
`scriptc build --emit=obj` produces one relocatable macOS arm64 program
object without invoking clang, a linker, or an SDK. The object defines
`scriptc build --emit=obj` produces one relocatable program object without
invoking clang, a linker, or an SDK on a supported target. The object defines
`main`; it is intended to become the program in an external native link. It
is not a host-callable library—use `scriptc build --lib --profile ...` for
that interface.
@@ -59,9 +59,12 @@ $ ./app-cc
42
```
The script compiles each reported source set and gives clang only the link
inputs and system libraries from the document. `--emit=obj` itself remains
clang-free; this compiler invocation belongs to the external runtime build.
The script is the macOS external-link example: it compiles each reported
source set and gives clang only the link inputs and system libraries from the
document. `--emit=obj` itself remains clang-free; this compiler invocation
belongs to the external runtime build. Linux objects need the target glibc or
musl linker/sysroot; Windows MSVC objects need the Windows SDK/MSVC CRT; WASI
objects need a Preview 1 linker/sysroot.
## Native Apple linker
+10 -10
View File
@@ -1,8 +1,8 @@
# Platform Support
## macOS (arm64)
## Owned LLVM targets
The primary platform. Source artifacts selected with <code>--emit=ir|c|llvm</code> need only Node. On macOS 15+ arm64, <code>--emit=asm|obj</code> uses the version-matched <code>@scriptc/llvm-darwin-arm64</code> helper installed with scriptc, emits <code>arm64-apple-macosx14.0.0</code> artifacts, and does not invoke a compiler, archiver, linker, or SDK. Ordinary LLVM-tier executables additionally need the Xcode platform linker/SDK; the helper emits the program object and the packaged runtime supplies objects/archives, so <code>clang</code> is only a linker driver (selectable with <code>SCRIPTC_LINKER</code>) and compiles neither program nor runtime C. Explicit C builds, LLVM fallbacks, and <code>--sanitize</code> still require a C compiler. Object output retains undefined runtime references and is not a library archive. The full executable surface is supported: the language, the stdlib, the Node API surface including the server stack, `--dynamic`, and the sanitizer lane.
Source artifacts selected with <code>--emit=ir|c|llvm</code> need only Node. On supported hosts, <code>--emit=asm|obj</code> uses the version-matched platform helper installed with scriptc and does not invoke a compiler, archiver, linker, or SDK. Supported assembly/object targets are macOS arm64/x64 (macOS 14.0 artifacts; helper needs macOS 15+), Linux x64/arm64 glibc, Windows x64 MSVC, Linux x64/arm64 musl, and WASI Preview 1. Ordinary LLVM-tier executables additionally need the platform linker and SDK/sysroot; the helper emits the program object and the packaged runtime supplies objects/archives, so that driver compiles neither program nor runtime C. Explicit C builds, LLVM fallbacks, and <code>--sanitize</code> still require a C compiler. Object output retains undefined runtime references and is not a library archive.
## Cross-compilation via zig
@@ -83,19 +83,19 @@ The remaining executable boundary is host capability, not language coverage. WAS
</thead>
<tbody>
<tr>
<td>macOS arm64</td>
<td>native</td>
<td>Primary: full surface, <code>--dynamic</code>, sanitizer lane</td>
<td>macOS arm64 / x64</td>
<td>native host helper + runtime pack</td>
<td>Full surface, <code>--dynamic</code>, sanitizer lane (external C route)</td>
</tr>
<tr>
<td>Linux arm64 / x86_64</td>
<td><code>SCRIPTC_CC=zigcc SCRIPTC_TARGET=&lt;arch&gt;-linux-gnu.2.36</code> or <code>&lt;arch&gt;-linux-musl</code></td>
<td>Static and dynamic surfaces incl. servers, TLS, fetch, fs.watch, and child_process; verified against Linux Node on Bookworm and Alpine</td>
<td>native host helper + runtime pack; <code>SCRIPTC_TARGET=&lt;arch&gt;-linux-musl</code> selects musl</td>
<td>Static and dynamic surfaces incl. servers, TLS, fetch, fs.watch, and child_process; executable linking needs the matching libc/sysroot</td>
</tr>
<tr>
<td>Windows x86_64</td>
<td><code>SCRIPTC_CC=zigcc SCRIPTC_TARGET=x86_64-windows-gnu</code></td>
<td>Static and dynamic surfaces incl. servers, TLS, fetch, and child_process; verified against Windows Node</td>
<td>native x64 MSVC helper + runtime pack</td>
<td>Static and dynamic surfaces incl. servers, TLS, fetch, and child_process; executable linking needs the Windows SDK/MSVC CRT</td>
</tr>
<tr>
<td>iOS arm64 (device and simulator)</td>
@@ -109,7 +109,7 @@ The remaining executable boundary is host capability, not language coverage. WAS
</tr>
<tr>
<td>WebAssembly / WASI Preview 1</td>
<td><code>SCRIPTC_CC=zigcc SCRIPTC_TARGET=wasm32-wasi</code></td>
<td>matching host helper + WASI runtime pack</td>
<td>Production LLVM target; full language and dynamic tiers, bounded by WASI P1 host capabilities</td>
</tr>
</tbody>
+60 -6
View File
@@ -1,10 +1,24 @@
cmake_minimum_required(VERSION 3.24)
set(SCRIPTC_PACKAGE_VERSION "0.0.0-dev" CACHE STRING
"scriptc npm package version reported by the helper protocol")
set(SCRIPTC_DEFAULT_TARGET "arm64-apple-macosx14.0.0" CACHE STRING
"Default target reported by the helper protocol")
set(SCRIPTC_DEFAULT_DATA_LAYOUT
"e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
CACHE STRING "Data layout for SCRIPTC_DEFAULT_TARGET")
set(SCRIPTC_TARGET_BACKENDS "AArch64" CACHE STRING
"Semicolon-separated LLVM target backends compiled into the helper")
set(SCRIPTC_ALLOWED_TARGETS "arm64-apple-macosx14.0.0" CACHE STRING
"Comma-separated target triples accepted by this helper")
set(SCRIPTC_HELPER_ARCH "arm64" CACHE STRING
"macOS architecture of the helper executable")
# These must be cache variables before project() enables the Apple toolchain.
# A target property named OSX_DEPLOYMENT_TARGET is not recognized by CMake and
# silently leaves the executable at the build host/SDK's deployment version.
if(CMAKE_HOST_APPLE)
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING
if(APPLE)
set(CMAKE_OSX_ARCHITECTURES "${SCRIPTC_HELPER_ARCH}" CACHE STRING
"Architectures for the macOS helper" FORCE)
# Homebrew's pinned LLVM 22 bottle is built for macOS 15. The helper must
# tell the truth about that host requirement; the objects it emits retain
@@ -15,10 +29,12 @@ endif()
project(scriptc_llvm_codegen LANGUAGES C CXX)
find_package(LLVM 22.1.8 EXACT REQUIRED CONFIG)
# Official LLVM binary distributions retain zstd as an imported dependency in
# LLVMSupport. Resolve it before loading LLVM's exported targets so CMake never
# leaves an unresolved `zstd::libzstd_static` edge on Linux or Windows.
find_package(zstd CONFIG QUIET)
set(SCRIPTC_PACKAGE_VERSION "0.0.0-dev" CACHE STRING
"scriptc npm package version reported by the helper protocol")
find_package(LLVM 22.1.8 EXACT REQUIRED CONFIG)
# LLVM's exported Support target names zstd's shared target even when the
# selected LLVM components are static. The npm sidecar must not retain a
@@ -49,9 +65,47 @@ target_compile_definitions(scriptc-llvm-codegen PRIVATE
${LLVM_DEFINITIONS}
SCRIPTC_PACKAGE_VERSION="${SCRIPTC_PACKAGE_VERSION}"
)
# The official Linux and Windows LLVM archives are commonly built with RTTI
# disabled. Mixing RTTI modes turns otherwise ordinary virtual typeinfo into
# an unresolved link symbol when the helper links those static components.
if(NOT LLVM_ENABLE_RTTI)
if(MSVC)
target_compile_options(scriptc-llvm-codegen PRIVATE /GR-)
else()
target_compile_options(scriptc-llvm-codegen PRIVATE -fno-rtti)
endif()
endif()
foreach(SCRIPTC_TARGET_BACKEND IN LISTS SCRIPTC_TARGET_BACKENDS)
if(SCRIPTC_TARGET_BACKEND STREQUAL "AArch64")
list(APPEND SCRIPTC_LLVM_TARGET_LIBS AArch64CodeGen)
target_compile_definitions(scriptc-llvm-codegen PRIVATE SCRIPTC_ENABLE_AARCH64=1)
elseif(SCRIPTC_TARGET_BACKEND STREQUAL "X86")
list(APPEND SCRIPTC_LLVM_TARGET_LIBS X86CodeGen)
target_compile_definitions(scriptc-llvm-codegen PRIVATE SCRIPTC_ENABLE_X86=1)
elseif(SCRIPTC_TARGET_BACKEND STREQUAL "WebAssembly")
list(APPEND SCRIPTC_LLVM_TARGET_LIBS WebAssemblyCodeGen)
target_compile_definitions(scriptc-llvm-codegen PRIVATE SCRIPTC_ENABLE_WEBASSEMBLY=1)
else()
message(FATAL_ERROR "unsupported SCRIPTC_TARGET_BACKENDS entry: ${SCRIPTC_TARGET_BACKEND}")
endif()
endforeach()
# The WebAssembly target machine emits either an object or final wasm module,
# but LLVM's normal `CodeGen` component remains the shared driver interface.
# Keep the backend list canonical in the protocol rather than relying on the
# host's installed LLVM target registry.
string(REPLACE ";" "," SCRIPTC_TARGET_BACKENDS_JSON "${SCRIPTC_TARGET_BACKENDS}")
target_compile_definitions(scriptc-llvm-codegen PRIVATE
SCRIPTC_DEFAULT_TARGET="${SCRIPTC_DEFAULT_TARGET}"
SCRIPTC_DEFAULT_DATA_LAYOUT="${SCRIPTC_DEFAULT_DATA_LAYOUT}"
SCRIPTC_TARGET_BACKENDS="${SCRIPTC_TARGET_BACKENDS_JSON}"
SCRIPTC_ALLOWED_TARGETS="${SCRIPTC_ALLOWED_TARGETS}"
)
llvm_map_components_to_libnames(SCRIPTC_LLVM_LIBS
AArch64CodeGen
${SCRIPTC_LLVM_TARGET_LIBS}
CodeGen
Core
Coroutines
+2 -8
View File
@@ -71,10 +71,10 @@ static OptimizationLevel optimizationLevel(StringRef Level) {
}
int emit(const EmitOptions &Options) {
if (Options.Target != DefaultTarget)
if (!supportsTarget(Options.Target))
return reportError("unsupported_target",
Twine("unsupported target '") + Options.Target +
"' (supported: " + DefaultTarget + ")",
"' (supported: " + AllowedTargets + ")",
Options.DiagnosticFormat);
if (Options.FileType != "obj" && Options.FileType != "asm")
return reportError("invalid_filetype", "filetype must be obj or asm",
@@ -113,12 +113,6 @@ int emit(const EmitOptions &Options) {
Triple TargetTriple(Options.Target);
Mod->setTargetTriple(TargetTriple);
Mod->setDataLayout(Machine->createDataLayout());
if (Mod->getDataLayoutStr() != DefaultDataLayout)
return reportError("data_layout_mismatch",
Twine("LLVM produced unexpected data layout '") +
Mod->getDataLayoutStr() + "'",
Options.DiagnosticFormat);
std::string VerificationError;
raw_string_ostream VerificationStream(VerificationError);
if (verifyModule(*Mod, &VerificationStream))
+16 -1
View File
@@ -32,13 +32,28 @@ int version(int Argc, char **Argv) {
if (!Machine)
return scriptc::reportError("target_machine_failed", Error);
json::Array Targets;
StringRef Backends = scriptc::TargetBackends;
while (!Backends.empty()) {
auto Split = Backends.split(',');
Targets.push_back(Split.first);
Backends = Split.second;
}
json::Array SupportedTargets;
StringRef TargetsText = scriptc::AllowedTargets;
while (!TargetsText.empty()) {
auto Split = TargetsText.split(',');
SupportedTargets.push_back(Split.first);
TargetsText = Split.second;
}
json::Object Response{
{"ok", true},
{"protocol_version", scriptc::ProtocolVersion},
{"scriptc_package_version", SCRIPTC_PACKAGE_VERSION},
{"llvm_version", LLVM_VERSION_STRING},
{"host_triple", sys::getDefaultTargetTriple()},
{"targets", json::Array{"AArch64"}},
{"targets", std::move(Targets)},
{"supported_targets", std::move(SupportedTargets)},
{"default_target", scriptc::DefaultTarget},
{"data_layout", Machine->createDataLayout().getStringRepresentation()},
};
+71
View File
@@ -8,12 +8,30 @@
using namespace llvm;
#if defined(SCRIPTC_ENABLE_AARCH64)
extern "C" {
void LLVMInitializeAArch64TargetInfo();
void LLVMInitializeAArch64Target();
void LLVMInitializeAArch64TargetMC();
void LLVMInitializeAArch64AsmPrinter();
}
#endif
#if defined(SCRIPTC_ENABLE_X86)
extern "C" {
void LLVMInitializeX86TargetInfo();
void LLVMInitializeX86Target();
void LLVMInitializeX86TargetMC();
void LLVMInitializeX86AsmPrinter();
}
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
extern "C" {
void LLVMInitializeWebAssemblyTargetInfo();
void LLVMInitializeWebAssemblyTarget();
void LLVMInitializeWebAssemblyTargetMC();
void LLVMInitializeWebAssemblyAsmPrinter();
}
#endif
namespace scriptc {
@@ -21,13 +39,62 @@ void initializeTargets() {
static bool Initialized = false;
if (Initialized)
return;
#if defined(SCRIPTC_ENABLE_AARCH64)
LLVMInitializeAArch64TargetInfo();
LLVMInitializeAArch64Target();
LLVMInitializeAArch64TargetMC();
LLVMInitializeAArch64AsmPrinter();
#endif
#if defined(SCRIPTC_ENABLE_X86)
LLVMInitializeX86TargetInfo();
LLVMInitializeX86Target();
LLVMInitializeX86TargetMC();
LLVMInitializeX86AsmPrinter();
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
LLVMInitializeWebAssemblyTargetInfo();
LLVMInitializeWebAssemblyTarget();
LLVMInitializeWebAssemblyTargetMC();
LLVMInitializeWebAssemblyAsmPrinter();
#endif
Initialized = true;
}
bool supportsTarget(StringRef TripleName) {
bool Allowed = false;
StringRef Remaining = AllowedTargets;
while (!Remaining.empty()) {
auto Split = Remaining.split(',');
if (Split.first == TripleName) {
Allowed = true;
break;
}
Remaining = Split.second;
}
if (!Allowed)
return false;
Triple Triple(TripleName);
switch (Triple.getArch()) {
#if defined(SCRIPTC_ENABLE_AARCH64)
case Triple::aarch64:
case Triple::aarch64_be:
return Triple.isOSBinFormatMachO() || Triple.isOSBinFormatELF();
#endif
#if defined(SCRIPTC_ENABLE_X86)
case Triple::x86_64:
case Triple::x86:
return Triple.isOSBinFormatMachO() || Triple.isOSBinFormatELF() ||
Triple.isOSBinFormatCOFF();
#endif
#if defined(SCRIPTC_ENABLE_WEBASSEMBLY)
case Triple::wasm32:
return Triple.isOSBinFormatWasm();
#endif
default:
return false;
}
}
static CodeGenOptLevel codeGenLevel(StringRef Level) {
if (Level == "0")
return CodeGenOptLevel::None;
@@ -42,6 +109,10 @@ std::unique_ptr<TargetMachine> createTargetMachine(StringRef TripleName,
StringRef OptLevel,
std::string &Error) {
initializeTargets();
if (!supportsTarget(TripleName)) {
Error = (Twine("target '") + TripleName + "' was not compiled into this helper").str();
return nullptr;
}
Triple TargetTriple(TripleName);
const Target *Definition = TargetRegistry::lookupTarget(TargetTriple, Error);
if (Definition == nullptr)
+17 -2
View File
@@ -13,12 +13,27 @@ namespace scriptc {
inline constexpr llvm::StringLiteral ProtocolVersion = "1";
inline constexpr llvm::StringLiteral LlvmVersion = "22.1.8";
#ifndef SCRIPTC_DEFAULT_TARGET
#define SCRIPTC_DEFAULT_TARGET "arm64-apple-macosx14.0.0"
#endif
#ifndef SCRIPTC_DEFAULT_DATA_LAYOUT
#define SCRIPTC_DEFAULT_DATA_LAYOUT "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
#endif
#ifndef SCRIPTC_TARGET_BACKENDS
#define SCRIPTC_TARGET_BACKENDS "AArch64"
#endif
#ifndef SCRIPTC_ALLOWED_TARGETS
#define SCRIPTC_ALLOWED_TARGETS "arm64-apple-macosx14.0.0"
#endif
inline constexpr llvm::StringLiteral DefaultTarget =
"arm64-apple-macosx14.0.0";
SCRIPTC_DEFAULT_TARGET;
inline constexpr llvm::StringLiteral DefaultDataLayout =
"e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32";
SCRIPTC_DEFAULT_DATA_LAYOUT;
inline constexpr llvm::StringLiteral TargetBackends = SCRIPTC_TARGET_BACKENDS;
inline constexpr llvm::StringLiteral AllowedTargets = SCRIPTC_ALLOWED_TARGETS;
void initializeTargets();
bool supportsTarget(llvm::StringRef Triple);
std::unique_ptr<llvm::TargetMachine>
createTargetMachine(llvm::StringRef Triple, llvm::StringRef OptLevel,
std::string &Error);
+3 -3
View File
@@ -22,7 +22,7 @@ $ scriptc build fib.ts -o fib && ./fib
$ npm install -g scriptc
```
Requires Node.js 24. Executable builds require a platform linker driver and SDK. On macOS 15+ arm64, LLVM-tier executables use the matching optional helper and precompiled runtime pack, so the driver only links; select that driver with `SCRIPTC_LINKER`. Explicit C builds, LLVM fallbacks, `--sanitize`, and the deprecated `SCRIPTC_CC=clang|zigcc` compatibility route still compile C. `--emit=ir|c|llvm` requires only Node, while `--emit=asm|obj` requires neither an external compiler nor a linker.
Requires Node.js 24. Executable builds require a platform linker driver and SDK/sysroot. On supported macOS, Linux, and Windows hosts, LLVM-tier executables use the matching optional helper and precompiled runtime pack, so the driver only links; select that driver with `SCRIPTC_LINKER`. Explicit C builds, LLVM fallbacks, `--sanitize`, and the deprecated `SCRIPTC_CC=clang|zigcc` compatibility route still compile C. `--emit=ir|c|llvm` requires only Node, while `--emit=asm|obj` requires neither an external compiler nor a linker.
Builds use a bounded persistent cache by default. Exact unchanged library builds validate their recorded TypeScript/module-resolution inputs and restore the generated C/LLVM unit before starting the frontend. TypeScript comment-only edits can restore validated lowered IR instead, rebasing source locations and regenerating exact-source build identity before emission; directives, JSDoc-bearing JavaScript, token edits, configuration, package resolution, and newly appearing candidates still invalidate it. Library identity getters live in a tiny C translation unit, so build-id-only changes reuse the large compiled program object and compile only that small member before rearchiving. The native cache then applies its independent toolchain checks. Unchanged executables and library archives skip native code generation and linking after fresh compiler metadata probes, while edited builds reuse stable runtime objects. Experimental provenance-source builds bypass the early frontend tier because their fetched-source registry is process state. FFI builds with archive/object inputs or ambient `system_libraries` relink every time but still reuse runtime objects. Mutable compiler input paths such as `CPATH` and `SDKROOT`, and compiler wrappers, bypass persistent native artifacts and objects so same-path dependency edits cannot go stale. Opaque archiver wrappers rebuild library program members and archives while retaining runtime-object reuse. Direct Clang, Apple's system Clang shim, `zig cc`, trusted platform archivers, and `zig ar` retain their applicable persistent tiers. Set `SCRIPTC_NO_CACHE=1` to bypass every cache or `SCRIPTC_CACHE_DIR` to choose its location; an existing POSIX override must already be private, otherwise caching is bypassed without changing its permissions.
@@ -35,8 +35,8 @@ Builds use a bounded persistent cache by default. Exact unchanged library builds
`scriptc build app.ts --emit=ir|c|llvm|asm|obj` selects serialized typed IR,
readable C, textual LLVM IR, target assembly, or a relocatable program object
as the one primary artifact. `--emit=exe` is the default. Assembly/object
emission currently requires a macOS 15+ arm64 host and emits objects targeting
macOS 14.0 (`arm64-apple-macosx14.0.0`).
emission uses the matching helper on supported macOS, Linux, and Windows hosts
(and produces WASI artifacts when that target is selected).
Objects retain undefined `scr_*` runtime references plus the
`scr_runtime_abi_v1` compatibility marker; they are not library archives.
External consumption is experimental and requires the exact matching runtime.
+3 -1
View File
@@ -6,5 +6,7 @@ export function hostSupportsRuntimePack(
hostRelease = release(),
) {
const darwinMajor = Number.parseInt(hostRelease.split(".", 1)[0] ?? "", 10);
return platform === "darwin" && architecture === "arm64" && darwinMajor >= 24;
return (platform === "darwin" && (architecture === "arm64" || architecture === "x64") && darwinMajor >= 24) ||
(platform === "linux" && (architecture === "arm64" || architecture === "x64")) ||
(platform === "win32" && architecture === "x64");
}
+2 -2
View File
@@ -12,8 +12,8 @@ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
if (
!existsSync(join(packageRoot, "src")) &&
process.env["SCRIPTC_NO_CACHE"] !== "1" &&
// A supported macOS arm64 install already carries immutable release/dev
// runtime artifacts. Older macOS hosts retain the source-toolchain path.
// A supported platform install already carries immutable release/dev
// runtime artifacts. Unsupported hosts retain the source-toolchain path.
!hostSupportsRuntimePack()
) {
try {
+17 -12
View File
@@ -3,11 +3,9 @@
import { spawn } from "node:child_process";
import { readFile, rm } from "node:fs/promises";
import { enableCompileCache } from "node:module";
import { arch } from "node:process";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { hostSupportsRuntimePack } from "../scripts/runtime-pack-host.mjs";
import { LEGACY_C_EXECUTABLE_WARNING, shouldWarnLegacyCExecutable } from "./legacy-c-warning.js";
import { CLI_OPTIONS, USAGE } from "./usage.js";
@@ -83,7 +81,7 @@ async function tryFastPath(): Promise<number | null> {
let buildPlatform: string;
try {
startup = await import("@scriptc/compiler/startup-cache");
buildPlatform = startup.targetPlatform(startup.resolveCc());
buildPlatform = startup.configuredTargetPlatform();
} catch {
return null;
}
@@ -101,16 +99,21 @@ async function tryFastPath(): Promise<number | null> {
const ffiBytes = ffiPath === null ? null : await readFile(ffiPath).catch(() => null);
if (ffiPath !== null && ffiBytes === null) return null;
const root = await startup.prepareBuildCacheRoot(startup.resolveBuildCacheRoot());
const helperObjectRoute = hostSupportsRuntimePack(process.platform, arch) &&
(process.env["SCRIPTC_TARGET"] ?? "") === "" &&
backend !== "c" && !values.sanitize &&
process.env["SCRIPTC_RUNTIME_PACK"] !== "0" &&
process.env["SCRIPTC_FETCH_CURL"] !== "1" &&
!startup.legacyCExecutablePathRequested();
// This must exactly mirror the ordinary LLVM executable's route in the
// full compiler. Otherwise a valid helper/runtime-pack cache entry has a
// different target/compiler identity and bootstrap must unnecessarily load
// the whole compiler graph to rediscover it.
const helperRuntimePackTarget = backend !== "c" && !values.sanitize
? startup.precompiledRuntimePackTarget()
: null;
const helperObjectRoute = helperRuntimePackTarget !== null;
let nativeEnvironment: string | null;
try {
nativeEnvironment = helperObjectRoute
? await startup.executableLinkerEnvironmentFingerprint()
? await startup.executableLinkerEnvironmentFingerprint(
process.env,
helperRuntimePackTarget.defaultLinker,
)
: await startup.executableNativeEnvironmentFingerprint();
} catch {
nativeEnvironment = null;
@@ -127,10 +130,12 @@ async function tryFastPath(): Promise<number | null> {
...(optimization === "dev" ? { optimization: "dev" as const } : {}),
npmStatic,
ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! },
target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${arch}:${
target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${
helperObjectRoute ? "runtime-pack" : "driver-tu"
}`,
compiler: [helperObjectRoute ? startup.resolvePlatformLinker() : (process.env["SCRIPTC_CC"] ?? "clang")],
compiler: [helperObjectRoute
? startup.resolvePlatformLinker(process.env, helperRuntimePackTarget.defaultLinker)
: (process.env["SCRIPTC_CC"] ?? "clang")],
nativeEnvironment,
nodeVersion: process.version,
});
+2 -2
View File
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { analyze, buildTargetPlatform, compile, compileExternalC, compileLibrary, isExactExternalTypeSpecifier, renderDiagnostics, renderCoverage, resolveProvenanceSources, setProvenanceSources, warmNativeCaches, type NativeCacheWarmProfile } from "@scriptc/compiler";
import { analyze, buildTargetPlatform, compile, compileExternalC, compileLibrary, isExactExternalTypeSpecifier, renderDiagnostics, renderCoverage, resolveProvenanceSources, setProvenanceSources, sourceTargetPlatform, warmNativeCaches, type NativeCacheWarmProfile } from "@scriptc/compiler";
import { LEGACY_C_EXECUTABLE_WARNING, shouldWarnLegacyCExecutable } from "./legacy-c-warning.js";
import { resolveOutputOptions } from "./output-options.js";
import { selectOutputPaths } from "./paths.js";
@@ -329,7 +329,7 @@ async function main(): Promise<number> {
if (command === "run") {
return new Promise<number>((resolveExit) => {
let child;
if (buildTargetPlatform() === "wasi") {
if (sourceTargetPlatform() === "wasi") {
const builtRunner = fileURLToPath(new URL("./wasi-runner.js", import.meta.url));
const runner = existsSync(builtRunner)
? builtRunner
+2 -2
View File
@@ -1,6 +1,6 @@
import { tmpdir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { buildTargetPlatform, wasiGuestPath } from "@scriptc/compiler";
import { buildTargetPlatform, sourceTargetPlatform, wasiGuestPath } from "@scriptc/compiler";
export type CliOutputKind = "ir" | "c" | "llvm" | "asm" | "obj" | "exe";
@@ -25,7 +25,7 @@ export function defaultOutputName(
kind: CliOutputKind,
platform?: string,
): string {
const selectedPlatform = platform ?? (kind === "exe" ? buildTargetPlatform() : process.platform);
const selectedPlatform = platform ?? (kind === "exe" ? sourceTargetPlatform() : process.platform);
if (kind === "exe" && selectedPlatform === "wasi") return `${stem}.wasm`;
return `${stem}${(selectedPlatform === "win32" ? WINDOWS_SUFFIXES : POSIX_SUFFIXES)[kind]}`;
}
+1 -1
View File
@@ -19,7 +19,7 @@ Usage:
Options:
-o, --out <path> primary output path (default: .scriptc/<name><suffix>)
--emit <kind> primary output: ir, c, llvm, asm, obj, or exe
(default: exe). asm/obj currently support macOS 15+ arm64
(default: exe). asm/obj use the matching platform helper
--print <kind> print machine-readable metadata instead of the output path
(native-link-info implies --emit=obj and never links)
--backend <b> code generator. llvm is the default and the output that
+6 -3
View File
@@ -2,10 +2,13 @@ import { describe, expect, test } from "vitest";
import { hostSupportsRuntimePack } from "../scripts/runtime-pack-host.mjs";
describe("runtime-pack host support", () => {
test("requires macOS 15 or newer on arm64", () => {
test("recognizes each platform with a release-built runtime pack", () => {
expect(hostSupportsRuntimePack("darwin", "arm64", "24.0.0")).toBe(true);
expect(hostSupportsRuntimePack("darwin", "x64", "24.0.0")).toBe(true);
expect(hostSupportsRuntimePack("darwin", "arm64", "23.6.0")).toBe(false);
expect(hostSupportsRuntimePack("darwin", "x64", "24.0.0")).toBe(false);
expect(hostSupportsRuntimePack("linux", "arm64", "24.0.0")).toBe(false);
expect(hostSupportsRuntimePack("linux", "x64", "6.8.0")).toBe(true);
expect(hostSupportsRuntimePack("linux", "arm64", "6.8.0")).toBe(true);
expect(hostSupportsRuntimePack("win32", "x64", "10.0.0")).toBe(true);
expect(hostSupportsRuntimePack("linux", "ia32", "6.8.0")).toBe(false);
});
});
@@ -0,0 +1,44 @@
import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { createRequire } from "node:module";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { afterEach, expect, test } from "vitest";
const execFileAsync = promisify(execFile);
const require = createRequire(import.meta.url);
const repoRoot = join(import.meta.dirname, "../../..");
const cliEntry = join(repoRoot, "packages/cli/src/main.ts");
const tsxLoader = join(dirname(require.resolve("tsx/package.json")), "dist/loader.mjs");
const helperPackage = require.resolve("@scriptc/llvm-darwin-arm64/package.json");
const helper = join(dirname(helperPackage), "bin", "scriptc-llvm-codegen");
const runtimePackage = require.resolve("@scriptc/runtime-wasm32-wasi/package.json");
const runtimeManifest = join(dirname(runtimePackage), "runtime-pack.json");
const supported = existsSync(helper) && existsSync(runtimeManifest);
const dirs: string[] = [];
afterEach(async () => {
await Promise.all(dirs.splice(0).map((path) => rm(path, { recursive: true, force: true })));
});
test.runIf(supported)("WASI helper object plus runtime pack builds and runs without SCRIPTC_CC", async () => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-wasi-runtime-pack-"));
dirs.push(dir);
const output = join(dir, "hello.wasm");
const zigCache = join(dir, "zig-cache");
await expect(execFileAsync(process.execPath, [
"--import", tsxLoader, cliEntry, "run", join(repoRoot, "tests/corpus/001-hello.ts"), "-o", output,
], {
env: {
...process.env,
SCRIPTC_TARGET: "wasm32-wasi",
SCRIPTC_NO_CACHE: "1",
SCRIPTC_LEGACY_C_PIPELINE: "0",
ZIG_GLOBAL_CACHE_DIR: join(zigCache, "global"),
ZIG_LOCAL_CACHE_DIR: join(zigCache, "local"),
},
encoding: "utf8",
})).resolves.toMatchObject({ stdout: "hello world\n" });
});
+14 -1
View File
@@ -38,6 +38,19 @@
},
"optionalDependencies": {
"@scriptc/llvm-darwin-arm64": "workspace:*",
"@scriptc/runtime-darwin-arm64": "workspace:*"
"@scriptc/llvm-darwin-x64": "workspace:*",
"@scriptc/llvm-linux-x64-gnu": "workspace:*",
"@scriptc/llvm-linux-arm64-gnu": "workspace:*",
"@scriptc/llvm-linux-x64-musl": "workspace:*",
"@scriptc/llvm-linux-arm64-musl": "workspace:*",
"@scriptc/llvm-win32-x64-msvc": "workspace:*",
"@scriptc/runtime-darwin-arm64": "workspace:*",
"@scriptc/runtime-darwin-x64": "workspace:*",
"@scriptc/runtime-linux-x64-gnu": "workspace:*",
"@scriptc/runtime-linux-arm64-gnu": "workspace:*",
"@scriptc/runtime-linux-x64-musl": "workspace:*",
"@scriptc/runtime-linux-arm64-musl": "workspace:*",
"@scriptc/runtime-win32-x64-msvc": "workspace:*",
"@scriptc/runtime-wasm32-wasi": "workspace:*"
}
}
+4 -2
View File
@@ -11,6 +11,7 @@
import {
compileC,
compileLibArchive,
configuredTargetPlatform,
type CcOptions,
type LibArchiveOptions,
} from "./native-toolchain.js";
@@ -20,6 +21,7 @@ export {
compileC,
compileLibArchive,
compilerDriverSupportsPersistentCache,
configuredTargetPlatform,
executableNativeEnvironmentFingerprint,
isAndroidTarget,
isIosTarget,
@@ -72,8 +74,8 @@ export function legacyCExecutablePipelineEnabled(
}
/** `SCRIPTC_CC` selects the external C compiler route during the migration.
* An unset value lets an LLVM-tier macOS build use the helper plus runtime
* pack and reserve `SCRIPTC_LINKER` for the platform-linker driver. */
* An unset value lets every supported LLVM-tier target use its helper plus
* runtime pack and reserve `SCRIPTC_LINKER` for the platform-linker driver. */
export function legacyCExecutablePathRequested(
env: NodeJS.ProcessEnv = process.env,
): boolean {
+3 -1
View File
@@ -51,7 +51,9 @@ export async function createNativeLinkPlan(options: {
...(options.ffi?.systemLibraries ?? []),
...runtimePack.systemLibraries,
])],
driverFlags: [...options.target.executableLinkerArgs],
driverFlags: options.target.executableLinkerArgs.map((arg, index, args) =>
index > 0 && args[index - 1] === "-target" ? options.target.linkerTargetTriple : arg
),
dependencyPaths: [
...runtimePack.dependencyPaths,
...(options.ffi?.libraries ?? []),
+12 -4
View File
@@ -28,8 +28,11 @@ const execFileAsync = promisify(execFile);
/** The linker (or linker driver) is independent from SCRIPTC_CC. A plain
* `clang` is the initial macOS driver because it locates the selected SDK and
* CRT inputs; it receives only objects and archives on this route. */
export function resolvePlatformLinker(env: NodeJS.ProcessEnv = process.env): string {
return env["SCRIPTC_LINKER"] || "clang";
export function resolvePlatformLinker(
env: NodeJS.ProcessEnv = process.env,
defaultLinker: string = "clang",
): string {
return env["SCRIPTC_LINKER"] || defaultLinker;
}
/** Exact executable selected for a linker spelling. The object-only route
@@ -74,8 +77,9 @@ function platformLinkerIdentity(
* only the helper-produced program object and runtime-pack artifacts. */
export async function executableLinkerEnvironmentFingerprint(
env: NodeJS.ProcessEnv = process.env,
defaultLinker: string = "clang",
): Promise<string> {
const linker = resolvePlatformLinker(env);
const linker = resolvePlatformLinker(env, defaultLinker);
const linkerIdentity = platformLinkerIdentity(env);
let effectiveDriverIdentity: string;
try {
@@ -112,7 +116,9 @@ export async function executableLinkerEnvironmentFingerprint(
* a caller-selected SCRIPTC_LINKER may be a wrapper with hidden inputs. */
export function platformLinkerSupportsPersistentCache(
env: NodeJS.ProcessEnv = process.env,
target?: Pick<NativeLinkPlan["target"], "platform" | "defaultLinker">,
): boolean {
if (target !== undefined && target.platform !== "darwin") return false;
// The Apple system shim is a stable front door to the active SDK/linker;
// linkNativeExecutable snapshots the selected transitive inputs before it
// publishes the final cache entry. A PATH wrapper is intentionally opaque.
@@ -198,7 +204,8 @@ export async function linkNativeExecutable(
onArtifactReady?: (artifact: { dependencies: NativeArtifactDependency[] }) => Promise<void>;
} = {},
): Promise<void> {
const linker = options.linker ?? resolvePlatformLinker();
const explicitLinker = options.linker ?? process.env["SCRIPTC_LINKER"];
const linker = explicitLinker ?? plan.target.defaultLinker;
// ld64 derives an ad-hoc signature identifier from the output basename.
// Keep that basename caller-visible while a private sibling directory gives
// the link its own inode and preserves an atomic same-filesystem install.
@@ -211,6 +218,7 @@ export async function linkNativeExecutable(
const staged = await stageRuntimePackArtifacts(plan.runtimePack);
stagedRoot = staged.root;
const args = [
...(explicitLinker === undefined ? plan.target.defaultLinkerArgs : []),
...plan.driverFlags,
...plan.inputs.map((input) => staged.replacements.get(input) ?? input),
...plan.systemLibraries.map((name) => `-l${name}`),
@@ -2,9 +2,9 @@ import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/pr
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, expect, test } from "vitest";
import { emitNativeArtifact, NativeCodegenError } from "./native-codegen.js";
import { emitNativeArtifact, NativeCodegenError, validateNativeCodegenVersion } from "./native-codegen.js";
import { nativeArtifactDependenciesStillMatch } from "./native-toolchain.js";
import { MACOS_ARM64_TARGET } from "./targets.js";
import { LINUX_X64_GNU_TARGET, MACOS_ARM64_TARGET, WASM32_WASI_TARGET } from "./targets.js";
import { compilerReleaseVersion } from "../library/sidecar.js";
const dirs: string[] = [];
@@ -34,6 +34,7 @@ async function fakePackage(options: {
llvm_version: "22.1.8",
host_triple: "arm64-apple-darwin24.0.0",
targets: ["AArch64"],
supported_targets: [MACOS_ARM64_TARGET.llvmTriple],
default_target: MACOS_ARM64_TARGET.llvmTriple,
data_layout: MACOS_ARM64_TARGET.dataLayout,
});
@@ -63,18 +64,57 @@ ${options.emitFailure === true
return { packageJson, bin, log, root };
}
function request(root: string, packageJson: string, output = join(root, "program.o")) {
function request(root: string, packageJson: string, output = join(root, "program.o"), target = MACOS_ARM64_TARGET) {
return {
outputPath: output,
llvm: "define i32 @answer() { ret i32 42 }\n",
outputKind: "obj" as const,
sourcePath: "/source/app.ts",
target: MACOS_ARM64_TARGET,
target,
resolvePackageJson: () => packageJson,
cacheRoot: join(root, "cache"),
};
}
test("resolves the target's matching platform helper rather than a fixed macOS package", async () => {
const pkg = await fakePackage();
const output = join(pkg.root, "linux.o");
await writeFile(pkg.packageJson, JSON.stringify({ name: LINUX_X64_GNU_TARGET.helperPackage }));
await writeFile(pkg.bin, (await readFile(pkg.bin, "utf8"))
.replaceAll(MACOS_ARM64_TARGET.llvmTriple, LINUX_X64_GNU_TARGET.helper.defaultTarget)
.replaceAll(MACOS_ARM64_TARGET.dataLayout, LINUX_X64_GNU_TARGET.helper.defaultDataLayout)
.replaceAll('"targets":["AArch64"]', '"targets":["X86"]')
.replaceAll(`"supported_targets":["${MACOS_ARM64_TARGET.llvmTriple}"]`, `"supported_targets":["${LINUX_X64_GNU_TARGET.llvmTriple}"]`));
await emitNativeArtifact({
...request(pkg.root, pkg.packageJson, output, LINUX_X64_GNU_TARGET),
helperHost: { platform: "linux", arch: "x64" },
});
expect(await readFile(output, "utf8")).toContain("define i32 @answer");
});
test("requires a portable target backend in addition to the host helper backend", async () => {
const pkg = await fakePackage();
await expect(emitNativeArtifact({
...request(pkg.root, pkg.packageJson, join(pkg.root, "program.wasm"), WASM32_WASI_TARGET),
helperHost: { platform: "darwin", arch: "arm64" },
})).rejects.toMatchObject({ diagnosticCode: "SC3003", detailCode: "version_mismatch" });
});
test("accepts a WASI-capable helper whose default host target differs", () => {
const helper = WASM32_WASI_TARGET.hostHelpers?.["darwin-arm64"]!;
expect(validateNativeCodegenVersion({
ok: true,
protocol_version: "1",
scriptc_package_version: compilerReleaseVersion(),
llvm_version: "22.1.8",
host_triple: "arm64-apple-darwin25.0.0",
targets: ["AArch64", "WebAssembly"],
supported_targets: [helper.defaultTarget, WASM32_WASI_TARGET.llvmTriple],
default_target: helper.defaultTarget,
data_layout: helper.defaultDataLayout,
}, WASM32_WASI_TARGET, helper).targets).toContain("WebAssembly");
});
test("resolves a package helper, emits atomically, and caches by all native inputs", async () => {
const pkg = await fakePackage();
const first = join(pkg.root, "first.o");
+66 -22
View File
@@ -19,7 +19,13 @@ import {
snapshotNativeArtifactDependencies,
type NativeArtifactDependency,
} from "./native-toolchain.js";
import { nativeCodegenTarget, nativeCodegenTargetRefusal, type NativeTargetSpec } from "./targets.js";
import {
nativeCodegenTarget,
nativeCodegenTargetRefusal,
nativeHelperForTarget,
type NativeHelperSpec,
type NativeTargetSpec,
} from "./targets.js";
import { compilerReleaseVersion } from "../library/sidecar.js";
const execFileAsync = promisify(execFile);
@@ -46,6 +52,7 @@ export interface NativeCodegenVersion {
llvm_version: string;
host_triple: string;
targets: string[];
supported_targets: string[];
default_target: string;
data_layout: string;
}
@@ -78,6 +85,8 @@ export interface NativeCodegenOptions {
optimization?: "0" | "1" | "2" | "3" | "s" | "z";
sanitize?: boolean;
target?: NativeTargetSpec;
/** Test seam for package selection on a simulated host. */
helperHost?: { platform: NodeJS.Platform; arch: string };
/** Test seam: still resolves a package path, never searches PATH. */
resolvePackageJson?: (specifier: string) => string;
/** Internal/test override; omitted production calls use the shared cache. */
@@ -122,12 +131,16 @@ async function invoke(binaryPath: string, args: string[]): Promise<{ stdout: str
}
}
function validateVersion(value: Record<string, unknown>, target: NativeTargetSpec): NativeCodegenVersion {
export function validateNativeCodegenVersion(
value: Record<string, unknown>,
target: NativeTargetSpec,
helper: NativeHelperSpec,
): NativeCodegenVersion {
const expectedPackageVersion = compilerReleaseVersion();
const mismatch = (field: string, expected: string): never => {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper is incompatible: ${field} is ${JSON.stringify(value[field])}, expected ${JSON.stringify(expected)}; reinstall scriptc so its compiler and ${target.helperPackage} packages have matching versions`,
`LLVM native helper is incompatible: ${field} is ${JSON.stringify(value[field])}, expected ${JSON.stringify(expected)}; reinstall scriptc so its compiler and ${helper.packageName} packages have matching versions`,
"version_mismatch",
);
};
@@ -141,14 +154,17 @@ function validateVersion(value: Record<string, unknown>, target: NativeTargetSpe
if (value["llvm_version"] !== NATIVE_CODEGEN_LLVM_VERSION) {
mismatch("llvm_version", NATIVE_CODEGEN_LLVM_VERSION);
}
if (value["default_target"] !== target.llvmTriple) {
mismatch("default_target", target.llvmTriple);
if (value["default_target"] !== helper.defaultTarget) {
mismatch("default_target", helper.defaultTarget);
}
if (value["data_layout"] !== target.dataLayout) {
mismatch("data_layout", target.dataLayout);
if (value["data_layout"] !== helper.defaultDataLayout) {
mismatch("data_layout", helper.defaultDataLayout);
}
if (!Array.isArray(value["targets"]) || !value["targets"].includes("AArch64")) {
mismatch("targets", "an array containing AArch64");
if (!Array.isArray(value["targets"]) || !value["targets"].includes(target.llvmBackend)) {
mismatch("targets", `an array containing ${target.llvmBackend}`);
}
if (!Array.isArray(value["supported_targets"]) || !value["supported_targets"].includes(target.llvmTriple)) {
mismatch("supported_targets", `an array containing ${target.llvmTriple}`);
}
if (typeof value["host_triple"] !== "string") mismatch("host_triple", "a string");
return value as unknown as NativeCodegenVersion;
@@ -157,20 +173,37 @@ function validateVersion(value: Record<string, unknown>, target: NativeTargetSpe
async function resolveHelper(
target: NativeTargetSpec,
resolver?: (specifier: string) => string,
host?: { platform: NodeJS.Platform; arch: string },
): Promise<ResolvedHelper> {
const helper = nativeHelperForTarget(
target,
host?.platform,
host?.arch,
);
if (helper === null) {
throw new NativeCodegenError(
"SC3002",
`no scriptc LLVM helper supports ${target.name} on this host`,
"unsupported_helper_host",
);
}
const resolvePackageJson = resolver ?? ((specifier: string) =>
createRequire(import.meta.url).resolve(specifier));
let packageJsonPath: string;
try {
packageJsonPath = resolvePackageJson(`${target.helperPackage}/package.json`);
packageJsonPath = resolvePackageJson(`${helper.packageName}/package.json`);
} catch {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} is not installed; reinstall scriptc with optional dependencies enabled for macOS arm64`,
`LLVM native helper package ${helper.packageName} is not installed; reinstall scriptc with optional dependencies enabled for this host`,
"missing_package",
);
}
const binaryPath = join(dirname(packageJsonPath), "bin", "scriptc-llvm-codegen");
const binaryPath = join(
dirname(packageJsonPath),
"bin",
process.platform === "win32" ? "scriptc-llvm-codegen.exe" : "scriptc-llvm-codegen",
);
let binaryStat;
try {
binaryStat = await stat(binaryPath);
@@ -178,7 +211,7 @@ async function resolveHelper(
} catch {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} is incomplete: ${binaryPath} is missing; reinstall scriptc`,
`LLVM native helper package ${helper.packageName} is incomplete: ${binaryPath} is missing; reinstall scriptc`,
"missing_binary",
);
}
@@ -191,7 +224,7 @@ async function resolveHelper(
} catch {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} is incomplete: ${binaryPath} is not readable and executable; reinstall scriptc`,
`LLVM native helper package ${helper.packageName} is incomplete: ${binaryPath} is not readable and executable; reinstall scriptc`,
"unusable_binary",
);
}
@@ -201,11 +234,15 @@ async function resolveHelper(
} catch {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} changed while its inputs were being inspected; retry the build or reinstall scriptc`,
`LLVM native helper package ${helper.packageName} changed while its inputs were being inspected; retry the build or reinstall scriptc`,
"helper_changed",
);
}
const cacheKey = JSON.stringify(dependencies);
// A single host helper can own more than one backend (for example its
// host-native ISA plus WebAssembly). Cache only after the target backend
// has also been checked; otherwise a prior X86 lookup could accidentally
// bless a later WASI request against an older X86-only package.
const cacheKey = JSON.stringify({ dependencies, targetBackend: target.llvmBackend });
const load = async (): Promise<ResolvedHelper> => {
let stdout: string;
let binary: Buffer;
@@ -217,7 +254,7 @@ async function resolveHelper(
} catch (error) {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} could not be read and executed for its identity check: ${error instanceof Error ? error.message : String(error)}; reinstall scriptc`,
`LLVM native helper package ${helper.packageName} could not be read and executed for its identity check: ${error instanceof Error ? error.message : String(error)}; reinstall scriptc`,
"identity_probe_failed",
);
}
@@ -225,14 +262,14 @@ async function resolveHelper(
if (rawVersion === null) {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper returned an invalid version response; reinstall scriptc and ${target.helperPackage}`,
`LLVM native helper returned an invalid version response; reinstall scriptc and ${helper.packageName}`,
"invalid_version_response",
);
}
if (!(await nativeArtifactDependenciesStillMatch(dependencies).catch(() => false))) {
throw new NativeCodegenError(
"SC3003",
`LLVM native helper package ${target.helperPackage} changed during its identity check; retry the build or reinstall scriptc`,
`LLVM native helper package ${helper.packageName} changed during its identity check; retry the build or reinstall scriptc`,
"helper_changed",
);
}
@@ -241,9 +278,9 @@ async function resolveHelper(
binaryPath,
dependencies,
identity: {
packageName: target.helperPackage,
packageName: helper.packageName,
binaryDigest: createHash("sha256").update(binary).digest("hex"),
version: validateVersion(rawVersion, target),
version: validateNativeCodegenVersion(rawVersion, target, helper),
},
};
};
@@ -304,6 +341,13 @@ export async function emitNativeArtifact(options: NativeCodegenOptions): Promise
"unsupported_target",
);
}
if (!target.supports[options.outputKind]) {
throw new NativeCodegenError(
"SC3002",
`--emit=${options.outputKind} is not supported for ${target.name}`,
"unsupported_output_kind",
);
}
if (options.sanitize === true) {
throw new NativeCodegenError(
"SC3002",
@@ -311,7 +355,7 @@ export async function emitNativeArtifact(options: NativeCodegenOptions): Promise
"sanitize_unsupported",
);
}
const helper = await resolveHelper(target, options.resolvePackageJson);
const helper = await resolveHelper(target, options.resolvePackageJson, options.helperHost);
const root = await prepareBuildCacheRoot(
options.cacheRoot === undefined ? buildCacheRoot() : options.cacheRoot,
);
@@ -68,7 +68,7 @@ export interface NativeLinkInfo {
target: {
name: NativeTargetSpec["name"];
llvm_triple: NativeTargetSpec["llvmTriple"];
architecture: "arm64";
architecture: NativeTargetSpec["architecture"];
object_format: NativeTargetSpec["objectFormat"];
minimum_os: NativeTargetSpec["minimumOs"];
relocation_model: NativeTargetSpec["relocationModel"];
@@ -117,11 +117,8 @@ function runtimeSourceRecipe(
env: NodeJS.ProcessEnv,
optimization: "release" | "dev",
): { sets: NativeSourceSet[]; systemLibraries: string[]; driverFlags: string[] } {
// Native link info currently describes the supported Mach-O external-object
// recipe. Keep this in the same target-aware helper as compileC so a
// consumer's link has the identical executable reachability semantics.
const executableSectionFlags = executableSectionEliminationFlags("darwin");
const dynamic = features.dynamic;
const executableSectionFlags = executableSectionEliminationFlags(target.platform);
const curlFetch = dynamic && features.fetch && env["SCRIPTC_FETCH_CURL"] === "1";
const nativeFetch = features.fetch && !curlFetch;
const netIsland = dynamic && (features.netIsland || nativeFetch);
@@ -129,8 +126,19 @@ function runtimeSourceRecipe(
const http = features.http || nativeFetch || netIsland;
const tls = features.tls || nativeFetch || netIsland;
const tlsCa = features.tlsCa || tls;
const wasiUnavailableSources = new Set([
"scr_child.c", "scr_dyn_handle.c", "scr_loop_kqueue.c", "scr_loop_epoll.c",
"scr_loop_wsapoll.c", "scr_net.c", "scr_http.c", "scr_http2.c", "scr_dgram.c",
"scr_watch.c", "scr_tls_ca.c", "scr_tls.c", "scr_fetch.c", "scr_net_island.c",
]);
const runtimeSources = [
...EXECUTABLE_RUNTIME_SOURCES,
...EXECUTABLE_RUNTIME_SOURCES.filter((source) =>
target.platform !== "wasi" || !wasiUnavailableSources.has(source)
),
...(target.platform === "win32" ? ["scr_win.c"] : []),
...(target.name === "linux-x64-musl" || target.name === "linux-arm64-musl"
? ["scr_musl.c"]
: []),
...(features.copying ? ["scr_copying.c"] : []),
...(features.fileHandle ? ["scr_file_handle.c"] : []),
...(features.regex ? ["scr_regex.c"] : []),
@@ -174,12 +182,14 @@ function runtimeSourceRecipe(
]
: []),
];
const commonTargetFlags = ["-target", target.llvmTriple];
const filteredSources = target.platform === "wasi"
? runtimeSources.filter((source) => !wasiUnavailableSources.has(source))
: runtimeSources;
const sets: NativeSourceSet[] = [{
name: "runtime",
output: "objects",
suggested_output: "runtime/*.o",
sources: unique(runtimeSources).map((source) => packagePath("src", source)),
sources: unique(filteredSources).map((source) => packagePath("src", source)),
include_directories: unique([
"src",
...(features.regex || dynamic ? ["vendor/quickjs-ng"] : []),
@@ -187,11 +197,13 @@ function runtimeSourceRecipe(
...(curlFetch ? ["vendor/curl/include"] : []),
]),
defines: unique([
...target.runtimeCompileDefines,
...(features.textDecoderLegacy ? ["SCR_TEXT_DECODER_LEGACY"] : []),
...(dynamic ? ["SCR_DYNAMIC"] : []),
]),
c_flags: [
"-std=c11", ...commonTargetFlags, "-pthread",
"-std=c11", "-target", target.llvmTriple,
...(target.platform === "win32" || target.platform === "wasi" ? [] : ["-pthread"]),
optimization === "dev" ? "-O0" : "-O2",
...executableSectionFlags.compile,
"-fno-math-errno", "-fno-strict-aliasing", "-Wno-deprecated-declarations",
@@ -205,7 +217,11 @@ function runtimeSourceRecipe(
sources: LRE_SOURCES.map((source) => packagePath("vendor", "quickjs-ng", source)),
include_directories: ["vendor/quickjs-ng"],
defines: [],
c_flags: ["-std=c11", ...commonTargetFlags, "-Os"],
c_flags: [
"-std=c11", "-target", target.llvmTriple,
...target.runtimeCompileDefines.map((define) => `-D${define}`),
"-Os",
],
});
}
if (dynamic) {
@@ -217,7 +233,9 @@ function runtimeSourceRecipe(
include_directories: ["vendor/quickjs-ng"],
defines: ["QUICKJS_NG_BUILD", "_GNU_SOURCE", "NDEBUG"],
c_flags: [
"-std=gnu11", ...commonTargetFlags, "-fvisibility=hidden",
"-std=gnu11", "-target", target.llvmTriple,
...target.runtimeCompileDefines.map((define) => `-D${define}`),
"-fvisibility=hidden",
"-funsigned-char", "-Os",
],
});
@@ -233,22 +251,29 @@ function runtimeSourceRecipe(
sources: [],
include_directories: ["vendor/mbedtls/include", "vendor/mbedtls/library"],
defines: [],
c_flags: ["-std=c11", ...commonTargetFlags, "-Os"],
c_flags: [
"-std=c11", "-target", target.llvmTriple,
...target.runtimeCompileDefines.map((define) => `-D${define}`),
"-Os",
],
});
}
if (target.platform === "wasi") {
const blocked = features.net || features.http || features.http2 || features.dgram ||
features.watch || features.tls || features.tlsCa || features.fetch || features.netIsland;
if (blocked) throw new Error("WASI native link info requested an unavailable network/TLS runtime surface");
}
const systemLibraries = unique([
"System",
...((features.zlib || nativeFetch) ? ["z"] : []),
...(dynamic ? ["m"] : []),
...target.runtimeSystemLibraries,
...((features.zlib || nativeFetch) && target.platform !== "win32" ? ["z"] : []),
...(dynamic && target.platform !== "win32" ? ["m"] : []),
...(curlFetch ? ["curl"] : []),
]);
return {
sets,
systemLibraries,
driverFlags: [
...commonTargetFlags,
"-pthread",
...executableSectionFlags.link,
...target.executableLinkerArgs,
],
};
}
@@ -293,7 +318,7 @@ export async function createNativeLinkInfo(options: {
target: {
name: options.target.name,
llvm_triple: options.target.llvmTriple,
architecture: "arm64",
architecture: options.target.architecture,
object_format: options.target.objectFormat,
minimum_os: options.target.minimumOs,
relocation_model: options.target.relocationModel,
@@ -0,0 +1,66 @@
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, expect, test } from "vitest";
import { loadRuntimePack, type RuntimePackManifest } from "./runtime-pack.js";
import {
LINUX_ARM64_GNU_TARGET,
LINUX_ARM64_MUSL_TARGET,
LINUX_X64_GNU_TARGET,
LINUX_X64_MUSL_TARGET,
MACOS_X64_TARGET,
WASM32_WASI_TARGET,
WINDOWS_X64_MSVC_TARGET,
type NativeTargetSpec,
} from "./targets.js";
import { compilerReleaseVersion } from "../library/sidecar.js";
const dirs: string[] = [];
afterEach(async () => Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))));
async function fixture(target: NativeTargetSpec) {
const root = await mkdtemp(join(tmpdir(), "scriptc-runtime-pack-target-"));
dirs.push(root);
const packagePath = join(root, "package.json");
const bytes = Buffer.from("runtime object");
const artifact = { path: "artifacts/runtime.o", sha256: createHash("sha256").update(bytes).digest("hex"), size: bytes.length };
await mkdir(dirname(join(root, artifact.path)), { recursive: true });
await Promise.all([
writeFile(join(root, artifact.path), bytes),
writeFile(join(root, "LICENSE"), "license"),
writeFile(packagePath, JSON.stringify({ name: target.runtimePackPackage, version: compilerReleaseVersion() })),
]);
const manifest: RuntimePackManifest = {
schema: "scriptc.runtime-pack.v1", format: 1, package: target.runtimePackPackage, version: compilerReleaseVersion(),
target: { name: target.name, llvm_triple: target.llvmTriple, architecture: target.architecture, object_format: target.objectFormat, minimum_os: target.minimumOs },
runtime_abi: { version: 1, marker: "scr_runtime_abi_v1" },
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
flavors: {
release: { optimization: "-O2", runtime_units: [{ source: "scr_runtime.c", predicate: true, variants: [{ id: "default", when: {}, defines: [], ...artifact }] }] },
dev: { optimization: "-O0", runtime_units: [{ source: "scr_runtime.c", predicate: true, variants: [{ id: "default", when: {}, defines: [], ...artifact }] }] },
},
archives: [], system_libraries: [], licenses: [{ path: "LICENSE", license: "fixture" }],
};
await writeFile(join(root, "runtime-pack.json"), JSON.stringify(manifest));
return { packagePath };
}
const features = {
dynamic: false, regex: false, copying: false, textDecoderLegacy: false, fileHandle: false,
fetch: false, netIsland: false, zlib: false, assert: false, inspect: false, dynInvoke: false,
dc: false, dynAsync: false, events: false, emitter: false, symbol: false, searchParams: false,
qs: false, parseArgs: false, stream: false, net: false, http: false, http2: false,
dgram: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, tlsCa: false,
};
test.each([
MACOS_X64_TARGET, LINUX_X64_GNU_TARGET, LINUX_ARM64_GNU_TARGET,
WINDOWS_X64_MSVC_TARGET, LINUX_X64_MUSL_TARGET, LINUX_ARM64_MUSL_TARGET, WASM32_WASI_TARGET,
])("runtime-pack validation selects the exact $name package", async (target) => {
const pack = await fixture(target);
const result = await loadRuntimePack({ target, features, optimization: "release", resolver: () => pack.packagePath });
expect(result.manifest.package).toBe(target.runtimePackPackage);
expect(result.manifest.target).toMatchObject({ name: target.name, architecture: target.architecture, object_format: target.objectFormat });
});
+15 -8
View File
@@ -51,7 +51,7 @@ export interface RuntimePackManifest {
target: {
name: NativeTargetSpec["name"];
llvm_triple: NativeTargetSpec["llvmTriple"];
architecture: "arm64";
architecture: NativeTargetSpec["architecture"];
object_format: NativeTargetSpec["objectFormat"];
minimum_os: NativeTargetSpec["minimumOs"];
};
@@ -157,11 +157,10 @@ export function parseRuntimePackManifest(value: unknown): RuntimePackManifest {
if (
manifest?.schema !== RUNTIME_PACK_SCHEMA || manifest.format !== RUNTIME_PACK_FORMAT ||
typeof manifest.package !== "string" || typeof manifest.version !== "string" ||
target?.name !== "macos-arm64" || target.llvm_triple !== "arm64-apple-macosx14.0.0" ||
target.architecture !== "arm64" || target.object_format !== "macho" || target.minimum_os !== "14.0" ||
!isRuntimePackTarget(target) ||
abi?.version !== RUNTIME_ABI_VERSION || abi.marker !== RUNTIME_ABI_MARKER ||
typeof compiler?.command !== "string" || typeof compiler.identity !== "string" ||
compiler.target !== target.llvm_triple ||
compiler.target !== (target?.llvm_triple) ||
!Array.isArray(macros?.executable) || !macros.executable.every((entry) => typeof entry === "string") ||
!Array.isArray(macros.excluded) || !macros.excluded.every((entry) => typeof entry === "string") ||
macros.sanitizer !== "external-toolchain-required" ||
@@ -182,6 +181,15 @@ export function parseRuntimePackManifest(value: unknown): RuntimePackManifest {
return manifest as unknown as RuntimePackManifest;
}
function isRuntimePackTarget(target: Record<string, unknown> | null): boolean {
return target !== null &&
typeof target.name === "string" &&
typeof target.llvm_triple === "string" &&
(target.architecture === "arm64" || target.architecture === "x64" || target.architecture === "wasm32") &&
(target.object_format === "macho" || target.object_format === "elf" || target.object_format === "coff" || target.object_format === "wasm") &&
typeof target.minimum_os === "string";
}
export function effectiveRuntimeFeatures(
features: NativeLinkFeatures,
env: NodeJS.ProcessEnv = process.env,
@@ -324,16 +332,14 @@ export async function loadRuntimePack(options: {
env?: NodeJS.ProcessEnv;
resolver?: (specifier: string) => string;
}): Promise<RuntimePackSelection> {
const packageName = options.target.name === "macos-arm64"
? "@scriptc/runtime-darwin-arm64"
: (() => { throw new RuntimePackError(`no runtime pack supports ${options.target.name}`, "unsupported"); })();
const packageName = options.target.runtimePackPackage;
const resolvePackageJson = options.resolver ?? ((specifier: string) => createRequire(import.meta.url).resolve(specifier));
let packagePath: string;
try {
packagePath = resolvePackageJson(`${packageName}/package.json`);
} catch {
throw new RuntimePackError(
`precompiled runtime package ${packageName} is not installed; reinstall scriptc with optional dependencies enabled for macOS arm64`,
`precompiled runtime package ${packageName} is not installed; reinstall scriptc with optional dependencies enabled for ${options.target.name}`,
"missing",
);
}
@@ -368,6 +374,7 @@ export async function loadRuntimePack(options: {
if (
manifest.target.name !== options.target.name ||
manifest.target.llvm_triple !== options.target.llvmTriple ||
manifest.target.architecture !== options.target.architecture ||
manifest.target.object_format !== options.target.objectFormat ||
manifest.target.minimum_os !== options.target.minimumOs
) throw new RuntimePackError(`runtime pack does not support target ${options.target.name}`, "invalid");
+31 -5
View File
@@ -1,12 +1,34 @@
import { describe, expect, test } from "vitest";
import { MACOS_ARM64_TARGET, nativeCodegenTarget, nativeCodegenTargetRefusal } from "./targets.js";
import {
LINUX_ARM64_GNU_TARGET,
LINUX_ARM64_MUSL_TARGET,
LINUX_X64_GNU_TARGET,
LINUX_X64_MUSL_TARGET,
MACOS_ARM64_TARGET,
MACOS_X64_TARGET,
WASM32_WASI_TARGET,
WINDOWS_X64_MSVC_TARGET,
nativeCodegenTarget,
nativeCodegenTargetRefusal,
} from "./targets.js";
describe("native code-generation targets", () => {
test("admits only host-native macOS arm64", () => {
test("selects only fully specified host-native targets", () => {
expect(nativeCodegenTarget({}, "darwin", "arm64", "24.0.0")).toEqual(MACOS_ARM64_TARGET);
expect(nativeCodegenTarget({}, "darwin", "x64", "24.0.0")).toEqual(MACOS_X64_TARGET);
expect(nativeCodegenTarget({}, "linux", "x64", "6.8.0", "gnu")).toEqual(LINUX_X64_GNU_TARGET);
expect(nativeCodegenTarget({}, "linux", "arm64", "6.8.0", "gnu")).toEqual(LINUX_ARM64_GNU_TARGET);
expect(nativeCodegenTarget({}, "linux", "x64", "6.8.0", "musl")).toEqual(LINUX_X64_MUSL_TARGET);
expect(nativeCodegenTarget({}, "linux", "arm64", "6.8.0", "musl")).toEqual(LINUX_ARM64_MUSL_TARGET);
expect(nativeCodegenTarget({}, "win32", "x64", "10.0.0")).toEqual(WINDOWS_X64_MSVC_TARGET);
expect(nativeCodegenTarget({}, "darwin", "arm64", "23.6.0")).toBeNull();
expect(nativeCodegenTarget({}, "darwin", "x64", "24.0.0")).toBeNull();
expect(nativeCodegenTarget({}, "linux", "arm64", "6.8.0")).toBeNull();
expect(nativeCodegenTarget({}, "linux", "ia32", "6.8.0")).toBeNull();
expect(nativeCodegenTarget({ SCRIPTC_TARGET: "x86_64-linux-musl" }, "linux", "x64", "6.8.0", "musl"))
.toEqual(LINUX_X64_MUSL_TARGET);
expect(nativeCodegenTarget({ SCRIPTC_TARGET: "aarch64-linux-musl" }, "linux", "arm64", "6.8.0", "musl"))
.toEqual(LINUX_ARM64_MUSL_TARGET);
expect(nativeCodegenTarget({ SCRIPTC_TARGET: "wasm32-wasi" }, "darwin", "x64", "24.0.0"))
.toEqual(WASM32_WASI_TARGET);
expect(nativeCodegenTarget(
{ SCRIPTC_TARGET: "aarch64-apple-ios" }, "darwin", "arm64", "24.0.0",
))
@@ -14,7 +36,7 @@ describe("native code-generation targets", () => {
});
test("refusals name the unsupported host or cross target", () => {
expect(nativeCodegenTargetRefusal({}, "linux", "x64", "6.8.0")).toContain("linux x64");
expect(nativeCodegenTargetRefusal({}, "linux", "ia32", "6.8.0")).toContain("linux ia32");
expect(nativeCodegenTargetRefusal({}, "darwin", "arm64", "23.6.0"))
.toContain("requires macOS 15.0 or newer");
expect(nativeCodegenTargetRefusal(
@@ -33,4 +55,8 @@ describe("native code-generation targets", () => {
"-Wl,-dead_strip",
]);
});
test("describes the WASI relocatable-object ABI", () => {
expect(WASM32_WASI_TARGET.supports).toMatchObject({ asm: true, obj: true, exe: true });
});
});
+346 -47
View File
@@ -1,60 +1,360 @@
import { release } from "node:os";
/** A helper package is host-specific, even when it emits a portable target
* (the WASI helpers are the first intentionally cross-host case). */
export interface NativeHelperSpec {
packageName: string;
/** The helper protocol reports this primary target and its layout. */
defaultTarget: string;
defaultDataLayout: string;
backend: "AArch64" | "X86" | "WebAssembly";
}
export type NativeTargetName =
| "macos-arm64"
| "macos-x64"
| "linux-x64-gnu"
| "linux-arm64-gnu"
| "windows-x64-msvc"
| "linux-x64-musl"
| "linux-arm64-musl"
| "wasm32-wasi";
export type NativeObjectFormat = "macho" | "elf" | "coff" | "wasm";
export type NativeTargetPlatform = "darwin" | "linux" | "win32" | "wasi";
export interface NativeTargetSpec {
/** Stable scriptc-facing identity used in cache keys and diagnostics. */
name: "macos-arm64";
llvmTriple: "arm64-apple-macosx14.0.0";
dataLayout: "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32";
name: NativeTargetName;
llvmTriple: string;
dataLayout: string;
cpu: "generic";
features: "";
pointerBits: 64;
pointerBits: 32 | 64;
endianness: "little";
objectFormat: "macho";
objectFormat: NativeObjectFormat;
relocationModel: "pic";
codeModel: "small";
minimumOs: "14.0";
helperMinimumOs: "15.0";
outputSuffixes: { asm: ".s"; obj: ".o"; exe: "" };
/** OS deployment level, libc floor, or WASI ABI contract. */
minimumOs: string;
/** Minimum host contract for the helper package itself. */
helperMinimumOs: string;
architecture: "arm64" | "x64" | "wasm32";
platform: NativeTargetPlatform;
outputSuffixes: { asm: ".s" | ".asm"; obj: ".o" | ".obj"; exe: "" | ".exe" | ".wasm" };
/** Arguments for the platform linker driver once scriptc has produced an
* object. These are target ABI policy, rather than C compiler settings. */
executableLinkerArgs: readonly ["-target", "arm64-apple-macosx14.0.0", "-pthread", "-Wl,-dead_strip"];
supports: { asm: true; obj: true; exe: true; library: false };
helperPackage: "@scriptc/llvm-darwin-arm64";
executableLinkerArgs: readonly string[];
defaultLinker: string;
defaultLinkerArgs: readonly string[];
/** Driver spelling for the native-codegen triple. Linker drivers such as
* zig use a different user-facing triple from LLVM's canonical spelling. */
linkerTargetTriple: string;
runtimeSystemLibraries: readonly string[];
/** A source-level flag needed by the matching precompiled runtime units.
* The helper's program object never needs this (it is already LLVM IR). */
runtimeCompileDefines: readonly string[];
supports: { asm: boolean; obj: boolean; exe: boolean; library: boolean };
/** Primary host-native helper. The resolved helper can differ for WASI. */
helperPackage: string;
helper: NativeHelperSpec;
llvmBackend: NativeHelperSpec["backend"];
hostHelpers?: Partial<Record<NativeHelperHost, NativeHelperSpec>>;
runtimePackPackage: string;
}
export const MACOS_ARM64_TARGET: NativeTargetSpec = {
name: "macos-arm64",
llvmTriple: "arm64-apple-macosx14.0.0",
dataLayout: "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32",
cpu: "generic",
features: "",
pointerBits: 64,
endianness: "little",
objectFormat: "macho",
relocationModel: "pic",
codeModel: "small",
minimumOs: "14.0",
helperMinimumOs: "15.0",
outputSuffixes: { asm: ".s", obj: ".o", exe: "" },
executableLinkerArgs: ["-target", "arm64-apple-macosx14.0.0", "-pthread", "-Wl,-dead_strip"],
supports: { asm: true, obj: true, exe: true, library: false },
helperPackage: "@scriptc/llvm-darwin-arm64",
export type NativeHelperHost =
| "darwin-arm64"
| "darwin-x64"
| "linux-x64-gnu"
| "linux-x64-musl"
| "linux-arm64-gnu"
| "linux-arm64-musl"
| "win32-x64";
export type LinuxLibc = "gnu" | "musl";
const ARM64_DARWIN_LAYOUT = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32";
const X64_DARWIN_LAYOUT = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
const X64_ELF_LAYOUT = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
const ARM64_ELF_LAYOUT = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32";
const X64_COFF_LAYOUT = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
const WASI_LAYOUT = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20";
const DARWIN_ARM64_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-darwin-arm64",
defaultTarget: "arm64-apple-macosx14.0.0",
defaultDataLayout: ARM64_DARWIN_LAYOUT,
backend: "AArch64",
};
const DARWIN_X64_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-darwin-x64",
defaultTarget: "x86_64-apple-macosx14.0.0",
defaultDataLayout: X64_DARWIN_LAYOUT,
backend: "X86",
};
const LINUX_X64_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-linux-x64-gnu",
defaultTarget: "x86_64-unknown-linux-gnu",
defaultDataLayout: X64_ELF_LAYOUT,
backend: "X86",
};
const LINUX_ARM64_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-linux-arm64-gnu",
defaultTarget: "aarch64-unknown-linux-gnu",
defaultDataLayout: ARM64_ELF_LAYOUT,
backend: "AArch64",
};
const LINUX_X64_MUSL_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-linux-x64-musl",
defaultTarget: "x86_64-unknown-linux-musl",
defaultDataLayout: X64_ELF_LAYOUT,
backend: "X86",
};
const LINUX_ARM64_MUSL_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-linux-arm64-musl",
defaultTarget: "aarch64-unknown-linux-musl",
defaultDataLayout: ARM64_ELF_LAYOUT,
backend: "AArch64",
};
const WINDOWS_X64_HELPER: NativeHelperSpec = {
packageName: "@scriptc/llvm-win32-x64-msvc",
defaultTarget: "x86_64-pc-windows-msvc",
defaultDataLayout: X64_COFF_LAYOUT,
backend: "X86",
};
export const MACOS_ARM64_TARGET: NativeTargetSpec = {
name: "macos-arm64", llvmTriple: "arm64-apple-macosx14.0.0", dataLayout: ARM64_DARWIN_LAYOUT,
cpu: "generic", features: "", pointerBits: 64, endianness: "little", objectFormat: "macho",
relocationModel: "pic", codeModel: "small", minimumOs: "14.0", helperMinimumOs: "15.0",
architecture: "arm64", platform: "darwin", outputSuffixes: { asm: ".s", obj: ".o", exe: "" },
executableLinkerArgs: ["-target", "arm64-apple-macosx14.0.0", "-pthread", "-Wl,-dead_strip"],
defaultLinker: "clang", defaultLinkerArgs: [], runtimeSystemLibraries: ["System"],
linkerTargetTriple: "arm64-apple-macosx14.0.0",
runtimeCompileDefines: [],
supports: { asm: true, obj: true, exe: true, library: false },
helperPackage: DARWIN_ARM64_HELPER.packageName, helper: DARWIN_ARM64_HELPER,
llvmBackend: "AArch64",
runtimePackPackage: "@scriptc/runtime-darwin-arm64",
};
export const MACOS_X64_TARGET: NativeTargetSpec = {
name: "macos-x64", llvmTriple: "x86_64-apple-macosx14.0.0", dataLayout: X64_DARWIN_LAYOUT,
cpu: "generic", features: "", pointerBits: 64, endianness: "little", objectFormat: "macho",
relocationModel: "pic", codeModel: "small", minimumOs: "14.0", helperMinimumOs: "15.0",
architecture: "x64", platform: "darwin", outputSuffixes: { asm: ".s", obj: ".o", exe: "" },
executableLinkerArgs: ["-target", "x86_64-apple-macosx14.0.0", "-pthread", "-Wl,-dead_strip"],
defaultLinker: "clang", defaultLinkerArgs: [], runtimeSystemLibraries: ["System"],
linkerTargetTriple: "x86_64-apple-macosx14.0.0",
runtimeCompileDefines: [],
supports: { asm: true, obj: true, exe: true, library: false },
helperPackage: DARWIN_X64_HELPER.packageName, helper: DARWIN_X64_HELPER,
llvmBackend: "X86",
runtimePackPackage: "@scriptc/runtime-darwin-x64",
};
export const LINUX_X64_GNU_TARGET: NativeTargetSpec = {
name: "linux-x64-gnu", llvmTriple: "x86_64-unknown-linux-gnu", dataLayout: X64_ELF_LAYOUT,
cpu: "generic", features: "", pointerBits: 64, endianness: "little", objectFormat: "elf",
relocationModel: "pic", codeModel: "small", minimumOs: "glibc 2.36", helperMinimumOs: "glibc 2.35",
architecture: "x64", platform: "linux", outputSuffixes: { asm: ".s", obj: ".o", exe: "" },
executableLinkerArgs: ["-target", "x86_64-unknown-linux-gnu", "-pthread", "-Wl,--gc-sections"],
defaultLinker: "clang", defaultLinkerArgs: [], runtimeSystemLibraries: ["m"],
linkerTargetTriple: "x86_64-unknown-linux-gnu",
runtimeCompileDefines: ["_GNU_SOURCE"],
supports: { asm: true, obj: true, exe: true, library: true },
helperPackage: LINUX_X64_HELPER.packageName, helper: LINUX_X64_HELPER,
llvmBackend: "X86",
runtimePackPackage: "@scriptc/runtime-linux-x64-gnu",
};
export const LINUX_ARM64_GNU_TARGET: NativeTargetSpec = {
name: "linux-arm64-gnu", llvmTriple: "aarch64-unknown-linux-gnu", dataLayout: ARM64_ELF_LAYOUT,
cpu: "generic", features: "", pointerBits: 64, endianness: "little", objectFormat: "elf",
relocationModel: "pic", codeModel: "small", minimumOs: "glibc 2.36", helperMinimumOs: "glibc 2.35",
architecture: "arm64", platform: "linux", outputSuffixes: { asm: ".s", obj: ".o", exe: "" },
executableLinkerArgs: ["-target", "aarch64-unknown-linux-gnu", "-pthread", "-Wl,--gc-sections"],
defaultLinker: "clang", defaultLinkerArgs: [], runtimeSystemLibraries: ["m"],
linkerTargetTriple: "aarch64-unknown-linux-gnu",
runtimeCompileDefines: ["_GNU_SOURCE"],
supports: { asm: true, obj: true, exe: true, library: true },
helperPackage: LINUX_ARM64_HELPER.packageName, helper: LINUX_ARM64_HELPER,
llvmBackend: "AArch64",
runtimePackPackage: "@scriptc/runtime-linux-arm64-gnu",
};
export const WINDOWS_X64_MSVC_TARGET: NativeTargetSpec = {
name: "windows-x64-msvc", llvmTriple: "x86_64-pc-windows-msvc", dataLayout: X64_COFF_LAYOUT,
cpu: "generic", features: "", pointerBits: 64, endianness: "little", objectFormat: "coff",
relocationModel: "pic", codeModel: "small", minimumOs: "Windows 10", helperMinimumOs: "Windows 10",
architecture: "x64", platform: "win32", outputSuffixes: { asm: ".asm", obj: ".obj", exe: ".exe" },
// The runtime pack is built against Zig's MinGW-compatible Windows sysroot;
// its x64 COFF ABI is compatible with the helper's MSVC-flavored object.
executableLinkerArgs: ["-target", "x86_64-windows-gnu", "-Wl,--gc-sections"],
defaultLinker: "zig", defaultLinkerArgs: ["cc"], runtimeSystemLibraries: ["advapi32", "iphlpapi", "ws2_32"],
linkerTargetTriple: "x86_64-windows-gnu",
runtimeCompileDefines: [],
supports: { asm: true, obj: true, exe: true, library: true },
helperPackage: WINDOWS_X64_HELPER.packageName, helper: WINDOWS_X64_HELPER,
llvmBackend: "X86",
runtimePackPackage: "@scriptc/runtime-win32-x64-msvc",
};
export const LINUX_X64_MUSL_TARGET: NativeTargetSpec = {
...LINUX_X64_GNU_TARGET,
name: "linux-x64-musl", llvmTriple: "x86_64-unknown-linux-musl",
minimumOs: "musl 1.2", helperMinimumOs: "glibc 2.35",
executableLinkerArgs: ["-target", "x86_64-unknown-linux-musl", "-pthread", "-Wl,--gc-sections"],
defaultLinker: "zig", defaultLinkerArgs: ["cc"], linkerTargetTriple: "x86_64-linux-musl",
runtimePackPackage: "@scriptc/runtime-linux-x64-musl",
runtimeCompileDefines: ["_GNU_SOURCE", "SCR_MUSL"],
helperPackage: LINUX_X64_MUSL_HELPER.packageName,
helper: LINUX_X64_MUSL_HELPER,
};
export const LINUX_ARM64_MUSL_TARGET: NativeTargetSpec = {
...LINUX_ARM64_GNU_TARGET,
name: "linux-arm64-musl", llvmTriple: "aarch64-unknown-linux-musl",
minimumOs: "musl 1.2", helperMinimumOs: "glibc 2.35",
executableLinkerArgs: ["-target", "aarch64-unknown-linux-musl", "-pthread", "-Wl,--gc-sections"],
defaultLinker: "zig", defaultLinkerArgs: ["cc"], linkerTargetTriple: "aarch64-linux-musl",
runtimePackPackage: "@scriptc/runtime-linux-arm64-musl",
runtimeCompileDefines: ["_GNU_SOURCE", "SCR_MUSL"],
helperPackage: LINUX_ARM64_MUSL_HELPER.packageName,
helper: LINUX_ARM64_MUSL_HELPER,
};
export const WASM32_WASI_TARGET: NativeTargetSpec = {
name: "wasm32-wasi", llvmTriple: "wasm32-unknown-wasi", dataLayout: WASI_LAYOUT,
cpu: "generic", features: "", pointerBits: 32, endianness: "little", objectFormat: "wasm",
relocationModel: "pic", codeModel: "small", minimumOs: "WASI Preview 1", helperMinimumOs: "host helper contract",
architecture: "wasm32", platform: "wasi", outputSuffixes: { asm: ".s", obj: ".o", exe: ".wasm" },
executableLinkerArgs: ["-target", "wasm32-wasi"], defaultLinker: "zig", defaultLinkerArgs: ["cc"],
linkerTargetTriple: "wasm32-wasi",
runtimeSystemLibraries: ["wasi-emulated-signal", "wasi-emulated-process-clocks"],
runtimeCompileDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"],
supports: { asm: true, obj: true, exe: true, library: false },
helperPackage: DARWIN_ARM64_HELPER.packageName, helper: DARWIN_ARM64_HELPER,
llvmBackend: "WebAssembly",
hostHelpers: {
"darwin-arm64": DARWIN_ARM64_HELPER,
"darwin-x64": DARWIN_X64_HELPER,
"linux-x64-gnu": LINUX_X64_HELPER,
"linux-x64-musl": LINUX_X64_MUSL_HELPER,
"linux-arm64-gnu": LINUX_ARM64_HELPER,
"linux-arm64-musl": LINUX_ARM64_MUSL_HELPER,
"win32-x64": WINDOWS_X64_HELPER,
},
runtimePackPackage: "@scriptc/runtime-wasm32-wasi",
};
export const NATIVE_TARGETS = [
MACOS_ARM64_TARGET, MACOS_X64_TARGET, LINUX_X64_GNU_TARGET,
LINUX_ARM64_GNU_TARGET, WINDOWS_X64_MSVC_TARGET, LINUX_X64_MUSL_TARGET,
LINUX_ARM64_MUSL_TARGET, WASM32_WASI_TARGET,
] as const;
function detectedLinuxLibc(): LinuxLibc {
// Node exposes glibc's runtime version without any external command or
// filesystem probe. Its absence on Linux is the portable musl signal used
// by npm's own optional-dependency selection conventions.
const report = process.report?.getReport() as { header?: { glibcVersionRuntime?: unknown } } | undefined;
const header = report?.header;
return typeof header?.glibcVersionRuntime === "string" ? "gnu" : "musl";
}
function helperHost(
platform: NodeJS.Platform,
arch: string,
linuxLibc: LinuxLibc = detectedLinuxLibc(),
): NativeHelperHost | null {
if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
if (platform === "darwin" && arch === "x64") return "darwin-x64";
if (platform === "linux" && arch === "x64") return `linux-x64-${linuxLibc}`;
if (platform === "linux" && arch === "arm64") return `linux-arm64-${linuxLibc}`;
if (platform === "win32" && arch === "x64") return "win32-x64";
return null;
}
export function nativeHelperForTarget(
target: NativeTargetSpec,
hostPlatform: NodeJS.Platform = process.platform,
hostArch: string = process.arch,
linuxLibc?: LinuxLibc,
): NativeHelperSpec | null {
const host = helperHost(hostPlatform, hostArch, linuxLibc);
if (host === null) return null;
return target.hostHelpers?.[host] ?? (
target.platform === hostPlatform && target.architecture === hostArch ? target.helper : null
);
}
function nativeHostTarget(
hostPlatform: NodeJS.Platform,
hostArch: string,
hostRelease: string,
linuxLibc: LinuxLibc,
): NativeTargetSpec | null {
if (hostPlatform === "darwin") {
const major = Number.parseInt(hostRelease.split(".", 1)[0] ?? "", 10);
if (!Number.isFinite(major) || major < 24) return null;
return hostArch === "arm64" ? MACOS_ARM64_TARGET : hostArch === "x64" ? MACOS_X64_TARGET : null;
}
if (hostPlatform === "linux") {
return hostArch === "x64"
? linuxLibc === "musl" ? LINUX_X64_MUSL_TARGET : LINUX_X64_GNU_TARGET
: hostArch === "arm64"
? linuxLibc === "musl" ? LINUX_ARM64_MUSL_TARGET : LINUX_ARM64_GNU_TARGET
: null;
}
return hostPlatform === "win32" && hostArch === "x64" ? WINDOWS_X64_MSVC_TARGET : null;
}
function requestedTarget(
raw: string,
host: NativeTargetSpec | null,
hostPlatform: NodeJS.Platform,
hostArch: string,
linuxLibc: LinuxLibc,
): NativeTargetSpec | null {
if (raw === "") return host;
switch (raw) {
case "arm64-apple-macosx14.0.0": return host?.name === "macos-arm64" ? MACOS_ARM64_TARGET : null;
case "x86_64-apple-macosx14.0.0":
case "x86_64-apple-macos": return host?.name === "macos-x64" ? MACOS_X64_TARGET : null;
case "x86_64-unknown-linux-gnu":
case "x86_64-linux-gnu":
case "x86_64-linux-gnu.2.36": return host?.name === "linux-x64-gnu" ? LINUX_X64_GNU_TARGET : null;
case "aarch64-unknown-linux-gnu":
case "aarch64-linux-gnu":
case "aarch64-linux-gnu.2.36": return host?.name === "linux-arm64-gnu" ? LINUX_ARM64_GNU_TARGET : null;
case "x86_64-unknown-linux-musl":
case "x86_64-linux-musl":
return host?.platform === "linux" && host.architecture === "x64" ? LINUX_X64_MUSL_TARGET : null;
case "aarch64-unknown-linux-musl":
case "aarch64-linux-musl":
return host?.platform === "linux" && host.architecture === "arm64" ? LINUX_ARM64_MUSL_TARGET : null;
case "wasm32-wasi":
case "wasm32-unknown-wasi":
return helperHost(hostPlatform, hostArch, linuxLibc) === null ? null : WASM32_WASI_TARGET;
case "x86_64-pc-windows-msvc": return host?.name === "windows-x64-msvc" ? WINDOWS_X64_MSVC_TARGET : null;
default: return null;
}
}
/** Select only a fully described scriptc target. LLVM accepting an arbitrary
* triple is never evidence of its object ABI, runtime pack, link, or run. */
export function nativeCodegenTarget(
env: NodeJS.ProcessEnv = process.env,
hostPlatform: NodeJS.Platform = process.platform,
hostArch: string = process.arch,
hostRelease: string = release(),
linuxLibc: LinuxLibc = detectedLinuxLibc(),
): NativeTargetSpec | null {
// The first helper is host-native only. In particular, do not reinterpret
// an existing SCRIPTC_TARGET cross-build as macOS merely because its OS
// family is Darwin.
if ((env["SCRIPTC_TARGET"] ?? "") !== "") return null;
const darwinMajor = Number.parseInt(hostRelease.split(".", 1)[0] ?? "", 10);
return hostPlatform === "darwin" && hostArch === "arm64" && darwinMajor >= 24
? MACOS_ARM64_TARGET
: null;
const host = nativeHostTarget(hostPlatform, hostArch, hostRelease, linuxLibc);
const target = requestedTarget(env["SCRIPTC_TARGET"] ?? "", host, hostPlatform, hostArch, linuxLibc);
return target !== null && nativeHelperForTarget(target, hostPlatform, hostArch, linuxLibc) !== null ? target : null;
}
export function nativeCodegenTargetRefusal(
@@ -62,18 +362,17 @@ export function nativeCodegenTargetRefusal(
hostPlatform: NodeJS.Platform = process.platform,
hostArch: string = process.arch,
hostRelease: string = release(),
linuxLibc: LinuxLibc = detectedLinuxLibc(),
): string | null {
if (nativeCodegenTarget(env, hostPlatform, hostArch, hostRelease) !== null) return null;
const crossTarget = env["SCRIPTC_TARGET"] ?? "";
if (crossTarget !== "") {
return `native assembly/object emission does not support SCRIPTC_TARGET=${crossTarget}; the first supported target is host-native macOS arm64`;
if (nativeCodegenTarget(env, hostPlatform, hostArch, hostRelease, linuxLibc) !== null) return null;
const requested = env["SCRIPTC_TARGET"] ?? "";
if (requested !== "") {
return `native assembly/object emission does not support SCRIPTC_TARGET=${requested} on this ${hostPlatform} ${hostArch} host; supported hosts are macOS arm64/x64, Linux x64/arm64, and Windows x64 (with wasm32-wasi on each)`;
}
if (hostPlatform === "darwin" && hostArch === "arm64") {
const darwinMajor = Number.parseInt(hostRelease.split(".", 1)[0] ?? "", 10);
const macosMajor = Number.isFinite(darwinMajor) && darwinMajor >= 20
? String(darwinMajor - 9)
: "unknown";
return `native assembly/object emission requires macOS ${MACOS_ARM64_TARGET.helperMinimumOs} or newer; this host is macOS ${macosMajor} (Darwin ${hostRelease})`;
if (hostPlatform === "darwin" && (hostArch === "arm64" || hostArch === "x64")) {
const major = Number.parseInt(hostRelease.split(".", 1)[0] ?? "", 10);
const macos = Number.isFinite(major) && major >= 20 ? String(major - 9) : "unknown";
return `native assembly/object emission requires macOS 15.0 or newer; this host is macOS ${macos} (Darwin ${hostRelease})`;
}
return `native assembly/object emission is supported on macOS arm64 hosts; this host is ${hostPlatform} ${hostArch}`;
return `native assembly/object emission is supported on macOS arm64/x64, Linux x64/arm64, and Windows x64 hosts; this host is ${hostPlatform} ${hostArch}`;
}
+25 -4
View File
@@ -1089,7 +1089,10 @@ async function compileExecutableNative(
programObjectDependencies,
});
const cacheableLinker =
onArtifactReady !== undefined && ffi === null && platformLinkerSupportsPersistentCache();
onArtifactReady !== undefined && ffi === null && platformLinkerSupportsPersistentCache(
process.env,
runtimePackTarget,
);
await linkNativeExecutable(plan, {
// A caller-selected linker can be a mutable wrapper with hidden inputs,
// and a PATH-selected `clang` can be one too. FFI profiles and mutable
@@ -1259,7 +1262,22 @@ async function compileTracked(
}
let buildPlatform: string;
if (outputKind === "exe") {
buildPlatform = buildTargetPlatform();
// Target semantics are needed before choosing the helper/runtime-pack
// path. Do not make them contingent on the legacy C-driver resolver:
// an LLVM-owned WASI build deliberately needs no SCRIPTC_CC.
try {
buildPlatform = sourceTargetPlatform();
} catch (err) {
return {
ok: false,
diagnostics: [{
code: "SC3002",
message: err instanceof Error ? err.message : String(err),
loc: { file: entryPath, start: 0, end: 0 },
}],
sourceTexts: new Map(),
};
}
} else {
try {
buildPlatform = sourceTargetPlatform();
@@ -1355,11 +1373,14 @@ async function compileTracked(
}`,
compiler: [
helperObjectRoute
? resolvePlatformLinker()
? resolvePlatformLinker(process.env, nativeCodegenTarget()?.defaultLinker)
: (process.env["SCRIPTC_CC"] ?? "clang"),
],
nativeEnvironment: helperObjectRoute
? await executableLinkerEnvironmentFingerprint()
? await executableLinkerEnvironmentFingerprint(
process.env,
nativeCodegenTarget()?.defaultLinker,
)
: await executableNativeEnvironmentFingerprint(),
nodeVersion: process.version,
implementation: implementation.digest,
+16
View File
@@ -5,6 +5,7 @@ export { readRoutedExecutableCache } from "./executable/executable-cache.js";
export type { EarlyExecutableRouteOptions } from "./executable/executable-cache.js";
export {
executableNativeEnvironmentFingerprint,
configuredTargetPlatform,
legacyCExecutablePathRequested,
resolveCc,
targetPlatform,
@@ -14,3 +15,18 @@ export {
executableLinkerEnvironmentFingerprint,
resolvePlatformLinker,
} from "./backend/linker.js";
import { legacyCExecutablePathRequested } from "./backend/external-c.js";
import { nativeCodegenTarget, type NativeTargetSpec } from "./backend/targets.js";
/** Whether an ordinary LLVM executable can take the helper/object plus
* precompiled-runtime-pack route. Keep this startup-sized: the CLI bootstrap
* needs the exact cache identity before deciding whether it must import the
* full compiler graph. */
export function precompiledRuntimePackTarget(
env: NodeJS.ProcessEnv = process.env,
): NativeTargetSpec | null {
if (env["SCRIPTC_RUNTIME_PACK"] === "0" ||
env["SCRIPTC_FETCH_CURL"] === "1" ||
legacyCExecutablePathRequested(env)) return null;
return nativeCodegenTarget(env);
}
+5 -1
View File
@@ -20,6 +20,8 @@ execFileSync("cmake", [
"-G", "Ninja",
`-DLLVM_DIR=${llvmDir}`,
`-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`,
"-DSCRIPTC_TARGET_BACKENDS=AArch64;WebAssembly",
"-DSCRIPTC_ALLOWED_TARGETS=arm64-apple-macosx14.0.0,wasm32-unknown-wasi",
"-DCMAKE_BUILD_TYPE=Release",
], { stdio: "inherit" });
execFileSync("cmake", ["--build", buildDir, "--target", "scriptc-llvm-codegen"], {
@@ -38,7 +40,9 @@ const version = JSON.parse(execFileSync(output, ["version", "--format=json"], {
if (
version.protocol_version !== "1" ||
version.scriptc_package_version !== manifest.version ||
version.llvm_version !== "22.1.8"
version.llvm_version !== "22.1.8" ||
!Array.isArray(version.supported_targets) ||
!version.supported_targets.includes("arm64-apple-macosx14.0.0")
) {
throw new Error(`built helper reported an incompatible identity: ${JSON.stringify(version)}`);
}
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
+1
View File
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@scriptc/llvm-darwin-x64",
"version": "0.0.36",
"description": "Pinned LLVM code-generation helper for scriptc on macOS x64",
"license": "Apache-2.0 WITH LLVM-exception",
"homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-darwin-x64" },
"os": ["darwin"],
"cpu": ["x64"],
"files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"],
"scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -x bin/scriptc-llvm-codegen" },
"publishConfig": { "access": "public" }
}
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "darwin" || process.arch !== "x64") {
process.stdout.write("@scriptc/llvm-darwin-x64: skipped on this host\n");
process.exit(0);
}
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
const build = join(repo, "node_modules/.cache/scriptc-llvm-darwin-x64");
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Ninja", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/local/opt/llvm@22/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_HELPER_ARCH=x86_64", "-DSCRIPTC_DEFAULT_TARGET=x86_64-apple-macosx14.0.0", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "-DSCRIPTC_TARGET_BACKENDS=X86;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=x86_64-apple-macosx14.0.0,wasm32-unknown-wasi", "-DCMAKE_BUILD_TYPE=Release"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { stdio: "inherit" });
mkdirSync(join(root, "bin"), { recursive: true });
const output = join(root, "bin", "scriptc-llvm-codegen");
copyFileSync(join(build, "scriptc-llvm-codegen"), output);
execFileSync("strip", ["-x", output], { stdio: "inherit" });
chmodSync(output, 0o755);
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
@@ -0,0 +1,4 @@
{
"name": "@scriptc/llvm-linux-arm64-gnu", "version": "0.0.36", "description": "Pinned LLVM code-generation helper for scriptc on Linux arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-linux-arm64-gnu" }, "os": ["linux"], "cpu": ["arm64"], "files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -x bin/scriptc-llvm-codegen" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url))); const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "linux" || process.arch !== "arm64") { process.stdout.write("@scriptc/llvm-linux-arm64-gnu: skipped on this host\n"); process.exit(0); }
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const build = join(repo, "node_modules/.cache/scriptc-llvm-linux-arm64-gnu");
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Ninja", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_DEFAULT_TARGET=aarch64-unknown-linux-gnu", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", "-DSCRIPTC_TARGET_BACKENDS=AArch64;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=aarch64-unknown-linux-gnu,aarch64-unknown-linux-musl,wasm32-unknown-wasi", "-DCMAKE_BUILD_TYPE=Release"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { stdio: "inherit" }); mkdirSync(join(root, "bin"), { recursive: true }); const output = join(root, "bin", "scriptc-llvm-codegen"); copyFileSync(join(build, "scriptc-llvm-codegen"), output); execFileSync("strip", [output], { stdio: "inherit" }); chmodSync(output, 0o755);
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
@@ -0,0 +1,12 @@
{
"name": "@scriptc/llvm-linux-arm64-musl",
"version": "0.0.36",
"description": "Statically linked LLVM code-generation helper for scriptc on Linux arm64 musl",
"license": "Apache-2.0 WITH LLVM-exception",
"homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-linux-arm64-musl" },
"os": ["linux"], "cpu": ["arm64"],
"files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"],
"scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -x bin/scriptc-llvm-codegen" },
"publishConfig": { "access": "public" }
}
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url))); const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "linux" || process.arch !== "arm64") { process.stdout.write("@scriptc/llvm-linux-arm64-musl: skipped on this host\n"); process.exit(0); }
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const build = join(repo, "node_modules/.cache/scriptc-llvm-linux-arm64-musl");
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Ninja", "-DCMAKE_C_COMPILER=zig", "-DCMAKE_C_COMPILER_ARG1=cc", "-DCMAKE_CXX_COMPILER=zig", "-DCMAKE_CXX_COMPILER_ARG1=c++", "-DCMAKE_C_COMPILER_TARGET=aarch64-linux-musl", "-DCMAKE_CXX_COMPILER_TARGET=aarch64-linux-musl", "-DCMAKE_EXE_LINKER_FLAGS=-static", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_DEFAULT_TARGET=aarch64-unknown-linux-musl", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", "-DSCRIPTC_TARGET_BACKENDS=AArch64;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=aarch64-unknown-linux-musl,wasm32-unknown-wasi", "-DCMAKE_BUILD_TYPE=Release"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { stdio: "inherit" }); mkdirSync(join(root, "bin"), { recursive: true }); const output = join(root, "bin", "scriptc-llvm-codegen"); copyFileSync(join(build, "scriptc-llvm-codegen"), output); execFileSync("strip", [output], { stdio: "inherit" }); chmodSync(output, 0o755);
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@scriptc/llvm-linux-x64-gnu",
"version": "0.0.36",
"description": "Pinned LLVM code-generation helper for scriptc on Linux x64",
"license": "Apache-2.0 WITH LLVM-exception",
"homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-linux-x64-gnu" },
"os": ["linux"], "cpu": ["x64"],
"files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"],
"scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -x bin/scriptc-llvm-codegen" },
"publishConfig": { "access": "public" }
}
@@ -0,0 +1,15 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "linux" || process.arch !== "x64") { process.stdout.write("@scriptc/llvm-linux-x64-gnu: skipped on this host\n"); process.exit(0); }
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
const build = join(repo, "node_modules/.cache/scriptc-llvm-linux-x64-gnu");
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Ninja", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_DEFAULT_TARGET=x86_64-unknown-linux-gnu", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "-DSCRIPTC_TARGET_BACKENDS=X86;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl,wasm32-unknown-wasi", "-DCMAKE_BUILD_TYPE=Release"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { stdio: "inherit" });
mkdirSync(join(root, "bin"), { recursive: true });
const output = join(root, "bin", "scriptc-llvm-codegen"); copyFileSync(join(build, "scriptc-llvm-codegen"), output); execFileSync("strip", [output], { stdio: "inherit" }); chmodSync(output, 0o755);
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@scriptc/llvm-linux-x64-musl",
"version": "0.0.36",
"description": "Statically linked LLVM code-generation helper for scriptc on Linux x64 musl",
"license": "Apache-2.0 WITH LLVM-exception",
"homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-linux-x64-musl" },
"os": ["linux"], "cpu": ["x64"],
"files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"],
"scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -x bin/scriptc-llvm-codegen" },
"publishConfig": { "access": "public" }
}
@@ -0,0 +1,13 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "linux" || process.arch !== "x64") { process.stdout.write("@scriptc/llvm-linux-x64-musl: skipped on this host\n"); process.exit(0); }
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
const build = join(repo, "node_modules/.cache/scriptc-llvm-linux-x64-musl");
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Ninja", "-DCMAKE_C_COMPILER=zig", "-DCMAKE_C_COMPILER_ARG1=cc", "-DCMAKE_CXX_COMPILER=zig", "-DCMAKE_CXX_COMPILER_ARG1=c++", "-DCMAKE_C_COMPILER_TARGET=x86_64-linux-musl", "-DCMAKE_CXX_COMPILER_TARGET=x86_64-linux-musl", "-DCMAKE_EXE_LINKER_FLAGS=-static", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_DEFAULT_TARGET=x86_64-unknown-linux-musl", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "-DSCRIPTC_TARGET_BACKENDS=X86;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=x86_64-unknown-linux-musl,wasm32-unknown-wasi", "-DCMAKE_BUILD_TYPE=Release"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { stdio: "inherit" }); mkdirSync(join(root, "bin"), { recursive: true }); const output = join(root, "bin", "scriptc-llvm-codegen"); copyFileSync(join(build, "scriptc-llvm-codegen"), output); execFileSync("strip", [output], { stdio: "inherit" }); chmodSync(output, 0o755);
+1
View File
@@ -0,0 +1 @@
Apache License 2.0. See https://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1 @@
Copyright scriptc contributors. Licensed under Apache-2.0.
@@ -0,0 +1 @@
This package contains LLVM components licensed under Apache-2.0 with LLVM Exceptions.
@@ -0,0 +1,4 @@
{
"name": "@scriptc/llvm-win32-x64-msvc", "version": "0.0.36", "description": "Pinned LLVM code-generation helper for scriptc on Windows x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/llvm-win32-x64-msvc" }, "os": ["win32"], "cpu": ["x64"], "files": ["bin", "LICENSE", "SCRIPTC_LICENSE", "THIRD_PARTY_NOTICES"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "test -f bin/scriptc-llvm-codegen.exe" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,14 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = dirname(dirname(fileURLToPath(import.meta.url))); const repo = fileURLToPath(new URL("../../..", import.meta.url));
if (process.platform !== "win32" || process.arch !== "x64") { process.stdout.write("@scriptc/llvm-win32-x64-msvc: skipped on this host\n"); process.exit(0); }
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const build = join(repo, "node_modules/.cache/scriptc-llvm-win32-x64-msvc");
// LLVM's official Windows development archive contains MSVC-built static
// libraries. Use Visual Studio's generator, rather than inheriting a caller's
// clang/ninja defaults, so it selects the matching CRT, SDK, manifest tools,
// and current MSVC standard-library implementation.
execFileSync("cmake", ["-S", join(repo, "native/llvm-codegen"), "-B", build, "-G", "Visual Studio 17 2022", "-A", "x64", `-DLLVM_DIR=${process.env.LLVM_DIR ?? "C:/Program Files/LLVM/lib/cmake/llvm"}`, `-DSCRIPTC_PACKAGE_VERSION=${manifest.version}`, "-DSCRIPTC_DEFAULT_TARGET=x86_64-pc-windows-msvc", "-DSCRIPTC_DEFAULT_DATA_LAYOUT=e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "-DSCRIPTC_TARGET_BACKENDS=X86;WebAssembly", "-DSCRIPTC_ALLOWED_TARGETS=x86_64-pc-windows-msvc,wasm32-unknown-wasi"], { stdio: "inherit" });
execFileSync("cmake", ["--build", build, "--config", "Release", "--target", "scriptc-llvm-codegen"], { stdio: "inherit" }); mkdirSync(join(root, "bin"), { recursive: true }); copyFileSync(join(build, "Release", "scriptc-llvm-codegen.exe"), join(root, "bin", "scriptc-llvm-codegen.exe"));
@@ -99,10 +99,7 @@ export const RUNTIME_PACK_MATRIX = {
object_format: "macho",
minimum_os: "14.0",
},
flavors: {
release: { optimization: "-O2" },
dev: { optimization: "-O0" },
},
flavors: { release: { optimization: "-O2" }, dev: { optimization: "-O0" } },
executable_section_elimination: EXECUTABLE_SECTION_ELIMINATION,
runtime_units: [
...BASE_RUNTIME_SOURCES.map((source) => ({ source, predicate: true })),
@@ -114,8 +111,5 @@ export const RUNTIME_PACK_MATRIX = {
{ id: "zlib", predicate: "zlibEffective" },
{ id: "mbedtls", predicate: "tlsEffective" },
],
system_libraries: [
{ name: "System", predicate: true },
{ name: "m", predicate: "dynamic" },
],
system_libraries: [{ name: "System", predicate: true }, { name: "m", predicate: "dynamic" }],
};
+5
View File
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-darwin-x64", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for macOS x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-darwin-x64" }, "os": ["darwin"], "cpu": ["x64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,5 @@
import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "darwin" || process.arch !== "x64") { process.stdout.write("@scriptc/runtime-darwin-x64: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "darwin", runtimeDefines: [], threadArgs: ["-pthread"], target: { name: "macos-x64", llvm_triple: "x86_64-apple-macosx14.0.0", architecture: "x64", object_format: "macho", minimum_os: "14.0" }, targetArgs: ["-target", "x86_64-apple-macosx14.0.0"], compileFlags: [], systemLibraries: [{ name: "System", predicate: true }, { name: "m", predicate: "dynamic" }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-linux-arm64-gnu", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for Linux arm64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-linux-arm64-gnu" }, "os": ["linux"], "cpu": ["arm64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,5 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "linux" || process.arch !== "arm64") { process.stdout.write("@scriptc/runtime-linux-arm64-gnu: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-arm64-gnu", llvm_triple: "aarch64-unknown-linux-gnu", architecture: "arm64", object_format: "elf", minimum_os: "glibc 2.36" }, targetArgs: ["-target", "aarch64-unknown-linux-gnu"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-linux-arm64-musl", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for Linux arm64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-linux-arm64-musl" }, "os": ["linux"], "cpu": ["arm64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,5 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "linux" || process.arch !== "arm64") { process.stdout.write("@scriptc/runtime-linux-arm64-musl: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE", "SCR_MUSL"], threadArgs: ["-pthread"], extraRuntimeSources: ["scr_musl.c"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "linux-arm64-musl", llvm_triple: "aarch64-unknown-linux-musl", architecture: "arm64", object_format: "elf", minimum_os: "musl 1.2" }, targetArgs: ["-target", "aarch64-linux-musl"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-linux-x64-gnu", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for Linux x64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-linux-x64-gnu" }, "os": ["linux"], "cpu": ["x64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,5 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "linux" || process.arch !== "x64") { process.stdout.write("@scriptc/runtime-linux-x64-gnu: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE"], threadArgs: ["-pthread"], target: { name: "linux-x64-gnu", llvm_triple: "x86_64-unknown-linux-gnu", architecture: "x64", object_format: "elf", minimum_os: "glibc 2.36" }, targetArgs: ["-target", "x86_64-unknown-linux-gnu"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-linux-x64-musl", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for Linux x64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-linux-x64-musl" }, "os": ["linux"], "cpu": ["x64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,5 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "linux" || process.arch !== "x64") { process.stdout.write("@scriptc/runtime-linux-x64-musl: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "linux", runtimeDefines: ["_GNU_SOURCE", "SCR_MUSL"], threadArgs: ["-pthread"], extraRuntimeSources: ["scr_musl.c"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "linux-x64-musl", llvm_triple: "x86_64-unknown-linux-musl", architecture: "x64", object_format: "elf", minimum_os: "musl 1.2" }, targetArgs: ["-target", "x86_64-linux-musl"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "m", predicate: true }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,6 @@
{
"name": "@scriptc/runtime-pack-common",
"version": "0.0.36",
"private": true,
"type": "module"
}
@@ -0,0 +1,93 @@
/**
* Shared release-runtime pack matrix. Platform package scripts provide the
* target descriptor, then this module fixes feature reachability and the
* compile/link flags required by every generated runtime object.
*/
const any = (...features) => ({ any: features });
const all = (...features) => ({ all: features });
const BASE_RUNTIME_SOURCES = [
"scr_number.c", "scr_string.c", "scr_array.c", "scr_bytes.c",
"scr_bytes_io.c", "scr_map.c", "scr_closure.c", "scr_ffi.c",
"scr_object.c", "scr_union.c", "scr_exception.c", "scr_error.c",
"scr_console.c", "scr_lib.c", "scr_path.c", "scr_url.c", "scr_json.c",
"scr_async.c", "scr_child.c", "scr_cycle.c",
];
const OPTIONAL = [
["scr_copying.c", "copying"], ["scr_file_handle.c", "fileHandle"],
["scr_regex.c", "regex"], ["scr_assert.c", any("assert", "regex", "symbol")],
["scr_inspect.c", "inspect"], ["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")],
["scr_dc.c", "dc"], ["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "nativeFetch")],
["scr_zlib.c", "zlib"], ["scr_zlib_island.c", all("zlib", "dynamic")],
["scr_events.c", "events"], ["scr_readline.c", "events"],
["scr_events_emitter.c", "emitter"], ["scr_dyn_handle.c", any("emitter", "netEffective")],
["scr_symbol.c", "symbol"], ["scr_url_params.c", "searchParams"],
["scr_qs.c", "qs"], ["scr_util.c", "parseArgs"], ["scr_stream.c", "stream"],
["scr_loop_kqueue.c", any("netEffective", "dgram")],
["scr_loop_epoll.c", any("netEffective", "dgram")],
["scr_loop_wsapoll.c", any("netEffective", "dgram")],
["scr_net.c", "netEffective"], ["scr_http.c", "httpEffective"],
["scr_http2.c", "http2"], ["scr_dgram.c", "dgram"], ["scr_watch.c", "watch"],
["scr_ffi_queue.c", "foreignFfi"], ["scr_test.c", "nodeTest"],
["scr_tls_ca.c", "tlsCaEffective"], ["scr_tls.c", "tlsEffective"],
["scr_fetch.c", "nativeFetch"], ["scr_island.c", "dynamic"],
["scr_web.c", "dynamic"], ["scr_inspect_island.c", all("dynamic", "inspect")],
["scr_net_island.c", "netIslandEffective"],
];
function variantsFor(source) {
const dynamicOnly = new Set([
"scr_zlib_island.c", "scr_island.c", "scr_web.c",
"scr_inspect_island.c", "scr_net_island.c",
]);
if (dynamicOnly.has(source)) {
return [{ id: "dynamic", when: { dynamic: true }, defines: ["SCR_DYNAMIC"] }];
}
const variants = [{ id: "default", when: {}, defines: [] }];
if (source === "scr_bytes.c") {
variants.push({ id: "text-decoder-legacy", when: { textDecoderLegacy: true }, defines: ["SCR_TEXT_DECODER_LEGACY"] });
}
variants.push({ id: "dynamic", when: { dynamic: true }, defines: ["SCR_DYNAMIC"] });
if (source === "scr_bytes.c") {
variants.push({
id: "dynamic-text-decoder-legacy", when: { dynamic: true, textDecoderLegacy: true },
defines: ["SCR_DYNAMIC", "SCR_TEXT_DECODER_LEGACY"],
});
}
return variants;
}
export function createRuntimePackMatrix({
target,
compileFlags,
systemLibraries,
extraRuntimeSources = [],
omitRuntimeSources = [],
omitOptionalSources = [],
omitArchives = [],
}) {
const vendorArchives = [
{ id: "quickjs", predicate: "dynamic" },
{ id: "libregexp", predicate: { all: ["regex"], not: ["dynamic"] } },
{ id: "zlib", predicate: "zlibEffective" },
{ id: "mbedtls", predicate: "tlsEffective" },
];
return {
schema: "scriptc.runtime-pack-matrix.v1",
target,
flavors: { release: { optimization: "-O2" }, dev: { optimization: "-O0" } },
executable_section_elimination: { compile_flags: compileFlags },
runtime_units: [
...BASE_RUNTIME_SOURCES
.filter((source) => !omitRuntimeSources.includes(source))
.map((source) => ({ source, predicate: true })),
...extraRuntimeSources.map((source) => ({ source, predicate: true })),
...OPTIONAL
.filter(([source]) => !omitOptionalSources.includes(source))
.map(([source, predicate]) => ({ source, predicate })),
].map((unit) => ({ ...unit, variants: variantsFor(unit.source) })),
archives: vendorArchives.filter(({ id }) => !omitArchives.includes(id)),
system_libraries: systemLibraries,
};
}
@@ -0,0 +1,35 @@
import { readFile, writeFile } from "node:fs/promises";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
const globalHeader = "!<arch>\n";
const memberHeaderSize = 60;
function replaceField(bytes, offset, width, value) {
bytes.write(value.padEnd(width, " "), offset, width, "ascii");
}
async function normalizeMetadata(output) {
const bytes = await readFile(output);
if (bytes.subarray(0, globalHeader.length).toString("ascii") !== globalHeader) throw new Error(`archiver produced an invalid archive: ${output}`);
let offset = globalHeader.length;
while (offset < bytes.length) {
const end = offset + memberHeaderSize;
if (end > bytes.length || bytes.subarray(offset + 58, end).toString("ascii") !== "`\n") throw new Error(`archiver produced a malformed member header: ${output}`);
const size = Number(bytes.subarray(offset + 48, offset + 58).toString("ascii").trim());
if (!Number.isSafeInteger(size) || size < 0 || end + size > bytes.length) throw new Error(`archiver produced an invalid member size: ${output}`);
replaceField(bytes, offset + 16, 12, "0");
replaceField(bytes, offset + 28, 6, "0");
replaceField(bytes, offset + 34, 6, "0");
replaceField(bytes, offset + 40, 8, "100644");
offset = end + size + (size % 2);
}
if (offset !== bytes.length) throw new Error(`archiver produced a truncated archive: ${output}`);
await writeFile(output, bytes);
}
export async function createDeterministicArchive(archiver, output, objects, args = []) {
await run(archiver, [...args, "rcs", output, ...objects], { env: { ...process.env, ZERO_AR_DATE: "1" } });
await normalizeMetadata(output);
}
@@ -0,0 +1 @@
export { installRuntimePack, withBuildLock } from "../../runtime-darwin-arm64/scripts/build-state.mjs";
@@ -0,0 +1,125 @@
#!/usr/bin/env node
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { availableParallelism, tmpdir } from "node:os";
import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
import { basename, dirname, join, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { createRuntimePackMatrix } from "../runtime-pack-matrix.mjs";
import { createDeterministicArchive } from "./archive.mjs";
import { installRuntimePack, withBuildLock } from "./build-state.mjs";
const run = promisify(execFile);
const packageRoot = process.env.SCRIPTC_RUNTIME_PACK_ROOT;
const configText = process.env.SCRIPTC_RUNTIME_PACK_CONFIG;
if (!packageRoot || !configText) throw new Error("runtime-pack build requires package wrapper configuration");
const config = JSON.parse(configText);
const repoRoot = fileURLToPath(new URL("../../..", import.meta.url));
const runtimeRoot = join(repoRoot, "packages/runtime");
const runtimeSrc = join(runtimeRoot, "src");
const vendorRoot = join(runtimeRoot, "vendor");
const outputRoot = join(packageRoot, "artifacts");
const manifestPath = join(packageRoot, "runtime-pack.json");
const matrix = createRuntimePackMatrix(config);
async function build() {
const buildRoot = join(packageRoot, `.runtime-pack-build-${process.pid}-${Math.random().toString(36).slice(2)}`);
const stagedOutputRoot = join(buildRoot, "artifacts");
const stagedManifestPath = join(buildRoot, "runtime-pack.json");
const artifactPath = (path) => ["artifacts", ...relative(stagedOutputRoot, path).split(sep)].join("/");
const sourcePathFlags = [`-ffile-prefix-map=${buildRoot}=${packageRoot}`, `-ffile-prefix-map=${repoRoot}=.`];
const packageManifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
const compiler = process.env.CC ?? config.compiler ?? "clang";
const compilerArgs = config.compilerArgs ?? [];
const archiver = process.env.AR ?? config.archiver ?? "ar";
const archiverArgs = config.archiverArgs ?? [];
const commonFlags = [
...config.targetArgs, "-std=c11", ...(config.threadArgs ?? []), "-fno-math-errno", "-fno-strict-aliasing",
...matrix.executable_section_elimination.compile_flags, "-Wno-deprecated-declarations", "-I", runtimeSrc,
...(config.runtimeDefines ?? []).map((define) => `-D${define}`),
];
const quickjs = join(vendorRoot, "quickjs-ng");
const zlib = join(vendorRoot, "zlib");
const mbedtls = join(vendorRoot, "mbedtls");
const qjsSources = ["dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"];
const lreSources = ["libregexp.c", "libunicode.c"];
const zlibSources = ["adler32.c", "compress.c", "crc32.c", "deflate.c", "infback.c", "inffast.c", "inflate.c", "inftrees.c", "trees.c", "uncompr.c", "zutil.c"];
const sha256 = async (path) => createHash("sha256").update(await readFile(path)).digest("hex");
const compile = async (source, output, flags) => {
await mkdir(dirname(output), { recursive: true });
await run(compiler, [...compilerArgs, ...sourcePathFlags, ...flags, "-c", source, "-o", output]);
};
const parallel = async (items, task) => {
const width = Math.max(1, Math.min(8, availableParallelism()));
for (let i = 0; i < items.length; i += width) await Promise.all(items.slice(i, i + width).map(task));
};
const archive = async (id, sources, sourceRoot, flags) => {
const root = join(stagedOutputRoot, "vendor", id);
const objectRoot = join(root, "objects");
await parallel(sources, async (source) => compile(join(sourceRoot, source), join(objectRoot, source.replace(/\.c$/, ".o")), flags));
const output = join(root, `libscriptc-${id}.a`);
await createDeterministicArchive(archiver, output, sources.map((source) => join(objectRoot, source.replace(/\.c$/, ".o"))), archiverArgs);
await rm(objectRoot, { recursive: true, force: true });
return { id, path: artifactPath(output), sha256: await sha256(output), size: (await stat(output)).size };
};
await rm(buildRoot, { recursive: true, force: true });
try {
await mkdir(stagedOutputRoot, { recursive: true });
// Zig 0.16 creates a zero-byte `a.o` in its working directory for
// `zig cc --version`. Probe from a private temporary directory so a
// runtime-pack build never leaves that compiler byproduct in the package.
const versionProbeRoot = await mkdtemp(join(tmpdir(), "scriptc-runtime-pack-version-"));
let compilerVersion;
try {
compilerVersion = (await run(compiler, [...compilerArgs, "--version"], {
cwd: versionProbeRoot,
})).stdout.split("\n", 1)[0].trim();
} finally {
await rm(versionProbeRoot, { recursive: true, force: true });
}
const flavors = {};
for (const [flavor, flavorSpec] of Object.entries(matrix.flavors)) {
const units = [];
for (const unit of matrix.runtime_units) {
const variants = [];
for (const variant of unit.variants) {
const output = join(stagedOutputRoot, flavor, "runtime", variant.id, unit.source.replace(/\.c$/, ".o"));
const includeFlags = [
...(unit.source === "scr_regex.c" || variant.defines.includes("SCR_DYNAMIC") ? ["-I", quickjs] : []),
...(unit.source === "scr_tls.c" ? ["-I", join(mbedtls, "include")] : []),
...(unit.source === "scr_zlib.c" || unit.source === "scr_fetch.c" ? ["-I", zlib] : []),
];
await compile(join(runtimeSrc, unit.source), output, [...commonFlags, flavorSpec.optimization, ...variant.defines.map((define) => `-D${define}`), ...includeFlags]);
variants.push({ id: variant.id, when: variant.when, defines: variant.defines, path: artifactPath(output), sha256: await sha256(output), size: (await stat(output)).size });
}
units.push({ source: unit.source, predicate: unit.predicate, variants });
}
flavors[flavor] = { optimization: flavorSpec.optimization, runtime_units: units };
}
const mbedtlsSources = (await readdir(join(mbedtls, "library"))).filter((name) => !name.startsWith(".") && name.endsWith(".c")).sort();
// Vendored QuickJS reads `clock_gettime` for its monotonic clock. WASI
// exposes that declaration only when its process-clock emulation ABI is
// selected, so vendor sources inherit the target's runtime defines too.
const vendorTarget = [
...config.targetArgs,
...(config.runtimeDefines ?? []).map((define) => `-D${define}`),
];
const requestedArchives = new Set(matrix.archives.map((entry) => entry.id));
const archives = config.vendorArchives === false ? [] : [
...(requestedArchives.has("quickjs") ? [await archive("quickjs", qjsSources, quickjs, [...vendorTarget, "-std=gnu11", "-fvisibility=hidden", "-funsigned-char", "-DQUICKJS_NG_BUILD", "-D_GNU_SOURCE", "-DNDEBUG", "-Os", "-I", quickjs])] : []),
...(requestedArchives.has("libregexp") ? [await archive("libregexp", lreSources, quickjs, [...vendorTarget, "-std=c11", "-Os", "-I", quickjs])] : []),
...(requestedArchives.has("zlib") ? [await archive("zlib", zlibSources, zlib, [...vendorTarget, "-std=c11", "-Os", "-I", zlib])] : []),
...(requestedArchives.has("mbedtls") ? [await archive("mbedtls", mbedtlsSources, join(mbedtls, "library"), [...vendorTarget, "-std=c11", "-Os", "-I", join(mbedtls, "include"), "-I", join(mbedtls, "library")])] : []),
];
const archiveSpecs = new Map(matrix.archives.map((entry) => [entry.id, entry]));
const licensed = [[join(runtimeRoot, "LICENSE"), "artifacts/licenses/scriptc-runtime.txt", "Apache-2.0"], [join(quickjs, "LICENSE"), "artifacts/licenses/quickjs-ng.txt", "MIT"], [join(vendorRoot, "ryu", "LICENSE-Boost"), "artifacts/licenses/ryu.txt", "BSL-1.0"], [join(zlib, "LICENSE"), "artifacts/licenses/zlib.txt", "Zlib"], [join(mbedtls, "LICENSE"), "artifacts/licenses/mbedtls.txt", "Apache-2.0"]];
await Promise.all(licensed.map(async ([source, destination]) => { const output = join(buildRoot, destination); await mkdir(dirname(output), { recursive: true }); await copyFile(source, output); }));
const manifest = { schema: "scriptc.runtime-pack.v1", format: 1, package: packageManifest.name, version: packageManifest.version, target: matrix.target, runtime_abi: { version: 1, marker: "scr_runtime_abi_v1" }, compiler: { command: compiler, identity: compilerVersion, target: matrix.target.llvm_triple }, macros: { executable: ["SCR_DYNAMIC", "SCR_TEXT_DECODER_LEGACY"], excluded: ["SCR_LIB", "SCR_THREAD_INSTANCES", "SCR_RC_AUDIT", "SCR_ASAN_FIBERS"], sanitizer: "external-toolchain-required" }, flavors, archives: archives.map((entry) => ({ ...entry, predicate: archiveSpecs.get(entry.id).predicate })), system_libraries: matrix.system_libraries, licenses: licensed.map(([, path, license]) => ({ path, license })) };
await writeFile(stagedManifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
const suffix = `${process.pid}-${Math.random().toString(36).slice(2)}`;
await installRuntimePack({ outputRoot, manifestPath, stagedOutputRoot, stagedManifestPath, backupRoot: join(packageRoot, `.runtime-pack-artifacts-backup-${suffix}`), backupManifestPath: join(packageRoot, `.runtime-pack-manifest-backup-${suffix}`) });
process.stdout.write(`built ${packageManifest.name}@${packageManifest.version}: ${Object.keys(flavors).length} flavors, ${archives.length} vendor archives\n`);
} finally { await rm(buildRoot, { recursive: true, force: true }); }
}
await withBuildLock(join(packageRoot, ".runtime-pack-build.lock"), build);
@@ -0,0 +1,17 @@
#!/usr/bin/env node
import { createHash } from "node:crypto";
import { access, readFile } from "node:fs/promises";
import { join } from "node:path";
const root = process.env.SCRIPTC_RUNTIME_PACK_ROOT;
if (!root) throw new Error("runtime-pack verification requires package wrapper configuration");
const manifest = JSON.parse(await readFile(join(root, "runtime-pack.json"), "utf8"));
const packageManifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
if (manifest.schema !== "scriptc.runtime-pack.v1" || manifest.format !== 1 || manifest.package !== packageManifest.name || manifest.version !== packageManifest.version) throw new Error("runtime pack identity does not match package.json");
const artifacts = [...Object.values(manifest.flavors).flatMap((flavor) => flavor.runtime_units.flatMap((unit) => unit.variants)), ...manifest.archives];
for (const artifact of artifacts) {
const bytes = await readFile(join(root, artifact.path));
if (bytes.length !== artifact.size || createHash("sha256").update(bytes).digest("hex") !== artifact.sha256) throw new Error(`runtime pack hash mismatch: ${artifact.path}`);
}
for (const license of manifest.licenses) await access(join(root, license.path));
process.stdout.write(`verified ${manifest.package}@${manifest.version}: ${artifacts.length} artifacts\n`);
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-wasm32-wasi", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for WASI Preview 1", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-wasm32-wasi" },
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,4 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "wasi", runtimeDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"], omitRuntimeSources: ["scr_child.c"], omitOptionalSources: ["scr_dyn_handle.c", "scr_loop_kqueue.c", "scr_loop_epoll.c", "scr_loop_wsapoll.c", "scr_net.c", "scr_http.c", "scr_http2.c", "scr_dgram.c", "scr_watch.c", "scr_tls_ca.c", "scr_tls.c", "scr_fetch.c", "scr_net_island.c"], omitArchives: ["mbedtls"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "wasm32-wasi", llvm_triple: "wasm32-unknown-wasi", architecture: "wasm32", object_format: "wasm", minimum_os: "WASI Preview 1" }, targetArgs: ["-target", "wasm32-wasi"], compileFlags: [], systemLibraries: [{ name: "wasi-emulated-signal", predicate: true }, { name: "wasi-emulated-process-clocks", predicate: true }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
@@ -0,0 +1,5 @@
{
"name": "@scriptc/runtime-win32-x64-msvc", "version": "0.0.36", "description": "Precompiled scriptc runtime pack for Windows x64 MSVC", "license": "Apache-2.0", "homepage": "https://scriptc.dev",
"repository": { "type": "git", "url": "git+https://github.com/vercel-labs/scriptc.git", "directory": "packages/runtime-win32-x64-msvc" }, "os": ["win32"], "cpu": ["x64"],
"files": ["artifacts", "runtime-pack.json"], "scripts": { "build": "true", "build:native": "node scripts/build.mjs", "prepack": "node scripts/verify.mjs" }, "publishConfig": { "access": "public" }
}
@@ -0,0 +1,9 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
if (process.platform !== "win32" || process.arch !== "x64") { process.stdout.write("@scriptc/runtime-win32-x64-msvc: skipped on this host\n"); process.exit(0); }
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
// The helper emits x64 COFF objects, whose calling convention is compatible
// with Zig's MinGW runtime. The runtime itself intentionally uses the
// POSIX-shaped Windows surface (unistd, dirent, clock_gettime), so compile it
// against Zig's Windows GNU sysroot rather than MSVC's headers.
process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ platform: "win32", runtimeDefines: [], extraRuntimeSources: ["scr_win.c"], compiler: "zig", compilerArgs: ["cc"], archiver: "zig", archiverArgs: ["ar"], target: { name: "windows-x64-msvc", llvm_triple: "x86_64-pc-windows-msvc", architecture: "x64", object_format: "coff", minimum_os: "Windows 10" }, targetArgs: ["-target", "x86_64-windows-gnu"], compileFlags: ["-ffunction-sections", "-fdata-sections"], systemLibraries: [{ name: "advapi32", predicate: true }, { name: "iphlpapi", predicate: true }, { name: "ws2_32", predicate: true }, { name: "bcrypt", predicate: "tlsEffective" }, { name: "crypt32", predicate: "tlsCaEffective" }] });
await import("../../runtime-pack-common/scripts/build.mjs");
@@ -0,0 +1,2 @@
import { dirname } from "node:path"; import { fileURLToPath } from "node:url";
process.env.SCRIPTC_RUNTIME_PACK_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); await import("../../runtime-pack-common/scripts/verify.mjs");
+6
View File
@@ -19,6 +19,12 @@
* Win32's missing POSIX/BSD functions live in scr_win.c. Zig's musl sysroot
* additionally lacks arc4random_buf; scr_musl.c supplies it from Linux's
* getrandom syscall. Both files are selected by native-toolchain.ts only for their target. */
#if defined(_WIN32) && defined(_MSC_VER)
/* MSVC's C headers do not define POSIX ssize_t. Keep the runtime's byte-count
* interfaces pointer-sized on Windows without requiring a Windows SDK header
* (and leave the Zig/MinGW compatibility route's native typedef intact). */
typedef intptr_t ssize_t;
#endif
#ifdef _WIN32
#include <time.h> /* time_t / struct tm for the gmtime_r shim */
char *stpcpy(char *dst, const char *src);
+67
View File
@@ -54,16 +54,83 @@ importers:
'@scriptc/llvm-darwin-arm64':
specifier: workspace:*
version: link:../llvm-darwin-arm64
'@scriptc/llvm-darwin-x64':
specifier: workspace:*
version: link:../llvm-darwin-x64
'@scriptc/llvm-linux-arm64-gnu':
specifier: workspace:*
version: link:../llvm-linux-arm64-gnu
'@scriptc/llvm-linux-arm64-musl':
specifier: workspace:*
version: link:../llvm-linux-arm64-musl
'@scriptc/llvm-linux-x64-gnu':
specifier: workspace:*
version: link:../llvm-linux-x64-gnu
'@scriptc/llvm-linux-x64-musl':
specifier: workspace:*
version: link:../llvm-linux-x64-musl
'@scriptc/llvm-win32-x64-msvc':
specifier: workspace:*
version: link:../llvm-win32-x64-msvc
'@scriptc/runtime-darwin-arm64':
specifier: workspace:*
version: link:../runtime-darwin-arm64
'@scriptc/runtime-darwin-x64':
specifier: workspace:*
version: link:../runtime-darwin-x64
'@scriptc/runtime-linux-arm64-gnu':
specifier: workspace:*
version: link:../runtime-linux-arm64-gnu
'@scriptc/runtime-linux-arm64-musl':
specifier: workspace:*
version: link:../runtime-linux-arm64-musl
'@scriptc/runtime-linux-x64-gnu':
specifier: workspace:*
version: link:../runtime-linux-x64-gnu
'@scriptc/runtime-linux-x64-musl':
specifier: workspace:*
version: link:../runtime-linux-x64-musl
'@scriptc/runtime-wasm32-wasi':
specifier: workspace:*
version: link:../runtime-wasm32-wasi
'@scriptc/runtime-win32-x64-msvc':
specifier: workspace:*
version: link:../runtime-win32-x64-msvc
packages/llvm-darwin-arm64: {}
packages/llvm-darwin-x64: {}
packages/llvm-linux-arm64-gnu: {}
packages/llvm-linux-arm64-musl: {}
packages/llvm-linux-x64-gnu: {}
packages/llvm-linux-x64-musl: {}
packages/llvm-win32-x64-msvc: {}
packages/runtime: {}
packages/runtime-darwin-arm64: {}
packages/runtime-darwin-x64: {}
packages/runtime-linux-arm64-gnu: {}
packages/runtime-linux-arm64-musl: {}
packages/runtime-linux-x64-gnu: {}
packages/runtime-linux-x64-musl: {}
packages/runtime-pack-common: {}
packages/runtime-wasm32-wasi: {}
packages/runtime-win32-x64-msvc: {}
packages:
'@bytecodealliance/preview2-shim@0.17.6':
+11 -1
View File
@@ -28,7 +28,17 @@ const root = fileURLToPath(new URL("..", import.meta.url));
const readJson = (path) => JSON.parse(readFileSync(root + path, "utf8"));
const version = readJson("packages/cli/package.json").version;
for (const pkg of ["runtime", "runtime-darwin-arm64", "llvm-darwin-arm64", "compiler"]) {
for (const pkg of [
"runtime",
"runtime-darwin-arm64", "runtime-darwin-x64",
"runtime-linux-x64-gnu", "runtime-linux-arm64-gnu",
"runtime-linux-x64-musl", "runtime-linux-arm64-musl",
"runtime-win32-x64-msvc", "runtime-wasm32-wasi",
"llvm-darwin-arm64", "llvm-darwin-x64",
"llvm-linux-x64-gnu", "llvm-linux-arm64-gnu",
"llvm-linux-x64-musl", "llvm-linux-arm64-musl", "llvm-win32-x64-msvc",
"compiler",
]) {
const v = readJson(`packages/${pkg}/package.json`).version;
if (v !== version) {
console.error(
+11 -1
View File
@@ -16,7 +16,17 @@ if (typeof version !== "string" || version.length === 0) {
process.exit(1);
}
for (const pkg of ["runtime", "runtime-darwin-arm64", "compiler", "llvm-darwin-arm64"]) {
for (const pkg of [
"runtime",
"runtime-darwin-arm64", "runtime-darwin-x64",
"runtime-linux-x64-gnu", "runtime-linux-arm64-gnu",
"runtime-linux-x64-musl", "runtime-linux-arm64-musl",
"runtime-win32-x64-msvc", "runtime-wasm32-wasi",
"compiler",
"llvm-darwin-arm64", "llvm-darwin-x64",
"llvm-linux-x64-gnu", "llvm-linux-arm64-gnu",
"llvm-linux-x64-musl", "llvm-linux-arm64-musl", "llvm-win32-x64-msvc",
]) {
const path = manifest(pkg);
const json = read(path);
if (json.version === version) {

Some files were not shown because too many files have changed in this diff Show More