Disclaimer
This document was AI curated from the working port, local build scripts, test harnesses, and diagnostic notes produced during development. The build artifacts, patches, and supporting components are being cleaned up and will be published to the repository once that process is complete.
Overview
This document describes how GNU Guile 3.0.11 was adapted to run as a browser-hosted WebAssembly runtime. The target was not just a successful compile to .wasm, but a usable browser runtime with pthread support, preloaded modules, worker-based execution, and end-to-end browser validation.
The resulting browser build ships as guile.js, guile.wasm, and guile.data. It runs inside a dedicated host worker, uses Emscripten pthreads internally, and passes a browser test suite that exercises evaluation, module loading, filesystem preload, networking-related modules, sockets, Guile threads, mutexes, condition variables, and SRFI-18 primitives.
Some limitations remain inherent to the browser platform. JIT is unavailable, native host dlopen is not supported in the browser environment, and cross-origin isolation is required for the pthread-enabled build.
Scope
The browser port was designed to provide the following:
stable browser-loadable output artifacts
pthread-enabled execution under Emscripten
a worker-hosted runtime that avoids page-thread deadlocks
preloaded Scheme modules and stage files
end-to-end browser validation rather than compile-only success
Porting Challenges
Several aspects of Guile do not map directly onto the browser runtime model:
Guile and its dependencies assume a POSIX-like environment.
Browser pthreads require shared memory and strict cross-origin isolation.
WebAssembly enforces stricter function-pointer behavior than many native targets.
Guile’s runtime depends on a garbage collector that must coordinate correctly with threads.
Browser deployment adds worker bootstrap, preload packaging, message passing, and response-header constraints.
As a result, the work required both source-level portability changes and browser-specific runtime integration.
Build Layout
The working browser build uses Emscripten with pthread support enabled and emits three browser-facing artifacts:
guile.jsguile.wasmguile.data
The browser link step is configured for workers, shared memory, filesystem preload, and a fixed pthread pool:
emcc \ -pthread \ -sENVIRONMENT=web,worker \ -sPTHREAD_POOL_SIZE=8 \ -sALLOW_MEMORY_GROWTH=1 \ -sFORCE_FILESYSTEM=1 \ -o wasm-browser/guile.jsThe final browser bundle links together:
Guile itself
a pthread-capable Boehm GC
libffilibunistringpreloaded Guile module files
preloaded stage artifacts
Static dependency builds were staged into a dedicated WebAssembly prefix so the final browser link step could be performed against a consistent set of pthread-enabled dependencies.
Guile Source Changes
The Guile source tree required several browser- and WebAssembly-oriented changes before the final bundle could link and run reliably.
Exact Callback Signatures
WebAssembly is less tolerant of loose function-pointer use than native builds. Several files required callback signature cleanup so indirect calls would match exactly under Emscripten:
libguile/hashtab.clibguile/gsubr.clibguile/smob.c
Extension and Loader Cleanup
Guile’s extension-loading path needed browser-safe callback handling and declaration cleanup:
libguile/extensions.clibguile/extensions.h
These changes did not make native host library loading meaningful in the browser, but they did keep Guile’s internal loader plumbing from failing on the browser target.
GC and Runtime Integration Fixes
Several runtime adjustments were required for the browser environment:
libguile/gc.cfor static-root handlinglibguile/i18n.cfor locale fallback behaviorlibguile/threads.cfor stack-base fallback during browser pthread startup
Emscripten Environment Wrapping
The helper in build-aux/with-emscripten-env.sh was updated to provide a stable Emscripten environment for repeatable browser builds.
Boehm GC Changes
Guile 3.0.11 depends on Boehm GC, so pthread support required a compatible GC build. A pthread-capable GC build was assembled in a temporary source tree with local changes to:
stop forcing Emscripten into
THREADS=nolink with
-lpthreaddefine
GC_THREADSfor the Emscripten pthread buildfollow the Linux pthread backend path
replace an unsupported
sigsuspend()-based wait path with an atomic wait loop suitable for the browser build
This was a key part of making the pthread-enabled port viable. Without a compatible GC build, Guile could be compiled to WebAssembly, but not exercised credibly as a multi-threaded browser runtime.
Runtime Architecture
The final browser runtime does not execute Guile directly on the page thread.
The runtime flow is:
The page loads the browser UI and checks
crossOriginIsolated.If isolation is present, the page starts a dedicated host worker.
The host worker loads
guile.js.guile.jsbecomes the Emscripten main runtime and manages its internal pthread worker pool.The page communicates with the Guile runtime through
postMessageand a small RPC surface.
This worker-hosted design was necessary because a direct page-thread Module.ccall(...) path was not reliable once real thread joins and mutex waits were exercised.
The stable C entry points remained:
guile_init_runtimeguile_eval_to_stringguile_last_statusguile_last_textguile_has_jit
Browser Integration Notes
Cross-Origin Isolation
The pthread-enabled build requires a properly isolated browser context. This is a runtime requirement, not just a deployment preference. The reference harness includes both a positive isolated mode and a negative non-isolated mode to verify that startup is explicitly blocked when the required headers are missing.
Chunked Bootstrap Evaluation
One important integration issue appeared after the binary itself was already viable. A large monolithic Scheme bootstrap blob, evaluated through a single guile_eval_to_string(...) call, caused failures that disappeared once the same bootstrap content was split into top-level forms and evaluated sequentially.
The working direction was:
represent bootstrap content as ordered top-level forms
build sequential chunks from those forms
evaluate chunks one at a time
keep a fail-closed single-flight guard so only one top-level eval runs per runtime instance at once
This single-flight guard does not disable pthreads. It only prevents overlapping top-level eval calls into the same runtime instance. A single eval can still create and use multiple Guile threads internally.
Validation
The port was validated with a browser harness and an end-to-end test runner rather than a compile-only workflow.
The browser test coverage includes:
arithmetic evaluation
SRFI module loading
(ice-9 format)preloaded filesystem access
(web uri)(web client)(system foreign-library)module loadingbrowser-side socket creation
(ice-9 threads)spawned thread creation and join
mutex-protected shared state
condition-variable signal and broadcast
distinct
current-threadidentitiesSRFI-18 thread, mutex, and condition-variable behavior
The final isolated positive run produced:
{ "runtimeReady": true, "isolationReady": true, "threadMode": "pthreads", "jitEnabled": false, "passCount": 15, "expectedLimitations": 1, "hardFailureCount": 0 }Working Capabilities
The current browser port supports:
a pthread-enabled Guile runtime under Emscripten
worker-hosted browser execution
preloaded Guile modules and stage files
real Guile thread creation and synchronization
networking-related module loading
browser-side socket creation
deterministic browser end-to-end validation
Known Limitations
The port remains bounded by the browser platform and current runtime design.
JIT remains unavailable in the browser build.
Named native
dlopenof host libraries remains unsupported.Cross-origin isolation is mandatory.
Browser networking and socket behavior follow browser and Emscripten semantics rather than native POSIX behavior.
Startup still emits several noisy but non-fatal warnings in the current tested path.
Summary
This port demonstrates that Guile 3.0.11 can be adapted into a practical browser-hosted WebAssembly runtime with pthread support, worker-hosted execution, module preload, and real browser validation.
The result is not native Guile transplanted unchanged into the browser. It is a browser-specific port with explicit compatibility work in Guile, Boehm GC, the build scripts, and the runtime harness. Once the build scripts, temporary dependency patches, and harness components are cleaned up, the relevant components will be published to the repository.