Ap­pli­ca­tions that run within a browser (instead of from a hard drive) are becoming more and more common. Much like office software de­vel­op­ers like Microsoft (Microsoft 365) and Google (Docs and Sheets), which add new functions to their packages all the time, browser-based games are becoming in­creas­ing­ly complex and are using more resources. In many cases, these web ap­pli­ca­tions are written using JavaScript. More recently, however, a growing number of de­vel­op­ers are turning to We­bAssem­bly – a new approach with brilliant results.

What is We­bAssem­bly?

We­bAssem­bly (Wasm) provides web de­vel­op­ers with a new way of making ap­pli­ca­tions available online. Pre­vi­ous­ly, the only option was JavaScript. The problem is that JavaScript runs rel­a­tive­ly slowly and per­for­mance can be a struggle in certain sit­u­a­tions. Thus, the World Wide Web Con­sor­tium (W3C) came up with a new method – We­bAssem­bly. In order for Wasm to function, the browser used must be able to handle the language. Mozilla (Firefox), Microsoft (Edge), Apple (Safari) and Google (Chrome) have all been involved in the de­vel­op­ment process. We­bAssem­bly ap­pli­ca­tions are com­pat­i­ble with all of the latest browser versions by these de­vel­op­ers.

Tip

To ex­pe­ri­ence the power of We­bAssem­bly for yourself, try your hand at Funky Karts. The game was orig­i­nal­ly developed as a mobile app, but has been compiled to We­bAssem­bly so that it can be played in a browser. The developer wrote an in­ter­est­ing blog post about the project, giving a step-by-step de­scrip­tion of how he went about the com­pi­la­tion process.

The­o­ret­i­cal­ly speaking, We­bAssem­bly is rep­re­sent­ed in bytecode. You can think of it as a middle ground between machine code, which can only be un­der­stood by a computer, and a typical pro­gram­ming language, which is un­der­stood by humans but only after it has been compiled. This is what makes We­bAssem­bly so fast in com­par­i­son to other languages – computers can compile the code with virtually no effort. Indeed, writing in bytecode is somewhat different. The advantage of Wasm is that you don’t have to work in this pro­gram­ming language yourself. In practice, de­vel­op­ers write their ap­pli­ca­tions using C or C++, for example.

The source text is then compiled using the Em­scripten ap­pli­ca­tion. This tool existed long before We­bAssem­bly and was used to compile C/C++ code into JavaScript (or ams.js). It can now also be used to convert code to Wasm. This means the code is pre-compiled, so that it doesn't have to be compiled or in­ter­pret­ed when the program is run. When a user opens the ap­pli­ca­tion in a browser, a small virtual machine starts up and runs the ap­pli­ca­tion.

Ad­van­tages of We­bAssem­bly

We­bAssem­bly currently only has one real dis­ad­van­tage: it isn’t catching on fast enough. Web de­vel­op­ers are used to working with JavaScript and there are no plans to replace JavaScript. The project managers have stated very clearly that they want to promote Wasm as an al­ter­na­tive to JavaScript. However, thanks to the support of the major browser providers and the W3C, the pop­u­lar­i­ty of We­bAssem­bly is growing. This is also helped by the fact that website visitors don’t actually have to do anything them­selves – web ap­pli­ca­tions using We­bAssem­bly load just as easily as JavaScript code, only more quickly.

De­vel­op­ers who are already familiar with languages such as C, C++ or Rust can now write programs directly for the web. This choice of pro­gram­ming language also opens up more design pos­si­bil­i­ties. If you cannot find the necessary JavaScript libraries or frame­works for your program, there are other ways to make it work. Here are some of the reasons why de­vel­op­ers should take a closer look at We­bAssem­bly:

  • W3C open web standard
  • High per­for­mance and small file sizes
  • Perfect for mobile browsing
  • Even virtual reality programs can the­o­ret­i­cal­ly run in a browser
  • No need to learn a new pro­gram­ming language
  • C, C++ and Rust can now all be used to program web ap­pli­ca­tions
  • Supported by all of the major browser man­u­fac­tur­ers
  • No lim­i­ta­tions for users

We­bAssem­bly practical examples

De­vel­op­ers don’t actually need to write their programs in We­bAssem­bly. One of the major ad­van­tages of the solution is that pro­gram­mers can use any language such as C and later transfer their code into Wasm format. Nev­er­the­less, it makes sense to learn more about the compiled code to uncover how We­bAssem­bly works.

The source code is available in two versions: We­bAssem­bly Text Format (WAT) and We­bAssem­bly Binary Format (Wasm). The latter is the actual code a machine would run. Because Wasm is mostly binary code and difficult to un­der­stand, it is more or less unusable for human analysis. Thus, the WAT sub-form has been developed. This type of code uses readable ex­pres­sions and can be examined by a pro­gram­mer. However, it doesn’t provide the same pro­gram­ming comfort more es­tab­lished pro­gram­ming languages provide.

Let’s look at an example using a simple line of code written in C:

#define WASM_EXPORT __attribute__((visibility("default")))
WASM_EXPORT
int main() {
    return 1;
}

The same code in WAT format is sig­nif­i­cant­ly longer:

(module
    (type $t0 (func))
    (type $t1 (func (result i32)))
    (func $__wasm_call_ctors (type $t0))
    (func $main (export "main") (type $t1) (result i32)
        i32.const 1)
    (table $T0 1 1 anyfunc)
    (memory $memory (export "memory") 2)
    (global $g0 (mut i32) (i32.const 66560))
    (global $__heap_base (export "__heap_base") i32 (i32.const 66560))
    (global $__data_end (export "__data_end") i32 (i32.const 1024)))

Although read­abil­i­ty is di­min­ished, certain elements can still be de­ci­phered. We­bAssem­bly puts every­thing into modules which are further clas­si­fied into functions that can be specified using pa­ra­me­ters. In the code above, we can make out five different elements:

  • module: main unit in We­bAssem­bly
  • function: grouping within a module
  • memory: array with bytes
  • global: a value that can be used across multiple modules
  • table: reference storage

Once the code is trans­lat­ed into binary code, it becomes un­read­able:

0000000: 0061 736d                                 ; WASM_BINARY_MAGIC
0000004: 0100 0000                                 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01                                        ; section code
0000009: 00                                        ; section size (guess)
000000a: 02                                        ; num types
; type 0
000000b: 60                                        ; func
000000c: 00                                        ; num params
000000d: 00                                        ; num results
; type 1
000000e: 60                                        ; func
000000f: 00                                        ; num params
0000010: 01                                        ; num results
0000011: 7f                                        ; i32
0000009: 08                                        ; FIXUP section size
; section "Function" (3)
0000012: 03                                        ; section code
0000013: 00                                        ; section size (guess)
0000014: 02                                        ; num functions
0000015: 00                                        ; function 0 signature index
0000016: 01                                        ; function 1 signature index
0000013: 03                                        ; FIXUP section size
; section "Table" (4)
0000017: 04                                        ; section code
0000018: 00                                        ; section size (guess)
0000019: 01                                        ; num tables
; table 0
000001a: 70                                        ; funcref
000001b: 01                                        ; limits: flags
000001c: 01                                        ; limits: initial
000001d: 01                                        ; limits: max
0000018: 05                                        ; FIXUP section size
; section "Memory" (5)
000001e: 05                                        ; section code
000001f: 00                                        ; section size (guess)
0000020: 01                                        ; num memories
; memory 0
0000021: 00                                        ; limits: flags
0000022: 02                                        ; limits: initial
000001f: 03                                        ; FIXUP section size
; section "Global" (6)
0000023: 06                                        ; section code
0000024: 00                                        ; section size (guess)
0000025: 03                                        ; num globals
0000026: 7f                                        ; i32
0000027: 01                                        ; global mutability
0000028: 41                                        ; i32.const
0000029: 8088 04                                   ; i32 literal
000002c: 0b                                        ; end
000002d: 7f                                        ; i32
000002e: 00                                        ; global mutability
000002f: 41                                        ; i32.const
0000030: 8088 04                                   ; i32 literal
0000033: 0b                                        ; end
0000034: 7f                                        ; i32
0000035: 00                                        ; global mutability
0000036: 41                                        ; i32.const
0000037: 8008                                      ; i32 literal
0000039: 0b                                        ; end
0000024: 15                                        ; FIXUP section size
; section "Export" (7)
000003a: 07                                        ; section code
000003b: 00                                        ; section size (guess)
000003c: 04                                        ; num exports
000003d: 04                                        ; string length
000003e: 6d61 696e                                main  ; export name
0000042: 00                                        ; export kind
0000043: 01                                        ; export func index
0000044: 06                                        ; string length
0000045: 6d65 6d6f 7279                           memory  ; export name
000004b: 02                                        ; export kind
000004c: 00                                        ; export memory index
000004d: 0b                                        ; string length
000004e: 5f5f 6865 6170 5f62 6173 65              __heap_base  ; export name
0000059: 03                                        ; export kind
000005a: 01                                        ; export global index
000005b: 0a                                        ; string length
000005c: 5f5f 6461 7461 5f65 6e64                 __data_end  ; export name
0000066: 03                                        ; export kind
0000067: 02                                        ; export global index
000003b: 2c                                        ; FIXUP section size
; section "Code" (10)
0000068: 0a                                        ; section code
0000069: 00                                        ; section size (guess)
000006a: 02                                        ; num functions
; function body 0
000006b: 00                                        ; func body size (guess)
000006c: 00                                        ; local decl count
000006d: 0b                                        ; end
000006b: 02                                        ; FIXUP func body size
; function body 1
000006e: 00                                        ; func body size (guess)
000006f: 00                                        ; local decl count
0000070: 41                                        ; i32.const
0000071: 01                                        ; i32 literal
0000072: 0b                                        ; end
000006e: 04                                        ; FIXUP func body size
0000069: 09                                        ; FIXUP section size
; section "name"
0000073: 00                                        ; section code
0000074: 00                                        ; section size (guess)
0000075: 04                                        ; string length
0000076: 6e61 6d65                                name  ; custom section name
000007a: 01                                        ; function name type
000007b: 00                                        ; subsection size (guess)
000007c: 02                                        ; num functions
000007d: 00                                        ; function index
000007e: 11                                        ; string length
000007f: 5f5f 7761 736d 5f63 616c 6c5f 6374 6f72  __wasm_call_ctor
000008f: 73                                       s  ; func name 0
0000090: 01                                        ; function index
0000091: 04                                        ; string length
0000092: 6d61 696e                                main  ; func name 1
000007b: 1a                                        ; FIXUP subsection size
0000096: 02                                        ; local name type
0000097: 00                                        ; subsection size (guess)
0000098: 02                                        ; num functions
0000099: 00                                        ; function index
000009a: 00                                        ; num locals
000009b: 01                                        ; function index
000009c: 00                                        ; num locals
0000097: 05                                        ; FIXUP subsection size
0000074: 28                                        ; FIXUP section size
Tip

If you’re keen to give We­bAssem­bly a go, check out the We­bAssem­bly Studio – an online de­vel­op­ment en­vi­ron­ment for Wasm.

Go to Main Menu