Compiler
The WebAssembly (browser) target
Building a program as a WebAssembly module for the browser, what it can use, what it cannot, and how it reaches the page.
since 0.1.0-alpha.1linuxwindowswasm
Description
With --target wasm32-unknown-unknown the LLVM backend produces a WebAssembly module instead of a
native executable. It is the same language and the same compiler; the target changes which
standard-library file is read, which system services are available, and how the program talks to
the outside world, which is through a JavaScript host.
leviathan --build-native <out> --target wasm32-unknown-unknown <source-file>
The output is <out>.wasm; the compiler adds the suffix when it is missing. Producing the module
needs three things, like any cross build (see lang.cross-compilation):
- the runtime library for the target, which
runtime/build-triple.sh wasm32-unknown-unknownbuilds in a source checkout. It needs a WASI C library (thewasi-libcpackage) and the WebAssembly builtins library that comes with clang; the script says which one it cannot find; - the linker
wasm-ld(the compiler also trieswasm-ld-18), which comes with LLVM'slld; - a JavaScript host to run the module. The module imports the system services a program needs from
the host, and
runtime/lv_host.jsin a source checkout is the host that supplies them in a page or under Node. The module exportsmain.
--native-obj <out.o> --target wasm32-unknown-unknown writes just the WebAssembly object file and
needs none of these, so it is a quick way to check that a program is acceptable for the browser.
What a program can use
Pure computation works: the whole language, collections, strings, math, JSON, text encoding and
regular expressions. Console output, timers, the time, and
async/await also compile for the browser, and HttpClient's awaitable fetch methods call the
browser's own fetch.
What a program cannot use
The browser has no file system, no operating-system threads, no raw network sockets and no terminal, so code that reaches those is rejected at compile time with a diagnostic of the form
error: LLVM backend: wasm-browser: 'File' is not available on this target (no filesystem in a browser)
The rejected services are the file system, reading standard input, raw sockets and the TLS layer
built on them, program arguments and environment variables, terminal control, operating-system
signals, threads and channels (spawn, Channel), starting child processes, and the desktop GUI.
The rule applies to what your program actually reaches. A prelude function that your code never
calls does not stop the build.
Reaching the page
On this target the standard library gains a small DOM layer in the namespace Dom, and the classes
DomNode and DomEvent. Dom::body(), Dom::create(tag), Dom::textNode(text) and
Dom::byId(id) give you nodes. A node can be given attributes and text, can have children appended,
and can listen for events either as a stream (events) or with a callback (on). Only this layer
is needed to build a page; the program does not handle JavaScript values directly.
Code that should run both natively and in the browser selects the right part with the
compile-time target constants (see lang.cross-compilation). The branch that is not selected is not
compiled, so a program can mention Dom and still build natively:
One source file, two targets
string greeting = "hello from " + "Leviathan".toUpper();
comptime if (target::os == "wasm") {
Dom::body().append(Dom::create("p").setText(greeting));
} else {
console.writeln(greeting);
}
hello from LEVIATHAN
Built for the browser, the same source adds a paragraph to the page instead of printing:
leviathan --build-native app --target wasm32-unknown-unknown app.lev
That leaves app.wasm.
Rules
- The target name must begin with
wasm32; the output name always ends in.wasm. - Code that reaches a service the browser lacks fails to compile with a
wasm-browser:diagnostic. The program is not built. - A WebAssembly build reads an additional standard-library file that defines
Dom; native builds never read it, soDomis unknown outside this target unless it is inside acomptime if (target::os == "wasm")branch. --runtime <path>and--opt-levelwork as for other targets.
Examples
Checking that a program is acceptable for the browser without any toolchain beyond the compiler:
leviathan --native-obj app.o --target wasm32-unknown-unknown app.lev
A program that reads a file compiles natively but not for the browser:
Rejected when built for the browser
File f = File("/tmp/notes.txt", read);
console.writeln(f.path);
Notes
- Building and running a WebAssembly module end to end needs the runtime library for the target
and a JavaScript host. See
lang.cross-compilationfor how the runtime library is found.
See also
- Cross-compilation: --target and per-target runtimes — Building executables for another operating system or processor, what the build needs for each target, and what a target can reject.
- Native backends: LLVM and C++ — The two ways to turn a program into a native executable, what each one covers, and the runtime limits of natively built programs.
- The leviathan command line — Every option of the
leviathancompiler, grouped by what it does, with the exit statuses and how arguments reach your program. - Dom — The browser document surface: entry points for reaching and creating DOM nodes.
- DomNode — A handle to one node in the browser page.
- DomEvent — A DOM event delivered to a handler, as a typed handle.
- target:: — the compilation-target constants —
target::os,target::archandtarget::tripleare compile-time strings that describe the platform being compiled for. - await — waiting for a promise —
awaitunwraps a promise to its value, suspending only the current task while other work keeps running.