DEV Community

Cover image for WebAssembly: byte-code of the future
Joshua Nussbaum
Joshua Nussbaum

Posted on • Edited on

WebAssembly: byte-code of the future

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`
  )
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Then, call one of the exported functions:

const result = module.instance.exports.add(1, 2)
Enter fullscreen mode Exit fullscreen mode

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
  }
})
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Then, call one of the exported functions, just like we did in the browser:

const result = module.instance.exports.add(1, 2)
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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.
  2. Faster deploys: All that gets uploaded is a simple binary.
  3. Polyglot hosting: All languages that compile to WASM can be deployed to the cloud without requiring any special runtime.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

To run in dev mode:

spin watch
Enter fullscreen mode Exit fullscreen mode

To deploy to The Cloudâ„¢, run spin deploy:

spin deploy
Enter fullscreen mode Exit fullscreen mode

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.

Oldest comments (31)

Collapse
 
ant_f_dev profile image
Anthony Fung

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.)?

Collapse
 
joshnuss profile image
Joshua Nussbaum

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.

Collapse
 
ant_f_dev profile image
Anthony Fung

'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!

Collapse
 
iamcymentho profile image
Odumosu Matthew

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!

Collapse
 
ironside profile image
HighFather (LightSide)

can it thread and also talk to os directly ?

Collapse
 
joshnuss profile image
Joshua Nussbaum

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:

WebAssembly.instantiate(assembly, {
  imports: {
     readFile: ...,
     writeFile: ...
     // etc...
  }
})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ironside profile image
HighFather (LightSide)

Nice 👌

Collapse
 
gabrielfallen profile image
Alexander Chichigin

talk to os directly

Nope, that would be a huge security hole. Wasm can only use APIs explicitly provided by its host with Object Capabilities-based Access Control.

Collapse
 
ironside profile image
HighFather (LightSide)

i see, thanks

Collapse
 
koteisaev profile image
Kote Isaev - #StandWithUkraine

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?

Collapse
 
joshnuss profile image
Joshua Nussbaum

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?

Collapse
 
koteisaev profile image
Kote Isaev - #StandWithUkraine

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.

Collapse
 
gabrielfallen profile image
Alexander Chichigin

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...

Collapse
 
koteisaev profile image
Kote Isaev - #StandWithUkraine

Big potential was essentially replacing JavaScript, to say it simply.

Thread Thread
 
gabrielfallen profile image
Alexander Chichigin

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.

Thread Thread
 
koteisaev profile image
Kote Isaev - #StandWithUkraine

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.

Thread Thread
 
gabrielfallen profile image
Alexander Chichigin

If JavaScript will not be replaced, it will always pressurize other languages out of Web.

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.

when browser have single runtime (WASM) and compiling JavaScript to WASM

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.

simplifying browser internals by having single execution environment

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.

Thread Thread
 
koteisaev profile image
Kote Isaev - #StandWithUkraine

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.

Thread Thread
 
reflectedlightent profile image
rick balkins

"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.

Collapse
 
chideracode profile image
Chidera Humphrey

Seems like writing WASM will be more verbose.

Collapse
 
joshnuss profile image
Joshua Nussbaum

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.

Collapse
 
reflectedlightent profile image
rick balkins

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.

Collapse
 
gabrielfallen profile image
Alexander Chichigin

Actual .wasm is binary, it's extremely terse. 😃

Collapse
 
chideracode profile image
Chidera Humphrey

But you don't have to write it yourself, right?

Thread Thread
 
joshnuss profile image
Joshua Nussbaum

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.

Collapse
 
samanmahmood profile image
Saman Mahmood

not now more about but it might attention topic

Collapse
 
gabrielfallen profile image
Alexander Chichigin

WebAssembly (aka WASM)

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... 😃

Collapse
 
gabrielfallen profile image
Alexander Chichigin

Wat is WAT?

Did you mean "What is WAT?"

Collapse
 
gabrielfallen profile image
Alexander Chichigin

Here's what a basic module looks like

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... 😉

Collapse
 
gabrielfallen profile image
Alexander Chichigin

Now the .wasm can be executed from any host.

I'd also add that many hosts/runtimes, Wasmtime included, can load and execute .wat file directly.