LEVIATHAN v962456e · 962456eee1

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-unknown builds in a source checkout. It needs a WASI C library (the wasi-libc package) and the WebAssembly builtins library that comes with clang; the script says which one it cannot find;
  • the linker wasm-ld (the compiler also tries wasm-ld-18), which comes with LLVM's lld;
  • a JavaScript host to run the module. The module imports the system services a program needs from the host, and runtime/lv_host.js in a source checkout is the host that supplies them in a page or under Node. The module exports main.

--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, so Dom is unknown outside this target unless it is inside a comptime if (target::os == "wasm") branch.
  • --runtime <path> and --opt-level work 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-compilation for 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 leviathan compiler, 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::arch and target::triple are compile-time strings that describe the platform being compiled for.
  • await — waiting for a promise — await unwraps a promise to its value, suspending only the current task while other work keeps running.