cat blog/gnu-guile-3011-webassembly-browser-port-notes.md
    $ cat blog/gnu-guile-3011-webassembly-browser-port-notes.md
    ·6 min read#guile#wasm#guide#porting

    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:

    1. Guile and its dependencies assume a POSIX-like environment.

    2. Browser pthreads require shared memory and strict cross-origin isolation.

    3. WebAssembly enforces stricter function-pointer behavior than many native targets.

    4. Guile’s runtime depends on a garbage collector that must coordinate correctly with threads.

    5. 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.js

    • guile.wasm

    • guile.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.js

    The final browser bundle links together:

    • Guile itself

    • a pthread-capable Boehm GC

    • libffi

    • libunistring

    • preloaded 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.c

    • libguile/gsubr.c

    • libguile/smob.c

    Extension and Loader Cleanup

    Guile’s extension-loading path needed browser-safe callback handling and declaration cleanup:

    • libguile/extensions.c

    • libguile/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.c for static-root handling

    • libguile/i18n.c for locale fallback behavior

    • libguile/threads.c for 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=no

    • link with -lpthread

    • define GC_THREADS for the Emscripten pthread build

    • follow 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:

    1. The page loads the browser UI and checks crossOriginIsolated.

    2. If isolation is present, the page starts a dedicated host worker.

    3. The host worker loads guile.js.

    4. guile.js becomes the Emscripten main runtime and manages its internal pthread worker pool.

    5. The page communicates with the Guile runtime through postMessage and 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_runtime

    • guile_eval_to_string

    • guile_last_status

    • guile_last_text

    • guile_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 loading

    • browser-side socket creation

    • (ice-9 threads)

    • spawned thread creation and join

    • mutex-protected shared state

    • condition-variable signal and broadcast

    • distinct current-thread identities

    • SRFI-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 dlopen of 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.