Skip to content

feat: add browser XGo executor#3344

Draft
nighca wants to merge 6 commits into
goplus:devfrom
nighca:issue-3341
Draft

feat: add browser XGo executor#3344
nighca wants to merge 6 commits into
goplus:devfrom
nighca:issue-3341

Conversation

@nighca

@nighca nighca commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #3341

Summary

  • add a browser-side XGo executor backed by ixgo and xgobuild
  • isolate every executor in its own Web Worker/WASM runtime so compilation stays off the UI thread and concurrent executors do not share Go bridge globals
  • distinguish XGo-to-UI capability calls from UI-to-XGo framework events, with JSON request/response and asynchronous frontend handlers
  • add a tutorial class framework and keep its implementation separate from its xgoexec binding
  • expose program output and lifecycle events, and add a debug page covering plain XGo and tutorial interactions

Tutorial mechanism exploration

  • showMessage: output with a UI side effect
  • readCode: asynchronous input read from the UI
  • setProgress: structured output used to update UI state
  • onSubmit: an event listener driven by events dispatched from the UI into the running course

The plain XGo case exercises a long-running program and common standard packages. The embedded standard-library set currently includes bytes, encoding/json, errors, fmt, io, math, math/rand, sort, strconv, strings, and time.

Validation

  • go test ./... in tools/xgoexec
  • pnpm run lint
  • pnpm run type-check
  • pnpm run test -- --run (717 tests)
  • pnpm run build
  • browser validation for observable per-second output, common standard-library APIs, concurrent isolated executors, asynchronous capability results, repeated onSubmit events, immediate completion, and stopping both runtimes

gemini-code-assist[bot]

This comment was marked as outdated.

@nighca

nighca commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

xgoexec cost evaluation

I measured the current implementation on an ARM64 MacBook Air with Chromium and the local Vite server. The numbers below are intended to establish the order of magnitude; lower-end and mobile devices still need separate validation.

Summary

Dimension xgoexec measurement Comparable JavaScript implementation
Additional network resource 25.20 MiB raw / 5.11 MiB gzip / 4.03 MiB Brotli Usually a few KiB or part of the existing bundle
Memory per active executor About 49–50 MiB of WASM linear memory after build Reuses the page JS runtime; application state is relatively small
Warm startup to execution About 210–290 ms Usually below a few milliseconds
First startup in this browser session About 690 ms before execution, excluding a real public-network download Usually below a few milliseconds
1,000,000 integer-loop iterations 590–837 ms About 1.31 ms
Capability round trip 100 sequential calls in 21 ms, about 0.21 ms/call Direct calls are effectively negligible
Event dispatch acknowledgement 2–3 ms/event Direct callbacks are effectively negligible

Network

The additional payload is almost entirely xgoexec.wasm:

  • raw: 25.20 MiB
  • gzip: 5.11 MiB
  • Brotli: 4.03 MiB
  • worker JavaScript: about 3.2 KiB gzip
  • debug/module JavaScript: about 2.6 KiB gzip

At an ideal 10 Mbps, transferring the compressed WASM alone takes about 3.4 seconds with Brotli or 4.3 seconds with gzip. At 50 Mbps it takes about 0.68–0.86 seconds. Serving the uncompressed 25.2 MiB artifact would be unacceptable on slower connections, so production must verify compression and long-lived immutable caching for .wasm.

The WASM is loaded lazily when an executor is created. Multiple executors can reuse the browser's cached network response, but each deployment that changes the content hash requires a complete download again.

Memory

Observed WebAssembly.Memory.buffer.byteLength:

Stage Linear memory
Instantiated 43.625 MiB
Go runtime ready 45 MiB
Framework configured 46.5 MiB
Plain project built 49.06 MiB
Tutorial project built 49.75 MiB
Compute-heavy program completed 53–53.6 MiB

Two concurrent executors used about 98.8 MiB of linear memory in total. This is not the complete browser RSS: compiled WASM code, Worker JS heaps, and browser/runtime bookkeeping are additional, so 50 MiB per executor should be treated as a confirmed baseline rather than an upper bound.

Waiting for submit does not continuously consume CPU, but the roughly 50 MiB runtime remains resident. After exit or stop, worker.terminate() makes the whole runtime eligible for asynchronous browser reclamation.

Startup and concurrency

Warm sequential samples for this small project:

  • Worker creation, WASM instantiation, and Go bridge ready: 90–131 ms
  • framework configuration: 30–37 ms
  • XGo build: 87–124 ms
  • total before program execution: about 210–290 ms

The first run in the browser session took about 690 ms before execution. This is not a strict public-network cold-start measurement, but it demonstrates the additional first-time compilation and initialization cost.

Starting plain and tutorial executors concurrently increased build time from roughly 0.1 seconds to roughly 0.3 seconds. The two programs reached execution in approximately 0.66 and 0.95 seconds, showing CPU contention in addition to linear memory growth.

Execution and communication

The integer-loop benchmark was about 450–640 times slower under ixgo interpretation than JavaScript JIT execution. This comparison is specifically ixgo-interpreted XGo versus JavaScript, not native compiled Go/WASM.

This makes xgoexec suitable for tutorial orchestration, state changes, and event-driven code, but not for computation-heavy algorithms. Expensive processing should be delegated to a host capability, a specialized compiled module, or a backend.

Communication overhead is much less concerning for the tutorial use case:

  • 100 sequential readCode capability calls: 21 ms total
  • submit event dispatch acknowledgement: 2–3 ms

The event number measures acceptance/queueing, not completion of the whole onSubmit callback.

Additional scaling concern

All supported frameworks and standard packages are linked into the same WASM artifact. Adding frameworks therefore increases download, initialization, and resident-code costs even when a particular project does not use them. We should record the binary-size delta for each new framework and consider separate artifacts only if this monolithic package starts growing materially.

Initial recommendation

  • Keep one active executor per Tutorial surface by default.
  • Preload or warm the executor after entering Tutorial if startup latency becomes visible.
  • Verify Brotli/gzip, correct WASM MIME type, and immutable CDN caching in production.
  • Avoid computation-heavy XGo course logic.
  • Track a compressed WASM size budget, for example no more than 5 MiB Brotli for the first version.
  • Validate startup time and memory on low-end Windows and Android devices before production integration.

Overall, the main costs are the 4–5 MiB compressed download and roughly 50 MiB of memory per active executor. For event-driven Tutorial programs, execution and communication costs are acceptable; for CPU-heavy logic or many concurrent executors, the current model is expensive compared with a direct JavaScript implementation.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a frontend module for running XGo code in the browser

1 participant