diff --git a/PLAN.md b/PLAN.md index cadba6e..59d6c80 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,7 +1,7 @@ # AArch64 ELF-to-WebAssembly Browser Runtime ## Engineering Build Plan and Agent Handoff -**Status:** Phase 5 complete; Phase 6 in progress (native Clang/LLD acceptance complete) +**Status:** Phase 5 complete; Phase 6 in progress (browser Clang/LLD acceptance complete) **Primary implementation language:** Rust **Initial browser target:** Google Chrome **Guest architecture:** AArch64, little-endian, Linux userspace @@ -1881,6 +1881,8 @@ The promised distribution now has a reproducible, checksum-pinned producer for L The native source-to-ELF acceptance path is now complete and reproducible through `toolchains/clang-musl/verify.sh`. A bounded Clang `-cc1` run compiles a minimal C `main` into `/project/main.o`, a separate bounded LLD run links that object with the packaged musl startup objects and `libc.a`, and a generic checked-in AArch64 launcher uses `execve` to run the guest-created `/project/hello`, which exits 42. The observed workload added scalar `FNEG`, a conservative `DCZID_EL0` value that disables `DC ZVA`, `pread64`, `ftruncate`, and stable path-derived inode identities so Clang does not collapse distinct include roots. Clang completes below a 50-million-instruction ceiling and LLD completes under the same ceiling. The next checkpoint is this exact compile/link/execute sequence in the translated Chromium host; direct child-process orchestration and pipes remain subsequent Phase 6 work. +The same source-to-ELF sequence now passes in a real Chromium Worker through the opt-in `toolchains/clang-musl/verify-browser.sh` regression. In one persisted browser session, Clang exits zero after 32,725,151 guest instructions and 507,877 translated-block executions, LLD exits zero after 11,026,753 interpreted guest instructions, and the generic launcher executes the browser-created ELF to exit 42 after 739 instructions. The measured end-to-end browser run completes in 19.2 minutes on the reference host. This satisfies the core local compile/link/execute path with staged process orchestration; the remaining Phase 6 work is editor-to-filesystem integration, compiler source diagnostics in the product UI, direct child-process and pipe semantics, cache policy, and interactive-performance improvements. + Do not begin the full web IDE before item 30 passes. --- diff --git a/toolchains/clang-musl/README.md b/toolchains/clang-musl/README.md index ea361bf..28e6177 100644 --- a/toolchains/clang-musl/README.md +++ b/toolchains/clang-musl/README.md @@ -43,3 +43,14 @@ The verifier compiles the packaged `project/main.c` under a 50-million guest instruction limit, links it against the packaged musl startup objects and `libc.a` in a second bounded run, then executes the guest-created ELF through the generic `execve` launcher. Success is the compiled program's exit code 42. + +The equivalent opt-in Chromium acceptance requires a repo-local Playwright +Chromium installation and runs for roughly 20 minutes on the reference host: + +```sh +toolchains/clang-musl/verify-browser.sh +``` + +The browser test imports the same images, uploads the same Clang and LLD ELFs, +persists `main.o` and `hello` between Worker runs, and executes `hello` through +the same generic launcher. It remains outside the normal fast browser suite. diff --git a/toolchains/clang-musl/verify-browser.sh b/toolchains/clang-musl/verify-browser.sh new file mode 100755 index 0000000..b0864f3 --- /dev/null +++ b/toolchains/clang-musl/verify-browser.sh @@ -0,0 +1,39 @@ +#!/bin/sh +set -eu + +toolchain_directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +workspace_directory=$(CDPATH= cd -- "$toolchain_directory/../.." && pwd) +work_directory=$workspace_directory/.tmp/phase6-clang +web_directory=$workspace_directory/web +clang_runtime=$work_directory/package/toolchain/bin/clang +lld_runtime=$work_directory/package/toolchain/bin/lld +toolchain_image=$work_directory/clang-musl.bnfs +project_image=$work_directory/clang-project.bnfs + +for artifact in "$clang_runtime" "$lld_runtime" "$toolchain_image" "$project_image"; do + if [ ! -f "$artifact" ]; then + echo "missing toolchain artifact: $artifact; run toolchains/clang-musl/build.sh" >&2 + exit 1 + fi +done + +mkdir -p \ + "$workspace_directory/.tmp/cargo-home" \ + "$workspace_directory/.tmp/cargo-target" \ + "$workspace_directory/.tmp/pnpm-home" \ + "$workspace_directory/.tmp/playwright-browsers" \ + "$workspace_directory/.tmp/rust-tmp" +export CARGO_HOME=$workspace_directory/.tmp/cargo-home +export CARGO_TARGET_DIR=$workspace_directory/.tmp/cargo-target +export PNPM_HOME=$workspace_directory/.tmp/pnpm-home +export PLAYWRIGHT_BROWSERS_PATH=$workspace_directory/.tmp/playwright-browsers +export TMPDIR=$workspace_directory/.tmp/rust-tmp + +env CARGO_NET_OFFLINE=true pnpm --dir "$web_directory" generate:probes +BINARROW_CLANG_RUNTIME=$clang_runtime \ +BINARROW_LLD_RUNTIME=$lld_runtime \ +BINARROW_CLANG_TOOLCHAIN_IMAGE=$toolchain_image \ +BINARROW_CLANG_PROJECT_IMAGE=$project_image \ + pnpm --dir "$web_directory" exec playwright test \ + tests/clang-toolchain.spec.ts \ + --project chromium diff --git a/web/tests/clang-toolchain.spec.ts b/web/tests/clang-toolchain.spec.ts new file mode 100644 index 0000000..a0940e0 --- /dev/null +++ b/web/tests/clang-toolchain.spec.ts @@ -0,0 +1,141 @@ +import { fileURLToPath } from "node:url"; + +import { expect, test, type Page } from "@playwright/test"; + +const clangRuntime = process.env.BINARROW_CLANG_RUNTIME; +const lldRuntime = process.env.BINARROW_LLD_RUNTIME; +const toolchainImage = process.env.BINARROW_CLANG_TOOLCHAIN_IMAGE; +const projectImage = process.env.BINARROW_CLANG_PROJECT_IMAGE; +const launcher = fileURLToPath( + new URL( + "../../guest-tests/exec-from-filesystem/exec-from-filesystem.aarch64.elf", + import.meta.url, + ), +); + +interface GuestReport { + diagnosticCode: string; + exitCode: string; + instructions: string; + output: string; + status: string; + translatedBlocks: string; +} + +async function runUploadedProgram( + page: Page, + executable: string, + argv0: string, + arguments_: readonly string[], + instructionBudget: string, + timeout: number, +): Promise { + await page.getByLabel("AArch64 ELF executable").setInputFiles(executable); + await page.getByLabel("argv[0]").fill(argv0); + await page.getByLabel("Arguments (one per line)").fill(arguments_.join("\n")); + await page.getByLabel("Instruction budget").fill(instructionBudget); + await page.getByRole("button", { name: "Start" }).click(); + const status = page.getByRole("status"); + await expect(status).toHaveText("Guest running…"); + await expect(status).not.toHaveText("Guest running…", { timeout }); + const translatedBlocks = await page + .locator("#translated-block-executions") + .textContent(); + const report = { + diagnosticCode: (await page.locator("#diagnostic-code").textContent()) ?? "", + exitCode: (await page.locator("#exit-code").textContent()) ?? "", + instructions: (await page.locator("#instruction-count").textContent()) ?? "", + output: (await page.getByLabel("Guest terminal output").textContent()) ?? "", + status: (await status.textContent()) ?? "", + translatedBlocks: translatedBlocks ?? "0", + }; + console.log(`Guest stage report: ${JSON.stringify(report)}`); + expect(report.status, JSON.stringify(report)).toBe("Guest exited"); + return report; +} + +test("compiles, links, and runs C with packaged Clang", async ({ page }) => { + test.skip( + !clangRuntime || !lldRuntime || !toolchainImage || !projectImage, + "set all BINARROW_CLANG and BINARROW_LLD artifact variables", + ); + if (!clangRuntime || !lldRuntime || !toolchainImage || !projectImage) { + return; + } + test.setTimeout(60 * 60 * 1_000); + + await page.goto("/"); + await expect(page.getByRole("status")).toHaveText("Browser runtime ready"); + await page.getByLabel("Filesystem byte limit").fill("268435456"); + await page.getByLabel("Memory byte limit").fill("1073741824"); + await page + .getByLabel("Snapshot or package image") + .setInputFiles(toolchainImage); + await page.getByRole("button", { name: "Replace project" }).click(); + await expect(page.getByRole("status")).toHaveText( + "Project snapshot imported", + { timeout: 3 * 60 * 1_000 }, + ); + await page.getByLabel("Snapshot or package image").setInputFiles(projectImage); + await page.getByRole("button", { name: "Install image" }).click(); + await expect(page.getByRole("status")).toHaveText("Project image installed", { + timeout: 3 * 60 * 1_000, + }); + await page.getByLabel("Fixture").selectOption("uploaded-program"); + + const clang = await runUploadedProgram( + page, + clangRuntime, + "/project/toolchain/bin/clang", + [ + "-cc1", + "-triple", + "aarch64-unknown-linux-musl", + "-isysroot", + "/project/toolchain/sysroot", + "-resource-dir", + "/project/toolchain/lib/clang/22", + "-internal-isystem", + "/project/toolchain/lib/clang/22/include", + "-internal-externc-isystem", + "/project/toolchain/sysroot/usr/include", + "-emit-obj", + "-o", + "/project/main.o", + "/project/main.c", + ], + "50000000", + 30 * 60 * 1_000, + ); + expect(clang.exitCode, JSON.stringify(clang)).toBe("0"); + expect(BigInt(clang.translatedBlocks)).toBeGreaterThan(0n); + + const lld = await runUploadedProgram( + page, + lldRuntime, + "/project/toolchain/bin/ld.lld", + [ + "-static", + "-o", + "/project/hello", + "/project/toolchain/sysroot/usr/lib/crt1.o", + "/project/toolchain/sysroot/usr/lib/crti.o", + "/project/main.o", + "/project/toolchain/sysroot/usr/lib/libc.a", + "/project/toolchain/sysroot/usr/lib/crtn.o", + ], + "50000000", + 20 * 60 * 1_000, + ); + expect(lld.exitCode, JSON.stringify(lld)).toBe("0"); + + const program = await runUploadedProgram( + page, + launcher, + "/exec-from-filesystem", + ["/project/hello"], + "100000", + 60_000, + ); + expect(program.exitCode, JSON.stringify(program)).toBe("42"); +});