Skip to main content
skill CX Codex

wasm-emscripten — Claude Code skill: C/C++ to WebAssembly

A skill that walks the agent through compiling C and C++ to WebAssembly: emcc flags, function exports, the memory model, Asyncify and .wasm debugging.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add mohitmishra786/low-level-dev-skills --skill wasm-emscripten -a claude-code
Open on GitHub

What wasm-emscripten does

The skill covers the path from installing the Emscripten SDK to debugging the finished binary. Seven steps sit inside: a first build, exporting functions to JavaScript, the linear memory model, Asyncify for async C, optimization with wasm-opt, debugging in browser devtools, and picking between WASI and a browser target. The agent gets concrete emcc invocations with flags instead of general advice.

Function exports are spelled out. EMSCRIPTEN_KEEPALIVE keeps a function from being dropped as dead code, and names in EXPORTED_FUNCTIONS carry a leading underscore. Calling from JS has three routes: direct Module._add, type-checked ccall, or cwrap, which hands back a plain JS function. Heap sizing goes through INITIAL_MEMORY, MAXIMUM_MEMORY and ALLOW_MEMORY_GROWTH.

WASM needs HTTP, not file://, so local testing runs through python3 -m http.server. Asyncify behaves better with optimization enabled, which is why the example passes -O2. For a WASI target the skill notes that wasi-sdk offers fuller support than Emscripten.

What it helps with

  • compile a C function to .wasm and call it from JavaScript
  • export functions without the linker dropping them
  • read and write linear memory from JS
  • turn synchronous C code async with Asyncify
  • shrink a .wasm binary through wasm-opt

Example prompts for the agent

  • Compile math.c to WebAssembly and export add and sqrt_approx
  • Why is my C function missing from JavaScript after the emcc build?
  • Set up Asyncify so fetch runs from synchronous C code

How to install wasm-emscripten

1Skills CLI (Vercel)

npx skills add mohitmishra786/low-level-dev-skills --skill wasm-emscripten -a claude-code

2Manually: put the files in place

~/.claude/skills/wasm-emscripten/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/wasm-emscripten/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about wasm-emscripten

How do I install wasm-emscripten in Claude?

Run npx skills add mohitmishra786/low-level-dev-skills --skill wasm-emscripten -a claude-code. The skill lands in ~/.claude/skills/wasm-emscripten/SKILL.md.

What does wasm-emscripten do?

It guides the agent through compiling C and C++ to WebAssembly with emcc: build flags, function exports, the memory model, Asyncify, debugging and target choice.

How does WASI differ from the browser target?

A browser build reaches Web APIs such as fetch and WebGL and runs in a JS engine. WASI exposes a POSIX subset and runs under wasmtime, wasmer or Node.js.

Source text

SKILL.md · MIT
---
name: wasm-emscripten
description: WebAssembly with Emscripten skill for C/C++ to WASM compilation. Use when compiling C/C++ to WebAssembly with emcc, configuring EXPORTED_FUNCTIONS, understanding the WASM memory model, using Asyncify for async C code, debugging .wasm with browser devtools or wasm-opt, or targeting WASI vs browser environments. Activates on queries about Emscripten, emcc, WebAssembly from C/C++, WASM memory model, Asyncify, EXPORTED_FUNCTIONS, WASI, or wasm-opt.
---

# WebAssembly with Emscripten

## Purpose

Guide agents through compiling C/C++ to WebAssembly using Emscripten: emcc flag selection, function exports, memory model configuration, Asyncify for asynchronous C code, debugging WASM binaries, and targeting WASI vs browser.

## Triggers

- "How do I compile C to WebAssembly with Emscripten?"
- "How do I export a C function to JavaScript?"
- "How does WebAssembly memory work with Emscripten?"
- "How do I debug a .wasm file?"
- "How do I use Asyncify to make synchronous C code async?"
- "What's the difference between WASI and Emscripten browser target?"

## Workflow

### 1. Setup and first build

```bash
# Install Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh    # add emcc to PATH

# Verify
emcc --version

# Compile C to WASM (browser target)
emcc hello.c -o hello.html          # generates hello.html + hello.js + hello.wasm
emcc hello.c -o hello.js            # just JS + WASM (no HTML shell)

# Serve locally (WASM requires HTTP, not file://)
python3 -m http.server 8080
# Open: http://localhost:8080/hello.html
```

### 2. Exporting functions to JavaScript

```c
// math.c
#include <emscripten.h>

// EMSCRIPTEN_KEEPALIVE prevents dead-code elimination
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

EMSCRIPTEN_KEEPALIVE
double sqrt_approx(double x) {
    return x * 0.5 + 1.0;
}
```

```bash
# Export specific functions
emcc math.c -o math.js \
  -s EXPORTED_FUNCTIONS='["_add","_sqrt_approx"]' \
  -s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
  -s MODULARIZE=1 \
  -s EXPORT_NAME=MathModule

# The leading underscore is required for C functions
```

```javascript
// Using exported functions in JS
const Module = await MathModule();

// Direct call
const result = Module._add(3, 4);

// Via ccall (type-safe)
const result2 = Module.ccall('add', 'number', ['number', 'number'], [3, 4]);

// Via cwrap (creates a callable JS function)
const add = Module.cwrap('add', 'number', ['number', 'number']);
console.log(add(3, 4));  // 7
```

### 3. Memory model

Emscripten provides a linear memory heap accessible from both C and JS:

```bash
# Configure initial and maximum heap
emcc prog.c -o prog.js \
  -s INITIAL_MEMORY=16MB \
  -s MAXIMUM_MEMORY=256MB \
  -s ALLOW_MEMORY_GROWTH=1    # allow dynamic growth

# Stack size (default 64KB)
emcc prog.c -o prog.js -s STACK_SIZE=1MB

# Shared memory (for SharedArrayBuffer / threads)
emcc prog.c -o prog.js -s SHARED_MEMORY=1 -s USE_PTHREADS=1
```

```javascript
// Accessing C memory from JS
const ptr = Module._malloc(1024);     // allocate
Module.HEAPU8.set([1, 2, 3], ptr);   // write bytes
Module._free(ptr);                     // free

// Read a C string
const strPtr = Module.ccall('get_message', 'number', [], []);
const str = Module.UTF8ToString(strPtr);

// Write a string to C
const jsStr = "hello";
const cStr = Module.stringToNewUTF8(jsStr);  // malloc + copy
Module._process_string(cStr);
Module._free(cStr);
```

### 4. Asyncify — synchronous C in async environments

Asyncify lets synchronous C code suspend and resume for async operations (like `fetch()`, sleep, etc.):

```c
// async.c
#include <emscripten.h>

// Synchronous sleep in C (blocks C, but yields to JS event loop)
EM_JS(void, do_fetch, (const char *url), {
    // Emscripten generates wrappers to suspend C while JS runs
    Asyncify.handleAsync(async () => {
        const resp = await fetch(UTF8ToString(url));
        const text = await resp.text();
        console.log(text);
    });
});

void process_url(const char *url) {
    do_fetch(url);     // looks synchronous in C
    printf("fetch complete\n");
}
```

```bash
# Enable Asyncify
emcc async.c -o async.js \
  -s ASYNCIFY \
  -s ASYNCIFY_STACK_SIZE=16384 \
  -O2   # Asyncify works better with optimization
```

### 5. Optimization and wasm-opt

```bash
# Optimization levels
emcc prog.c -O0 -o prog.js    # no optimization (fastest build)
emcc prog.c -O2 -o prog.js    # balanced
emcc prog.c -O3 -o prog.js    # aggressive
emcc prog.c -Os -o prog.js    # optimize for size
emcc prog.c -Oz -o prog.js    # aggressive size (Emscripten's smallest)

# Post-process with wasm-opt (Binaryen)
wasm-opt -Oz -o prog.opt.wasm prog.wasm    # optimize for size
wasm-opt -O4 -o prog.opt.wasm prog.wasm    # optimize for speed

# Compare sizes
ls -lh prog.wasm prog.opt.wasm
```

### 6. Debugging WASM

```bash
# Build with debug info
emcc prog.c -g -O0 -o prog.html \
  -s ASSERTIONS=1 \
  -s SAFE_HEAP=1      # catch misaligned accesses

# In Chrome DevTools:
# Sources → prog.wasm → line-by-line C source debugging
# (requires -g and browser with WASM debugging support)

# LLDB with WASM (wasmtime)
# See skills/runtimes/wasm-wasmtime for CLI WASM debugging
```

```bash
# Emscripten debug helpers
emcc prog.c -o prog.js \
  -s ASSERTIONS=2       # extensive runtime checks
  -s SAFE_HEAP=1        # sanitize heap accesses
  -s STACK_OVERFLOW_CHECK=1

# Print generated JS
emcc prog.c -o prog.js && cat prog.js | head -100
```

### 7. WASI vs browser target

| Feature | Browser (Emscripten) | WASI |
|---------|---------------------|------|
| Host APIs | Web APIs (fetch, WebGL, etc.) | POSIX subset (files, stdin/stdout) |
| Runtime | Browser JS engine | wasmtime, wasmer, WAMR, Node.js |
| Threads | SharedArrayBuffer + pthreads | wasi-threads (limited) |
| Networking | fetch(), WebSocket | wasi-http (preview2) |
| Use case | Web applications | Server-side, CLI tools, edge |

```bash
# Build for WASI (no browser JS, pure WASM)
emcc prog.c -o prog.wasm --target=wasi

# Or use wasi-sdk (better WASI support than Emscripten)
/opt/wasi-sdk/bin/clang --sysroot=/opt/wasi-sdk/share/wasi-sysroot \
  prog.c -o prog.wasm
wasmtime prog.wasm
```

For Emscripten linker flags reference, see [references/emscripten-linker-flags.md](references/emscripten-linker-flags.md).

## Related skills

- Use `skills/runtimes/wasm-wasmtime` for server-side WASM with wasmtime CLI and Rust embedding
- Use `skills/compilers/clang` for Clang-based WASM compilation with WASI SDK
- Use `skills/binaries/elf-inspection` for inspecting WASM binary structure

© mohitmishra786 · MIT · mohitmishra786/low-level-dev-skills

Similar skills

All skills for Programming languages →

From the same set62

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor