koda

Bootstrapping an assembler from hand-written hex. Every byte of these WebAssembly modules is written by hand, then run in Wasmtime with WASI.

echo

Copy console input to console output; Ctrl-D (0x04) or end of input exits with code 0. Through WASI, one byte at a time.

wasm · 235 bytes · exit code 0 · echo.wasm

Input

hello
␄

Output

hello

Source: echo.hex

# echo: copy console input to console output; Ctrl-D (0x04) or end of input
# exits with code 0. Through WASI, one byte at a time.
#
# Memory layout: 0: iovec {buf = 16, len = 1}   8: nread   12: nwritten
#                16: the byte

00 61 73 6d 01 00 00 00                          # magic "\0asm", version 1

01 10 03                                         # type section, 16 bytes, 3 types
60 04 7f 7f 7f 7f 01 7f                          # 0: (i32 i32 i32 i32) -> i32   fd_read, fd_write
60 00 00                                         # 1: () -> ()                   _start
60 01 7f 00                                      # 2: (i32) -> ()                proc_exit

02 67 03                                         # import section, 103 bytes, 3 imports
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
07 66 64 5f 72 65 61 64 00 00                    # "fd_read": type 0 -> func 0
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
08 66 64 5f 77 72 69 74 65 00 00                 # "fd_write": type 0 -> func 1
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
09 70 72 6f 63 5f 65 78 69 74 00 02              # "proc_exit": type 2 -> func 2

03 02 01 01                                      # function section: 1 function, type 1 -> func 3
05 03 01 00 01                                   # memory section: 1 memory, at least 1 page

07 13 02                                         # export section, 19 bytes, 2 exports
06 6d 65 6d 6f 72 79 02 00                       # "memory": memory 0
06 5f 73 74 61 72 74 00 03                       # "_start": func 3

0a 38 01                                         # code section, 56 bytes, 1 body
36 00                                            # body of func 3: 54 bytes, no locals
02 40         ; block                            # exit: br to the end of this block
03 40         ; loop                             # next: br to the top of this loop
41 00         ; i32.const 0                      # fd: stdin
41 00         ; i32.const 0                      # iovs
41 01         ; i32.const 1                      # one iovec
41 08         ; i32.const 8                      # where to store the count read
10 00         ; call 0                           # fd_read
1a            ; drop                             # ignore the error code
41 08         ; i32.const 8
28 02 00      ; i32.load 2 0                     # nread (align 2^2, offset 0)
45            ; i32.eqz
0d 01         ; br_if 1                          # end of input: exit
41 10         ; i32.const 16
2d 00 00      ; i32.load8_u 0 0                  # the byte
41 04         ; i32.const 4
46            ; i32.eq
0d 01         ; br_if 1                          # Ctrl-D: exit
41 01         ; i32.const 1                      # fd: stdout
41 00         ; i32.const 0                      # iovs (len 1: the byte read)
41 01         ; i32.const 1
41 0c         ; i32.const 12                     # where to store the count written
10 01         ; call 1                           # fd_write
1a            ; drop
0c 00         ; br 0                             # next byte
0b            ; end                              # loop
0b            ; end                              # block
41 00         ; i32.const 0
10 02         ; call 2                           # proc_exit(0)
0b            ; end

0b 0e 01                                         # data section, 14 bytes, 1 segment
00 41 00 0b 08                                   # active, memory 0, at i32.const 0, 8 bytes:
10 00 00 00 01 00 00 00                          # iovec: buf = 16, len = 1

hello

Print "OK\n" and exit 0, through WASI.

wasm · 176 bytes · exit code 0 · hello.wasm

Output

OK

Source: hello.hex

# hello: print "OK\n" and exit 0, through WASI.
#
# A WebAssembly module: a header, then sections (id, size in bytes, contents).
# Sizes and counts are LEB128; all of them here fit in one byte. The program
# is the function _start, which the runtime calls; I/O goes through the WASI
# functions it imports (wasi_snapshot_preview1). Memory layout:
#   0: iovec {buf = 16, len = 3}   8: nwritten   16: "OK\n"

00 61 73 6d 01 00 00 00                          # magic "\0asm", version 1

01 10 03                                         # type section, 16 bytes, 3 types
60 04 7f 7f 7f 7f 01 7f                          # 0: (i32 i32 i32 i32) -> i32   fd_write
60 00 00                                         # 1: () -> ()                   _start
60 01 7f 00                                      # 2: (i32) -> ()                proc_exit

02 46 02                                         # import section, 70 bytes, 2 imports
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1" (22)
70 72 65 76 69 65 77 31
08 66 64 5f 77 72 69 74 65 00 00                 # "fd_write": function, type 0 -> func 0
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
09 70 72 6f 63 5f 65 78 69 74 00 02              # "proc_exit": function, type 2 -> func 1

03 02 01 01                                      # function section: 1 function, type 1 -> func 2
05 03 01 00 01                                   # memory section: 1 memory, at least 1 page (64 KB)

07 13 02                                         # export section, 19 bytes, 2 exports
06 6d 65 6d 6f 72 79 02 00                       # "memory": memory 0
06 5f 73 74 61 72 74 00 02                       # "_start": func 2

0a 13 01                                         # code section, 19 bytes, 1 body
11 00                                            # body of func 2: 17 bytes, no locals
41 01         ; i32.const 1                      # fd: stdout
41 00         ; i32.const 0                      # iovs: the iovec at 0
41 01         ; i32.const 1                      # one iovec
41 08         ; i32.const 8                      # where to store the count written
10 00         ; call 0                           # fd_write(1, 0, 1, 8)
1a            ; drop                             # ignore the error code
41 00         ; i32.const 0                      # exit code
10 01         ; call 1                           # proc_exit(0)
0b            ; end

0b 19 01                                         # data section, 25 bytes, 1 segment
00 41 00 0b 13                                   # active, memory 0, at i32.const 0, 19 bytes:
10 00 00 00 03 00 00 00                          # iovec: buf = 16, len = 3
00 00 00 00 00 00 00 00                          # nwritten, padding
4f 4b 0a                                         # "OK\n"

hex0

Turn hex text into bytes, the first stage of the bootstrap. Reads console input, writes a byte for every two hex digits (either case) and exits with code 0 at the end of input. '#' and ';' start a comment that runs to the end of the line; any other character is skipped. This is the format of koda's .hex files, so hex0 can build itself.

wasm · 364 bytes · exit code 0 · hex0.wasm

Input

# hex0 test: comments, either case, any whitespace
48 65 6C 6c 6F ; "Hello" (an annotation: 99 is ignored)
0a␉# tab before the comment
2C20776f726c64  # no spaces: ", world"
xyz 0A, 21 ; characters that are not hex digits are skipped; this line has no newline

Output

Hello
, world
!

Source: hex0.hex

# hex0: turn hex text into bytes, the first stage of the bootstrap. Reads
# console input, writes a byte for every two hex digits (either case) and
# exits with code 0 at the end of input. '#' and ';' start a comment that
# runs to the end of the line; any other character is skipped. This is the
# format of koda's .hex files, so hex0 can build itself.
#
# Memory layout: 0: iovec {buf = 16, len = 1}   8: nread   12: nwritten
#                16: the byte read or written
# Locals of _start: 0: the character   1: its digit value
#                   2: the high digit waiting for its low one, -1 if none

00 61 73 6d 01 00 00 00                          # magic "\0asm", version 1

01 14 04                                         # type section, 20 bytes, 4 types
60 04 7f 7f 7f 7f 01 7f                          # 0: (i32 i32 i32 i32) -> i32   fd_read, fd_write
60 00 00                                         # 1: () -> ()                   _start
60 01 7f 00                                      # 2: (i32) -> ()                proc_exit
60 00 01 7f                                      # 3: () -> i32                  getc

02 67 03                                         # import section, 103 bytes, 3 imports
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
07 66 64 5f 72 65 61 64 00 00                    # "fd_read": type 0 -> func 0
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
08 66 64 5f 77 72 69 74 65 00 00                 # "fd_write": type 0 -> func 1
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
09 70 72 6f 63 5f 65 78 69 74 00 02              # "proc_exit": type 2 -> func 2

03 03 02 01 03                                   # function section: 2 functions, type 1 -> func 3 (_start),
                                                 # type 3 -> func 4 (getc)
05 03 01 00 01                                   # memory section: 1 memory, at least 1 page

07 13 02                                         # export section, 19 bytes, 2 exports
06 6d 65 6d 6f 72 79 02 00                       # "memory": memory 0
06 5f 73 74 61 72 74 00 03                       # "_start": func 3

0a b3 01 02                                      # code section, 179 bytes, 2 bodies

91 01                                            # body of func 3 (_start): 145 bytes
01 03 7f                                         # locals: 3 i32
41 7f         ; i32.const 4294967295             # -1: no high digit yet
21 02         ; local.set 2
02 40         ; block                            # exit: br to the end of this block
03 40         ; loop                             # next: br to the top of this loop
10 04         ; call 4                           # getc
22 00         ; local.tee 0
41 7f         ; i32.const 4294967295
46            ; i32.eq
0d 01         ; br_if 1                          # end of input: exit
20 00         ; local.get 0
41 23         ; i32.const 35                     # '#'
46            ; i32.eq
20 00         ; local.get 0
41 3b         ; i32.const 59                     # ';'
46            ; i32.eq
72            ; i32.or
04 40         ; if                               # a comment:
03 40         ; loop                             # skip to the end of the line
10 04         ; call 4
22 00         ; local.tee 0
41 7f         ; i32.const 4294967295
46            ; i32.eq
0d 03         ; br_if 3                          # end of input: exit
20 00         ; local.get 0
41 0a         ; i32.const 10                     # '\n'
47            ; i32.ne
0d 00         ; br_if 0
0b            ; end                              # loop
0c 01         ; br 1                             # next
0b            ; end                              # if
02 40         ; block                            # a digit: br here with its value in local 1
20 00         ; local.get 0
41 30         ; i32.const 48                     # '0'
6b            ; i32.sub
22 01         ; local.tee 1
41 0a         ; i32.const 10
49            ; i32.lt_u
0d 00         ; br_if 0                          # '0'..'9'
20 00         ; local.get 0
41 20         ; i32.const 32                     # lower case
72            ; i32.or
41 e1 00      ; i32.const 97                     # 'a' (two bytes: 0x61 alone would be -31)
6b            ; i32.sub
22 01         ; local.tee 1
41 06         ; i32.const 6
49            ; i32.lt_u
45            ; i32.eqz
0d 01         ; br_if 1                          # not 'a'..'f' either: skip it, next
20 01         ; local.get 1
41 0a         ; i32.const 10
6a            ; i32.add
21 01         ; local.set 1
0b            ; end                              # block
20 02         ; local.get 2
41 00         ; i32.const 0
48            ; i32.lt_s
04 40         ; if                               # no high digit yet:
20 01         ; local.get 1
21 02         ; local.set 2                      # this is it
0c 01         ; br 1                             # next
0b            ; end                              # if
41 10         ; i32.const 16
20 02         ; local.get 2
41 04         ; i32.const 4
74            ; i32.shl
20 01         ; local.get 1
72            ; i32.or
3a 00 00      ; i32.store8 0 0                   # the byte: high * 16 + low
41 7f         ; i32.const 4294967295
21 02         ; local.set 2                      # no high digit
41 01         ; i32.const 1                      # fd: stdout
41 00         ; i32.const 0                      # iovs (len 1: the byte)
41 01         ; i32.const 1
41 0c         ; i32.const 12                     # where to store the count written
10 01         ; call 1                           # fd_write
1a            ; drop                             # ignore the error code
0c 00         ; br 0                             # next
0b            ; end                              # loop
0b            ; end                              # block
41 00         ; i32.const 0
10 02         ; call 2                           # proc_exit(0)
0b            ; end

1e                                               # body of func 4 (getc): 30 bytes
00                                               # no locals
41 00         ; i32.const 0                      # fd: stdin
41 00         ; i32.const 0                      # iovs
41 01         ; i32.const 1                      # one iovec
41 08         ; i32.const 8                      # where to store the count read
10 00         ; call 0                           # fd_read
1a            ; drop                             # ignore the error code
41 08         ; i32.const 8
28 02 00      ; i32.load 2 0                     # nread
45            ; i32.eqz
04 7f         ; if i32
41 7f         ; i32.const 4294967295             # end of input: -1
05            ; else
41 10         ; i32.const 16
2d 00 00      ; i32.load8_u 0 0                  # the byte
0b            ; end
0b            ; end

0b 0e 01                                         # data section, 14 bytes, 1 segment
00 41 00 0b 08                                   # active, memory 0, at i32.const 0, 8 bytes:
10 00 00 00 01 00 00 00                          # iovec: buf = 16, len = 1

hex1

Hex0 plus labels, so sizes no longer have to be counted by hand. The second stage of the bootstrap. Reads console input and writes bytes: two hex digits (either case) make a byte; '#' and ';' start a comment that runs to the end of the line; ":c" defines the label c (any one character) here; "@c" writes the distance from the end of the field to label c, as a 5-byte LEB128 (a section or body size: "@e" before the contents, ":e" after them). Any other character is skipped. Exits with code 0.

wasm · 594 bytes · exit code 0 · hex1.wasm

Input

# hex1 test: a module that prints "hex1", with every size a label: "@x"
# before the contents, ":x" after them. Labels are any one character.
00 61 73 6d 01 00 00 00                  # magic, version 1
01 @t 03                                 # type section
  60 04 7f 7f 7f 7f 01 7f                # (i32 i32 i32 i32) -> i32   fd_write
  60 00 00                               # () -> ()                   _start
  60 01 7f 00 :t                         # (i32) -> ()                proc_exit
02 @i 02                                 # import section
  16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f 70 72 65 76 69 65 77 31
  08 66 64 5f 77 72 69 74 65 00 00       # fd_write
  16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f 70 72 65 76 69 65 77 31
  09 70 72 6f 63 5f 65 78 69 74 00 02    # proc_exit
:i
03 @f 01 01 :f                           # function section
05 @m 01 00 01 :m                        # memory section
07 @E 02                                 # export section (E, not e: labels are case-sensitive)
  06 6D 65 6D 6F 72 79 02 00             # "memory"
  06 5F 73 74 61 72 74 00 02             # "_start"
:E
0a @c 01                                 # code section
  @1 00                                  # a body, no locals
    41 01 41 00 41 01 41 10 10 00 1a     # fd_write(1, the iovec at 0, 1, nwritten at 16)
    41 00 10 01 0b                       # proc_exit(0)
  :1
:c
0b @d 01 00 41 00 0b @s                  # data section: at 0
  08 00 00 00 05 00 00 00                # iovec: buf = 8, len = 5
  68 65 78 31 0a                         # "hex1\n"
:s :d

Output

0000  00 61 73 6d 01 00 00 00 01 90 80 80 80 00 03 60
0010  04 7f 7f 7f 7f 01 7f 60 00 00 60 01 7f 00 02 c6
0020  80 80 80 00 02 16 77 61 73 69 5f 73 6e 61 70 73
0030  68 6f 74 5f 70 72 65 76 69 65 77 31 08 66 64 5f
0040  77 72 69 74 65 00 00 16 77 61 73 69 5f 73 6e 61
0050  70 73 68 6f 74 5f 70 72 65 76 69 65 77 31 09 70
0060  72 6f 63 5f 65 78 69 74 00 02 03 82 80 80 80 00
0070  01 01 05 83 80 80 80 00 01 00 01 07 93 80 80 80
0080  00 02 06 6d 65 6d 6f 72 79 02 00 06 5f 73 74 61
0090  72 74 00 02 0a 97 80 80 80 00 01 91 80 80 80 00
00a0  00 41 01 41 00 41 01 41 10 10 00 1a 41 00 10 01
00b0  0b 0b 97 80 80 80 00 01 00 41 00 0b 8d 80 80 80
00c0  00 08 00 00 00 05 00 00 00 68 65 78 31 0a

Source: hex1.hex

# hex1: hex0 plus labels, so sizes no longer have to be counted by hand.
# The second stage of the bootstrap. Reads console input and writes bytes:
# two hex digits (either case) make a byte; '#' and ';' start a comment that
# runs to the end of the line; ":c" defines the label c (any one character)
# here; "@c" writes the distance from the end of the field to label c, as a
# 5-byte LEB128 (a section or body size: "@e" before the contents, ":e" after
# them). Any other character is skipped. Exits with code 0.
#
# The input is read whole into memory, then turned into bytes twice: the
# first pass only records where the labels are, the second writes.
#
# Memory layout: 0: read iovec {buf, len = 4096}   8: nread   12: nwritten
#                16: write iovec {buf = 24, len = 1}   24: the byte written
#                256: the labels, 4 bytes for each of the 256 characters
#                4096: the input, as long as it is (memory grows to fit)
# Globals: 0: length of the input   1: where getc reads next

00 61 73 6d 01 00 00 00                          # magic "\0asm", version 1

01 14 04                                         # type section, 20 bytes, 4 types
60 04 7f 7f 7f 7f 01 7f                          # 0: (i32 i32 i32 i32) -> i32   fd_read, fd_write
60 00 00                                         # 1: () -> ()                   _start
60 01 7f 00                                      # 2: (i32) -> ()                proc_exit, putc, pass
60 00 01 7f                                      # 3: () -> i32                  getc

02 67 03                                         # import section, 103 bytes, 3 imports
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
07 66 64 5f 72 65 61 64 00 00                    # "fd_read": type 0 -> func 0
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
08 66 64 5f 77 72 69 74 65 00 00                 # "fd_write": type 0 -> func 1
16 77 61 73 69 5f 73 6e 61 70 73 68 6f 74 5f     # "wasi_snapshot_preview1"
70 72 65 76 69 65 77 31
09 70 72 6f 63 5f 65 78 69 74 00 02              # "proc_exit": type 2 -> func 2

03 05 04 01 03 02 02                             # function section: 4 functions: type 1 -> func 3
                                                 # (_start), 3 -> 4 (getc), 2 -> 5 (putc), 2 -> 6 (pass)
05 03 01 00 01                                   # memory section: 1 memory, at least 1 page

06 0b 02                                         # global section, 11 bytes, 2 globals
7f 01 41 00 0b                                   # 0: mutable i32 = 0   input length
7f 01 41 00 0b                                   # 1: mutable i32 = 0   getc's position

07 13 02                                         # export section, 19 bytes, 2 exports
06 6d 65 6d 6f 72 79 02 00                       # "memory": memory 0
06 5f 73 74 61 72 74 00 03                       # "_start": func 3

0a fa 02 04                                      # code section, 378 bytes, 4 bodies

54                                               # body of func 3 (_start): 84 bytes
01 01 7f                                         # locals: 1 i32 (0: bytes read)
02 40         ; block                            # read the whole input
03 40         ; loop
23 00         ; global.get 0
41 80 c0 00   ; i32.const 8192                   # 4096 + room for one more read
6a            ; i32.add
3f 00         ; memory.size 0
41 10         ; i32.const 16
74            ; i32.shl                          # bytes of memory
4b            ; i32.gt_u
04 40         ; if                               # no room:
41 01         ; i32.const 1
40 00         ; memory.grow 0                    # one more 64 KB page
1a            ; drop
0b            ; end
41 00         ; i32.const 0
23 00         ; global.get 0
41 80 20      ; i32.const 4096
6a            ; i32.add
36 02 00      ; i32.store 2 0                    # read to the end of the input so far
41 00         ; i32.const 0                      # fd: stdin
41 00         ; i32.const 0                      # iovs
41 01         ; i32.const 1
41 08         ; i32.const 8                      # where to store the count read
10 00         ; call 0                           # fd_read
1a            ; drop                             # ignore the error code
41 08         ; i32.const 8
28 02 00      ; i32.load 2 0
22 00         ; local.tee 0
45            ; i32.eqz
0d 01         ; br_if 1                          # end of input: done
23 00         ; global.get 0
20 00         ; local.get 0
6a            ; i32.add
24 00         ; global.set 0                     # the input is that much longer
0c 00         ; br 0
0b            ; end                              # loop
0b            ; end                              # block
41 00         ; i32.const 0
10 06         ; call 6                           # pass 1: find the labels
41 01         ; i32.const 1
10 06         ; call 6                           # pass 2: write the bytes
41 00         ; i32.const 0
10 02         ; call 2                           # proc_exit(0)
0b            ; end

1a                                               # body of func 4 (getc): 26 bytes
00                                               # no locals
23 01         ; global.get 1
23 00         ; global.get 0
4f            ; i32.ge_u
04 7f         ; if i32
41 7f         ; i32.const 4294967295             # end of input: -1
05            ; else
23 01         ; global.get 1
2d 00 80 20   ; i32.load8_u 0 4096               # the next character of the input
23 01         ; global.get 1
41 01         ; i32.const 1
6a            ; i32.add
24 01         ; global.set 1
0b            ; end
0b            ; end

14                                               # body of func 5 (putc): 20 bytes
00                                               # no locals; param 0: the byte
41 18         ; i32.const 24
20 00         ; local.get 0
3a 00 00      ; i32.store8 0 0
41 01         ; i32.const 1                      # fd: stdout
41 10         ; i32.const 16                     # iovs: the write iovec
41 01         ; i32.const 1
41 0c         ; i32.const 12                     # where to store the count written
10 01         ; call 1                           # fd_write
1a            ; drop                             # ignore the error code
0b            ; end

f2 01                                            # body of func 6 (pass): 242 bytes
01 05 7f                                         # locals: 5 i32. Param 0: write the bytes (pass 2)?
                                                 # 1: the character   2: its digit value or a distance
                                                 # 3: the high digit waiting for its low one, -1 if none
                                                 # 4: position in the output   5: a count
41 00         ; i32.const 0
24 01         ; global.set 1                     # from the start of the input
41 7f         ; i32.const 4294967295
21 03         ; local.set 3                      # no high digit yet
02 40         ; block                            # exit: br to the end of this block
03 40         ; loop                             # next: br to the top of this loop
10 04         ; call 4                           # getc
22 01         ; local.tee 1
41 7f         ; i32.const 4294967295
46            ; i32.eq
0d 01         ; br_if 1                          # end of input: exit
20 01         ; local.get 1
41 23         ; i32.const 35                     # '#'
46            ; i32.eq
20 01         ; local.get 1
41 3b         ; i32.const 59                     # ';'
46            ; i32.eq
72            ; i32.or
04 40         ; if                               # a comment:
03 40         ; loop                             # skip to the end of the line
10 04         ; call 4
22 01         ; local.tee 1
41 7f         ; i32.const 4294967295
46            ; i32.eq
0d 03         ; br_if 3                          # end of input: exit
20 01         ; local.get 1
41 0a         ; i32.const 10                     # '\n'
47            ; i32.ne
0d 00         ; br_if 0
0b            ; end                              # loop
0c 01         ; br 1                             # next
0b            ; end                              # if
20 01         ; local.get 1
41 3a         ; i32.const 58                     # ':'
46            ; i32.eq
04 40         ; if                               # a label:
10 04         ; call 4                           # its name
41 ff 01      ; i32.const 255
71            ; i32.and                          # (-1 at the end of input: 255)
41 02         ; i32.const 2
74            ; i32.shl
20 04         ; local.get 4
36 02 80 02   ; i32.store 2 256                  # is here
0c 01         ; br 1                             # next
0b            ; end                              # if
20 01         ; local.get 1
41 c0 00      ; i32.const 64                     # '@' (two bytes: 0x40 alone would be -64)
46            ; i32.eq
04 40         ; if                               # a distance to a label:
10 04         ; call 4                           # its name
41 ff 01      ; i32.const 255
71            ; i32.and
41 02         ; i32.const 2
74            ; i32.shl
28 02 80 02   ; i32.load 2 256                   # where it is (pass 1: not known yet)
20 04         ; local.get 4
41 05         ; i32.const 5
6a            ; i32.add
22 04         ; local.tee 4                      # the end of the field
6b            ; i32.sub
21 02         ; local.set 2                      # the distance
20 00         ; local.get 0
04 40         ; if                               # pass 2: write it, 5 bytes of LEB128
41 04         ; i32.const 4
21 05         ; local.set 5
03 40         ; loop                             # 4 bytes of 7 bits with the "more" bit
20 02         ; local.get 2
41 80 01      ; i32.const 128
72            ; i32.or
10 05         ; call 5                           # putc (the low 8 bits)
20 02         ; local.get 2
41 07         ; i32.const 7
76            ; i32.shr_u
21 02         ; local.set 2
20 05         ; local.get 5
41 01         ; i32.const 1
6b            ; i32.sub
22 05         ; local.tee 5
0d 00         ; br_if 0
0b            ; end                              # loop
20 02         ; local.get 2
10 05         ; call 5                           # then the last 4 bits
0b            ; end                              # if
0c 01         ; br 1                             # next
0b            ; end                              # if
02 40         ; block                            # a digit: br here with its value in local 2
20 01         ; local.get 1
41 30         ; i32.const 48                     # '0'
6b            ; i32.sub
22 02         ; local.tee 2
41 0a         ; i32.const 10
49            ; i32.lt_u
0d 00         ; br_if 0                          # '0'..'9'
20 01         ; local.get 1
41 20         ; i32.const 32                     # lower case
72            ; i32.or
41 e1 00      ; i32.const 97                     # 'a'
6b            ; i32.sub
22 02         ; local.tee 2
41 06         ; i32.const 6
49            ; i32.lt_u
45            ; i32.eqz
0d 01         ; br_if 1                          # not 'a'..'f' either: skip it, next
20 02         ; local.get 2
41 0a         ; i32.const 10
6a            ; i32.add
21 02         ; local.set 2
0b            ; end                              # block
20 03         ; local.get 3
41 00         ; i32.const 0
48            ; i32.lt_s
04 40         ; if                               # no high digit yet:
20 02         ; local.get 2
21 03         ; local.set 3                      # this is it
0c 01         ; br 1                             # next
0b            ; end                              # if
20 00         ; local.get 0
04 40         ; if                               # pass 2: write the byte
20 03         ; local.get 3
41 04         ; i32.const 4
74            ; i32.shl
20 02         ; local.get 2
72            ; i32.or
10 05         ; call 5                           # putc: high * 16 + low
0b            ; end                              # if
41 7f         ; i32.const 4294967295
21 03         ; local.set 3                      # no high digit
20 04         ; local.get 4
41 01         ; i32.const 1
6a            ; i32.add
21 04         ; local.set 4                      # one byte further
0c 00         ; br 0                             # next
0b            ; end                              # loop
0b            ; end                              # block
0b            ; end

0b 1e 01                                         # data section, 30 bytes, 1 segment
00 41 00 0b 18                                   # active, memory 0, at i32.const 0, 24 bytes:
00 10 00 00 00 10 00 00                          # read iovec: buf = 4096 (set before each read), len = 4096
00 00 00 00 00 00 00 00                          # nread, nwritten
18 00 00 00 01 00 00 00                          # write iovec: buf = 24, len = 1