Platform research: what WebAssembly can and cannot do, August 2026
Every constraint that shaped Kite's design, with sources. Read this before disagreeing with a decision in the specification — most of them are forced.
1. WebAssembly 3.0 is ratified and shipped
The specification landed 13 June 2026, standardising nine features. All nine ship in current versions of every major browser, Safari included.
| Feature | What it gives a language implementer |
|---|---|
| WasmGC | struct and array heap types managed by the host collector |
| Native exception handling | exnref, first-class throw/catch primitives |
| Tail calls | Recursion without stack growth |
| Typed function references | Type-checked indirect calls — the basis for cheap vtables |
| Memory64 | Past the 4 GB ceiling |
| 128-bit SIMD | Deterministic vectorisation |
| Relaxed SIMD | Wider vectorisation for numeric and ML work |
| Multiple memories | Separate memory regions per module |
| Extended constant expressions | Richer module initialisation |
Consequence for Kite: the language may assume GC, exceptions, tail calls, and typed function references are present. No feature detection, no polyfill, no fallback path in v1.
Sources: WebAssembly 3.0 spec release · State of WebAssembly 2026
2. WasmGC is baseline, including Safari
This is the finding that makes the whole project viable.
- Chrome enabled WasmGC by default in December 2023.
- Firefox shipped it in the same window.
- Safari 18.2 shipped it in December 2024, completing cross-browser baseline.
Consequence: Kite ships no garbage collector in the .wasm binary. The host engine traces Kite's objects natively. Reported effects across the ecosystem are 2–4 MB of runtime eliminated and 3–10× reductions in binary size and startup time for managed languages.
This is precisely why Grain and early AssemblyScript carry weight Kite will not: they were designed when a language had to bring its own collector, and Grain's own roadmap names WasmGC as the thing that will make it "even more effective."
Timing matters here. This design was not viable in 2022 and is straightforward in 2026. Starting now is the correct call.
Sources: WasmGC enabled by default in Chrome · V8: bringing GC languages to Wasm · The State of WebAssembly 2025–2026
3. WasmGC's limitations, and how Kite avoids each
WasmGC is not a general-purpose heap. Its restrictions were the strongest influence on Kite's type system.
| Limitation | Effect on a naive design | Kite's answer |
|---|---|---|
| No interior pointers | Go cannot be compiled faithfully — Go relies on &struct.field | No & operator exists. Unobservable. |
| No flat aggregates in arrays | []Point becomes an array of references, not a packed buffer. A flat array of tuples must be transposed into parallel arrays or accept one GC object per element. | Accepted for ordinary code. buffer.F64 gives a flat linear-memory buffer for the layout engine and renderer, where it matters. |
| Fixed field indices and types | Languages wanting dynamic field access must work around it | Kite is statically typed with no reflection. Unobservable. |
| No weak references or finalizers | Weak caches and resource cleanup on collection are impossible | defer for scope-bound resources; explicit eviction policies for caches. |
| GC values cannot cross threads | Any shared-memory threading model is unimplementable | See §5. This is the big one. |
Sources: V8: bringing GC languages to Wasm · WebAssembly limitations
4. Strings are language-owned; the DOM is still not
JS String Builtins — useful, but not Kite's representation
Landed in Safari 26.2 in 2025, completing browser coverage. Wasm modules can operate on JavaScript string primitives directly — concat, compare, length — with no glue code and no copying.
Kite deliberately does not make another language's primitive its string model. Its web representation is a traced WasmGC array with one Unicode scalar value per i32, which gives the same indexing semantics as the bytecode and native targets and works in every engine Kite already requires. Language operations stay in Wasm. A JavaScript or DOM boundary performs one linear conversion through fixed scratch storage and retains no host-side string state.
Direct DOM access — does not exist
There is no standardised way for Wasm to call a Web API without JavaScript glue. Interest in Web IDL bindings for Wasm exists, and a Component Model subset has been floated, but as of August 2026 there is no formal proposal.
Consequence: Kite defines an explicit extern host boundary (spec §15) and generates the JavaScript glue from those declarations. Pretending direct DOM access exists would produce a design that cannot be implemented; hiding the boundary entirely would make its cost invisible to the programmer. Declaring it once, in Kite, and generating from it, is the honest middle.
Sources: JS String Builtins proposal · State of WebAssembly 2026
5. Threads and WasmGC are currently incompatible
The finding that determined Kite's concurrency design.
There is no way to use threads with WasmGC programs at all, because there is no way to share reference values across threads.
The shared-everything-threads proposal exists to fix exactly this. It adds shared annotations on tables, functions and globals; sequentially-consistent and release-acquire accesses to shared WasmGC data; and managed waiter queues for a futex-like wait/notify usable with GC references. It is a draft. It has not shipped.
What this means in practice, from languages already in production
- Kotlin/Wasm:
Dispatchers.DefaultandDispatchers.IOexist and appear
to offer parallelism, but on Wasm they behave like Dispatchers.Main — they run on the same thread. Threads must be implemented via Web Workers, and a specific Wasm function cannot be spawned onto one.
- Flutter Web: multi-threaded rendering requires the server to send COOP/COEP
headers, and even then the object graph is not shared.
- Web Workers are not threads. A worker runs its own browser-managed event
loop with its own heap; a native thread executes a function until it returns. The abstractions do not line up.
Consequence — and the key forward-compatibility bet: Kite's async/await surface says nothing about thread count. The Share marker (spec §12.4) enforces the exact invariant shared-everything-threads will require, starting in v1. Native and bytecode targets get a real work-stealing pool immediately. The web target gets isolate-based parallelism now and true shared-heap parallelism with no source change when the proposal ships.
Sources: shared-everything-threads · Kotlin/Wasm and web workers · Concurrency in WebAssembly, ACM Queue
6. HTML-in-Canvas: correcting a common misreading
This project was initially scoped around the belief that HTML-in-canvas is the future of the web. The research does not support that, in two distinct ways, and the standard library design changed as a result.
It is Chrome-only, and behind a flag
- Chrome: origin trial, M148–M151, plus
chrome://flags/#canvas-draw-element. - Brave / Edge / Chromium forks: same, inherited.
- Firefox: no implementation announced.
- Safari / WebKit: no implementation announced.
A single-engine API in origin trial is not a foundation for a language's standard library.
It points the opposite way to the assumption
drawElementImage() and texElementImage2D() render DOM elements onto a canvas. The proposal exists so that canvas-based applications can embed real, accessible, CSS-styled DOM — live form controls, selectable text, working screen-reader semantics. It is a fix for canvas UI's weaknesses, not a replacement for the DOM.
What canvas-only UI actually costs
Flutter's CanvasKit renderer is the largest deployed example, and the results are sobering:
- Accessibility is implemented by maintaining a **parallel hidden DOM semantics
tree** (<flt-semantics-host>, <flt-semantics>) mirroring the canvas.
- It is off by default for performance. Users must activate an invisible
button labelled "Enable accessibility."
- Text fields are announced as "edit, blank" by NVDA and VoiceOver because
Flutter does not emit <label> elements.
- Accessibility is named as the biggest remaining Flutter Web gap in 2026, and
Lighthouse scores it inaccurately — a perfect score on a Flutter app is misleading.
Text input is the deeper problem: IME composition for Chinese, Japanese and Korean input, password managers, autofill, native text selection, spellcheck and right-to-left cursor behaviour are all browser features that a canvas renderer must reimplement from nothing.
The conclusion Kite draws
The underlying instinct — that GPU-composited, retained-mode UI is worth having — is correct for the applications that need it. Figma, Zed and Google Docs took that path for good reasons, and every one of those reasons is about a specific, demanding surface: a document canvas, a code editor, a design tool. None of them is a reason to render a settings page that way. What does not follow from any of it is that the standard library should be able to target only canvas.
Kite therefore does not bet on a Chrome-only origin trial, and does not put canvas on the critical path of an ordinary page.
Amended. This section originally concluded that Kite should specify one UI API with two renderers — the same program emitting either a real DOM tree or canvas draw commands, with layout computed in Kite so both paths agreed exactly. That was built and then removed. The evidence above is right and the conclusion drawn from it was half a step short: computing layout in Kite in order to make the two agree meant the DOM path emitted positioned elements, which no stylesheet written by anyone else can address. The corrected conclusion is docs/04 — the browser lays out, HTML and CSS keep their jobs, and Kite replaces JavaScript only. Canvas remains, as an element a program draws into.
The document is the product, and canvas is a surface inside it. An earlier draft of this document called canvas first-class; a later one called the two renderers peers. Both were wrong, in the same direction and by different amounts. Everything this section catalogues — the parallel semantics tree, the reimplemented text input, IME, autofill, selection, the Lighthouse score that lies — is the cost of not using the platform. A web application that renders to a canvas has to rebuild HTML and CSS badly before it can start.
So a Kite program uses the platform rather than working around it: a field is a real <input>, a picture a real <img>, a heading a real <h1> — because they are the elements the program creates, not shapes a renderer chose to draw. And because they are real elements with real class names, somebody else's stylesheet works on them, which is the thing neither earlier draft asked for.
Canvas remains for the work it is actually better at — dense, animated, GPU-composited surfaces; charts; games; anything where a thousand elements would be a thousand elements. It is a <canvas> in the page, drawn into by the program. Choosing it is a decision about that surface, not about the language.
Sources: HTML-in-Canvas browser support · WICG HTML-in-Canvas explainer · Exploring the HTML-in-Canvas proposal, Codrops · Flutter web accessibility · Lighthouse gives your Flutter app a perfect accessibility score — it's lying
7. Go's error handling is frozen
In 2025 the Go team published an official post announcing they would pursue no further error-handling syntax proposals. Several were live at the time — if err ... shorthand, ? return, a ternary form — and all are now closed.
Consequence: the (T, error) shape is worth keeping; its enforcement is worth adding, because Go itself has closed the door on doing so. Kite keeps the shape and adds compile-time taint tracking (spec §7.3) so an error cannot be dropped and a value cannot be read on a failure path.
Sources: Go issue 73897 · Go issue 71528 · Go's last words on error handling syntax
8. Prior art, and where Kite sits
| Language | Approach | Why Kite differs |
|---|---|---|
| MoonBit | Wasm-first, targets wasm/wasm-gc/js/native. No LLVM — emits WAT, then wasm-tools. Reference counting on plain Wasm, host GC on WasmGC. | Closest neighbour, and validates the architecture. MoonBit is ML-flavoured with a rich type system; Kite deliberately trades expressiveness for a smaller concept budget and Go-shaped errors. |
| Grain | Functional, Wasm-native, ships its own GC (2017 design). | Predates WasmGC. Kite assumes the host collector and is imperative. |
| AssemblyScript | TypeScript subset to Wasm, linear memory. | Tied to TS semantics and npm-adjacent expectations. Kite has no JS-compatibility constraint. |
| Onyx | Compiles solely to Wasm, complete toolchain, Wasmer/WASIX runtime. | Systems-leaning and server-oriented. Kite is application- and UI-oriented. |
| Virgil | Lightweight high-performance systems language by a Wasm co-creator. | Systems focus, no UI story. |
| Kotlin / Dart | Mature WasmGC backends. | Carry large language surfaces and existing ecosystems. Kite's premise is a small surface. |
Kite's actual niche: a minimal, explicit, application-and-UI language with Go-shaped-but-enforced errors, no shared-memory concurrency concepts, and a renderer-agnostic UI standard library. Nothing in the table above occupies it.
The architectural lesson worth copying from MoonBit: do not use LLVM for the Wasm target. Emit Wasm directly. It keeps the compiler fast, the toolchain small, and the generated code inspectable.
Sources: MoonBit announcement · Introduction to MoonBit, The New Stack · Grain, The New Stack · Onyx · Introduction to Virgil
9. Native compilation
For the native target the practical choice in Rust is Cranelift: written in Rust, developed by the Bytecode Alliance, designed as a backend for Wasm and language implementations, and explicitly prioritising compilation speed and simplicity over peak runtime performance.
Rust's own rustc_codegen_cranelift is a Rust project goal targeting production-readiness, showing roughly 20% reduction in code generation time versus LLVM on large projects. The tradeoff is less optimised output.
Consequence: Kite uses Cranelift for AOT native compilation and accepts the optimisation gap. An LLVM backend remains possible later for release builds where peak performance justifies the toolchain weight, but it is not on the v1 path — adding LLVM early would dominate build times and binary size for the entire project.
Sources: Cranelift · rustc_codegen_cranelift · Production-ready Cranelift, Rust project goals
10. Server-side and WASI
WASI 0.2 stabilised in January 2026 on the Component Model. WASI 0.3, adding native async I/O to the Component Model, was scheduled for February 2026.
Consequence: out of scope for v1, but Kite's extern host boundary is designed so a WASI world can be generated from the same declarations that generate the browser glue. Kite programs should eventually run server-side without a language change. This is a v2 concern; the design merely avoids foreclosing it.
Sources: Component Model · WASI and the Component Model: current status
Summary: the forcing constraints
- WasmGC is baseline → ship no collector, use host GC, expect small binaries.
- GC refs cannot cross threads, and the fix is a draft → thread-agnostic
async surface plus a Share marker enforced from v1.
- No direct DOM access, and none imminent → explicit
externboundary with
generated glue.
- JS strings are host values, not Kite values →
strstays a traced
Unicode-scalar array and converts only at an explicit host boundary.
- No interior pointers, no flat aggregates → no
&operator; a typed-buffer
escape hatch where layout matters.
- HTML-in-Canvas is Chrome-only and points the other way → one UI API, two
renderers.
- Go froze its error syntax → keep the shape, add the enforcement.