Generating WebAssembly with LDC

From D Wiki

Starting with v1.11, LDC supports compiling and linking directly to WebAssembly. This page shows how to get started.

Building WebAssembly

Let's generate a .wasm file for this D code (wasm.d):

extern(C): // disable D mangling double add(double a, double b) { return a + b; } // seems to be the required entry point
void _start() {}

Build wasm.wasm:

ldc2 -mtriple=wasm32-unknown-unknown-wasm -betterC wasm.d

In case LDC errors out (e.g., with unsupported -link-internally), try an official prebuilt release package.

Test in HTML page

Let's test it with a little HTML page, loading and invoking the WebAssembly via JavaScript. Generate an .html file in the same directory as the .wasm file, with the following contents:

<html> <head> <script> const request = new XMLHttpRequest();'GET', 'wasm.wasm');
request.responseType = 'arraybuffer';
request.onload = () => { console.log('response received'); const bytes = request.response; const importObject = {}; WebAssembly.instantiate(bytes, importObject).then(result => { console.log('instantiated'); const { exports } = result.instance; // finally, call the add() function implemented in D: const r = exports.add(42, -2.5); console.log('r = ' + r); });
console.log('request sent'); </script> </head> <body> Test page </body>

Note that fetch() doesn't work for files in the local filesystem (file://), but XMLHttpRequest does in Firefox (not in Chrome though IIRC).

Open the HTML page; the JavaScript console should show:

request sent
response received
r = 39.5

Calling external functions

The minimal example above only calls in one direction, from JavaScript to WebAssembly. Here's how to call external functions in D:


extern(C): // disable D mangling void callback(double a, double b, double c); double add(double a, double b)
{ const c = a + b; callback(a, b, c); return c;
} void _start() {}

Add -L-allow-undefined as linker flag to the LDC command line, otherwise LLD refuses to link due to undefined callback().

Implement the callback() function in JavaScript and specify it in importObject.env:

const callback = (a, b, c) => { console.log(`callback from D: ${a} + ${b} = ${c}`);
}; // ... const importObject = { env: { callback } };

The log should now show:

request sent
response received
callback from D: 42 + -2.5 = 39.5
r = 39.5