Skip to content

perf(trampoline): raw Win32 rewrite shrinks vp-shim to 8KB - #2466

Draft
fengmk2 wants to merge 3 commits into
mainfrom
perf/trampoline-raw-win32
Draft

perf(trampoline): raw Win32 rewrite shrinks vp-shim to 8KB#2466
fengmk2 wants to merge 3 commits into
mainfrom
perf/trampoline-raw-win32

Conversation

@fengmk2

@fengmk2 fengmk2 commented Aug 16, 2026

Copy link
Copy Markdown
Member

Alternative to #2465 for comparison: same build-std plumbing (first commit is the same change), plus a raw Win32 rewrite of the Windows shim on top. vp-shim.exe goes from 213KB to 8KB.

The rewrite follows the uv-trampoline structure:

  • #![no_main] with a mainCRTStartup entry point: no CRT startup, no std runtime init. Requires the atexit stub described in the RFC (current nightlies register TLS cleanup through C atexit, which otherwise drags CRT startup into the link).
  • Raw KERNEL32 calls replace std::process::Command. The child command line is the raw GetCommandLineW tail after the program argument, forwarded byte for byte, so the caller's quoting survives exactly. When the parent redirects stdio, the std handles are forced inheritable before CreateProcessW (same as uv-trampoline and distlib's launcher).
  • Error reporting improves over the current shim, which exits silently on most failures: every failure path now reports the failed call, the path involved, and the GetLastError code; a missing vp.exe also prints a recovery hint (reinstall or vp env setup). No core::fmt anywhere; diagnostics go through WriteFile with a hand-rolled decimal formatter.
  • The non-Windows build keeps the portable std::process::Command implementation (Unix shims are symlinks and never use it). The pure UTF-16 helpers live in cmdline.rs with unit tests that run on every platform.

Measured with cargo-xwin:

Target main #2465 (build-std only) this PR
x86_64-pc-windows-msvc 212,992 B 73,728 B 8,192 B
aarch64-pc-windows-msvc 190,976 B 71,168 B 9,216 B

For reference: uv-trampoline ships 45KB (it also carries PE resources, path canonicalization, and job objects), Scoop's default C shim is 136KB. The exe here imports only KERNEL32.

Error reporting compared with #2465

The #2465 source keeps the old code, which avoids core::fmt by discarding every io::Error and exiting bare, so it cannot print a reason. This PR prints GetLastError codes through a hand-rolled decimal formatter, at a cost of about 1.5KB:

Scenario #2465 (std source) this PR (raw Win32)
Cannot resolve own exe path, or the path has no parent directory Silent exit(1), no output at all Message names the failed call or prints the path it could not parse, plus the Win32 error code
vp.exe missing (broken install, deleted current junction, upgrade race) vite-plus: failed to execute <path>, no reason given Same prefix, plus the Win32 error code, plus a recovery hint: vp.exe is missing; reinstall vite-plus or run vp env setup
vp.exe present but not runnable (access denied, corrupt binary) Same generic message, no reason The error code distinguishes it (5 = access denied, 193 = not a valid executable, and so on)
Failure to set VP_HOME / VP_SHIM_TOOL Not detected; the child runs with wrong dispatch state Reported with the variable name and error code, then exits
Wait or exit-code retrieval fails Cannot fail separately (inside Command::status) Reported with the call name and error code
A genuine bug panics Silent abort Silent abort (identical; panic = "immediate-abort" in both)

Verified locally: release builds for both Windows targets and the host, 8 unit tests, crate clippy/fmt (in-crate and via --manifest-path from the root), and cargo metadata --locked on the workspace. The Windows implementation is new code, so the Windows CI leg (PTY snapshot suite, env_install_* and corepack shim fixtures) is the behavior gate that matters before either PR lands.

Move crates/vp_trampoline out of the workspace: cargo ignores `panic` in
per-package profile overrides, so the crate needs its own release profile
with panic = "immediate-abort". A crate-local .cargo/config.toml enables
build-std, which recompiles std under the size profile and compiles out
the panic formatting, unwinding, and backtrace machinery. The source is
unchanged.

vp-shim.exe: 212,992 B to 73,728 B on x86_64-pc-windows-msvc,
190,976 B to 71,168 B on aarch64-pc-windows-msvc.

The crate config keeps artifacts in the repo-root target/ directory, so
CI steps, the snapshot runner, and install-global-cli find vp-shim.exe in
the same place as before. Every former `cargo build -p vp_trampoline`
call site now builds from the crate directory so the config applies.
rust-toolchain.toml adds the rust-src component (build-std needs the std
sources). The root Cargo.lock drops the stale vp_trampoline entry, which
--locked commands require. The trampoline RFC records the measured
variant table down to a 7KB raw-Win32 recipe for future size work.
…to 8KB

On top of the build-std profile, replace the Windows implementation:
#![no_main] with a mainCRTStartup entry point (no CRT startup, no std
runtime init) and raw KERNEL32 calls instead of std::process::Command,
in the uv-trampoline structure. The child command line is the raw
GetCommandLineW tail after the program argument, forwarded byte for
byte, so the caller's quoting survives exactly. When the parent
redirects stdio, the std handles are forced inheritable before
CreateProcessW, the same way uv-trampoline and distlib's launcher do.

Unlike the previous silent process::exit(1) paths, every failure now
reports the failed call, the path involved, and the GetLastError code
to stderr; a missing vp.exe additionally prints a recovery hint.

vp-shim.exe: 73,728 B to 8,192 B on x86_64-pc-windows-msvc, 71,168 B to
9,216 B on aarch64-pc-windows-msvc. The exe imports only KERNEL32.

The non-Windows build keeps the portable std::process::Command
implementation so the crate builds and tests everywhere; Unix shims are
symlinks and never use it. The pure UTF-16 helpers (program-argument
skip, file stem, decimal formatter) live in cmdline.rs with unit tests
that run on every platform. The RFC records the raw design, the
measured size ladder, and the no_main link gotchas.
@netlify

netlify Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 8a5b436
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a81a95891786e00082d9e9f

@fengmk2 fengmk2 added the preview-build Publish this PR's commits to the registry bridge as preview builds label Aug 16, 2026
The Build Windows tests archive step enumerates crates/*/ and passed
-p vp_trampoline, which is no longer a workspace member; skip it like
the justfile test recipe does (its only test module is unix-only, so it
has nothing to contribute to the Windows archive). Also apply oxfmt
0.63.0 to the RFC tables, which vp check flags on the CLI E2E jobs.
@fengmk2 fengmk2 added preview-build Publish this PR's commits to the registry bridge as preview builds and removed preview-build Publish this PR's commits to the registry bridge as preview builds labels Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Native binary sizes (8a5b436)

Final release artifacts built by the canonical build-upstream and build-windows-cli actions.

Artifact Format Base PR Change
vp (Linux x64) Binary 10.67 MiB 10.68 MiB +4.00 KiB (+0.04%)
vp (Linux x64) gzip -9 4.62 MiB 4.62 MiB +566 B (+0.01%)
NAPI (Linux x64) Binary 32.02 MiB 32.03 MiB +8.00 KiB (+0.02%)
NAPI (Linux x64) gzip -9 12.61 MiB 12.61 MiB -476 B (-0.00%)
vp (macOS ARM64) Binary 7.98 MiB 7.98 MiB +16 B (+0.00%)
vp (macOS ARM64) gzip -9 4.03 MiB 4.03 MiB +312 B (+0.01%)
NAPI (macOS ARM64) Binary 39.68 MiB 39.68 MiB +32 B (+0.00%)
NAPI (macOS ARM64) gzip -9 16.92 MiB 16.92 MiB +893 B (+0.01%)
vp (Windows x64) Binary 8.55 MiB 8.56 MiB +7.00 KiB (+0.08%)
vp (Windows x64) gzip -9 3.73 MiB 3.73 MiB +1.87 KiB (+0.05%)
NAPI (Windows x64) Binary 26.89 MiB 26.90 MiB +8.50 KiB (+0.03%)
NAPI (Windows x64) gzip -9 10.68 MiB 10.69 MiB +1.81 KiB (+0.02%)
Trampoline (Windows x64) Binary 205.00 KiB 8.00 KiB -197.00 KiB (-96.10%)
Trampoline (Windows x64) gzip -9 99.00 KiB 3.46 KiB -95.54 KiB (-96.51%)
Installer (Windows x64) Binary 4.47 MiB 4.47 MiB +5.50 KiB (+0.12%)
Installer (Windows x64) gzip -9 2.09 MiB 2.09 MiB +1.48 KiB (+0.07%)

@fengmk2 fengmk2 self-assigned this Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Registry bridge build (8a5b436)

This commit build is published to the registry bridge, which serves these as ordinary npm versions (every other package proxies to npmjs):

Package Version
vite-plus 0.0.0-commit.8a5b4369463a2dff1b6a1cb84b78e4e14297e291
@voidzero-dev/vite-plus-core 0.0.0-commit.8a5b4369463a2dff1b6a1cb84b78e4e14297e291

Install the Vite+ CLI built from this commit, then migrate a project:

# macOS / Linux
curl -fsSL https://vite.plus | VP_PR_VERSION=2466 bash
# Windows (PowerShell)
$env:VP_PR_VERSION="2466"; irm https://vite.plus/ps1 | iex

After installing, upgrade the current project's vite-plus to this test build with:

vp migrate

Or point your package manager at the bridge registry https://registry-bridge.viteplus.dev/:

Package manager Registry config
npm / pnpm / Bun .npmrc: registry=https://registry-bridge.viteplus.dev/
Yarn (v2+) .yarnrc.yml: npmRegistryServer: "https://registry-bridge.viteplus.dev/"

Then pin the build (vite aliases to vite-plus-core; pnpm can use a catalog, npm an overrides entry):

{
  "devDependencies": {
    "vite-plus": "0.0.0-commit.8a5b4369463a2dff1b6a1cb84b78e4e14297e291",
    "vite": "npm:@voidzero-dev/vite-plus-core@0.0.0-commit.8a5b4369463a2dff1b6a1cb84b78e4e14297e291"
  }
}

@github-actions

Copy link
Copy Markdown
Contributor

🐳 Docker preview image

Built from this PR's registry bridge build:

Image Compressed size
ghcr.io/voidzero-dev/vite-plus:pr-2466 236MB
# remove any stale local copy from a previous run, then pull fresh
docker rmi ghcr.io/voidzero-dev/vite-plus:pr-2466 2>/dev/null; docker pull ghcr.io/voidzero-dev/vite-plus:pr-2466

Quick check:

docker run --rm ghcr.io/voidzero-dev/vite-plus:pr-2466 vp --version

See docs/guide/docker.md for usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview-build Publish this PR's commits to the registry bridge as preview builds

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant