cawfree / react-native-webassembly

⚛️ 🏎 WebAssembly for React Native powered by JSI.
https://twitter.com/cawfree
MIT License
329 stars 6 forks source link
react-native wasm webassembly

react-native-webassembly

This package enables WebAssembly for React Native powered by C++ TurboModules and Wasm3, a fast and universal WebAssembly runtime.

react-native-webassembly provides React Native applications with the capability to execute universal Wasm binaries with native speed.

✏️ This project is still in active development. The following tasks are still remaining to be completed:

  • Sanitize C++ memory management practices.
  • Normalize execution and result handling of userland export functions.
  • Test framework implementation.

Pull Requests are welcome! 🙏

📡 Installation

  1. First, ensure your React Native application supports the New Architecture:
  2. Install react-native-webassembly:

    yarn add react-native-webassembly # React Native
    npx expo install react-native-webassembly # Expo
  3. If you're using Expo, don't forget to run npx expo prebuild after installing.

✍️ Usage

The goal of react-native-webassembly is to export a browser-equivalent interface to the WebAssembly API.

To initialize a new WebAssembly module, we'll need to instantiate an module using a buffer populated with a .wasm binary:

import axios from 'axios';
import * as WebAssembly from 'react-native-webassembly';

import HelloWorld from './hello-world.wasm';

const module = await WebAssembly.instantiate<{
  add: (a: number, b: number) => number;
}>(HelloWorld);

Note

To import .wasm files directly, you will need to update your metro.config.js.

Alternatively, in the snippet below, we show how to download and instantiate the reference Hello World example stored at a remote location:

import axios from 'axios';
import * as WebAssembly from 'react-native-webassembly';

const {
  data: bufferSource,
} = await axios({
  url: 'https://github.com/torch2424/wasm-by-example/raw/master/examples/hello-world/demo/assemblyscript/hello-world.wasm',
  method: 'get',
  responseType: 'arraybuffer',
});

const module = await WebAssembly.instantiate<{
  add: (a: number, b: number) => number;
}>(bufferSource);

You'll notice that in our call to instantiate, we can also pass typing information for the Exports of the module. In this case, the hello-world.wasm binary exports a function to add two numbers, add.

Once configured, we can execute the compiled wasm module from our JavaScript code, using the type-safe exported interface:

module.instance.exports.add(1, 2); // 3.

It's also possible to declare an importObject to receive callbacks from the compiled module, which declares a list of callback function implementations which can be invoked by the WebAssembly runtime.

Warning

Some native modules require the presence of certain function implementations. Without specifying module-specific required dependencies, instantiation will fail.

For example, the Circom library converts arithmetic circuits used for generating, evaluating and verifying SNARKs are expressed as WASM modules which require the runtime to define an exceptionHandler function belonging to the namespace runtime.

It's simple to define an importObject:

const module = await WebAssembly.instantiate<{
  getVersion: () => number;
  getFieldNumLen32: () => number;
  // ...
}>(bufferSource, {
  // Declare custom memory implementation.
  env: {
    memory: new WebAssembly.Memory({ initial: 32767 }),
  },
  // Define the scope of the import functions.
  runtime: {
    exceptionHandler: (value: number) => console.error(value),
  },
});

Here, we declare an exceptionHandler as runtime imports to the compiled module. Without declaring this required dependency, the module would fail to compile.

You can find a working implementation of this process in the Example App.

🤔 Memory

Currently, wasm3 only supports a single memory region. This means that WebAssembly files which contain multiple memory allocations are not currently supported.

react-native-webassembly exposes access to the runtime memory element for allocated instances, which is represented using an ArrayBuffer named memory. This shares the same backing array as the native runtime.

It can accessed as follows:

const module = WebAssembly.instantiate(...);

const memory: ArrayBuffer | undefined = module.instance.exports.memory;

✌️ License

MIT