Difference between revisions of "Generating WebAssembly with LDC"

From D Wiki
Jump to: navigation, search
m (Test in HTML page)
Line 63: Line 63:
 
response received
 
response received
 
instantiated
 
instantiated
 +
r = 39.5
 +
</pre>
 +
 +
== 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:
 +
 +
<tt>wasm.d</tt>:
 +
 +
<syntaxhighlight lang="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() {}
 +
</syntaxhighlight>
 +
 +
Add <tt>-L-allow-undefined</tt> as linker flag to the LDC command line, otherwise LLD refuses to link due to undefined <tt>callback()</tt>.
 +
 +
Implement the <tt>callback()</tt> function in JavaScript and specify it in <tt>importObject.env</tt>:
 +
 +
<syntaxhighlight lang="JavaScript">
 +
const callback = (a, b, c) => {
 +
  console.log(`callback from D: ${a} + ${b} = ${c}`);
 +
};
 +
 +
// ...
 +
 +
  const importObject = {
 +
    env: { callback }
 +
  };
 +
</syntaxhighlight>
 +
 +
The log should now show:
 +
 +
<pre>
 +
request sent
 +
response received
 +
instantiated
 +
callback from D: 42 + -2.5 = 39.5
 
r = 39.5
 
r = 39.5
 
</pre>
 
</pre>
  
 
[[Category:LDC]]
 
[[Category:LDC]]

Revision as of 17:07, 15 July 2018

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 -link-internally 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();
request.open('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);
  });
};
request.send();
console.log('request sent');

    </script>
  </head>
  <body>
    Test page
  </body>
</html>

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
instantiated
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:

wasm.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
instantiated
callback from D: 42 + -2.5 = 39.5
r = 39.5