Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
Compiling to WASM with llvm on macOS#
howto-wasm-minimal by Zalka Ernő (my fork here) is a neat demo of a minimal WASM module. It uses C++ to define functions for simple image manipulation including blurring an image, compiles it to WASM using llvm/clang++, then uses JavaScript to run those functions against an image loaded into a <canvas> element.
I decided to try compiling it myself:
1cd /tmp2git clone https://github.com/ern0/howto-wasm-minimal3cd howto-wasm-minimal4./build.shThis gave me the following error:
1error: unable to create target: 'No available targets are compatible with triple "wasm32"'Searching for the error lead me to this comment:
You need to install
llvmfromHomebrew. Xcode’sclangdoesn’t have support for WebAssembly.
So I installed llvm from Homebrew:
1% brew install llvm2...3To use the bundled libc++ please add the following LDFLAGS:4 LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"5
6llvm is keg-only, which means it was not symlinked into /usr/local,7because macOS already provides this software and installing another version in8parallel can cause all kinds of trouble.9
10If you need to have llvm first in your PATH, run:11 echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc12
13For compilers to find llvm you may need to set:14 export LDFLAGS="-L/usr/local/opt/llvm/lib"15 export CPPFLAGS="-I/usr/local/opt/llvm/include"16...I’ve quoted the relevant output. I’m not ready to permanently replace the system llvm with the one from Homebrew, so I ran the build script like this instead:
1% PATH="/usr/local/opt/llvm/bin:$PATH" ./build.sh20000000 00 61 73 6d 01 00 00 00 01 14 04 60 00 00 60 01That created a inc.wasm file in my current folder.
I tried opening index.html in Firefox and got the following error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at file:///private/tmp/howto-wasm-minimal/inc.wasm. (Reason: CORS request not http).
So I ran a localhost web server instead using this Python one-liner:
1% python3 -m http.server 80042Serving HTTP on :: port 8004 (http://[::]:8004/) ...This worked! http://localhost:8004/ displayed the working demo:

For reference, here’s that build.sh script in full:
1#!/bin/bash2set -e3
4clang++ \5 --target=wasm32 \6 -nostdlib \7 -O3 \8 -Wl,--no-entry \9 -Wl,--export-all \10 -Wl,--lto-O3 \11 -Wl,--allow-undefined \12 -Wl,--import-memory \13 -o inc.wasm \14 inc.cpp15
16hexdump inc.wasm | head -n 117#wasm-objdump -x inc.wasmBonus: Web Assembly Binary Toolkit#
The last commented-out line of that script caught my attention:
1wasm-objdump -x inc.wasmI searched, and wasm-objdump is part of the tools provided by the WebAssembly Binary Toolkit .
You can install that with Homebrew by running brew install wabt.
Then this worked:
1% wasm-objdump -x inc.wasm2
3inc.wasm: file format wasm 0x14
5Section Details:6
7Type[4]:8 - type[0] () -> nil9 - type[1] (i32) -> i3210 - type[2] (i32, i32) -> nil11 - type[3] (i32, i32, i32) -> nil12Import[1]:13 - memory[0] pages: initial=2 <- env.memory14Function[7]:15 - func[0] sig=0 <__wasm_call_ctors>16 - func[1] sig=1 <inc>17 - func[2] sig=0 <incmem>18 - func[3] sig=2 <gray>19 - func[4] sig=2 <swaprg>20 - func[5] sig=3 <swap_red>21 - func[6] sig=2 <blur>22Global[7]:23 - global[0] i32 mutable=1 <__stack_pointer> - init i32=6656024 - global[1] i32 mutable=0 <__dso_handle> - init i32=102425 - global[2] i32 mutable=0 <__data_end> - init i32=102426 - global[3] i32 mutable=0 <__global_base> - init i32=102427 - global[4] i32 mutable=0 <__heap_base> - init i32=6656028 - global[5] i32 mutable=0 <__memory_base> - init i32=029 - global[6] i32 mutable=0 <__table_base> - init i32=130Export[13]:31 - func[0] <__wasm_call_ctors> -> "__wasm_call_ctors"32 - func[1] <inc> -> "inc"33 - func[2] <incmem> -> "incmem"34 - func[3] <gray> -> "gray"35 - func[4] <swaprg> -> "swaprg"36 - func[5] <swap_red> -> "swap_red"37 - func[6] <blur> -> "blur"38 - global[1] -> "__dso_handle"39 - global[2] -> "__data_end"40 - global[3] -> "__global_base"41 - global[4] -> "__heap_base"42 - global[5] -> "__memory_base"43 - global[6] -> "__table_base"44Code[7]:45 - func[0] size=2 <__wasm_call_ctors>46 - func[1] size=7 <inc>47 - func[2] size=69 <incmem>48 - func[3] size=101 <gray>49 - func[4] size=215 <swaprg>50 - func[5] size=1332 <swap_red>51 - func[6] size=615 <blur>52Custom:53 - name: "name"54 - func[0] <__wasm_call_ctors>55 - func[1] <inc>56 - func[2] <incmem>57 - func[3] <gray>58 - func[4] <swaprg>59 - func[5] <swap_red>60 - func[6] <blur>61 - global[0] <__stack_pointer>62Custom:63 - name: "producers"Also fun: wasm-decompile to “decompile a wasm binary into readable C-like syntax”:
1% wasm-decompile inc.wasm2import memory env_memory;3
4global stack_pointer:int = 66560;5export global dso_handle:int = 1024;6export global data_end:int = 1024;7export global global_base:int = 1024;8export global heap_base:int = 66560;9...And wasm-opcodecnt to “count opcode usage for instructions”:
1% wasm-opcodecnt inc.wasm2Total opcodes: 11883
4Opcode counts:5local.get: 2636i32.const: 2407i32.add: 1728local.set: 889...More in the README: https://github.com/WebAssembly/wabt/blob/main/README.md