Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bb5e985
implement host-side TLS termination proxy and guest-side shim for per…
mavdol Jul 15, 2026
7199976
implement TLS-terminating proxy with CA assets, guest SSL shims, and …
mavdol Jul 15, 2026
c973453
clean up build scripts, documentation and format rust code
mavdol Jul 15, 2026
0ce42be
format
mavdol Jul 15, 2026
be9bd6b
clippy formatting
mavdol Jul 15, 2026
afccd52
install zig in ci to compile c files
mavdol Jul 15, 2026
bf82c38
implement Python cold-start optimization using a persistent daemon an…
mavdol Jul 16, 2026
3791a62
add Python shim and TLS certificate interception for warm-start envir…
mavdol Jul 16, 2026
364573b
remove libssl shim and add Python benchmarking and integration tests
mavdol Jul 16, 2026
44ac861
improve TLS proxy
mavdol Jul 16, 2026
7c37bdf
update shim
mavdol Jul 16, 2026
c1b7c5a
TLS proxy interoperability tests
mavdol Jul 16, 2026
c17e041
move TLS proxy logic into a module
mavdol Jul 16, 2026
45f0b9d
venv-aware shim execution
mavdol Jul 16, 2026
d3dea0a
delete pip from install
mavdol Jul 17, 2026
c304cc1
implement riscv-core performance counters and CLI reporting integration
mavdol Jul 17, 2026
35c2d21
make perf call cleaner
mavdol Jul 17, 2026
1c9cb73
implement AOT compilation framework for riscv-core
mavdol Jul 17, 2026
0a5bd9b
implement MMU cache for load/store translations
mavdol Jul 18, 2026
521bed0
add differential testing harness, warm-python integration tests, and …
mavdol Jul 18, 2026
97f4fc9
store-fast TLB optimization to riscv-core
mavdol Jul 18, 2026
f3cb00e
optimize execution loop with interrupt-aware scheduling, timer fast-…
mavdol Jul 18, 2026
8090f72
implement AOT dispatch chaining and try add generate.rs in ci
mavdol Jul 19, 2026
d4fc6ec
fix ci
mavdol Jul 19, 2026
a0d5c8c
implement AOT differential testing harness and add Python integration…
mavdol Jul 19, 2026
b83b63d
improve AOT translation execution logic
mavdol Jul 19, 2026
e173d0f
delete mmu cache flush and invalidation
mavdol Jul 19, 2026
1d8a72e
Extend RISC-V Zba instruction support
mavdol Jul 20, 2026
eba3cc2
improve block.rs
mavdol Jul 20, 2026
7d591ab
enable AOT stub generation in CI
mavdol Jul 20, 2026
dfb5ca8
implement AOT translation script
mavdol Jul 20, 2026
f0a31e7
introduce AOT compilation for WASM modules with background prewarming…
mavdol Jul 21, 2026
d6a851a
implement AOT build support, CI pipelines for snapshots and AOT
mavdol Jul 21, 2026
498e4f5
use dynamic export access in command and code interfaces
mavdol Jul 21, 2026
6d15a1b
Add quick script to build snapshot for registry purporse
mavdol Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions .github/workflows/build-aot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Build AOT

# Traces a snapshot, translates its hot blocks, and emits the engine modules
# every other workflow consumes. See docs/design/release-strategy.md.
#
# The output is wasm32-wasip2 and therefore host-independent: one build serves
# all five platform targets. Running this inside build-binaries' matrix would
# pay the whole pipeline five times.

on:
workflow_call:
inputs:
snapshot:
description: 'Snapshot id to trace against'
required: false
default: 'alpine-3.23.0-256mb'
type: string
workflow_dispatch:
inputs:
snapshot:
description: 'Snapshot id to trace against'
required: false
default: 'alpine-3.23.0-256mb'
type: string

jobs:
build-aot:
name: Trace and translate
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Resolve snapshot in registry
id: registry
env:
SNAPSHOT: ${{ inputs.snapshot }}
run: |
python3 - <<'PY' >> "$GITHUB_OUTPUT"
import json, os, urllib.request

snap_id = os.environ["SNAPSHOT"]
registry = json.load(urllib.request.urlopen(
"https://registry.vpod.sh/v1/snapshots.json", timeout=60))
entry = next((s for s in registry["snapshots"] if s["id"] == snap_id), None)
if entry is None:
raise SystemExit(f"snapshot '{snap_id}' not in registry")
print(f"sha256={entry['sha256']}")
print(f"url={entry['url']}")
PY

- name: Cache AOT modules
id: cache
uses: actions/cache@v4
with:
path: dist/aot
key: >-
aot-${{ inputs.snapshot }}-${{ steps.registry.outputs.sha256 }}-${{ hashFiles(
'crates/riscv-core/**',
'crates/machine/**',
'crates/vpod-translate/**',
'crates/wasi-component/**',
'scripts/aot-snapshot.sh',
'scripts/build-wasm.sh') }}

- name: Install Rust
if: steps.cache.outputs.cache-hit != 'true'
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip2

- name: Cache cargo
if: steps.cache.outputs.cache-hit != 'true'
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: aot-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Fetch snapshot
if: steps.cache.outputs.cache-hit != 'true'
env:
SNAPSHOT: ${{ inputs.snapshot }}
SNAPSHOT_URL: ${{ steps.registry.outputs.url }}
SNAPSHOT_SHA256: ${{ steps.registry.outputs.sha256 }}
run: |
python3 -m pip install --quiet lz4
python3 - <<'PY'
import hashlib, os, shutil, urllib.request
from pathlib import Path
import lz4.frame

snap_id = os.environ["SNAPSHOT"]
url = os.environ["SNAPSHOT_URL"]
sha256 = os.environ["SNAPSHOT_SHA256"]

dist = Path("dist"); dist.mkdir(exist_ok=True)
packed = dist / f"{snap_id}.snap.lz4"
with urllib.request.urlopen(url, timeout=300) as r, open(packed, "wb") as f:
shutil.copyfileobj(r, f)

digest = hashlib.sha256(packed.read_bytes()).hexdigest()
if digest != sha256:
raise SystemExit(f"checksum mismatch: {digest} != {sha256}")

out = dist / f"{snap_id}.snap"
with lz4.frame.open(str(packed), "rb") as src, open(out, "wb") as dst:
shutil.copyfileobj(src, dst)
packed.unlink()
print(f"{out} ({out.stat().st_size/1e6:.1f} MB)")
PY

- name: Trace and translate
if: steps.cache.outputs.cache-hit != 'true'
run: ./scripts/aot-snapshot.sh "dist/${{ inputs.snapshot }}.snap" --force

- name: Build engine modules
if: steps.cache.outputs.cache-hit != 'true'
run: ./scripts/build-wasm.sh

- name: Collect
if: steps.cache.outputs.cache-hit != 'true'
run: |
mkdir -p dist/aot
cp sdks/python/vpod/vpod_wasi_lib_aot.wasm dist/aot/
cp sdks/python/vpod/vpod_wasi_lib.wasm dist/aot/
cp target/wasm32-wasip2/release/vpod-wasi-cli.wasm dist/aot/
printf '%s %s\n' "${{ inputs.snapshot }}" "${{ steps.registry.outputs.sha256 }}" > dist/aot/TRACED_SNAPSHOT
ls -lh dist/aot

- name: Verify
run: |
for f in vpod_wasi_lib_aot.wasm vpod_wasi_lib.wasm vpod-wasi-cli.wasm; do
test -s "dist/aot/$f" || { echo "missing dist/aot/$f" >&2; exit 1; }
done

- name: Upload
uses: actions/upload-artifact@v4
with:
name: aot-wasm
path: dist/aot
retention-days: 1
overwrite: true
41 changes: 34 additions & 7 deletions .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
name: Build Binaries

on:
release:
types: [published]
workflow_call:
inputs:
tag:
description: 'Release tag to upload binaries to (e.g. v0.1.3)'
required: true
type: string
snapshot:
description: 'Snapshot the AOT module is traced against'
required: false
default: 'alpine-3.23.0-256mb'
type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag to upload binaries to (e.g. v0.1.3)'
required: true
type: string
snapshot:
description: 'Snapshot the AOT module is traced against'
required: false
default: 'alpine-3.23.0-256mb'
type: string

permissions:
contents: write

jobs:
build-aot:
uses: ./.github/workflows/build-aot.yml
with:
snapshot: ${{ inputs.snapshot }}

build:
name: ${{ matrix.target }}
needs: build-aot
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -44,14 +64,14 @@ jobs:
- name: Inject version
shell: bash
run: |
V="${{ github.event_name == 'release' && github.ref_name || inputs.tag }}"
V="${{ inputs.tag }}"
V="${V#v}"
perl -i -pe "s/^version = .*/version = \"$V\"/" crates/vpod/Cargo.toml

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip2,${{ matrix.target }}
targets: ${{ matrix.target }}

- name: Install cargo-zigbuild (Linux aarch64)
if: matrix.zigbuild
Expand All @@ -60,8 +80,15 @@ jobs:
cargo install cargo-zigbuild
rustup target add aarch64-unknown-linux-gnu

- name: Build WASM component
run: cargo build --release --target wasm32-wasip2 -p wasi-component
- name: Fetch AOT engine module
uses: actions/download-artifact@v4
with:
name: aot-wasm
path: dist/aot

- name: Place module for build.rs
shell: bash
run: cp dist/aot/vpod-wasi-cli.wasm crates/vpod/vpod-wasi-cli.wasm

- name: Build binary (native)
if: "!matrix.zigbuild"
Expand Down Expand Up @@ -92,7 +119,7 @@ jobs:
- name: Upload to release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event_name == 'release' && github.ref_name || inputs.tag }}
tag_name: ${{ inputs.tag }}
files: ${{ env.ASSET }}

- name: Upload artifact
Expand Down
160 changes: 160 additions & 0 deletions .github/workflows/build-snapshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Build Snapshots

# Builds the registry snapshots and (optionally) publishes them to R2.
# See docs/design/release-strategy.md — this is "step 0": any snapshot change
# must be in the registry BEFORE build-aot traces against it, or the release
# ships an engine whose page hashes never match (silent interpreter fallback).
#
# Deliberately NOT run on every release: snapshot builds are not reproducible
# (apk pulls whatever Alpine serves that day), so every rebuild changes the
# bytes — and republishing changed bytes under the same ids silently degrades
# AOT for every previously shipped engine. Publish only when guest content
# actually changed, and always cut a release right after.
#
# The registry stores lz4-compressed files under the .snap name; sha256 and
# size in snapshots.json are of the *compressed* bytes (what the SDK and
# build-aot.yml verify before decompressing).

on:
workflow_dispatch:
inputs:
version:
description: 'Registry manifest version (e.g. 0.5.0)'
required: true
type: string
publish:
description: 'Upload to R2 (off = dry run, artifacts only)'
required: false
default: false
type: boolean
workflow_call:
inputs:
version:
required: true
type: string
publish:
required: false
default: false
type: boolean
secrets:
R2_ACCOUNT_ID:
required: false
R2_ACCESS_KEY_ID:
required: false
R2_SECRET_ACCESS_KEY:
required: false
R2_BUCKET:
required: false

jobs:
build-snapshots:
name: Build and publish snapshots
runs-on: ubuntu-latest
timeout-minutes: 300

steps:
- uses: actions/checkout@v4

- name: Install host tools
run: |
sudo apt-get update -q
sudo apt-get install -y -q libarchive-tools cpio lz4

- name: Install Zig
uses: mlugg/setup-zig@v1

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: snapshots-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Build default snapshot (alpine-3.23.0-256mb)
run: ./scripts/build-default-snapshot.sh

- name: Build data snapshot (vsnap-data-512mb)
run: ./scripts/build-data-snapshot.sh

- name: Compress for the registry
run: |
mkdir -p dist/registry
lz4 -9 -f dist/alpine-3.23.0-256mb.snap dist/registry/alpine-3.23.0-256mb.snap
cp dist/registry/alpine-3.23.0-256mb.snap dist/registry/vsnap-base-256mb.snap
lz4 -9 -f dist/vsnap-data-512mb.snap dist/registry/vsnap-data-512mb.snap
ls -lh dist/registry

- name: Generate snapshots.json
env:
VERSION: ${{ inputs.version }}
run: |
python3 - <<'PY'
import hashlib, json, os, urllib.request
from pathlib import Path

registry_dir = Path("dist/registry")
manifest = json.load(urllib.request.urlopen(
"https://registry.vpod.sh/v1/snapshots.json", timeout=60))

manifest["version"] = os.environ["VERSION"]
for entry in manifest["snapshots"]:
built = registry_dir / f"{entry['id']}.snap"
if not built.exists():
print(f" {entry['id']}: not rebuilt, keeping registry entry")
continue
data = built.read_bytes()
entry["sha256"] = hashlib.sha256(data).hexdigest()
entry["size"] = len(data)
print(f" {entry['id']}: sha256={entry['sha256'][:12]}… size={entry['size']}")

(registry_dir / "snapshots.json").write_text(
json.dumps(manifest, indent=2) + "\n")
PY

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: registry-snapshots
path: dist/registry/snapshots.json
retention-days: 30

- name: Publish to R2
if: ${{ inputs.publish }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
BUCKET: ${{ secrets.R2_BUCKET }}
run: |
for f in dist/registry/*.snap; do
aws s3 cp "$f" "s3://$BUCKET/v1/$(basename "$f")" --endpoint-url "$ENDPOINT"
done
aws s3 cp dist/registry/snapshots.json "s3://$BUCKET/v1/snapshots.json" \
--endpoint-url "$ENDPOINT" --content-type application/json

- name: Verify the live registry
if: ${{ inputs.publish }}
run: |
python3 - <<'PY'
import hashlib, json, urllib.request
from pathlib import Path

local = json.loads(Path("dist/registry/snapshots.json").read_text())
live = json.load(urllib.request.urlopen(
"https://registry.vpod.sh/v1/snapshots.json", timeout=60))
if live != local:
raise SystemExit("live snapshots.json does not match what we uploaded")

for entry in local["snapshots"]:
with urllib.request.urlopen(entry["url"], timeout=300) as r:
digest = hashlib.sha256(r.read()).hexdigest()
if digest != entry["sha256"]:
raise SystemExit(f"{entry['id']}: served bytes do not match manifest")
print(f"{entry['id']}: ok")
PY
Loading
Loading