Ever since Netscape launched JavaScript, there were some developers that liked it and others that didn't.
Regardless of which side you're on, I think we can all agree that it would be good if browsers supported more programming languages.
This is the promise of WebAssembly: Providing a generic runtime that any programming language can compile to.
Past Attempts
In the earlier days of the web, extensions were attempted with Java Applets and Microsoft ActiveX. But both were plagued by security issues and eventually dropped. The problem was they executed without access controls, which became a massive attack surface.
Later, Macromedia Flash and Silverlight had some success, but eventually met the same tragic fate. They both lacked an open standard, which made it hard for browser and OS vendors to support.
What is WebAssembly?
WebAssembly (aka WASM) is an open standard byte code format that works in all browsers. It's a low-level binary format and execution engine, conceptually similar to Oracle's JVM or Microsoft's CLR.
It's designed from the ground up to be hosted and safe. It cannot access the machine's memory or hard drive. Only the host can decide what APIs to expose.
WASM is a portable format, so it can support many programming languages. Think Rust, Ruby, Python and even JavaScript can be compiled to WASM byte code.
Though it was originally designed to target the browser, it works well outside the browser too.
It can run on the server, in the cloud, in hardware devices, or used a plugin system.
Writing WASM
There are several ways to create a .wasm
file:
- Write it by hand. (not recommended)
- Write it with Wasm Text Format.
- Use a higher level language like AssemblyScript, Rust, Ruby, etc.. and then compile it.
I'll show you a few examples:
Wat is WAT?
The WASM specification provides a text-based format for defining WASM modules that is called WAT (WAsm Text format). It uses S-expresions, similar to Lisp or Clojure.
Here's what a basic module looks like:
; define a module
(module
; define a function called "add"
; it takes 2 params:
; - $a is a 32-bit integer
; - $b is a 32-bit integer
; it returns an 32-bit integer
(fun add (param $a i32) (param $b i32) (result $i32)
; load param $a onto the stack
local.get $a
; load param $b onto the stack
local.get $b
; perform 32-bit integer "add" operation
i32.add
; the last value on the stack is returned
; which is the result of the `i32.add`
)
)
The .wat
file can be compiled to a .wasm
using wat2wasm
which is part of the WebAssembly Toolkit CLI tools:
# outputs example.wasm
> wat2wasm example.wat
Now the .wasm
can be executed from any host. It can even be executed from the command line using wasmtime
:
# invoke "add" function, and pass args 1,2
> wasmtime example.wasm --invoke add 1 2
3
AssemblyScript
There is also a higher-level language called AssemblyScript. It's like TypeScript for WebAssembly.
If we re-write the add()
function from the previous section with AssemblyScript, it would look like this:
// in add.ts
export function add(a: u32, b: u32): u32 {
return a + b;
}
As you can see, it's much more readable now.
To compile it, use AssemblyScript's compiler asc
:
pnpm install -D assemblyscript
pnpm run asc add.ts --outFile=math.wasm
Comparing formats
To compare AssemblyScript to WAT, I built a little tool:
https://assemblyscript-play.vercel.app
You can also use the CLI wasm2wat
to compare formats:
# outputs .wat format
wasm2wat math.wasm
Runtime execution
Just like there are many ways to compile wasm, there are many ways to execute it too.
Using WebAssembly in the browser
To use the WebAssembly API in the browser, first load the assembly:
// fetch .wasm file
const response = fetch('/path/to/some.wasm')
// instantiate module with streaming
const module = WebAssembly.instantiateStreaming(response)
Then, call one of the exported functions:
const result = module.instance.exports.add(1, 2)
An optional API can be passed into the module too:
// fetch .wasm file
const response = fetch('/path/to/some.wasm')
// instantiate module and pass an api
const module = WebAssembly.instantiateStreaming(response, {
imports: {
// share console.log
log: console.log
}
})
Using WebAssembly on the server
WebAssemblies can be executed on the server too. The API is virtually identical to the browser.
The only difference is that instead of fetching the .wasm
from a server using a URL, it can be read from the disk using fs.readFile()
:
import fs from 'fs'
// read .wasm file
const bytes = await fs.promises.readFile('/path/to/some.wasm')
// instantiate the module
const module = WebAssembly.instantiate(bytes)
Then, call one of the exported functions, just like we did in the browser:
const result = module.instance.exports.add(1, 2)
It's also possible to do this from many other languages. For example rust, ruby, python or from the CLI.
Using WebAssembly in the Cloud
Another big use-case for WASM is the cloud.
It has some advantages over JavaScript cloud functions:
-
No cold starts: The host only has to load a
.wasm
file instead of full app. Typical JS apps have many files to load, which takes a long time. - Faster deploys: All that gets uploaded is a simple binary.
- Polyglot hosting: All languages that compile to WASM can be deployed to the cloud without requiring any special runtime.
- Snapshots: Execution can be snapshotted. For example, an app that does expensive work during initialization can be snapshotted. Then future requests can start with the snapshot, eliminating the expensive startup time.
A great example of WASM in the cloud is Fermyon. It's like AWS Lambda but for WebAssembly.
To use Fermyon, install their CLI spin.
Then create a new project:
# create a new spin project
# template is "http-js"
# project name is "spin-example"
spin new http-js spin-example
cd spin-example
# install dependencies
npm install
Then define a handler in src/index.js
:
const encoder = new TextEncoder()
export async function handleRequest(request) {
return {
status: 200,
headers: { "content-type": "text/plain" },
body: encoder.encode("Hello World").buffer
}
}
To run in dev mode:
spin watch
To deploy to The Cloud™, run spin deploy
:
spin deploy
Notice how that deploy was instant?
Gotchas
There are still a couple rough edges of of WASM:
- WebAssembly is still kind of new and in active development. Though it is improving rapidly.
- Full support is not yet available for some programming languages.
- WASM doesn't have basic data types like strings or a standard library. This is by design. Languages are expected to be provide their own standard library.
- Because a "standard library" needs to live inside your
.wasm
, it can make file size large.
Most of these will be resolved with time.
The future
Over the past few years WebAssembly has made a lot of progress.
Eventually all languages will have compilation targets and runtimes for hosting it (if they don't already). This will enable all languages to run in the browser, server, or even in hardware.
It might also bring on new types of programming languages that are designed for a WebAssembly-first world.
Top comments (31)
I think that Assembly will not get big potential for in-browser use until these functionality will be available:
1) syntax to use WASM as a page scripting module, like
2) Garbage collection support (seem to be in progress).
3) standard way of marshaling API calls between WASM environment and browser standard API necessary, like DOM manipulation, fetch, events handling, Web Cryptography API access, window object access or access to self & globalThis for workers, etc. It does not mean that WASM will call things like Math.ceil, but it is about different widely used APIs like Canvas, WebGL, indexedDB, etc.
This still will require 3.1) WASM pre-declared permissions or capabilities request, with way to say what capabilities mandatory for a WASM module to start, which are optional. Actual list of available capabilities can be sent to initialization code of webAssembly if all mandatory features available for module.
Imagine a WASM file that gives API for an encrypted keys-container a-la KeePass for browser?
Good points!
Not having standard APIs does make the file size of WASM much bigger. Maybe it can be solved by the browser caching runtimes client side?
It can be solved by stating some dependencies, like list of urls and hashes as way of versioning.
But this will require a WASM dependencies trust policy specified as part of CSP - like, dependencies must be origin-relative, can be urls from white-listed domains, etc.
Idea is make that language-specific "runtime" a dependency, not embedded into browser, who just have to organize marshalling mechanism between self (as host) and WASM module, and declare its capabilities - WebGL, DOM, UI interaction, 2D Canvas, IndexedDB, Web Cryptography, "Native Windows" (opened by WASM module), Controllers support, other features.
Runtimes caching require dependencies between modules, otherwise not clear what to cache.
I don't know what you mean by "big potential", but chances are if you're using any Web app working with images (like resizing, cropping, etc.) it runs on (C libraries compiled to) Wasm, and "secretly" does so for several years already. 😉
I don't even mention Web apps using Wasm big time like Figma, Photoshop and many others...
Big potential was essentially replacing JavaScript, to say it simply.
Replacing JS was never the goal of Wasm. The goal is to bring additional languages to the browser and other environments (Function-as-a-Service and Edge, for instance) and augment JS.
I personally agree that JS needs no replacement, while bringing other languages and having more options is very useful.
If JavaScript will not be replaced, it will always pressurize other languages out of Web.
If WASM will get equal capabilities at Web as JavaScript, it will be eventually step to replacing JavaScript with WASM, when browser have single runtime (WASM) and compiling JavaScript to WASM and executing as it since, not JS.
This is what I meant "replacing JavaScript" - simplifying browser internals by having single execution environment.
If WASM will get all capabilities of JavaScript from Web & HTML5, desktop and web apps will be merged as phenomena, like "write once, run everywhere".
Of course, some enshitification can happen in the process, but most users may be happy about that or will not care - most users already don't care is their desktop app is a native app or electron wrapper around a web page.
You're making a statement about the future which is notoriously hard to get right, and I don't see even present agreeing with it.
If anything I see C/C++ libraries compiled to Wasm "secretly pressurize JS out of Web". And it's not as if nobody was using Elm, ClojureScript, PureScript, ReScript and other languages instead of and alongside JS. I don't see JS "pressurize" these languages out of Web. Thus I see no reasons for this trend to reverse in the future.
Any decent runtime compiles both JavaScript (for ages) and Wasm (nowadays) to machine code. It makes no sense whatsoever to compile JS to Wasm first. And sure enough both V8 and SpiderMonkey reuse the same back-end for both JS and Wasm.
Any single browser has single execution environment — V8 or SpiderMonkey or JavaScriptCore — and it's the smaller part of the browser. The bulk of a browser's complexity lies in the implementation of Web Standards and Web APIs, which are not going to go away.
Well, try use WASm beyond narrow niches of some "heavy computations" - manipulate DOM with WASM, or use Web Cryptography API or Credentials API with WASM, or try do a fetch with WASM, or try load WASM as a "normal" module, via pure script tag. Until this happen, WASM can not be considered as "first class citizen" of Web.
"It makes no sense whatsoever to compile JS to Wasm first"
While I would agree in part. Javascript need not necessarily compile to WASM, first on everything but can be an option and choice for optimizing performance by pre-compiling elements of code that would otherwise be slower by interpreting line by line the javascript. JIT compilers would be only equal to or faster ONCE the code compiled either to WASM or ML but only a rerunning of the code. The initial run of the code would take longer. Additionally, WASM can bring file size down which speeds up the stuff transferring over the bandwidth bottleneck (the internet connection itself) to the local side. Then we also have other optimizations like CDNs which can reduce latency as well. However, WASM serves for web browsing much what P-Code did back in the day of Pascal and BASIC which was often compiled to P-Code which was allowed the same code to be used on multiple computer platforms because ML is unique for each CPU Instruction Set Architecture (ISAs). ARM, X86, X86-64, PPC, etc. So WebAssembly is portable as P-Code was but engineered around Javascript and the web on purpose for interoperability by design. You can even write WebAssembly code by hand (text and binary... the latter is harder to do until you get your head around the WebAssembly's Virtual Machine Language's instruction set. I call it "Virtual Machine Language" as the stack processor of WebAssembly is the "virtual machine" or virtual CPU. So I state virtual for the point it is not the physical CPU's machine language but the "virtual one" represented by WebAssembly's specification and implemented in the web browser. Then "machine language" as a sort of parallel to physical machine language because it would actually look very much like a real CPU's machine language and you can use a hex editor to write the code by hand but that's a bit more intense than the slightly more human friendlier text format which is akin to assembly language as an intermediary format between high-level languages which can be BASIC, C/C++/C#, Java, Javascript, Lua, or any number of high level languages source code to this intermediate format before webassembly binary format. Only way you would get faster performance would be basically to compile to machine language but you lose the platform independence portability so you have to trade off some performance for portable code that you don't have to recompile, edit+recompile for all the different CPUs a web browser could be available for. Most of you will be using high-level languages to write most of the original source code and maybe handwrite some tuned code in webassembly but even then, maybe not because its not human friendly as Javascript (the "BASIC"/"PASCAL" for the web browser). So you have choices just as I saw with BASIC compilers back in the 1980s which would either compile to P-Code or actual machine code.
Seems like writing WASM will be more verbose.
WASM is designed to be generated by a compiler.
I think most developers wouldn't write it directly. The compiler would generate it for them.
You could say that about machine language of any CPU even the Intel 4004. However, so what. It is just that today's programmers and web programmers (especially the latter) is not accustom to low level programming in machine language so they would be comfortable writing in a higher level programming language where the syntax is more human readable and then "compile" the human readable code to machine language or a "virtual" machine language like WebAssembly binary format would sort of be.
Actual
.wasm
is binary, it's extremely terse. 😃But you don't have to write it yourself, right?
Most developers will never write
.wat
or.wasm
themselves. It will be generated for them by a compiler.The format is intended for programming language developers.
It's analogous to assembly language, most C++ and Java devs don't need to know it to be effective.
WebAssembly is truly revolutionizing the way we interact with the web. The concept of a portable, low-level bytecode that can be executed efficiently across different platforms opens up incredible possibilities for the future of web development. With WebAssembly, we're not only looking at enhanced performance but also a more secure environment, as the sandboxed execution ensures a level of isolation from potentially malicious code. It's exciting to think about the potential this technology holds for creating richer, more interactive, and faster web experiences. The future of web development is looking brighter than ever with WebAssembly paving the way!
I'd also add that many hosts/runtimes, Wasmtime included, can load and execute
.wat
file directly.Interesting - thanks for sharing.
Am I right in thinking that WebAssembly is conceptually a bit like a 'native' Silverlight: you build/compile modules (in any language with a compiler), point a browser to it, and it starts running your app?
Also, does WebAssembly support parallel processing (and all of the accompanying synchronization mechanisms, etc.)?
I'm not familiar with the term "native silverlight". But it is similar to Silverlight in that your build an assembly and it can be opened in the browser
WebAssembly can support multi-threading and atomics. There is a spec under development:
github.com/WebAssembly/threads
In some cases, you can build the multi-threading into the host, and then the WebAssembly modules can run in their own threads, they might not need to know about multi-threading. If they need to synchronize data, they can call an API provided by the host.
'Native' Silverlight was just a term I made up when trying to describe a Silverlight-like experience (developed in a language and then compiled into binaries), but without having to install a plugin to run it.
Thanks for the info!
Officially it's "Wasm"... 😂
Yeah, it looks like they specifically aimed at confusing everybody, but on the other hand WASM ain't no acronym either... 😃
To me loading only single Wasm file sounds more like a (temporary, until we have Component Model) drawback. With many JS files we might not even need them all at once and all the time, so we might often save some time not loading some files at all.
Besides, "cold start" usually refers to the first loading of a program exhibiting a "warm-up" behaviour: allocating memory, loading data, initializing stuff, filling up the resident set and caches, JIT-compiling and so on. Wasm programs inevitably exhibit "warm-up" behaviour too: they also need to allocate memory, load data and JIT-compile the code before they can start doing useful work, though, most often it happens much faster than for JS.
In principle, the same can be done for JS too, though I don't know if any engine actually does. For Java and C# snapshotting is definitely an option.
At the same time for Wasm it's not "free" either: one have to do it explicitly...
can it thread and also talk to os directly ?
There is a spec being developed for threading.
In some cases, the parallelization can be done inside the host, and the WebAssembly can be kept single-threaded and still achieve a multi-threaded system.
As for accessing OS, the host decides what APIs to provide. So a file-system API could be provided like this:
Nice 👌
Nope, that would be a huge security hole. Wasm can only use APIs explicitly provided by its host with Object Capabilities-based Access Control.
i see, thanks
I think it would be nice to mention that a
.wasm
file defines a module, which is a unit of code loading, somewhere before this point... 😉not now more about but it might attention topic