Getting started

The easiest way to get started with WasmEdge is to use its command line tools (CLI). You can then run our example WebAssembly and JavaScript programs in the WasmEdge CLI. After that, you can create new programs for WasmEdge and run them in different host applications or frameworks.

Install

You can install WasmEdge using our one-line installer. Your system should have git and curl as prerequisites.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

For Windows 10, you could use Windows Package Manager Client (aka winget.exe) to install WasmEdge with a single command in your terminal.

winget install wasmedge

If you would like to install WasmEdge with its Tensorflow and image processing extensions, please run the following command. It will attempt to install Tensorflow and image shared libraries on your system.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e all

Run the following command to make the installed binary available in the current session.

source $HOME/.wasmedge/env

Use Docker

If you use Docker, you can simply run the WasmEdge application developer Docker images (x86 and arm64). Those images contain all the tooling you need for quick WasmEdge development.

$ docker pull wasmedge/appdev_x86_64:0.9.0
$ docker run --rm -v $(pwd):/app -it wasmedge/appdev_x86_64:0.9.0
(docker) #

WebAssembly examples

We have several WebAssembly bytecode program examples for you to try out on your newly installed WasmEdge CLI.

Hello world

The hello.wasm WebAssembly program contains a main() function. Checkout its Rust source code project. It prints out hello followed by the command line arguments.

$ wasmedge hello.wasm second state
hello
second
state

Call a function written in Rust

The add.wasm WebAssembly program contains an add() function. Checkout its Rust source code project. We use WasmEdge in reactor mode to call the add() with two integer input parameters.

$ wasmedge --reactor add.wasm add 2 2
4

Call a function written in WAT

We created the fibonacci.wat program by hand and used the wat2wasm compiler to build the fibonacci.wasm WebAssembly program. It contains a fib() function which takes a single integer as input parameter. We use wasmedge in reactor mode to call the exported function.

$ wasmedge --reactor fibonacci.wasm fib 10
89

With Statistics enabled

The CLI supports --enable-all-statistics flags for the statistics and gas meter.

$ wasmedge --enable-all-statistics hello.wasm second state
hello
second
state
[2021-12-09 16:03:33.261] [info] ====================  Statistics  ====================
[2021-12-09 16:03:33.261] [info]  Total execution time: 268266 ns
[2021-12-09 16:03:33.261] [info]  Wasm instructions execution time: 251610 ns
[2021-12-09 16:03:33.261] [info]  Host functions execution time: 16656 ns
[2021-12-09 16:03:33.261] [info]  Executed wasm instructions count: 20425
[2021-12-09 16:03:33.261] [info]  Gas costs: 20425
[2021-12-09 16:03:33.261] [info]  Instructions per second: 81177218
[2021-12-09 16:03:33.261] [info] =======================   End   ======================

With gas-limit enabled

The CLI supports --gas-limit flags for controlling the execution costs.

# cd <path/to/WasmEdge>
$ cd examples/wasm
# With enough gas
$ wasmedge --enable-all-statistics --gas-limit 20425 hello.wasm second state
hello
second
state
[2021-12-09 16:03:33.261] [info] ====================  Statistics  ====================
[2021-12-09 16:03:33.261] [info]  Total execution time: 268266 ns
[2021-12-09 16:03:33.261] [info]  Wasm instructions execution time: 251610 ns
[2021-12-09 16:03:33.261] [info]  Host functions execution time: 16656 ns
[2021-12-09 16:03:33.261] [info]  Executed wasm instructions count: 20425
[2021-12-09 16:03:33.261] [info]  Gas costs: 20425
[2021-12-09 16:03:33.261] [info]  Instructions per second: 81177218
[2021-12-09 16:03:33.261] [info] =======================   End   ======================

# Without enough gas
$ wasmedge --enable-all-statistics --gas-limit 20 hello.wasm second state
[2021-12-23 15:19:06.690] [error] Cost exceeded limit. Force terminate the execution.
[2021-12-23 15:19:06.690] [error]     In instruction: ref.func (0xd2) , Bytecode offset: 0x00000000
[2021-12-23 15:19:06.690] [error]     At AST node: expression
[2021-12-23 15:19:06.690] [error]     At AST node: element segment
[2021-12-23 15:19:06.690] [error]     At AST node: element section
[2021-12-23 15:19:06.690] [error]     At AST node: module
[2021-12-23 15:19:06.690] [info] ====================  Statistics  ====================
[2021-12-23 15:19:06.690] [info]  Total execution time: 0 ns
[2021-12-23 15:19:06.690] [info]  Wasm instructions execution time: 0 ns
[2021-12-23 15:19:06.690] [info]  Host functions execution time: 0 ns
[2021-12-23 15:19:06.690] [info]  Executed wasm instructions count: 21
[2021-12-23 15:19:06.690] [info]  Gas costs: 20

JavaScript examples

It is possible to use WasmEdge as a high-performance, secure, extensible, easy to deploy, and Kubernetes-compliant JavaScript runtime.

The qjs.wasm program is a JavaScript interpreter compiled into WebAssembly. The hello.js file is a very simple JavaScript program.

$ wasmedge --dir .:. qjs.wasm hello.js 1 2 3
Hello 1 2 3

The qjs_tf.wasm is a JavaScript interpreter with WasmEdge Tensorflow extension compiled into WebAssembly. To run qjs_tf.wasm, you must use the wasmedge-tensorflow-lite CLI tool, which is a build of WasmEdge with Tensorflow extension built-in. You can download a full Tensorflow-based JavaScript example to classify images.

# Download the Tensorflow example
$ wget https://raw.githubusercontent.com/second-state/wasmedge-quickjs/main/example_js/tensorflow_lite_demo/aiy_food_V1_labelmap.txt
$ wget https://raw.githubusercontent.com/second-state/wasmedge-quickjs/main/example_js/tensorflow_lite_demo/food.jpg
$ wget https://raw.githubusercontent.com/second-state/wasmedge-quickjs/main/example_js/tensorflow_lite_demo/lite-model_aiy_vision_classifier_food_V1_1.tflite
$ wget https://raw.githubusercontent.com/second-state/wasmedge-quickjs/main/example_js/tensorflow_lite_demo/main.js

$ wasmedge-tensorflow-lite --dir .:. qjs_tf.wasm main.js
label: Hot dog
confidence: 0.8941176470588236

Read on and continue your learning of WasmEdge.

Install and uninstall WasmEdge

Quick install

The easiest way to install WasmEdge is to run the following command. Your system should have git and curl as prerequisites.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

For Windows 10, you could use Windows Package Manager Client (aka winget.exe) to install WasmEdge with a single command in your terminal.

winget install wasmedge

If you would like to install WasmEdge with its Tensorflow and image processing extensions, please run the following command. It will attempt to install Tensorflow and image shared libraries on your system.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e all

Run the following command to make the installed binary available in the current session.

source $HOME/.wasmedge/env

That's it! You can now use WasmEdge from the CLI, or launch it from an application. To update WasmEdge to a new release, just re-run the above command to write over the old files.

Install for all users

By default, WasmEdge is installed in the $HOME/.wasmedge directory. You can install it into a system directory, such as /usr/local to make it available to all users. To specify an install directory, you can run the install.sh script with the -p flag. You will need to run the following commands as the root user or sudo since they write into system directories.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -p /usr/local

Or, with all extensions

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e all -p /usr/local

Install a specific version of WasmEdge

You could install specific versions of WasmEdge, including pre-releases or old releases by passing the -v argument to the install script. Here is an example.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e all -v 0.10.0

If you are interested in the latest builds from the HEAD of the master branch, which is basically WasmEdge's nightly builds, you can download the release package directly from our Github Action's CI artifact. Here is an example.

What's installed

After installation, you have the following directories and files. Here we assume that you installed into the $HOME/.wasmedge directory. You could also change it to /usr/local if you did a system-wide install.

If you used winget to install WasmEdge, files are located at C:\Program Files\WasmEdge.

  • The $HOME/.wasmedge/bin directory contains the WasmEdge Runtime CLI executable files. You can copy and move them around on your file system.
    • The wasmedge tool is the standard WasmEdge runtime. You can use it from the CLI. wasmedge --dir .:. app.wasm
    • The wasmedgec tool is the AOT compiler to compile a wasm file into a native so file. wasmedgec app.wasm app.so The wasmedge can then execute the so file. wasmedge --dir .:. app.so
    • The wasmedge-tensorflow, wasmedge-tensorflow-lite tools are runtimes that support the WasmEdge tensorflow SDK.
  • The $HOME/.wasmedge/lib directory contains WasmEdge shared libraries, as well as dependency libraries. They are useful for WasmEdge SDKs to launch WasmEdge programs and functions from host applications.
  • The $HOME/.wasmedge/include directory contains the WasmEdge header files. They are useful for WasmEdge SDKs.

Uninstall

To uninstall WasmEdge, you can run the following command.

bash <(curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/uninstall.sh)

If wasmedge binary is not in PATH and it wasn't installed in the default $HOME/.wasmedge folder, then you must provide the installation path.

bash <(curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/uninstall.sh) -p /path/to/parent/folder

If you wish to uninstall uninteractively, you can pass in the --quick or -q flag.

bash <(curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/uninstall.sh) -q

If a parent folder of the wasmedge binary contains .wasmedge, the folder will be considered for removal. For example, the script removes the default $HOME/.wasmedge folder altogether.

If you used winget to install WasmEdge, run the following command.

winget uninstall wasmedge

Install WasmEdge for Node.js

WasmEdge can run WebAssembly functions emebedded in Node.js applications. To install the WasmEdge module in your Node.js environment is easy. Just use the npm tool.

npm install -g wasmedge-core # Append --unsafe-perm if permission denied

To install WasmEdge with Tensorflow and other extensions.

npm install -g wasmedge-extensions # Append --unsafe-perm if permission denied

The Second State Functions is a WasmEdge-based FaaS service build on Node.js.

Use Docker for WasmEdge app development

The appdev Docker images provide a complete WasmEdge application development environment. To use it, do the following.

On x86_64 machines

$ docker pull wasmedge/appdev_x86_64:0.9.0
$ docker run --rm -v $(pwd):/app -it wasmedge/appdev_x86_64:0.9.0
(docker) #

Here is the Dockerfile and Docker Hub image.

On arm64 machines

$ docker pull wasmedge/appdev_aarch64:0.9.0
$ docker run --rm -v $(pwd):/app -it wasmedge/appdev_aarch64:0.9.0
(docker) #

Here is the Dockerfile and Docker Hub image.

The WasmEdge application development Docker image installs the following components.

  • WasmEdge CLI and shared libraries
  • WasmEdge with Tensorflow extension CLI and libraries (x86_64 only)
  • Golang
  • Rust
  • Node.js with WasmEdge addons
  • Examples in the /root/examples/ folder

Examples

Hello World. See more simple examples

$ wasmedge hello.wasm world
hello
world

Use AOT to run it much faster.

$ wasmedgec hello.wasm hello.wasm
$ wasmedge hello.wasm world
hello
world

Here are some JavaScript examples. See more

$ wasmedge --dir .:. qjs.wasm hello.js 1 2 3
Hello 1 2 3

$ wasmedge-tensorflow-lite --dir .:. qjs_tf.wasm tf_image_classify.js
label: Hot dog
confidence: 0.8941176470588236

Build and publish the appdev images

Run these commands to build and publish the appdev Docker images.

Build on an x86_64 machine

docker build -t wasmedge/appdev_x86_64:0.9.0 -f Dockerfile.appdev_x86_64 ./ 
docker image push wasmedge/appdev_x86_64:0.9.0

Build on an ARM64 / aarch64 machine

docker build -t wasmedge/appdev_aarch64:0.9.0 -f Dockerfile.appdev_aarch64 ./
docker image push wasmedge/appdev_aarch64:0.9.0

WasmEdge command line tool (CLI)

After installing WasmEdge or starting the WasmEdge app dev Docker container, there are several ways to run compiled WebAssembly programs.

wasmedge

The wasmedge binary file is a command line interface (CLI) program that runs WebAssembly bytecode programs.

  • If the WebAssembly program contains a main() function, wasmedge would execute it as a standalone program in the command mode.
  • If the WebAssembly program contains one or more public functions, wasmedge could execute individual functions in the reactor mode.

Command line options

The options and flags for the wasmedge command are as follows.

  1. (Optional) Statistics information:
    • Use --enable-time-measuring to show the execution time.
    • Use --enable-gas-measuring to show the amount of used gas.
    • Use --enable-instruction-count to display the number of executed instructions.
    • Or use --enable-all-statistics to enable all of the statistics options.
  2. (Optional) Resource limitation:
    • Use --gas-limit to limit the execution cost.
    • Use --memory-page-limit to set the limitation of pages(as size of 64 KiB) in every memory instance.
  3. (Optional) Reactor mode: use --reactor to enable reactor mode. In the reactor mode, wasmedge runs a specified function from the WebAssembly program.
    • WasmEdge will execute the function which name should be given in ARG[0].
    • If there's exported function which names _initialize, the function will be executed with the empty parameter at first.
  4. (Optional) Binding directories into WASI virtual filesystem.
    • Each directory can be specified as --dir guest_path:host_path.
  5. (Optional) Environ variables.
    • Each variable can be specified as --env NAME=VALUE.
  6. Wasm file (/path/to/wasm/file).
  7. (Optional) Arguments.
    • In reactor mode, the first argument will be the function name, and the arguments after ARG[0] will be parameters of wasm function ARG[0].
    • In command mode, the arguments will be parameters of function _start. They are also known as command line arguments for a standalone program.

Once installed, you can review and run our examples.

wasmedgec

The wasmedgec binary file is a program to compile WebAssembly bytecode programs into native machine code (i.e., the AOT compiler). The compiled machine code could be stored in the original wasm file, and the wasmedge CLI will automatically choose to execute the native machine code whenever it is available.

The options and flags for the wasmedgec are as follows.

  1. Input Wasm file(/path/to/input/wasm/file).
  2. Output file name(/path/to/output/file).
    • By default, it will generate the universal Wasm binary format.
    • Users can still generate native binary only by specifying the .so, .dylib, or .dll extensions.
# This is slow
wasmedge app.wasm

# AOT compile
wasmedgec app.wasm app.wasm

# This is now MUCH faster
wasmedge app.wasm

On Linux systems, it could generate a so shared library file, which is then executed by the wasmedge CLI.

wasmedgec app.wasm app.so
wasmedge app.so

Universal Wasm Binary Format

WasmEdge could wrap the AOT-compiled native binary into a custom section in the origin wasm file. We call this the universal wasm binary format.

The AOT-compiled wasm file is compatible with any wasm runtime. However, when this wasm file is executed by the WasmEdge runtime, WasmEdge will extract the native binary from the custom section and execute it.

Of course, the user still has the option to generate the native binary file with the wasmedgec AOT compiler. WasmEdge uses the output file extension to determine generated file format. For example, if you set the wasmedgec output file extension to .so, it will generate native binary in Linux shared library format. Otherwise, it will generate a universal wasm binary by default.

What is WasmEdge

WasmEdge is a lightweight, high-performance, and extensible WebAssembly runtime for cloud native, edge, and decentralized applications. It powers serverless apps, embedded functions, microservices, smart contracts, and IoT devices.

WasmEdge is a CNCF Sandbox project now.

Use cases

WasmEdge is a cloud-native WebAssembly runtime hosted by the CNCF. It is widely used in edge computing, automotive, Jamstack, serverless, SaaS, service mesh, and even blockchain applications. Featuring AOT compiler optimization, WasmEdge is one of the fastest WebAssembly runtimes on the market today.

  • Modern web apps feature rich UIs that are rendered in the browser and/or on the edge cloud. WasmEdge works with popular web UI frameworks, such as React, Vue, Yew, and Percy, to support isomorphic server-side rendering (SSR) functions on edge servers. It could also support server-side rendering of Unity3D animations and AI-generated interactive videos for web applications on the edge cloud.

  • WasmEdge provides a lighweight, secure and high-performance runtime for microservices. It is fully compatible with application service frameworks such as Dapr, and service orchestrators like Kubernetes. WasmEdge microservices can run on edge servers, and have access to distributed cache, to support both stateless and stateful business logic functions for modern web apps. Also related: Serverless function-as-a-service in public clouds.

  • Serverless SaaS functions enables users to extend and customize their SaaS experience without operating their own API callback servers. The serverless functions can be embedded into the SaaS or reside on edge servers next to the SaaS servers. Developers simply upload functions to respond to SaaS events or to connect SaaS APIs.

  • Smart device apps could embed WasmEdge as a middleware runtime to render interactive content on the UI, connect to native device drivers, and access specialized hardware features (i.e, the GPU for AI inference). The benefits of the WasmEdge runtime over native-compiled machine code include security, safety, portability, manageability, and developer productivity. WasmEdge runs on Android, OpenHarmony, and seL4 RTOS devices.

If you have any great ideas on WasmEdge, don't hesitate to open a GitHub issue to discuss together.

Rendering modern web UI on the edge

Traditional web applications follows the client-server model. In the past era of application servers, the entire UI is dynamically generated from the server. The browser is simply a thin client that displays the rendered web pages at real time. However, as the browser becomes more capable and sophisticated, the client can now take on more workload to improve application UX, performance, and security.

That gives rise to the era of Jamstack. There is now a clear separation between frontend and backend services. The frontend is a static web site (HTML + JavaScript + WebAssembly) generated from UI frameworks such as React.js, Vue.js, Yew or Percy, and the backend consists of microservices. Yet, as Jamstack gains popularity, the diversity of clients (both browsers and apps) makes it very difficult to achieve great performance across all use cases.

The solution is server-side rendering (SSR). That is to have edge servers run the "client side" UI code (ie the React generated JavaScript OR Percy generated WebAssembly), and send back the rendered HTML DOM objects to the browser. In this case, the edge server must execute the exact same code (ie JavaScript and WebAssembly) as the browser to render the UI. That is called isomorphic Jamstack applications. The WasmEdge runtime provides a lightweight, high performance, OCI complaint, and polyglot container to run all kinds of SSR functions on edge servers.

We also exploring ways to render more complex UI and interactions on WasmEdge-based edge servers, and then stream the rendered results to the client application. Potential examples include

Of course, the edge cloud could grow well beyond SSR for UI components. It could also host high-performance microservices for business logic and serverless functions. Read on to the next chapter.

Microservice runtime

The edge cloud can run application logic microservices very close to the client device.

The edge cloud has advantages such as low latency, high security, and high performance. Operationally, WasmEdge can be embedded into cloud-native infrastructure via its SDKs in C, Go and Rust. It is also an OCI compliant runtime that can be directly managed by container tools as a lightweight and high-performance alternative to Linux containers. The following application frameworks have been tested to work with WasmEdge-based microservices.

Dapr (Distributed Application Runtime)

Service mesh (work in progress)

  • Linkerd
  • MOSN
  • Envoy

Orchestration and management

Serverless functions for SaaS

WasmEdge can support customized SaaS extensions or applications using serverless functions instead of traditional network APIs. That dramatically improves SaaS users' and developers' productivity.

  • WasmEdge could be embedded into SaaS products to execute user-defined functions. In this scenario, the WasmEdge function API replaces the SaaS web API. The embedded WasmEdge functions are much faster, safer, and easier to use than RPC functions over the web.
  • Edge servers could provide WasmEdge-based containers to interact with existing SaaS or PaaS APIs without requiring the user to run his own servers (eg callback servers). The serverless API services can be co-located in the same networks as the SaaS to provide optimal performance and security.

The examples below showcase how WasmEdge-based serverless functions connect together SaaS APIs from different services, and process data flows across those SaaS APIs according each user's business logic.

Slack

Lark

It is also known as 飞书 aka the Chinese Slack. It is created by Byte Dance, the parent company of Tiktok.

Smart devices

Smart device apps could embed WasmEdge as a middleware runtime to render interactive content on the UI, connect to native device drivers, and access specialized hardware features (i.e, the GPU for AI inference). The benefits of the WasmEdge runtime over native-compiled machine code include security, safety, portability, manageability, OTA upgradability, and developer productivity. WasmEdge runs on the following device OSes.

With WasmEdge on both the device and the edge server, we can support isomorphic Server-Side Rendering (SSR) and microservices for rich-client mobile applications that is both portable and upgradable.

Features

  • One of the fastest WebAssembly VMs on the market (based on LLVM AOT)
  • WasmEdge feature extensions
    • Network sockets (Rust and JavaScript SDKs)
    • Async polling (for Rust Future and JS async)
    • Tensorflow inference (Tutorial)
    • Key value storage
    • Database connector
    • Gas meters for resource constraints
  • JavaScript support
    • ES6 module and std API support
    • Implement JS APIs in Rust (Tutorial)
    • Import C native shared library functions as JS functions (Tutorial)
  • Cloud native management and orchestration
  • Cross-platform support
    • Linux OSes dated back to 2010 for both x86 and ARM CPUs
    • Mac OS X for both x86 and m1 CPUs
    • Windows
    • Microkernel and RTOS (e.g., the highly secure seL4 microkernel)
  • Easy extensibility
    • Build customized runtimes with native functions in C or GO
  • Easy to embed into a host application
    • Embed WasmEdge functions in C, Go, Rust, Node.js and Python host applications
    • Embedded function runtime for service mesh proxies (e.g., proxy-wasm for Envoy and MOSN proxies)

WebAssembly standard extensions

WebAssembly proposals

WasmEdge supports the following WebAssembly proposals. Those proposals are likely to become official WebAssembly specifications in the future.

ProposalWasmEdge CLI flagWasmEdge C API enumerationDefault turning onInterpreter modeAOT mode
Import/Export of Mutable Globals--disable-import-export-mut-globalsWasmEdge_Proposal_ImportExportMutGlobals
Non-trapping float-to-int conversions--disable-non-trap-float-to-intWasmEdge_Proposal_NonTrapFloatToIntConversions
Sign-extension operators--disable-sign-extension-operatorsWasmEdge_Proposal_SignExtensionOperators
Multi-value--disable-multi-valueWasmEdge_Proposal_MultiValue
Reference Types--disable-reference-typesWasmEdge_Proposal_ReferenceTypes
Bulk memory operations--disable-bulk-memoryWasmEdge_Proposal_BulkMemoryOperations
Fixed-width SIMD--disable-simdWasmEdge_Proposal_SIMD
Tail call--enable-tail-callWasmEdge_Proposal_TailCall
Multiple memories--enable-multi-memoryWasmEdge_Proposal_MultiMemories
Extended Constant Expressions--enable-extended-constWasmEdge_Proposal_ExtendedConst
Threads--enable-threadsWasmEdge_Proposal_Threads

WASI proposals

WasmEdge implements the following WASI proposals.

ProposalPlatforms
Sockets
Crypto
Machine Learning (wasi-nn)x86_64 Linux (OpenVINO backend)
proxy-wasmx86_64 Linux (Interpreter only)

WasmEdge extensions

A key differentiator of WasmEdge from other WebAssembly VMs is its support for non-standard extensions. The WASI spec provides a mechanism for developers to extend WebAssembly VMs efficiently and securely. The WasmEdge team created the following WASI-like extensions based on real-world customer demands.

  • Tensorflow. Developers can write Tensorflow inference functions using a simple Rust API, and then run the function securely and at native speed inside WasmEdge.
  • Other AI frameworks. Besides Tensorflow, the Second State team is building WASI-like extensions for AI frameworks such as ONNX and Tengine for WasmEdge.
  • Image processing. WasmEdge uses native libraries to manipulate images for computer vision tasks.
  • KV Storage. The WasmEdge storage interface allows WebAssembly programs to read and write a key value store.
  • Network sockets. WasmEdge applications can access the network sockets for TCP and HTTP connections.
  • Command interface. WasmEdge enables webassembly functions execute native commands in the host operating system. It supports passing arguments, environment variables, STDIN / STDOUT pipes, and security policies for host access.
  • Ethereum. The WasmEdge Ewasm extension supports Ethereum smart contracts compiled to WebAssembly. It is a leading implementation for Ethereum flavored WebAssembly (Ewasm).
  • Substrate. The Pallet allows WasmEdge to act as an Ethereum smart contract execution engine on any Substrate based blockchains.

WasmEdge integrations

WasmEdge is a "serverless" runtime for cloud native and edge computing applications. It allows developers safely embed third-party or "native" functions into a host application or a distributed computing framework.

Embed WasmEdge into a host application

A major use case of WasmEdge is to start an VM instance from a host application. Depending on your host application's programming language, you can use WasmEdge SDKs to start and call WasmEdge functions.

However, the WebAssembly spec only supports very limited data types as input parameters and return values for the WebAssembly bytecode functions. In order to pass complex data types, such as a string of an array, as call arguments into Rust-based WasmEdge function, you should use the bindgen solution provided by the rustwasmc toolchain. We currently supports bindgen in the Node.js and in Go. We are working on supporting interface types in place of bindgen for future releases.

Use WasmEdge as a Docker-like container

WasmEdge provides an OCI compliant interface. You can use container tools, such as CRI-O, Docker Hub, and Kubernetes, to orchestrate and manage WasmEdge runtimes.

Call native host functions from WasmEdge

A key feature of WasmEdge is its extensibility. WasmEdge APIs allow developers to register "host functions" from any shared library into a WasmEdge instance, and then call these functions from the WebAssembly bytecode program.

Here is an example of a JavaScript program in WasmEdge calling a C-based host function in the underlying OS.

The host functions break the Wasm sandbox to access the underly OS or hardware. But the sandbox breaking is done with explicit permission from the system’s operator.

FAQ abut WebAssembly and WasmEdge

Q: What's the relationship between WebAssembly and Docker?

A: Check out our infographic WebAssembly vs. Docker. WebAssembly runs side by side with Docker in cloud native and edge native applications.

Q: What's the difference for Native clients (NaCl), Application runtimes, and WebAssembly?

A: We created a handy table for comparison.

NaClApplication runtimes (eg Node & Python)Docker-like containerWebAssembly
PerformanceGreatPoorOKGreat
Resource footprintGreatPoorPoorGreat
IsolationPoorOKOKGreat
SafetyPoorOKOKGreat
PortabilityPoorGreatOKGreat
SecurityPoorOKOKGreat
Language and framework choiceN/AN/AGreatOK
Ease of useOKGreatGreatOK
ManageabilityPoorPoorGreatGreat

Q: What's the difference between WebAssembly and eBPF?

A: eBPF is the bytecode format for a Linux kernel space VM that is suitable for network or security related tasks. WebAssembly is the bytecode format for a user space VM that is suited for business applications. See details here

Develop a WasmEdge app

A key value proposition of WebAssembly is that it supports multiple programming languages. WebAssembly is a "managed runtime" for many programming languages including C/C++, Rust, Go, Swift, Kotlin, AssemblyScript, Grain and even JavaScript and Python.

  • For compiled languages (e.g., C and Rust), WasmEdge WebAssembly provides a safe, secure, isolated, and containerized runtime as opposed to Native Client (NaCl).
  • For interpreted or managed languages (e.g., JavaScript and Python), WasmEdge WebAssembly provides a secure, fast, lightweight, and containerized runtime as opposed to Docker + guest OS + native interpreter.

In this chapter, we will discuss how to compile and run programs in different languages in WasmEdge.

Rust

Rust is one of the "first-class citizen" programming languages in the WebAssembly ecosystem. All WasmEdge extensions to WebAssembly also come with Rust APIs for developers. In this chapter, we will show you how to compile your Rust applications to wasm bytecode and to run in the WasmEdge runtime.

Prerequisites

You need to install Rust and WasmEdge in order to get started. You should also install the wasm32-wasi target to the Rust toolchain.

rustup target add wasm32-wasi

Hello world

The Hello world example is a standalone Rust application that can be executed by the WasmEdge CLI. Its source code is available.

The full source code for the Rust main.rs file is as follows. It echoes the command line arguments passed to this program at runtime.

use std::env;

fn main() {
  println!("hello");
  for argument in env::args().skip(1) {
    println!("{}", argument);
  }
}

Hello world: Build the WASM bytecode

cargo build --target wasm32-wasi

Hello world: Run the application from command line

We will use the wasmedge command to run the program.

$ wasmedge target/wasm32-wasi/debug/hello.wasm second state
hello
second
state

A simple function

The add example is a Rust library function that can be executed by the WasmEdge CLI in the reactor mode.

The full source code for the Rust lib.rs file is as follows. It provides a simple add() function.


#![allow(unused)]
fn main() {
#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
  return a + b;
}
}

A simple function: Build the WASM bytecode

cargo build --target wasm32-wasi

A simple function: Run the application from command line

We will use wasmedge in reactor mode to run the program. We pass the function name and its input parameters as command line arguments.

$ wasmedge --reactor target/wasm32-wasi/debug/add.wasm add 2 2
4

Pass complex call parameters

Of course, in most cases, you will not call functions using CLI arguments. Instead, you will probably need to use a language SDK from WasmEdge to call the function, pass call parameters, and receive return values. Below are some SDK examples for complex call parameters and return values.

Improve performance

To achieve native Rust performance for those applications, you could use the wasmedgec command to AOT compile the wasm program, and then run it with the wasmedge command.

$ wasmedgec hello.wasm hello.wasm

$ wasmedge hello.wasm second state
hello
second
state

For the --reactor mode,

$ wasmedgec add.wasm add.wasm

$ wasmedge --reactor add.wasm add 2 2
4

Further readings

  • Access OS services via WASI shows how the WebAssembly program can access the underlying OS services, such as file system and environment variables.
  • Tensorflow shows how to create Tensorflow-based AI inference applications for WebAssembly using the WasmEdge TensorFlow Rust SDK.
  • Simple networking socket shows how to create simple HTTP client and server applications using the WasmEdge networking socket Rust SDK.
  • Non-blocking networking socket shows how to create a high-performance non-blocking networking applications with concurrent open connections using the WasmEdge networking socket Rust SDK.
  • Server-side rendering shows how to build an interactive web app with Rust, and then render the HTML DOM UI on the server using WasmEdge. The Rust source code is compiled to WebAssembly to render the HTML DOM in the browser or on the server.
  • Command interface shows how to create native command applications for WebAssembly using the Wasmedge command interface Rust SDK.

Call Rust functions

If your Rust program has a main() function, you could compile it into a WASM bytecode file, and run it using the wasmedge CLI tool as a standalone application. However, a far more common use case is to compile a Rust function into a WASM bytecode file, and then call it from a host application. That is known as an embedded WASM function. The host application uses WasmEdge language SDKs (e.g., Go, Rust, C, Python and Node.js) to call those WASM functions compiled from Rust source code.

All the WasmEdge host language SDKs support simple function calls. However, the WASM spec only supports a few simple data types as call parameters and return values. The wasmedge-bindgen crate would transform call parameters and return values of Rust functions into simple integer types when the Rust function is compiled into WASM. For example, a string is automatically converted into two integers, a memory address and a length, which can be handled by the standard WASM spec. It is very easy to do this in Rust source code. Just annotate your function with the #[wasmedge-bindgen] macro. You can compile the annotated Rust code using the standard Rust compiler toolchain (e.g., the latest Cargo).


#![allow(unused)]
fn main() {
use wasmedge_bindgen::*;
use wasmedge_bindgen_macro::*;

#[wasmedge_bindgen]
pub fn say(s: String) -> Result<Vec<u8>, String> {
  let r = String::from("hello ");
  return Ok((r + s.as_str()).as_bytes().to_vec());
}
}

Of course, once the above Rust code is compiled into WASM, the function say() no longer takes the String parameter nor returns the Vec<u8>. So, the caller (i.e., the host application) must also deconstruct the call parameter into the memory pointer first before the call, and assemble the return value from the memory pointer after the call. These actions can be handled automagically by the WasmEdge language SDKs. To see a complete example, including the Rust WASM function and the Go host application, check out our tutorial in the Go SDK documentation.

A complete wasmedge-bindgen example in Rust (WASM) and Go (host)

Of course, the developer could choose to do wasmedge-bindgen's work by hand and pass a memory pointer directly. If you are interested in this approach to call Rust compiled WASM functions, check out our examples in the Go SDK.

Access OS services

The WASI (WebAssembly Systems Interface) standard is designed to allow WebAssembly applications to access operating system services. The wasm32-wasi target in the Rust compiler supports WASI. In this section, we will use an example project to show how to use Rust standard APIs to access operating system services.

Random numbers

The WebAssembly VM is a pure software construct. It does not have a hardware entropy source for random numbers. That's why WASI defines a function for WebAssembly programs to call its host operating system to get a random seed. As a Rust developer, all you need is to use the popular (de facto standard) rand and/or getrandom crates. With the wasm32-wasi compiler backend, these crates generate the correct WASI calls in the WebAssembly bytecode. The Cargo.toml dependencies are as follows.

[dependencies]
rand = "0.7.3"
getrandom = "0.1.14"

The Rust code to get random number from WebAssembly is this.


#![allow(unused)]
fn main() {
use rand::prelude::*;

pub fn get_random_i32() -> i32 {
  let x: i32 = random();
  return x;
}

pub fn get_random_bytes() -> Vec<u8> {
  let mut rng = thread_rng();
  let mut arr = [0u8; 128];
  rng.fill(&mut arr[..]);
  return arr.to_vec();
}
}

Printing and debugging from Rust

The Rust println! marco just works in WASI. The statements print to the STDOUT of the process that runs the WasmEdge.


#![allow(unused)]
fn main() {
pub fn echo(content: &str) -> String {
  println!("Printed from wasi: {}", content);
  return content.to_string();
}
}

Arguments and environment variables

It is possible to pass CLI arguments to and access OS environment variables in a WasmEdge application. They are just env::args() and env::vars() arrays in Rust.


#![allow(unused)]
fn main() {
use std::env;

pub fn print_env() {
  println!("The env vars are as follows.");
  for (key, value) in env::vars() {
    println!("{}: {}", key, value);
  }

  println!("The args are as follows.");
  for argument in env::args() {
    println!("{}", argument);
  }
}
}

Reading and writing files

WASI allows your Rust functions to access the host computer's file system through the standard Rust std::fs API. In the Rust program, you operate on files through a relative path. The relative path's root is specified when you start the WasmEdge runtime.


#![allow(unused)]
fn main() {
use std::fs;
use std::fs::File;
use std::io::{Write, Read};

pub fn create_file(path: &str, content: &str) {
  let mut output = File::create(path).unwrap();
  output.write_all(content.as_bytes()).unwrap();
}

pub fn read_file(path: &str) -> String {
  let mut f = File::open(path).unwrap();
  let mut s = String::new();
  match f.read_to_string(&mut s) {
    Ok(_) => s,
    Err(e) => e.to_string(),
  }
}

pub fn del_file(path: &str) {
  fs::remove_file(path).expect("Unable to delete");
}
}

A main() app

With a main() function, the Rust program can be compiled into a standalone WebAssembly program.

fn main() {
  println!("Random number: {}", get_random_i32());
  println!("Random bytes: {:?}", get_random_bytes());
  println!("{}", echo("This is from a main function"));
  print_env();
  create_file("tmp.txt", "This is in a file");
  println!("File content is {}", read_file("tmp.txt"));
  del_file("tmp.txt");
}

Use the command below to compile the Rust project.

cargo build --target wasm32-wasi

To run it in wasmedge, do the following. The --dir option maps the current directory of the command shell to the file system current directory inside the WebAssembly app.

$ wasmedge --dir .:. target/wasm32-wasi/debug/wasi.wasm hello
Random number: -68634548
Random bytes: [87, 117, 194, 122, 74, 189, 29, 1, 113, 26, 90, 6, 151, 20, 11, 169, 131, 212, 161, 220, 216, 190, 77, 234, 30, 10, 159, 7, 14, 89, 81, 111, 247, 136, 39, 195, 83, 90, 153, 225, 66, 16, 150, 217, 137, 172, 216, 203, 251, 37, 4, 27, 32, 57, 76, 237, 99, 147, 24, 175, 208, 157, 3, 220, 46, 224, 199, 153, 144, 96, 120, 89, 160, 38, 171, 239, 87, 218, 41, 184, 220, 78, 157, 57, 229, 198, 222, 72, 219, 118, 237, 27, 229, 28, 51, 116, 88, 101, 40, 139, 160, 51, 156, 102, 66, 233, 101, 50, 131, 9, 253, 186, 73, 148, 85, 36, 155, 254, 168, 202, 23, 96, 181, 99, 120, 136, 28, 147]
This is from a main function
The env vars are as follows.
... ...
The args are as follows.
target/wasm32-wasi/debug/wasi.wasm
hello
File content is This is in a file

Functions

As we have seen, you can create WebAssembly functions in a Rust lib.rs project. You can also use WASI functions in those functions. However, an important caveat is that, without a main() function, you will need to explicitly call a helper function to initialize environment for WASI functions to work properly. In the Rust program, add a helper crate in Cargo.toml so that the WASI initialization code can be applied to your exported public library functions.

[dependencies]
... ...
wasmedge-wasi-helper = "=0.2.0"

In the Rust function, we need to call _initialize() before we access any arguments and environment variables or operate any files.


#![allow(unused)]
fn main() {
pub fn print_env() -> i32 {
  _initialize();
  ... ...
}

pub fn create_file(path: &str, content: &str) -> String {
  _initialize();
  ... ...
}

pub fn read_file(path: &str) -> String {
  _initialize();
  ... ...
}

pub fn del_file(path: &str) -> String {
  _initialize();
  ... ...
}
}

Tensorflow

AI inference is a computationally intensive task that could benefit greatly from the speed of Rust and WebAssembly. However, the standard WebAssembly sandbox provides very limited access to the native OS and hardware, such as multi-core CPUs, GPU and specialized AI inference chips. It is not ideal for the AI workload.

The popular WebAssembly System Interface (WASI) provides a design pattern for sandboxed WebAssembly programs to securely access native host functions. The WasmEdge Runtime extends the WASI model to support access to native Tensorflow libraries from WebAssembly programs. The WasmEdge Tensorflow Rust SDK provides the security, portability, and ease-of-use of WebAssembly and native speed for Tensorflow.

If you are not familiar with Rust, you can try our experimental AI inference DSL or try our JavaScript examples.

Table of contents

A Rust example

Prerequisite

You need to install WasmEdge and Rust.

Build

Check out the example source code.

git clone https://github.com/second-state/wasm-learning/
cd cli/tflite

Use Rust Cargo to build the WebAssembly target.

rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

Run

The wasmedge-tensorflow-lite utility is the WasmEdge build that includes the Tensorflow and Tensorflow Lite extensions.

$ wasmedge-tensorflow-lite target/wasm32-wasi/release/classify.wasm < grace_hopper.jpg
It is very likely a <a href='https://www.google.com/search?q=military uniform'>military uniform</a> in the picture

Make it run faster

To make Tensorflow inference run much faster, you could AOT compile it down to machine native code, and then use WasmEdge sandbox to run the native code.

$ wasmedgec target/wasm32-wasi/release/classify.wasm classify.wasm
$ wasmedge-tensorflow-lite classify.wasm < grace_hopper.jpg
It is very likely a <a href='https://www.google.com/search?q=military uniform'>military uniform</a> in the picture

Code walkthrough

It is fairly straightforward to use the WasmEdge Tensorflow API. You can see the entire source code in main.rs.

First, it reads the trained TFLite model file (ImageNet) and its label file. The label file maps numeric output from the model to English names for the classified objects.


#![allow(unused)]
fn main() {
  let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
  let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");
}

Next, it reads the image from STDIN and converts it to the size and RGB pixel arrangement required by the Tensorflow Lite model.


#![allow(unused)]
fn main() {
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);
}

Then, the program runs the TFLite model with its required input tensor (i.e., the flat image in this case), and receives the model output. In this case, the model output is an array of numbers. Each number corresponds to the probability of an object name in the label text file.


#![allow(unused)]
fn main() {
  let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
  session.add_input("input", &flat_img, &[1, 224, 224, 3])
         .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");
}

Let's find the object with the highest probability, and then look up the name in the labels file.


#![allow(unused)]
fn main() {
  let mut i = 0;
  let mut max_index: i32 = -1;
  let mut max_value: u8 = 0;
  while i < res_vec.len() {
    let cur = res_vec[i];
    if cur > max_value {
      max_value = cur;
      max_index = i as i32;
    }
    i += 1;
  }

  let mut label_lines = labels.lines();
  for _i in 0..max_index {
    label_lines.next();
  }
}

Finally, it prints the result to STDOUT.


#![allow(unused)]
fn main() {
  let class_name = label_lines.next().unwrap().to_string();
  if max_value > 50 {
    println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
  } else {
    println!("It does not appears to be any food item in the picture.");
  }
}

Deployment options

All the tutorials below use the WasmEdge Rust API for Tensorflow to create AI inference functions. Those Rust functions are then compiled to WebAssembly and deployed together with WasmEdge on the cloud.

Serverless functions

The following tutorials showcase how to deploy WebAssembly programs (written in Rust) on public cloud serverless platforms. The WasmEdge Runtime runs inside a Docker container on those platforms. Each serverless platform provides APIs to get data into and out of the WasmEdge runtime through STDIN and STDOUT.

Second Sate FaaS and Node.js

The following tutorials showcase how to deploy WebAssembly functions (written in Rust) on the Second State FaaS. Since the FaaS service is running on Node.js, you can follow the same tutorials for running those functions in your own Node.js server.

Service mesh

The following tutorials showcase how to deploy WebAssembly functions and programs (written in Rust) as sidecar microservices.

  • The Dapr template shows how to build and deploy Dapr sidecars in Go and Rust languages. The sidecars then use the WasmEdge SDK to start WebAssembly programs to process workloads to the microservices.

Data streaming framework

The following tutorials showcase how to deploy WebAssembly functions (written in Rust) as embedded handler functions in data streaming frameworks for AIoT.

  • The YoMo template starts the WasmEdge Runtime to process image data as the data streams in from a camera in a smart factory.

Simple networking sockets

The wasmedge_wasi_socket crate enables Rust developers to create networking applications and compile them into WebAssembly for WasmEdge Runtime. One of the key features of WasmEdge is that it supports non-blocking sockets. That allows even a single threaded WASM application to handle concurrent network requests. For example, while the program is waiting for data to stream in from one connection, it can start or handle another connection.

In this chapter, we will start with simple HTTP client and server examples. Then in the next chapter, we will cover the more complex non-blocking examples.

An HTTP client example

The source code for the HTTP client is available as follows.

use wasmedge_http_req::request;

fn main() {
  let mut writer = Vec::new(); //container for body of a response
  let res = request::get("http://127.0.0.1:1234/get", &mut writer).unwrap();

  println!("GET");
  println!("Status: {} {}", res.status_code(), res.reason());
  println!("Headers {}", res.headers());
  println!("{}", String::from_utf8_lossy(&writer));

  let mut writer = Vec::new(); //container for body of a response
  const BODY: &[u8; 27] = b"field1=value1&field2=value2";
  // let res = request::post("https://httpbin.org/post", BODY, &mut writer).unwrap();
  // no https , no dns
  let res = request::post("http://127.0.0.1:1234/post", BODY, &mut writer).unwrap();

  println!("POST");
  println!("Status: {} {}", res.status_code(), res.reason());
  println!("Headers {}", res.headers());
  println!("{}", String::from_utf8_lossy(&writer));
}

The following command compiles the Rust program.

cargo build --target wasm32-wasi --release

The following command runs the application in WasmEdge.

wasmedge target/wasm32-wasi/release/http_client.wasm

An HTTP server example

The source code for the HTTP server application is available as follows.

use bytecodec::DecodeExt;
use httpcodec::{HttpVersion, ReasonPhrase, Request, RequestDecoder, Response, StatusCode};
use std::io::{Read, Write};
#[cfg(feature = "std")]
use std::net::{Shutdown, TcpListener, TcpStream};
#[cfg(not(feature = "std"))]
use wasmedge_wasi_socket::{Shutdown, TcpListener, TcpStream};

fn handle_http(req: Request<String>) -> bytecodec::Result<Response<String>> {
  Ok(Response::new(
    HttpVersion::V1_0,
    StatusCode::new(200)?,
    ReasonPhrase::new("")?,
    format!("echo: {}", req.body()),
  ))
}

fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
  let mut buff = [0u8; 1024];
  let mut data = Vec::new();

  loop {
    let n = stream.read(&mut buff)?;
    data.extend_from_slice(&buff[0..n]);
    if n < 1024 {
      break;
    }
  }

  let mut decoder =
    RequestDecoder::<httpcodec::BodyDecoder<bytecodec::bytes::Utf8Decoder>>::default();

  let req = match decoder.decode_from_bytes(data.as_slice()) {
    Ok(req) => handle_http(req),
    Err(e) => Err(e),
  };

  let r = match req {
    Ok(r) => r,
    Err(e) => {
      let err = format!("{:?}", e);
      Response::new(
        HttpVersion::V1_0,
        StatusCode::new(500).unwrap(),
        ReasonPhrase::new(err.as_str()).unwrap(),
        err.clone(),
      )
    }
  };

  let write_buf = r.to_string();
  stream.write(write_buf.as_bytes())?;
  stream.shutdown(Shutdown::Both)?;
  Ok(())
}

fn main() -> std::io::Result<()> {
  let port = std::env::var("PORT").unwrap_or(1234.to_string());
  println!("new connection at {}", port);
  let listener = TcpListener::bind(format!("0.0.0.0:{}", port))?;
  loop {
    let _ = handle_client(listener.accept()?.0);
  }
}

The following command compiles the Rust program.

cargo build --target wasm32-wasi --release

The following command runs the application in WasmEdge.

$ wasmedge target/wasm32-wasi/release/http_server.wasm
new connection at 1234

To test the HTTP server, you can submit a HTTP request to it via curl.

$ curl -d "name=WasmEdge" -X POST http://127.0.0.1:1234
echo: name=WasmEdge

Non-blocking networking sockets

While the simple HTTP connections from the previous chapter are easy to implement, they are not ready for production use. If the program can only have one connection open at a time (e.g., blocking), the fast CPU would be waiting for the slow network. Non-blocking I/O means that the application program can keep multiple connections open at the same time, and process data in and out of those connections as they come in. The program can either alternatingly poll those open connections or wait for incoming data to trigger async functions. That allows I/O intensive programs to run much faster even in a single-threaded environment. In this chapter, we will cover both polling and async programming models.

A non-blocking HTTP client example

The source code for a non-blocking HTTP client application is available. The following main() function starts two HTTP connections. The program keeps both connections open, and alternatingly checks for incoming data from them. In another word, the two connections are not blocking each other. Their data are handled concurrently (or alternatingly) as the data comes in.

use httparse::{Response, EMPTY_HEADER};
use std::io::{self, Read, Write};
use std::str::from_utf8;
use wasmedge_wasi_socket::TcpStream;

fn main() {
    let req = "GET / HTTP/1.0\n\n";
    let mut first_connection = TcpStream::connect("127.0.0.1:80").unwrap();
    first_connection.set_nonblocking(true).unwrap();
    first_connection.write_all(req.as_bytes()).unwrap();

    let mut second_connection = TcpStream::connect("127.0.0.1:80").unwrap();
    second_connection.set_nonblocking(true).unwrap();
    second_connection.write_all(req.as_bytes()).unwrap();

    let mut first_buf = vec![0; 4096];
    let mut first_bytes_read = 0;
    let mut second_buf = vec![0; 4096];
    let mut second_bytes_read = 0;

    loop {
        let mut first_complete = false;
        let mut second_complete = false;
        if !first_complete {
            match read_data(&mut first_connection, &mut first_buf, first_bytes_read) {
                Ok((bytes_read, false)) => {
                    first_bytes_read = bytes_read;
                }
                Ok((bytes_read, true)) => {
                    println!("First connection completed");
                    if bytes_read != 0 {
                        parse_data(&first_buf, bytes_read);
                    }
                    first_complete = true;
                }
                Err(e) => {
                    println!("First connection error: {}", e);
                    first_complete = true;
                }
            }
        }
        if !second_complete {
            match read_data(&mut second_connection, &mut second_buf, second_bytes_read) {
                Ok((bytes_read, false)) => {
                    second_bytes_read = bytes_read;
                }
                Ok((bytes_read, true)) => {
                    println!("Second connection completed");
                    if bytes_read != 0 {
                        parse_data(&second_buf, bytes_read);
                    }
                    second_complete = true;
                }
                Err(e) => {
                    println!("Second connection error: {}", e);
                    second_complete = true;
                }
            }
        }
        if first_complete && second_complete {
            break;
        }
    }
}

The following command compiles the Rust program.

cargo build --target wasm32-wasi --release

The following command runs the application in WasmEdge.


#![allow(unused)]
fn main() {
wasmedge target/wasm32-wasi/release/nonblock_http_client.wasm
}

A non-blocking HTTP server example

The source code for a non-blocking HTTP server application is available. The following main() function starts an HTTP server. It receives events from multiple open connections, and processes those events as they are received by calling the async handler functions registered to each connection. This server can process events from multiple open connections concurrently.

fn main() -> std::io::Result<()> {
    let mut poll = Poll::new();
    let server = TcpListener::bind("127.0.0.1:1234", true)?;
    println!("Listening on 127.0.0.1:1234");
    let mut connections = HashMap::new();
    let mut handlers = HashMap::new();
    const SERVER: Token = Token(0);
    let mut unique_token = Token(SERVER.0 + 1);

    poll.register(&server, SERVER, Interest::Read);

    loop {
        let events = poll.poll().unwrap();

        for event in events {
            match event.token {
                SERVER => loop {
                    let (connection, address) = match server.accept(FDFLAGS_NONBLOCK) {
                        Ok((connection, address)) => (connection, address),
                        Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break,
                        Err(e) => panic!("accept error: {}", e),
                    };

                    println!("Accepted connection from: {}", address);

                    let token = unique_token.add();
                    poll.register(&connection, token, Interest::Read);
                    connections.insert(token, connection);
                },
                token => {
                    let done = if let Some(connection) = connections.get_mut(&token) {
                        let handler = match handlers.get_mut(&token) {
                            Some(handler) => handler,
                            None => {
                                let handler = Handler::new();
                                handlers.insert(token, handler);
                                handlers.get_mut(&token).unwrap()
                            }
                        };
                        handle_connection(&mut poll, connection, handler, &event)?
                    } else {
                        false
                    };
                    if done {
                        if let Some(connection) = connections.remove(&token) {
                            connection.shutdown(Shutdown::Both)?;
                            poll.unregister(&connection);
                            handlers.remove(&token);
                        }
                    }
                }
            }
        }
    }
}

The handle_connection() function processes the data from those open connections. In this case, it just writes the request body into the response. It is also done asynchronously -- meaning that the handle_connection() function creates an event for the response, and puts it in the queue. The main application loop processes the event and sends the response when it is waiting for data from other connections.


#![allow(unused)]
fn main() {
fn handle_connection(
    poll: &mut Poll,
    connection: &mut TcpStream,
    handler: &mut Handler,
    event: &Event,
) -> io::Result<bool> {
    if event.is_readable() {
        let mut connection_closed = false;
        let mut received_data = vec![0; 4096];
        let mut bytes_read = 0;
        loop {
            match connection.read(&mut received_data[bytes_read..]) {
                Ok(0) => {
                    connection_closed = true;
                    break;
                }
                Ok(n) => {
                    bytes_read += n;
                    if bytes_read == received_data.len() {
                        received_data.resize(received_data.len() + 1024, 0);
                    }
                }
                Err(ref err) if would_block(err) => {
                    if bytes_read != 0 {
                        let received_data = &received_data[..bytes_read];
                        let mut bs: parsed::stream::ByteStream =
                            match String::from_utf8(received_data.to_vec()) {
                                Ok(s) => s,
                                Err(_) => {
                                    continue;
                                }
                            }
                            .into();
                        let req = match parsed::http::parse_http_request(&mut bs) {
                            Some(req) => req,
                            None => {
                                break;
                            }
                        };
                        for header in req.headers.iter() {
                            if header.name.eq("Conntent-Length") {
                                let content_length = header.value.parse::<usize>().unwrap();
                                if content_length > received_data.len() {
                                    return Ok(true);
                                }
                            }
                        }
                        println!(
                            "{:?} request: {:?} {:?}",
                            connection.peer_addr().unwrap(),
                            req.method,
                            req.path
                        );
                        let res = Response {
                            protocol: "HTTP/1.1".to_string(),
                            code: 200,
                            message: "OK".to_string(),
                            headers: vec![
                                Header {
                                    name: "Content-Length".to_string(),
                                    value: req.content.len().to_string(),
                                },
                                Header {
                                    name: "Connection".to_string(),
                                    value: "close".to_string(),
                                },
                            ],
                            content: req.content,
                        };

                        handler.response = Some(res.into());

                        poll.reregister(connection, event.token, Interest::Write);
                        break;
                    } else {
                        println!("Empty request");
                        return Ok(true);
                    }
                }
                Err(ref err) if interrupted(err) => continue,
                Err(err) => return Err(err),
            }
        }

        if connection_closed {
            println!("Connection closed");
            return Ok(true);
        }
    }

    if event.is_writable() && handler.response.is_some() {
        let resp = handler.response.clone().unwrap();
        match connection.write(resp.as_bytes()) {
            Ok(n) if n < resp.len() => return Err(io::ErrorKind::WriteZero.into()),
            Ok(_) => {
                return Ok(true);
            }
            Err(ref err) if would_block(err) => {}
            Err(ref err) if interrupted(err) => {
                return handle_connection(poll, connection, handler, event)
            }
            Err(err) => return Err(err),
        }
    }

    Ok(false)
}
}

The following command compiles the Rust program.

cargo build --target wasm32-wasi --release

The following command runs the application in WasmEdge.

$ wasmedge target/wasm32-wasi/release/poll_http_server.wasm
new connection at 1234

To test the HTTP server, you can submit a HTTP request to it via curl.

$ curl -d "name=WasmEdge" -X POST http://127.0.0.1:1234
echo: name=WasmEdge

Server-side rendering

Frontend web frameworks allow developers to create web apps in a high level language and component model. The web app is built into a static web site to be rendered in the browser. While many frontend web frameworks are based on JavaScript, such as React and Vue, Rust-based frameworks are also emerging as the Rust language gains traction among developers. Those web frameworks render the HTML DOM UI using the WebAssembly, which is compiled from Rust source code. They use wasm-bindgen to tie the Rust to the HTML DOM. While all of these frameworks send .wasm files to the browser to render the UI on the client-side, some provide the additional choice for Server-side rendering. That is to run the WebAssembly code and build the HTML DOM UI on the server, and stream the HTML content to the browser for faster performance and startup time on slow devices and networks.

If you are interested in JavaScript-based Jamstack and SSR frameworks, such as React, please checkout our JavaScript SSR chapter.

This article will explore how to render the web UI on the server using WasmEdge. We pick Percy as our framework because it is relatively mature in SSR and Hydration. Percy already provides an example for SSR. It's highly recommended to read it first to understand how it works. The default SSR setup with Percy utilizes a native Rust web server. The Rust code is compiled to machine native code for the server. However, in order to host user applications on the server, we need a sandbox. While we could run native code inside a Linux container (Docker), a far more efficient (and safer) approach is to run the compiled code in a WebAssembly VM on the server, especially considerring the rendering code is already compiled into WebAssembly.

Now, let's go through the steps to run a Percy SSR service in a WasmEdge server.

Assuming we are in the examples/isomorphic directory, make a new crate beside the existing server.

cargo new server-wasmedge

You'll receive a warning to let you put the new crate into the workspace, so insert below into members of [workspace]. The file is ../../Cargo.toml.

"examples/isomorphic/server-wasmedge"

With the file open, put these two lines in the bottom:

[patch.crates-io]
wasm-bindgen = { git = "https://github.com/KernelErr/wasm-bindgen.git", branch = "wasi-compat" }

Why do we need a forked wasm-bindgen? That is because wasm-bindgen is the required glue between Rust and HTML in the browser. On the server, however, we need to build the Rust code to the wasm32-wasi target, which is incompatible with wasm-bindgen. Our forked wasm-bindgen has conditional configs that removes browser-specific code in the generated .wasm file for the wasm32-wasi target.

Then replace the crate's Cargo.toml with following content.

[package]
name = "isomorphic-server-wasmedge"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmedge_wasi_socket = "0"
querystring = "1.1.0"
parsed = { version = "0.3", features = ["http"] }
anyhow = "1"
serde = { version = "1.0", features = ["derive"] }
isomorphic-app = { path = "../app" } 

The wasmedge_wasi_socket crate is the socket API of WasmEdge. This project is under development. Next copy the index.html file into the crate's root.

cp server/src/index.html server-wasmedge/src/

Then let's create some Rust code to start a web service in WasmEdge! The main.rs program listens to the request and sends the response via the stream.

use std::io::Write;
use wasmedge_wasi_socket::{Shutdown, TcpListener};

mod handler;
mod mime;
mod response;

fn main() {
    let server = TcpListener::bind("127.0.0.1:3000", false).unwrap();
    println!("Server listening on 127.0.0.1:3000");

    // Simple single thread HTTP server
    // For server with Pool support, see https://github.com/second-state/wasmedge_wasi_socket/tree/main/examples/poll_http_server
    loop {
        let (mut stream, addr) = server.accept(0).unwrap();
        println!("Accepted connection from {}", addr);
        match handler::handle_req(&mut stream, addr) {
            Ok((res, binary)) => {
                let res: String = res.into();
                let bytes = res.as_bytes();
                stream.write_all(bytes).unwrap();
                if let Some(binary) = binary {
                    stream.write_all(&binary).unwrap();
                }
            }
            Err(e) => {
                println!("Error: {:?}", e);
            }
        };
        stream.shutdown(Shutdown::Both).unwrap();
    }
}

The handler.rs parses the received data to the path and query objects and return the corresponding response.


#![allow(unused)]
fn main() {
use crate::response;
use anyhow::Result;
use parsed::http::Response;
use std::io::Read;
use wasmedge_wasi_socket::{SocketAddr, TcpStream};

pub fn handle_req(stream: &mut TcpStream, addr: SocketAddr) -> Result<(Response, Option<Vec<u8>>)> {
    let mut buf = [0u8; 1024];
    let mut received_data: Vec<u8> = Vec::new();

    loop {
        let n = stream.read(&mut buf)?;
        received_data.extend_from_slice(&buf[..n]);
        if n < 1024 {
            break;
        }
    }

    let mut bs: parsed::stream::ByteStream = match String::from_utf8(received_data) {
        Ok(s) => s.into(),
        Err(_) => return Ok((response::bad_request(), None)),
    };

    let req = match parsed::http::parse_http_request(&mut bs) {
        Some(req) => req,
        None => return Ok((response::bad_request(), None)),
    };

    println!("{:?} request: {:?} {:?}", addr, req.method, req.path);

    let mut path_split = req.path.split("?");
    let path = path_split.next().unwrap_or("/");
    let query_str = path_split.next().unwrap_or("");
    let query = querystring::querify(&query_str);
    let mut init_count: Option<u32> = None;
    for (k, v) in query {
        if k.eq("init") {
            match v.parse::<u32>() {
                Ok(v) => init_count = Some(v),
                Err(_) => return Ok((response::bad_request(), None)),
            }
        }
    }

    let (res, binary) = if path.starts_with("/static") {
        response::file(&path)
    } else {
        // render page
        response::ssr(&path, init_count)
    }
    .unwrap_or_else(|_| response::internal_error());

    Ok((res, binary))
}
}

The response.rs program packs the response object for static assets and for server rendered content. For the latter, you could see that SSR happens at app.render().to_string(), the result string is put into HTML by replacing the placeholder text.


#![allow(unused)]
fn main() {
use crate::mime::MimeType;
use anyhow::Result;
use parsed::http::{Header, Response};
use std::fs::{read};
use std::path::Path;
use isomorphic_app::App;

const HTML_PLACEHOLDER: &str = "#HTML_INSERTED_HERE_BY_SERVER#";
const STATE_PLACEHOLDER: &str = "#INITIAL_STATE_JSON#";

pub fn ssr(path: &str, init: Option<u32>) -> Result<(Response, Option<Vec<u8>>)> {
    let html = format!("{}", include_str!("./index.html"));

    let app = App::new(init.unwrap_or(1001), path.to_string());
    let state = app.store.borrow();

    let html = html.replace(HTML_PLACEHOLDER, &app.render().to_string());
    let html = html.replace(STATE_PLACEHOLDER, &state.to_json());

    Ok((Response {
        protocol: "HTTP/1.0".to_string(),
        code: 200,
        message: "OK".to_string(),
        headers: vec![
            Header {
                name: "content-type".to_string(),
                value: MimeType::from_ext("html").get(),
            },
            Header {
                name: "content-length".to_string(),
                value: html.len().to_string(),
            },
        ],
        content: html.into_bytes(),
    }, None))
}

/// Get raw file content
pub fn file(path: &str) -> Result<(Response, Option<Vec<u8>>)> {
    let path = Path::new(&path);
    if path.exists() {
        let content_type: MimeType = match path.extension() {
            Some(ext) => MimeType::from_ext(ext.to_str().get_or_insert("")),
            None => MimeType::from_ext(""),
        };
        let content = read(path)?;

        Ok((Response {
            protocol: "HTTP/1.0".to_string(),
            code: 200,
            message: "OK".to_string(),
            headers: vec![
                Header {
                    name: "content-type".to_string(),
                    value: content_type.get(),
                },
                Header {
                    name: "content-length".to_string(),
                    value: content.len().to_string(),
                },
            ],
            content: vec![],
        }, Some(content)))
    } else {
        Ok((Response {
            protocol: "HTTP/1.0".to_string(),
            code: 404,
            message: "Not Found".to_string(),
            headers: vec![],
            content: vec![],
        }, None))
    }
}

/// Bad Request
pub fn bad_request() -> Response {
    Response {
        protocol: "HTTP/1.0".to_string(),
        code: 400,
        message: "Bad Request".to_string(),
        headers: vec![],
        content: vec![],
    }
}

/// Internal Server Error
pub fn internal_error() -> (Response, Option<Vec<u8>>) {
    (Response {
        protocol: "HTTP/1.0".to_owned(),
        code: 500,
        message: "Internal Server Error".to_owned(),
        headers: vec![],
        content: vec![],
    }, None)
}
}

The mime.rs program is a map for assets' extension name and the Mime type.


#![allow(unused)]
fn main() {
pub struct MimeType {
    pub r#type: String,
}

impl MimeType {
    pub fn new(r#type: &str) -> Self {
        MimeType {
            r#type: r#type.to_string(),
        }
    }

    pub fn from_ext(ext: &str) -> Self {
        match ext {
            "html" => MimeType::new("text/html"),
            "css" => MimeType::new("text/css"),
            "map" => MimeType::new("application/json"),
            "js" => MimeType::new("application/javascript"),
            "json" => MimeType::new("application/json"),
            "svg" => MimeType::new("image/svg+xml"),
            "wasm" => MimeType::new("application/wasm"),
            _ => MimeType::new("text/plain"),
        }
    }

    pub fn get(self) -> String {
        self.r#type
    }
}
}

That's it! Now let's build and run the web application. If you have tested the original example, you probably have already built the client WebAssembly.

cd client
./build-wasm.sh

Next, build and run the server.

cd ../server-wasmedge
cargo build --target wasm32-wasi
OUTPUT_CSS="$(pwd)/../client/build/app.css" wasmedge --dir /static:../client/build ../../../target/wasm32-wasi/debug/isomorphic-server-wasmedge.wasm

Navigate to http://127.0.0.1:3000 and you will see the web application in action.

Furthermore, you can place all the steps into a shell script ../start-wasmedge.sh.

#!/bin/bash

cd $(dirname $0)

cd ./client

./build-wasm.sh

cd ../server-wasmedge

OUTPUT_CSS="$(pwd)/../client/build/app.css" cargo run -p isomorphic-server-wasmedge

Add the following to the .cargo/config.toml file.

[build]
target = "wasm32-wasi"

[target.wasm32-wasi]
runner = "wasmedge --dir /static:../client/build" 

After that, a single CLI command ./start-wasmedge.sh would perform all the tasks to build and run the web application!

We forked the Percy repository and made a ready-to-build server-wasmedge example project for you. Happy coding!

Command interface

WASI enables WebAssembly programs to call standard library functions in the host operating system. It does so through a fine-grained security model known as “capability-based security”. The WebAssembly VM owner can grant access to host system resources when the VM starts up. The program cannot access any resources (e.g., file folders) that are not explicitly allowed.

Now, why limit ourselves to standard library functions? The same approach can be used to call just any host functions from WebAssembly. WasmEdge provides a WASI-like extension to access any command line programs in the host operating system.

The command line program can

  • Take input via command line arguments, as well as the STDIN stream.
  • Return value and data via the STDOUT stream.

Application developers for WasmEdge can use our Rust interface crate to access this functionality. In Cargo.toml, make sure that you have this dependency.

[dependencies]
rust_process_interface_library = "0.1.3"

In the Rust application, you can now use the API methods to start a new process for the operating system command program, pass in arguments via the arg() method as well as via the STDIN, and receives the return values via the STDOUT.


#![allow(unused)]
fn main() {
let mut cmd = Command::new("http_proxy");

cmd.arg("post")
   .arg("https://api.sendgrid.com/v3/mail/send")
   .arg(auth_header);  
cmd.stdin_u8vec(payload.to_string().as_bytes());

let out = cmd.output();
}

The Rust function is then compiled into WebAssembly and can run in the WasmEdge.

JavaScript

WebAssembly started as a "JavaScript alternative for browsers". The idea is to run high-performance applications compiled from languages like C/C++ or Rust safely in browsers. In the browser, WebAssembly runs side by side with JavaScript.

As WebAssembly is increasingly used in the cloud, it is now a universal runtime for cloud-native applications. Compared with Linux containers, WebAssembly runtimes achieve higher performance with lower resource consumption.

In cloud-native use cases, developers often want to use JavaScript to write business applications. That means we must now support JavaScript in WebAssembly. Furthermore, we should support calling C/C++ or Rust functions from JavaScript in a WebAssembly runtime to take advantage of WebAssembly's computational efficiency. The WasmEdge WebAssembly runtime allows you to do exactly that.

javascript

In this section, we will demonstrate how to run and enhance JavaScript in WasmEdge.

  • Getting started demonstrates how to run simple JavaScript programs in WasmEdge.
  • Networking sockets shows how to create non-blocking (async) HTTP client and server applications using the WasmEdge networking extension and its JavaScript API.
  • Fetch shows how to use the popular fetch API to fetch content across the network asynchronously.
  • TensorFlow shows how to use WasmEdge's TensorFlow extension from its JavaScript API.
  • React SSR shows example React SSR applications, including streaming SSR support.
  • ES6 modules shows how to incorporate ES6 modules in WasmEdge.
  • Node.js and NPM modules shows how to incorporate NPM modules in WasmEdge.
  • Built-in modules shows how to add JavaScript functions into the WasmEdge runtime as built-in API functions.
  • Use Rust to implement JS API discusses how to use Rust to implement and support a JavaScript API.
  • Node.js compatibility demonstrates the ability to use Node.js APIs in WasmEdge QuickJS applications.

A note on v8

Now, the choice of QuickJS as our JavaScript engine might raise the question of performance. Isn't QuickJS a lot slower than v8 due to a lack of JIT support? Yes, but ...

First of all, QuickJS is a lot smaller than v8. In fact, it only takes 1/40 (or 2.5%) of the runtime resources v8 consumes. You can run a lot more QuickJS functions than v8 functions on a single physical machine.

Second, for most business logic applications, raw performance is not critical. The application may have computationally intensive tasks, such as AI inference on the fly. WasmEdge allows the QuickJS applications to drop to high-performance WebAssembly for these tasks while it is not so easy with v8 to add such extensions modules.

Third, WasmEdge is itself an OCI compliant container. It is secure by default, supports resource isolation, and can be managed by container tools to run side by side with Linux containers in a single k8s cluster.

Finally, v8 has a very large attack surface and requires major efforts to run securely in a public cloud environment. It is known that many JavaScript security issues arise from JIT. Maybe turning off JIT in the cloud-native environment is not such a bad idea!

In the end, running v8 in a cloud-native environment often requires a full stack of software tools consisting of "Linux container + guest OS + node or deno + v8", which makes it much heavier and slower than a simple WasmEdge + QuickJS container runtime.

Quick start with JavaScript on WasmEdge

First, let's build a WebAssembly-based JavaScript interpreter program for WasmEdge. It is based on QuickJS with WasmEdge extensions, such as network sockets and Tensorflow inference, incorporated into the interpreter as JavaScript APIs. You will need to install Rust to build the interpreter.

If you just want to use the interpreter to run JavaScript programs, you can skip this section. Make sure you have installed Rust and WasmEdge.

Fork or clone the wasmedge-quickjs Github repository to get started.

git clone https://github.com/second-state/wasmedge-quickjs

Following the instructions from that repo, you will be able to build a JavaScript interpreter for WasmEdge.

# Install GCC
sudo apt update
sudo apt install build-essential

# Install wasm32-wasi target for Rust
rustup target add wasm32-wasi

# Build the QuickJS JavaScript interpreter
cargo build --target wasm32-wasi --release

The WebAssembly-based JavaScript interpreter program is located in the build target directory. You can now try a simple "hello world" JavaScript program (example_js/hello.js), which prints out the command line arguments to the console.

import * as os from 'os';
import * as std from 'std';

args = args.slice(1);
print('Hello', ...args);
setTimeout(() => {
  print('timeout 2s');
}, 2000);

Run the hello.js file in WasmEdge’s QuickJS runtime as follows.

$ cd example_js
$ wasmedge --dir .:. ../target/wasm32-wasi/release/wasmedge_quickjs.wasm hello.js WasmEdge Runtime
Hello WasmEdge Runtime

Note: the --dir .:. on the command line is to give wasmedge permission to read the local directory in the file system for the hello.js file. We will use --dir .:. in the following sections.

Make it faster

WasmEdge provides a wasmedgec utility to compile and add a native machine code section to the wasm file. You can use wasmedge to run the natively instrumented wasm file to get much faster performance.

wasmedgec ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasmedge_quickjs.wasm
wasmedge --dir .:. wasmedge_quickjs.wasm hello.js

Next, we will discuss more advanced use case for JavaScript in WasmEdge.

Async networking apps

The QuickJS WasmEdge Runtime supports the WasmEdge networking socket extension so that the JavaScript programs can make HTTP connections to the Internet. This article will show you both HTTP Client and HTTP Server examples.

The networking API in WasmEdge is non-blocking and hence supports asynchronous I/O intensive applications. With this API, the JavaScript program can open multiple connections concurrently. It polls those connections, or registers async callback functions, to process data whenever data comes in, without waiting for any one connection to complete its data transfer. That allows the single-threaded application to handle multiple multiple concurrent requests.

A JavaScript networking client example

Below is an example of JavaScript running an async HTTP client. You could find the code in example_js/wasi_http_client.js. The code below shows how to make an async HTTP GET request.

async function get_test() {
  try {
    let ss = await net.WasiTcpConn.connect('152.136.235.225:80');
    let req = new http.WasiRequest();
    req.headers = { 'Host': '152.136.235.225' };
    req.uri = '/get?a=123';
    req.method = 'GET';
    ss.write(req.encode());
    print('wait get');
    await handle_response(ss);
    print('get end');

  } catch (e) {
    print('catch:', e);
  }
}

The program can open multiple requests while waiting for the servers to respond. Once a server responds, the handle_response() function is called asynchronously to process the response and to print out the content.

async function handle_response(s) {
  let buf = new http.Buffer();
  let resp = undefined;
  while (true) {
    buf.append(await s.read());
    if (resp == undefined) {
      resp = buf.parseResponse();
    }
    if (resp instanceof http.WasiResponse) {
      let resp_length = resp.bodyLength;
      if (typeof (resp_length) === "number") {
        if (buf.length >= resp.bodyLength) {
          print('resp.body');
          print(newStringFromUTF8(buf.buffer));
          break;
        }
      } else {
        throw new Error('no support');
      }
    }
  }
}

To run the JavaScript in the WasmEdge runtime, you can do this on the CLI.

cd example_js
wasmedge --dir .:. ../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasi_http_client.js

The results printed to the console are as follows.

{
  "args": {
    "a": "123"
  }, 
  "data": "hello", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Content-Length": "5", 
    "Host": "152.136.235.225"
  }, 
  "json": null, 
  "origin": "20.124.39.106", 
  "url": "http://152.136.235.225/post?a=123"
}

The demo app does two HTTP requests. One is GET and the other is POST. The app waits for the responses from those two requests asynchronously, and processes them as they come in. From the console log, you can see how the two request handlers are interweaved.

A JavaScript networking server example

Below is an example of JavaScript running a TCP server listening at port 8000. The incoming requests are handled asynchronously. You could find the code in example_js/wasi_net_echo.js.

import * as net from 'wasi_net';

async function handle_client(cs) {
  try {
    while (true) {
      let d = await cs.read();
      if (d == undefined || d.byteLength <= 0) {
        break;
      }
      let s = newStringFromUTF8(d);
      cs.write('echo:' + s);
    }
  } catch (e) {
    print(e);
  }
}

async function server_start() {
  print('listen 8000 ...');
  try {
    let s = new net.WasiTcpServer(8000);
    for (var i = 0; i < 100; i++) {
      let cs = await s.accept();
      handle_client(cs);
    }
  } catch (e) {
    print(e)
  }
}

server_start();

The server_start() function starts the server at port 8000. When a request comes in, it accepts immediately and calls the handle_client() function to process it asynchronously when the request data is received later. While the handle_client() is waiting for the data to arrive from the network, the app could accept another request concurrently.

To run the JavaScript in the WasmEdge runtime, you can do this on the CLI. Since it is a server, you should run it in the background.

cd example_js
nohup wasmedge --dir .:. ../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasi_net_echo.js &

Then you can test the server by querying it over the network.

$ curl -d "WasmEdge" -X POST http://localhost:8000
echo:WasmEdge

The wasi_net package provides a flexible asynchronous networking stack for JavaScript applications in WasmEdge. We are wrap it in high-level APIs for more advanced use cases. In the next section, we will show you how to handle HTTP requests with ease. In the React SSR article, we will discuss how to create a React stream SSR server with the async networking API.

A JavaScript HTTP server example

If you already knew the server's requests and responses are in the HTTP protocol, there are additional helper functions to help you handle these requests. You could find the code in example_js/wasi_http_echo.js.

import * as http from 'wasi_http';
import * as net from 'wasi_net';

async function handle_client(cs, handler_req) {
  let buffer = new http.Buffer();

  while (true) {
    try {
      let d = await cs.read();
      if (d.byteLength <= 0) {
        return;
      }
      buffer.append(d);
      let req = buffer.parseRequest();
      if (req instanceof http.WasiRequest) {
        handler_req(cs, req);
        break;
      }
    } catch (e) {
      print(e);
    }
  }
}

function handler_req(cs, req) {
  print("version=", req.version);
  print("uri=", req.uri);
  print("method=", req.method);
  print("headers=", Object.keys(req.headers));
  print("body=", newStringFromUTF8(req.body));

  let resp = new http.WasiResponse();
  let body = 'echo:' + newStringFromUTF8(req.body);
  let r = resp.encode(body);
  cs.write(r);
}

async function server_start() {
  try {
    let s = new net.WasiTcpServer(8000);
    for (var i = 0; i < 100; i++) {
      let cs = await s.accept();
      try {
        handle_client(cs, handler_req);
      } catch (e) {
        print(e);
      }
    }
  } catch (e) {
    print(e);
  }
}

server_start();

The server_start() function starts the server at port 8000. When a request comes in, it accepts immediately and calls the handle_client() async function to process the request data when the data is received later. Once the request is validated as an HTTP request, the handler function in turn calls handle_req() to parse the fields in the HTTP request, compose a HTTP reponse, and then send the response back. While the program is waiting for the request data to arrive from the network, it can accept another request concurrently.

To run the JavaScript in the WasmEdge runtime, you can do this on the CLI. Since it is a server, you should run it in the background.

cd example_js
nohup wasmedge --dir .:. ../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasi_http_echo.js &

Then you can test the server by querying it over the network.

$ curl -d "WasmEdge" -X POST http://localhost:8000
echo:WasmEdge

With async HTTP networking, developers can create I/O intensive applications, such as database-driven microservices, in JavaScript and run them safely and efficiently in WasmEdge.

The fetch API

The fetch API is widely used in browser and node-based JavaScript applications to fetch content over the network. Building on top of its non-blocking aysnc network socket API, the WasmEdge QuickJS runtime supports the fetch API. That makes a lot of JS APIs and modules reusable out of the box.

The example_js/wasi_http_fetch.js example demonstrates how to use the fetch API in WasmEdge. The code snippet below shows an async HTTP GET. While the program waits for and processes the GET content, it can start another request.

import { fetch } from 'http'

async function test_fetch() {
  try {
    let r = await fetch("http://152.136.235.225/get?id=1")
    print('test_fetch\n', await r.text())
  } catch (e) {
    print(e)
  }
}
test_fetch()

The code snippet below shows how to do an sync HTTP POST to a remote server.

async function test_fetch_post() {
  try {
    let r = await fetch("http://152.136.235.225/post", { method: 'post', 'body': 'post_body' })
    print('test_fetch_post\n', await r.text())
  } catch (e) {
    print(e)
  }
}
test_fetch_post()

An async HTTP PUT request is as follows.

async function test_fetch_put() {
  try {
    let r = await fetch("http://152.136.235.225/put",
      {
        method: "put",
        body: JSON.stringify({ a: 1 }),
        headers: { 'Context-type': 'application/json' }
      })
    print('test_fetch_put\n', await r.text())
  } catch (e) {
    print(e)
  }
}
test_fetch_put()

To run those examples, use the following WasmEdge CLI command.

cd example_js
wasmedge --dir .:. ../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasi_http_fetch.js

You can see the HTTP responses printed to the console.

TensorFlow

The interpreter supports the WasmEdge TensorFlow lite inference extension so that your JavaScript can run an ImageNet model for image classification. This article will show you how to use the TensorFlow Rust SDK for WasmEdge from your javascript program.

Here is an example of JavaScript. You could find the full code from example_js/tensorflow_lite_demo/.

import {Image} from 'image';
import * as std from 'std';
import {TensorflowLiteSession} from 'tensorflow_lite';

let img = new Image('food.jpg');
let img_rgb = img.to_rgb().resize(192, 192);
let rgb_pix = img_rgb.pixels();

let session = new TensorflowLiteSession(
    'lite-model_aiy_vision_classifier_food_V1_1.tflite');
session.add_input('input', rgb_pix);
session.run();
let output = session.get_output('MobilenetV1/Predictions/Softmax');
let output_view = new Uint8Array(output);
let max = 0;
let max_idx = 0;
for (var i in output_view) {
  let v = output_view[i];
  if (v > max) {
    max = v;
    max_idx = i;
  }
}
let label_file = std.open('aiy_food_V1_labelmap.txt', 'r');
let label = '';
for (var i = 0; i <= max_idx; i++) {
  label = label_file.getline();
}
label_file.close();

print('label:');
print(label);
print('confidence:');
print(max / 255);

To run the JavaScript in the WasmEdge runtime, you can do the following on the CLI to re-build the QuickJS engine with TensorFlow and then run the JavaScript program with TensorFlow API.

$ cargo build --target wasm32-wasi --release --features=tensorflow
... ...
$ cd example_js/tensorflow_lite_demo
$ wasmedge-tensorflow-lite --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm main.js
label:
Hot dog
confidence:
0.8941176470588236

Note: the --dir .:. on the command line is to give wasmedge permission to read the local directory in the file system for the main.js file.

Note

  • The --features=tensorflow compiler flag builds a version of the QuickJS engine with WasmEdge TensorFlow extensions.
  • The wasmedge-tensorflow-lite program is part of the WasmEdge package. It is the WasmEdge runtime with the Tensorflow extension built in.

You should now see the name of the food item recognized by the TensorFlow lite ImageNet model.

Make it faster

The above Tensorflow inference example takes 1–2 seconds to run. It is acceptable in web application scenarios but could be improved. Recall that WasmEdge is the fastest WebAssembly runtime today due to its AOT (Ahead-of-time compiler) optimization. WasmEdge provides a wasmedgec utility to compile and add a native machine code section to the wasm file for much faster performance.

The following example uses the extended versions to wasmedge and wasmedgec to support the WasmEdge Tensorflow extension.

$ cd example_js/tensorflow_lite_demo
$ wasmedgec-tensorflow ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm wasmedge_quickjs.wasm
$ wasmedge-tensorflow-lite --dir .:. wasmedge_quickjs.wasm main.js
label:
Hot dog
confidence:
0.8941176470588236

You can see that the image classification task can be completed within 0.1s. It is at least 10x improvement!

React SSR

React is very popular JavaScript web UI framework. A React application is "compiled" into an HTML and JavaScript static web site. The web UI is rendered through the generated JavaScript code. However, it is often too slow and resource consuming to execute the complex generated JavaScript entirely in the browser to build the interactive HTML DOM objects. React Server Side Rendering (SSR) delegates the JavaScript UI rendering to a server, and have the server stream rendered HTML DOM objects to the browser. The WasmEdge JavaScript runtime provides a lightweight and high performance container to run React SSR functions on edge servers.

Server-side rendering (SSR) is a popular technique for rendering a client-side single page application (SPA) on the server and then sending a fully rendered page to the client. This allows for dynamic components to be served as static HTML markup. This approach can be useful for search engine optimization (SEO) when indexing does not handle JavaScript properly. It may also be beneficial in situations where downloading a large JavaScript bundle is impaired by a slow network. -- from Digital Ocean.

In this article, we will show you how to use the WasmEdge QuickJS runtime to implement a React SSR function. Compared with the Docker + Linux + nodejs + v8 approach, WasmEdge is much lighter (1% of the footprint) and safer, provides better resource isolation and management, and has similar non-JIT (safe) performance.

We will start from a complete tutorial to create and deploy a simple React SSR web application from the standard template. Then, we will discuss two modes of SSR: static and streaming rendering. Static rendering is easy to understand and implement. Streaming rendering, on the other hand, provides better user experience since the user can see partial results while waiting in front of the browser. We will walk through the key code snippets for both static and streaming rendering.

Getting started

Step 1 — Create the React App

First, use npx to create a new React app. Let’s name the app react-ssr-example.

npx create-react-app react-ssr-example

Then, cd into the directory for the newly created app.

cd react-ssr-example

Start the new app in order to verify the installation.

npm start

You should see the example React app displayed in your browser window. At this stage, the app is rendered in the browser. The browser runs the generated React JavaScript to build the HTML DOM UI.

Now in order to prepare for SSR, you will need to make some changes to the app's index.js file. Change ReactDOM's render method to hydrate to indicate to the DOM renderer that you intend to rehydrate the app after it is rendered on the server. Replace the contents of the index.js file with the following.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.hydrate(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

Note: you should import React redundantly in the src/App.js, so the server will recognize it.

import React from 'react';
//...

That concludes setting up the application, you can move on to setting up the server-side rendering functions.

Step 2 — Create an WasmEdge QuickJS Server and Render the App Component

Now that you have the app in place, let’s set up a server that will render the HTML DOM by running the React JavaScript and then send the rendered elements to the browser. We will use WasmEdge as a secure, high-performance and lightweight container to run React JavaScript.

Create a new server directory in the project's root directory.

mkdir server

Then, inside the server directory, create a new index.js file with the server code.

import * as React from 'react';
import ReactDOMServer from 'react-dom/server';
import * as std from 'std';
import * as http from 'wasi_http';
import * as net from 'wasi_net';

import App from '../src/App.js';

async function handle_client(cs) {
  print('open:', cs.peer());
  let buffer = new http.Buffer();

  while (true) {
    try {
      let d = await cs.read();
      if (d == undefined || d.byteLength <= 0) {
        return;
      }
      buffer.append(d);
      let req = buffer.parseRequest();
      if (req instanceof http.WasiRequest) {
        handle_req(cs, req);
        break;
      }
    } catch (e) {
      print(e);
    }
  }
  print('end:', cs.peer());
}

function enlargeArray(oldArr, newLength) {
  let newArr = new Uint8Array(newLength);
  oldArr && newArr.set(oldArr, 0);
  return newArr;
}

async function handle_req(s, req) {
  print('uri:', req.uri)

  let resp = new http.WasiResponse();
  let content = '';
  if (req.uri == '/') {
    const app = ReactDOMServer.renderToString(<App />);
    content = std.loadFile('./build/index.html');
    content = content.replace('<div id="root"></div>', `<div id="root">${app}</div>`);
  } else {
    let chunk = 1000; // Chunk size of each reading
    let length = 0; // The whole length of the file
    let byteArray = null; // File content as Uint8Array
    
    // Read file into byteArray by chunk
    let file = std.open('./build' + req.uri, 'r');
    while (true) {
      byteArray = enlargeArray(byteArray, length + chunk);
      let readLen = file.read(byteArray.buffer, length, chunk);
      length += readLen;
      if (readLen < chunk) {
        break;
      }
    }
    content = byteArray.slice(0, length).buffer;
    file.close();
  }
  let contentType = 'text/html; charset=utf-8';
  if (req.uri.endsWith('.css')) {
    contentType = 'text/css; charset=utf-8';
  } else if (req.uri.endsWith('.js')) {
    contentType = 'text/javascript; charset=utf-8';
  } else if (req.uri.endsWith('.json')) {
    contentType = 'text/json; charset=utf-8';
  } else if (req.uri.endsWith('.ico')) {
    contentType = 'image/vnd.microsoft.icon';
  } else if (req.uri.endsWith('.png')) {
    contentType = 'image/png';
  }
  resp.headers = {
    'Content-Type': contentType
  };

  let r = resp.encode(content);
  s.write(r);
}

async function server_start() {
  print('listen 8002...');
  try {
    let s = new net.WasiTcpServer(8002);
    for (var i = 0; ; i++) {
      let cs = await s.accept();
      handle_client(cs);
    }
  } catch (e) {
    print(e);
  }
}

server_start();

The server renders the <App> component, and then sends the rendered HTML string back to the browser. Three important things are taking place here.

  • ReactDOMServer's renderToString is used to render the <App/> to an HTML string.
  • The index.html file from the app's build output directory is loaded as a template. The app's content is injected into the <div> element with an id of "root". It is then sent back as HTTP response.
  • Other files from the build directory are read and served as needed at the requests of the browser.

Step 3 — Build and deploy

For the server code to work, you will need to bundle and transpile it. In this section, we will show you how to use webpack and Babel. In this next section, we will demonstrate an alternative (and potentially easier) approach using rollup.js.

Create a new Babel configuration file named .babelrc.json in the project's root directory and add the env and react-app presets.

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

Create a webpack config for the server that uses Babel Loader to transpile the code. Start by creating the webpack.server.js file in the project's root directory.

const path = require('path');
module.exports = {
  entry: './server/index.js',
  externals: [
    {"wasi_http": "wasi_http"},
    {"wasi_net": "wasi_net"},
    {"std": "std"}
  ],
  output: {
    path: path.resolve('server-build'),
    filename: 'index.js',
    chunkFormat: "module",
    library: {
      type: "module"
    },
  },
  experiments: {
    outputModule: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ["css-loader"]
      },
      {
        test: /\.svg$/,
        use: ["svg-url-loader"]
      }
    ]
  }
};

With this configuration, the transpiled server bundle will be output to the server-build folder in a file called index.js.

Next, add the svg-url-loader package by entering the following commands in your terminal.

npm install svg-url-loader --save-dev

This completes the dependency installation and webpack and Babel configuration.

Now, revisit package.json and add helper npm scripts. Add dev:build-server, dev:start-server scripts to the package.json file to build and serve the SSR application.

"scripts": {
  "dev:build-server": "NODE_ENV=development webpack --config webpack.server.js --mode=development",
  "dev:start-server": "wasmedge --dir .:. wasmedge_quickjs.wasm ./server-build/index.js",
  // ...
},
  • The dev:build-server script sets the environment to "development" and invokes webpack with the configuration file you created earlier.
  • The dev:start-server script runs the WasmEdge server from the wasmedge CLI tool to serve the built output. The wasmedge_quickjs.wasm program contains the QuickJS runtime. Learn more

Now you can run the following commands to build the client-side app, bundle and transpile the server code, and start up the server on :8002.

npm run build
npm run dev:build-server
npm run dev:start-server

Open http://localhost:8002/ in your web browser and observe your server-side rendered app.

Previously, the HTML source in the browser is simply the template with SSR placeholders.

Output
<div id="root"></div>

Now, with the SSR function running on the server, the HTML source in the browser is as follows.

Output
<div id="root"><div class="App" data-reactroot="">...</div></div>

Step 4 (alternative) -- build and deploy with rollup.js

Alternatively, you could use the rollup.js tool to package all application components and library modules into a single file for WasmEdge to execute.

Create a rollup config for the server that uses Babel Loader to transpile the code. Start by creating the rollup.config.js file in the project's root directory.

const {babel} = require('@rollup/plugin-babel');
const nodeResolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const replace = require('@rollup/plugin-replace');

const globals = require('rollup-plugin-node-globals');
const builtins = require('rollup-plugin-node-builtins');
const plugin_async = require('rollup-plugin-async');
const css = require("rollup-plugin-import-css");
const svg = require('rollup-plugin-svg');

const babelOptions = {
  babelrc: false,
  presets: [
    '@babel/preset-react'
  ],
  babelHelpers: 'bundled'
};

module.exports = [
  {
    input: './server/index.js',
    output: {
      file: 'server-build/index.js',
      format: 'esm',
    },
    external: [ 'std', 'wasi_net','wasi_http'],
    plugins: [
      plugin_async(),
      babel(babelOptions),
      nodeResolve({preferBuiltins: true}),
      commonjs({ignoreDynamicRequires: false}),
      css(),
      svg({base64: true}),
      globals(),
      builtins(),
      replace({
        preventAssignment: true,  
        'process.env.NODE_ENV': JSON.stringify('production'),
        'process.env.NODE_DEBUG': JSON.stringify(''),
      }),
    ],
  },
];

With this configuration, the transpiled server bundle will be output to the server-build folder in a file called index.js.

Next, add the dependent packages to the package.json then install with npm.

  "devDependencies": {
    //...
    "@rollup/plugin-babel": "^5.3.0",
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-node-resolve": "^7.1.3",
    "@rollup/plugin-replace": "^3.0.0",
    "rollup": "^2.60.1",
    "rollup-plugin-async": "^1.2.0",
    "rollup-plugin-import-css": "^3.0.3",
    "rollup-plugin-node-builtins": "^2.1.2",
    "rollup-plugin-node-globals": "^1.4.0",
    "rollup-plugin-svg": "^2.0.0"
  }
npm install

This completes the dependency installation and rollup configuration.

Now, revisit package.json and add helper npm scripts. Add dev:build-server, dev:start-server scripts to the package.json file to build and serve the SSR application.

"scripts": {
  "dev:build-server": "rollup -c rollup.config.js",
  "dev:start-server": "wasmedge --dir .:. wasmedge_quickjs.wasm ./server-build/index.js",
  // ...
},
  • The dev:build-server script sets the environment to "development" and invokes webpack with the configuration file you created earlier.
  • The dev:start-server script runs the WasmEdge server from the wasmedge CLI tool to serve the built output. The wasmedge_quickjs.wasm program contains the QuickJS runtime. Learn more

Now you can run the following commands to build the client-side app, bundle and transpile the server code, and start up the server on :8002.

npm run build
npm run dev:build-server
npm run dev:start-server

Open http://localhost:8002/ in your web browser and observe your server-side rendered app.

Previously, the HTML source in the browser is simply the template with SSR placeholders.

Output
<div id="root"></div>

Now, with the SSR function running on the server, the HTML source in the browser is as follows.

Output
<div id="root"><div class="App" data-reactroot="">...</div></div>

In the next two sections, we will dive deeper into the source code for two ready-made sample applications for static and streaming rendering of SSR.

Static rendering

The example_js/react_ssr folder in the GitHub repo contains the example's source code. It showcases how to compose HTML templates and render them into an HTML string in a JavaScript app running in WasmEdge.

The component/Home.jsx file is the main page template in React.

import React from 'react';
import Page from './Page.jsx';
class Home extends React.Component {
  render() {
    const { dataList = [] } = this.props;
    return (
      <div>
        <div>This is home</div>
        <Page></Page>
      </div>
    );
  }
};

export default Home;

The Home.jpx template includes a Page.jpx template for part of the page.

import React from 'react';

class Page extends React.Component {
  render() {
    const { dataList = [] } = this.props;
    return (
      <div>
        <div>This is page</div>
      </div>
    );
  }
};

export default Page;

The main.js file calls React to render the templates into HTML.

import React from 'react';
import {renderToString} from 'react-dom/server';

import Home from './component/Home.jsx';

const content = renderToString(React.createElement(Home));
console.log(content);

The rollup.config.js and package.json files are to build the React SSR dependencies and components into a bundled JavaScript file for WasmEdge. You should use the npm command to build it. The output is in the dist/main.js file.

npm install
npm run build

To run the example, do the following on the CLI. You can see that the templates are successfully composed into an HTML string.

$ cd example_js/react_ssr
$ wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm dist/main.js
<div data-reactroot=""><div>This is home</div><div><div>This is page</div></div></div>

Note: the --dir .:. on the command line is to give wasmedge permission to read the local directory in the file system for the dist/main.js file.

Streaming rendering

The example_js/react_ssr_stream folder in the GitHub repo contains the example's source code. It showcases how to streaming render an HTML string from templates in a JavaScript app running in WasmEdge.

The component/LazyHome.jsx file is the main page template in React. It "lazy" loads the inner page template after a 2s delay once the outer HTML is rendered and returned to the user.

import React, { Suspense } from 'react';
import * as LazyPage from './LazyPage.jsx';

async function sleep(ms) {
  return new Promise((r, _) => {
    setTimeout(() => r(), ms)
  });
}

async function loadLazyPage() {
  await sleep(2000);
  return LazyPage
}

class LazyHome extends React.Component {
  render() {
    let LazyPage1 = React.lazy(() => loadLazyPage());
    return (
      <html lang="en">
        <head>
          <meta charSet="utf-8" />
          <title>Title</title>
        </head>
        <body>
          <div>
            <div> This is LazyHome </div>
            <Suspense fallback={<div> loading... </div>}>
              <LazyPage1 />
            </Suspense>
          </div>
        </body>
      </html>
    );
  }
}

export default LazyHome;

The LazyPage.jsx is the inner page template. It is rendered 2s after the outer page is already returned to the user.

import React from 'react';

class LazyPage extends React.Component {
  render() {
    return (
      <div>
        <div>
          This is lazy page
        </div>
      </div>
    );
  }
}

export default LazyPage;

The main.mjs file starts a non-blocking HTTP server, and then renders the HTML page in multiple chuncks to the response. When a HTTP request comes in, the handle_client() function is called to render the HTML and to send back the results through the stream.

import * as React from 'react';
import { renderToPipeableStream } from 'react-dom/server';
import * as http from 'wasi_http';
import * as net from 'wasi_net';

import LazyHome from './component/LazyHome.jsx';

async function handle_client(s) {
  let resp = new http.WasiResponse();
  resp.headers = {
    "Content-Type": "text/html; charset=utf-8"
  }
  renderToPipeableStream(<LazyHome />).pipe(resp.chunk(s));
}

async function server_start() {
  print('listen 8001...');
  let s = new net.WasiTcpServer(8001);
  for (var i = 0; i < 100; i++) {
    let cs = await s.accept();
    handle_client(cs);
  }
}

server_start();

The rollup.config.js and package.json files are to build the React SSR dependencies and components into a bundled JavaScript file for WasmEdge. You should use the npm command to build it. The output is in the dist/main.mjs file.

npm install
npm run build

To run the example, do the following on the CLI to start the server.

cd example_js/react_ssr_stream
nohup wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm dist/main.mjs &

Send the server a HTTP request via curl or the browser.

curl http://localhost:8001

The results are as follows. The service first returns an HTML page with an empty inner section (i.e., the loading section), and then 2s later, the HTML content for the inner section and the JavaScript to display it.

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   211    0   211    0     0   1029      0 --:--:-- --:--:-- --:--:--  1024
100   275    0   275    0     0    221      0 --:--:--  0:00:01 --:--:--   220
100   547    0   547    0     0    245      0 --:--:--  0:00:02 --:--:--   245
100  1020    0  1020    0     0    413      0 --:--:--  0:00:02 --:--:--   413

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><title>Title</title></head><body><div><div> This is LazyHome </div><!--$?--><template id="B:0"></template><div> loading... </div><!--/$--></div></body></html><div hidden id="S:0"><template id="P:1"></template></div><div hidden id="S:1"><div><div>This is lazy page</div></div></div><script>function $RS(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};$RS("S:1","P:1")</script><script>function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}};$RC("B:0","S:0")</script>

React 18 SSR

In this section, we will demonstrate a complete React 18 SSR application. It renders the web UI through streaming SSR. The example_js/react18_ssr folder in the GitHub repo contains the example's source code. The component folder contains the entire React 18 application's source code, and the public folder contains the public resources (CSS and images) for the web application.

The main.mjs file starts a non-blocking HTTP server, maps the main.css and main.js files in the public folder to web URLs, and then renders the HTML page for each request in renderToPipeableStream().

import * as React from 'react';
import { renderToPipeableStream } from 'react-dom/server';
import * as http from 'wasi_http';
import * as net from 'wasi_net';
import * as std from 'std';

import App from './component/App.js';
import { DataProvider } from './component/data.js'

let assets = {
  'main.js': '/main.js',
  'main.css': '/main.css',
};

const css = std.loadFile('./public/main.css')

function createServerData() {
  let done = false;
  let promise = null;
  return {
    read() {
      if (done) {
        return;
      }
      if (promise) {
        throw promise;
      }
      promise = new Promise(resolve => {
        setTimeout(() => {
          done = true;
          promise = null;
          resolve();
        }, 2000);
      });
      throw promise;
    },
  };
}

async function handle_client(cs) {
  print('open:', cs.peer());
  let buffer = new http.Buffer();

  while (true) {
    try {
      let d = await cs.read();
      if (d == undefined || d.byteLength <= 0) {
        return;
      }
      buffer.append(d);
      let req = buffer.parseRequest();
      if (req instanceof http.WasiRequest) {
        handle_req(cs, req);
        break;
      }
    } catch (e) {
      print(e);
    }
  }
  print('end:', cs.peer());
}

async function handle_req(s, req) {
  print('uri:', req.uri)
  let resp = new http.WasiResponse();
  if (req.uri == '/main.css') {
    resp.headers = {
      "Content-Type": "text/css; charset=utf-8"
    }
    let r = resp.encode(css);
    s.write(r);
  } else {
    resp.headers = {
      "Content-Type": "text/html; charset=utf-8"
    }
    let data = createServerData()
    renderToPipeableStream(
      <DataProvider data={data}>
        <App assets={assets} />
      </DataProvider>
    ).pipe(resp.chunk(s));
  }
}

async function server_start() {
  print('listen 8002...');
  try {
    let s = new net.WasiTcpServer(8002);
    for (var i = 0; ; i++) {
      let cs = await s.accept();
      handle_client(cs);
    }
  } catch (e) {
    print(e)
  }
}

server_start();

The rollup.config.js and package.json files are to build the React 18 SSR dependencies and components into a bundled JavaScript file for WasmEdge. You should use the npm command to build it. The output is in the dist/main.mjs file.

npm install
npm run build

To run the example, do the following on the CLI to start the server.

cd example_js/react_ssr_stream
nohup wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm dist/main.mjs &

Send the server a HTTP request via curl or the browser.

curl http://localhost:8002

The results are as follows. The service first returns an HTML page with an empty inner section (i.e., the loading section), and then 2s later, the HTML content for the inner section and the JavaScript to display it.

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   439    0   439    0     0   1202      0 --:--:-- --:--:-- --:--:--  1199
100  2556    0  2556    0     0   1150      0 --:--:--  0:00:02 --:--:--  1150
100  2556    0  2556    0     0    926      0 --:--:--  0:00:02 --:--:--   926
100  2806    0  2806    0     0    984      0 --:--:--  0:00:02 --:--:--   984
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/main.css"/><title>Hello</title></head><body><noscript><b>Enable JavaScript to run this app.</b></noscript><!--$--><main><nav><a href="/">Home</a></nav><aside class="sidebar"><!--$?--><template id="B:0"></template><div class="spinner spinner--active" role="progressbar" aria-busy="true"></div><!--/$--></aside><article class="post"><!--$?--><template id="B:1"></template><div class="spinner spinner--active" role="progressbar" aria-busy="true"></div><!--/$--><section class="comments"><h2>Comments</h2><!--$?--><template id="B:2"></template><div class="spinner spinner--active" role="progressbar" aria-busy="true"></div><!--/$--></section><h2>Thanks for reading!</h2></article></main><!--/$--><script>assetManifest = {"main.js":"/main.js","main.css":"/main.css"};</script></body></html><div hidden id="S:0"><template id="P:3"></template></div><div hidden id="S:1"><template id="P:4"></template></div><div hidden id="S:2"><template id="P:5"></template></div><div hidden id="S:3"><h1>Archive</h1><ul><li>May 2021</li><li>April 2021</li><li>March 2021</li><li>February 2021</li><li>January 2021</li><li>December 2020</li><li>November 2020</li><li>October 2020</li><li>September 2020</li></ul></div><script>function $RS(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};$RS("S:3","P:3")</script><script>function $RC(a,b){a=document.getElementById(a);b=document.getElementById(b);b.parentNode.removeChild(b);if(a){a=a.previousSibling;var f=a.parentNode,c=a.nextSibling,e=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d)if(0===e)break;else e--;else"$"!==d&&"$?"!==d&&"$!"!==d||e++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;b.firstChild;)f.insertBefore(b.firstChild,c);a.data="$";a._reactRetry&&a._reactRetry()}};$RC("B:0","S:0")</script><div hidden id="S:4"><h1>Hello world</h1><p>This demo is <!-- --><b>artificially slowed down</b>. Open<!-- --> <!-- --><code>server/delays.js</code> to adjust how much different things are slowed down.<!-- --></p><p>Notice how HTML for comments &quot;streams in&quot; before the JS (or React) has loaded on the page.</p><p>Also notice that the JS for comments and sidebar has been code-split, but HTML for it is still included in the server output.</p></div><script>$RS("S:4","P:4")</script><script>$RC("B:1","S:1")</script><div hidden id="S:5"><p class="comment">Wait, it doesn&#x27;t wait for React to load?</p><p class="comment">How does this even work?</p><p class="comment">I like marshmallows</p></div><script>$RS("S:5","P:5")</script><script>$RC("B:2","S:2")</script>

The streaming SSR examples make use of WasmEdge's unique asynchronous networking capabilities and ES6 module support (i.e., the rollup bundled JS file contains ES6 modules). You can learn more about async networking and ES6 in this book.

ES6 module support

The WasmEdge QuickJS runtime supports ES6 modules. In fact, the rollup commands we used in the React SSR examples convert and bundle CommonJS and NPM modules into ES6 modules so that they can be executed in WasmEdge QuickJS. This article will show you how to use ES6 module in WasmEdge.

We will take the example in example_js/es6_module_demo folder as an example. The module_def.js file defines and exports a simple JS function.

function hello(){
  console.log('hello from module_def.js');
}

export {hello};

The module_def_async.js file defines and exports an aysnc function and a variable.

export async function hello() {
  console.log('hello from module_def_async.js');
  return 'module_def_async.js : return value';
}

export var something = 'async thing';

The demo.js file imports functions and variables from those modules and executes them.

import {hello as module_def_hello} from './module_def.js';

module_def_hello();

var f = async () => {
  let {hello, something} = await import('./module_def_async.js');
  await hello();
  console.log('./module_def_async.js `something` is ', something);
};

f();

To run the example, you can do the following on the CLI.

$ cd example_js/es6_module_demo
$ wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm demo.js
hello from module_def.js
hello from module_def_async.js
./module_def_async.js `something` is  async thing

Note: the --dir .:. on the command line is to give wasmedge permission to read the local directory in the file system for the demo.js file.

NodeJS and NPM module

With rollup.js, we can run CommonJS (CJS) and NodeJS (NPM) modules in WasmEdge too. The simple_common_js_demo/npm_main.js demo shows how it works. It utilizes the third-party md5 and mathjs modules.

const md5 = require('md5');
console.log('md5(message)=', md5('message'));

const {sqrt} = require('mathjs');
console.log('sqrt(-4)=', sqrt(-4).toString());

In order to run it, we must first use the rollup.js tool to build all dependencies into a single file. In the process, rollup.js converts CommonJS modules into WasmEdge-compatible ES6 modules. The build script is rollup.config.js.

const {babel} = require('@rollup/plugin-babel');
const nodeResolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const replace = require('@rollup/plugin-replace');

const globals = require('rollup-plugin-node-globals');
const builtins = require('rollup-plugin-node-builtins');
const plugin_async = require('rollup-plugin-async');

const babelOptions = {
  'presets': ['@babel/preset-react']
};

module.exports = [
  {
    input: './npm_main.js',
    output: {
      inlineDynamicImports: true,
      file: 'dist/npm_main.mjs',
      format: 'esm',
    },
    external: ['process', 'wasi_net','std'],
    plugins: [
      plugin_async(),
      nodeResolve(),
      commonjs({ignoreDynamicRequires: false}),
      babel(babelOptions),
      globals(),
      builtins(),
      replace({
        'process.env.NODE_ENV': JSON.stringify('production'),
        'process.env.NODE_DEBUG': JSON.stringify(''),
      }),
    ],
  },
];

The package.json file specifies the rollup.js dependencies and the command to build the npm_main.js demo program into a single bundle.

{
  "dependencies": {
    "mathjs": "^9.5.1",
    "md5": "^2.3.0"
  },
  "devDependencies": {
    "@babel/core": "^7.16.5",
    "@babel/preset-env": "^7.16.5",
    "@babel/preset-react": "^7.16.5",
    "@rollup/plugin-babel": "^5.3.0",
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-node-resolve": "^7.1.3",
    "@rollup/plugin-replace": "^3.0.0",
    "rollup": "^2.60.1",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-node-builtins": "^2.1.2",
    "rollup-plugin-node-globals": "^1.4.0",
    "rollup-plugin-async": "^1.2.0"
  },
  "scripts": {
    "build": "rollup -c rollup.config.js"
  }
}

Run the following NPM commands to build npm_main.js demo program into dist/npm_main.mjs.

npm install
npm run build

Run the result JS file in WasmEdge CLI as follows.

$ wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm dist/npm_main.mjs
md5(message)= 78e731027d8fd50ed642340b7c9a63b3
sqrt(-4)= 2i

You can import and run any pure-JS NPM packages in WasmEdge this way.

System modules

The WasmEdge QuickJS runtime supports ES6 and NPM modules for application developers. However, those approaches are too cumbersome for system developers. They need an easier way to add multiple JavaScript modules and APIs into the runtime without having to go through build tools like rollup.js. The WasmEdge QuickJS modules system allow developers to just drop JavaScript files into a modules folder, and have the JavaScript functions defined in the files immediately available to all JavaScript programs in the runtime. A good use case for this modules system is to support Node.js APIs in WasmEdge.

The module system is just a collection of JavaScript files in the modules directory in the WasmEdge QuickJS distribution. To use the JavaScript functions and APIs defined in those modules, you just need to map this directory to the /modules directory inside the WasmEdge Runtime instance. The following example shows how to do this on the WasmEdge CLI. You can do this with any of the host language SDKs that support embedded use of WasmEdge.

ls modules

    buffer.js encoding.js events.js http.js
    ... JavaScript files for the modules ...

wasmedge --dir .:. target/wasm32-wasi/release/wasmedge_quickjs.wasm example_js/hello.js WasmEdge Runtime

The module_demo shows how you can use the modules system to add your own JavaScript APIs. To run the demo, first copy the two files in the demo's modules directory to your WasmEdge QuickJS's modules directory.

cp example_js/module_demo/modules/* modules/

The two JavaScript files in the modules directory provide two simple functions. Below is the modules/my_mod_1.js file.

export function hello_mod_1(){
    console.log('hello from "my_mod_1.js"')
}

And the modules/my_mod_2.js file.

export function hello_mod_2(){
    console.log('hello from "my_mod_2.js"')
}

Then, just run the demo.js file to call the two exported functions from the modules.

import { hello_mod_1 } from 'my_mod_1'
import { hello_mod_2 } from 'my_mod_2'

hello_mod_1()
hello_mod_2()

Here is the command to run the demo and the output.

wasmedge --dir .:. target/wasm32-wasi/release/wasmedge_quickjs.wasm example_js/module_demo/demo.js

hello from "my_mod_1.js"
hello from "my_mod_2.js"

Following the above tutorials, you can easily add third-party JavaScript functions and APIs into your WasmEdge QuickJS runtime. For the official distribution, we included JavaScript files to support Node.js APIs. You can use those files as further examples.

Use Rust to implement JS API

For JavaScript developers, incorporating Rust functions into JavaScript APIs is useful. That enables developers to write programs in "pure JavaScript" and yet still take advantage of the high performance Rust functions. With the WasmEdge Runtime, you can do exactly that.

The internal_module folder in the official WasmEdge QuickJS distribution provides Rust-based implementations of some built-in JavaScript API functions. Those functions typically require interactions with host functions in the WasmEdge runtime (e.g., networking and tensorflow), and hence cannot be accessed by pure JavaScript implementations in modules.

Check out the wasmedge-quickjs Github repo and change to the examples/embed_js folder to follow along.

git clone https://github.com/second-state/wasmedge-quickjs
cd examples/embed_js

You must have Rust and WasmEdge installed to build and run the examples we show you.

The embed_js demo showcases several different examples on how to embed JavaScript inside Rust. You can build and run all the examples as follows.

cargo build --target wasm32-wasi --release
wasmedge --dir .:. target/wasm32-wasi/release/embed_js.wasm

Note: The --dir .:. on the command line is to give wasmedge permission to read the local directory in the file system.

Create a JavaScript function API

The following code snippet defines a Rust function that can be incorporate into the JavaScript interpreter as an API.


#![allow(unused)]
fn main() {
fn run_rust_function(ctx: &mut Context) {

  struct HelloFn;
  impl JsFn for HelloFn {
    fn call(_ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue {
      println!("hello from rust");
      println!("argv={:?}", argv);
      JsValue::UnDefined
    }
  }
  
  ...
}
}

The following code snippet shows how to add this Rust function into the JavaScript interpreter, give a name hi() as its JavaScript API, and then call it from JavaScript code.


#![allow(unused)]
fn main() {
fn run_rust_function(ctx: &mut Context) {
  ...
  
  let f = ctx.new_function::<HelloFn>("hello");
  ctx.get_global().set("hi", f.into());
  let code = r#"hi(1,2,3)"#;
  let r = ctx.eval_global_str(code);
  println!("return value:{:?}", r);
}
}

The execution result is as follows.

hello from rust
argv=[Int(1), Int(2), Int(3)]
return value:UnDefined

Using this approach, you can create a JavaScript interpreter with customized API functions. The interpreter runs inside WasmEdge, and can execute JavaScript code, which calls such API functions, from CLI or the network.

Create a JavaScript object API

In the JavaScript API design, we sometimes need to provide an object that encapsulates both data and function. In the following example, we define a Rust function for the JavaScript API.


#![allow(unused)]
fn main() {
fn rust_new_object_and_js_call(ctx: &mut Context) {
  struct ObjectFn;
  impl JsFn for ObjectFn {
    fn call(_ctx: &mut Context, this_val: JsValue, argv: &[JsValue]) -> JsValue {
      println!("hello from rust");
      println!("argv={:?}", argv);
      if let JsValue::Object(obj) = this_val {
        let obj_map = obj.to_map();
        println!("this={:#?}", obj_map);
      }
      JsValue::UnDefined
    }
  }

  ...
}
}

We then create an "object" on the Rust side, set its data fields, and then register the Rust function as a JavaScript function associated with the objects.


#![allow(unused)]
fn main() {
let mut obj = ctx.new_object();
obj.set("a", 1.into());
obj.set("b", ctx.new_string("abc").into());

let f = ctx.new_function::<ObjectFn>("anything");
obj.set("f", f.into());
}

Next, we make the Rust "object" available as JavaScript object test_obj in the JavaScript interpreter.


#![allow(unused)]
fn main() {
ctx.get_global().set("test_obj", obj.into());
}

In the JavaScript code, you can now directly use test_obj as part of the API.


#![allow(unused)]
fn main() {
let code = r#"
  print('test_obj keys=',Object.keys(test_obj))
  print('test_obj.a=',test_obj.a)
  print('test_obj.b=',test_obj.b)
  test_obj.f(1,2,3,"hi")
"#;

ctx.eval_global_str(code);
}

The execution result is as follows.

test_obj keys= a,b,f
test_obj.a= 1
test_obj.b= abc
hello from rust
argv=[Int(1), Int(2), Int(3), String(JsString(hi))]
this=Ok(
  {
    "a": Int(
      1,
    ),
    "b": String(
      JsString(
        abc,
      ),
    ),
    "f": Function(
      JsFunction(
        function anything() {
          [native code]
        },
      ),
    ),
  },
)

A complete JavaScript object API

In the previous example, we demonstrated simple examples to create JavaScript APIs from Rust. In this example, we will create a complete Rust module and make it available as a JavaScript object API. The project is in the examples/embed_rust_module folder. You can build and run it as a standard Rust application in WasmEdge.

cargo build --target wasm32-wasi --release
wasmedge --dir .:. target/wasm32-wasi/release/embed_rust_module.wasm

The Rust implementation of the object is a module as follows. It has data fields, constructor, getters and setters, and functions.


#![allow(unused)]
fn main() {
mod point {
  use wasmedge_quickjs::*;

  #[derive(Debug)]
  struct Point(i32, i32);

  struct PointDef;

  impl JsClassDef<Point> for PointDef {
    const CLASS_NAME: &'static str = "Point\0";
    const CONSTRUCTOR_ARGC: u8 = 2;

    fn constructor(_: &mut Context, argv: &[JsValue]) -> Option<Point> {
      println!("rust-> new Point {:?}", argv);
      let x = argv.get(0);
      let y = argv.get(1);
      if let ((Some(JsValue::Int(ref x)), Some(JsValue::Int(ref y)))) = (x, y) {
        Some(Point(*x, *y))
      } else {
        None
      }
    }

    fn proto_init(p: &mut JsClassProto<Point, PointDef>) {
      struct X;
      impl JsClassGetterSetter<Point> for X {
        const NAME: &'static str = "x\0";

        fn getter(_: &mut Context, this_val: &mut Point) -> JsValue {
          println!("rust-> get x");
          this_val.0.into()
        }

        fn setter(_: &mut Context, this_val: &mut Point, val: JsValue) {
          println!("rust-> set x:{:?}", val);
          if let JsValue::Int(x) = val {
            this_val.0 = x
          }
        }
      }

      struct Y;
      impl JsClassGetterSetter<Point> for Y {
        const NAME: &'static str = "y\0";

        fn getter(_: &mut Context, this_val: &mut Point) -> JsValue {
          println!("rust-> get y");
          this_val.1.into()
        }

        fn setter(_: &mut Context, this_val: &mut Point, val: JsValue) {
          println!("rust-> set y:{:?}", val);
          if let JsValue::Int(y) = val {
            this_val.1 = y
          }
        }
      }

      struct FnPrint;
      impl JsMethod<Point> for FnPrint {
        const NAME: &'static str = "pprint\0";
        const LEN: u8 = 0;

        fn call(_: &mut Context, this_val: &mut Point, _argv: &[JsValue]) -> JsValue {
          println!("rust-> pprint: {:?}", this_val);
          JsValue::Int(1)
        }
      }

      p.add_getter_setter(X);
      p.add_getter_setter(Y);
      p.add_function(FnPrint);
    }
  }

  struct PointModule;
  impl ModuleInit for PointModule {
    fn init_module(ctx: &mut Context, m: &mut JsModuleDef) {
      m.add_export("Point\0", PointDef::class_value(ctx));
    }
  }

  pub fn init_point_module(ctx: &mut Context) {
    ctx.register_class(PointDef);
    ctx.register_module("point\0", PointModule, &["Point\0"]);
  }
}
}

In the interpreter implementation, we call point::init_point_module first to register the Rust module with the JavaScript context, and then we can run a JavaScript program that simply use the point object.

use wasmedge_quickjs::*;
fn main() {
  let mut ctx = Context::new();
  point::init_point_module(&mut ctx);

  let code = r#"
    import('point').then((point)=>{
    let p0 = new point.Point(1,2)
    print("js->",p0.x,p0.y)
    p0.pprint()
    try{
      let p = new point.Point()
      print("js-> p:",p)
      print("js->",p.x,p.y)
      p.x=2
      p.pprint()
    } catch(e) {
      print("An error has been caught");
      print(e)
    }  
    })
  "#;

  ctx.eval_global_str(code);
  ctx.promise_loop_poll();
}

The execution result from the above application is as follows.

rust-> new Point [Int(1), Int(2)]
rust-> get x
rust-> get y
js-> 1 2
rust-> pprint: Point(1, 2)
rust-> new Point []
js-> p: undefined
An error has been caught
TypeError: cannot read property 'x' of undefined

Code reuse

Using the Rust API, we could create JavaScript classes that inherit (or extend) from existing classes. That allows developers to create complex JavaScript APIs using Rust by building on existing solutions. You can see an example here.

Next, you can see the Rust code in the internal_module folder for more examples on how to implement common JavaScript build-in functions including Node.js APIs.

Node.js support

Although WasmEdge QuickJS already supports JavaScript ES6 and CJS modules, many existing apps simply use Node.js built-in APIs. Those APIs are provided by Node.js and do not have corresponding external JavaScript files to rollup into a bundle. In order to reuse existing Node.js apps, we are in the process of implementing many Node.JS APIs for WasmEdge QuickJS. The goal is to have Node.js programs running without change in WasmEdge QuickJS.

The progress of Node.js support in WasmEdge QuickJS is tracked in this issue.

There are two approaches for supporting Node.js APIs in WasmEdge QuickJS.

The JavaScript modules

Some Node.js functions can be implemented in pure JavaScript using the modules approach. For example,

  • The querystring functions just perform string manipulations.
  • The buffer functions manage and encode arrays and memory structures.
  • The encoding and http functions provide a Node.js fetch() API wrapper around JavaScript functions implemented in Rust.

The Rust internal modules

Other Node.js functions must be implemented in Rust using the internal_module approach. There are two reasons for that. First, some Node.js API functions are CPU intensive (e.g., encoding) and is most efficiently implemented in Rust. Second, some Node.js API functions require access to the underlying system (e.g., networking and file system) through native host functions.

Node.js compatibility support in WasmEdge QuickJS is a work in progress. It is a great way for new developers to get familiar with WasmEdge QuickJS. Join us!

Go

The best way to run Go programs in WasmEdge is to compile Go source code to WebAssembly using TinyGo. In this article, we will show you how.

Install TinyGo

You must have Go already installed on your machine before installing TinyGo. Go v1.17 or above is recommended. For Ubuntu or other Debian-based Linux systems on x86 processors, you could use the following command line to install TinyGo. For other platforms, please refer to TinyGo docs.

wget https://github.com/tinygo-org/tinygo/releases/download/v0.21.0/tinygo_0.21.0_amd64.deb
sudo dpkg -i tinygo_0.21.0_amd64.deb`

Next, run the following command line to check out if the installation is successful.

$ tinygo version
tinygo version 0.21.0 linux/amd64 (using go version go1.16.7 and LLVM version 11.0.0)

Hello world

The simple Go app has a main() function to print a message to the console. The source code in main.go file is as follows.

package main

func main() {
  println("Hello TinyGo from WasmEdge!")
}

Inside the main() function, you can use Go standard API to read / write files, and access command line arguments and env variables.

Hello world: Compile and build

Next, compile the main.go program to WebAssembly using TinyGo.

tinygo build -o hello.wasm -target wasi main.go

You will see a file named hello.wasm in the same directory. This is a WebAssembly bytecode file.

Hello world: Run

You can run it with the WasmEdge CLI.

$ wasmedge hello.wasm
Hello TinyGo from WasmEdge!

A simple function

The second example is a Go function that takes a call parameter to compute a fibonacci number. However, in order for the Go application to set up proper access to the OS (e.g., to access the command line arguments), you must include an empty main() function in the source code.

package main

func main(){
}

//export fibArray
func fibArray(n int32) int32{
  arr := make([]int32, n)
  for i := int32(0); i < n; i++ {
    switch {
    case i < 2:
      arr[i] = i
    default:
      arr[i] = arr[i-1] + arr[i-2]
    }
  }
  return arr[n-1]
}

A simple function: Compile and build

Next, compile the main.go program to WebAssembly using TinyGo.

tinygo build -o fib.wasm -target wasi main.go

You will see a file named fib.wasm in the same directory. This is a WebAssembly bytecode file.

A simple function: Run

You can run it with the WasmEdge CLI in its --reactor mode. The command line arguments that follow the wasm file are the function name and its call parameters.

$ wasmedge --reactor fib.wasm fibArray 10
34

Improve performance

To achieve native Go performance for those applications, you could use the wasmedgec command to AOT compile the wasm program, and then run it with the wasmedge command.

$ wasmedgec hello.wasm hello.wasm

$ wasmedge hello.wasm
Hello TinyGo from WasmEdge!

For the --reactor mode,

$ wasmedgec fib.wasm fib.wasm

$ wasmedge --reactor fib.wasm fibArray 10
34

Swift

The swiftwasm project compiles Swift source code to WebAssembly.

AssemblyScript

AssemblyScript is a TypeScript-like language designed for WebAssembly. AssemblyScript programs can be easily compiled into WebAssembly.

Kotlin

Check out how to compile Kotlin programs to WebAssembly

Grain

Grain is a strongly typed languages designed for WebAssembly. Checkout its Hello world example.

Python

There are already several different language implementations of the Python runtime, and some of them support WebAssembly. This document will describe how to run RustPython on WasmEdge to execute Python programs.

Compile RustPython

To compile RustPython, you should have the Rust toolchain installed on your machine. And wasm32-wasi platform support should be enabled.

rustup target add wasm32-wasi

Then you could use the following command to clone and compile RustPython:

git clone https://github.com/RustPython/RustPython.git
cd RustPython
cargo build --release --target wasm32-wasi --features="freeze-stdlib"

freeze-stdlib feature is enabled for including Python standard library inside the binary file. The output file should be able at target/wasm32-wasi/release/rustpython.wasm.

AOT Compile

WasmEdge supports compiling WebAssembly bytecode programs into native machine code for better performance. It is highly recommended to compile the RustPython to native machine code before running.

wasmedgec ./target/wasm32-wasi/release/rustpython.wasm ./target/wasm32-wasi/release/rustpython.wasm

Since WasmEdge 0.9.0, the universal Wasm binary format has been introduced. So you could use the .wasm extension for generating cross-runtime compatible format, or use .so for Linux shared library format.

Run

wasmedge ./target/wasm32-wasi/release/rustpython.wasm

Then you could get a Python shell in WebAssembly!

Grant file system access

You can pre-open directories to let WASI programs have permission to read and write files stored on the real machine. The following command mounted the current working directory to the WASI virtual file system.

wasmedge --dir .:. ./target/wasm32-wasi/release/rustpython.wasm

Embed WasmEdge functions

A common use case for WasmEdge is to embed it in your own applications (called a host application). It allows you to support 3rd party plug-ins and extensions for your applications. Those plug-ins and extensions could be written in any of the languages WasmEdge supports, and by anyone as they are safely and securely executed in the WasmEdge sandbox.

In this chapter, we will discuss how to use WasmEdge SDKs to embed WasmEdge programs into C, Rust, Go, and Python host applications.

WasmEdge C SDK

The WasmEdge C API denotes an interface to embed the WasmEdge runtime into a C program. The followings are the quick start guides to working with the C APIs of WasmEdge. For the details of the WasmEdge C API, please refer to the full documentation.

The WasmEdge C API also the fundamental API for other languages' SDK.

Quick Start Guide for the WasmEdge runner

The following is an example for running a WASM file. Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test_wasmedge.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main(int Argc, const char* Argv[]) {
  /* Create the configure context and add the WASI support. */
  /* This step is not necessary unless you need WASI support. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
  /* The configure and store context to the VM creation can be NULL. */
  WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);

  /* The parameters and returns arrays. */
  WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(32) };
  WasmEdge_Value Returns[1];
  /* Function name. */
  WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
  /* Run the WASM function from file. */
  WasmEdge_Result Res = WasmEdge_VMRunWasmFromFile(VMCxt, Argv[1], FuncName, Params, 1, Returns, 1);

  if (WasmEdge_ResultOK(Res)) {
    printf("Get result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
  } else {
    printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
  }

  /* Resources deallocations. */
  WasmEdge_VMDelete(VMCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  WasmEdge_StringDelete(FuncName);
  return 0;
}

Then you can compile and run: (the 32th fibonacci number is 3524578 in 0-based index)

$ gcc test_wasmedge.c -lwasmedge_c -o test_wasmedge
$ ./test_wasmedge fibonacci.wasm
Get result: 3524578

Quick Start Guide for the WasmEdge AOT compiler

Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test_wasmedge_compiler.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main(int Argc, const char* Argv[]) {
  /* Create the configure context. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  /* ... Adjust settings in the configure context. */
  /* Result. */
  WasmEdge_Result Res;

  /* Create the compiler context. The configure context can be NULL. */
  WasmEdge_CompilerContext *CompilerCxt = WasmEdge_CompilerCreate(ConfCxt);
  /* Compile the WASM file with input and output paths. */
  Res = WasmEdge_CompilerCompile(CompilerCxt, Argv[1], Argv[2]);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Compilation failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }

  WasmEdge_CompilerDelete(CompilerCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  return 0;
}

Then you can compile and run (the output file is fibonacci.wasm.so):

$ gcc test_wasmedge_compiler.c -lwasmedge_c -o test_wasmedge_compiler
$ ./test_wasmedge_compiler fibonacci.wasm fibonacci.wasm.so
[2021-07-02 11:08:08.651] [info] compile start
[2021-07-02 11:08:08.653] [info] verify start
[2021-07-02 11:08:08.653] [info] optimize start
[2021-07-02 11:08:08.670] [info] codegen start
[2021-07-02 11:08:08.706] [info] compile done

The compiled-WASM file can be used as a WASM input for the WasmEdge runner. The following is the comparison of the interpreter mode and the AOT mode:

$ time ./test_wasmedge fibonacci.wasm
Get result: 5702887

real 0m2.715s
user 0m2.700s
sys 0m0.008s

$ time ./test_wasmedge fibonacci.wasm.so
Get result: 5702887

real 0m0.036s
user 0m0.022s
sys 0m0.011s

WasmEdge C 0.10.0 API Documentation

WasmEdge C API denotes an interface to access the WasmEdge runtime. The followings are the guides to working with the C APIs of WasmEdge.

This document is for the 0.10.0 version. For the older 0.9.1 version, please refer to the document here.

Developers can refer here to upgrade to 0.10.0.

Table of Contents

WasmEdge Installation

Download And Install

The easiest way to install WasmEdge is to run the following command. Your system should have git and wget as prerequisites.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.10.0

For more details, please refer to the Installation Guide for the WasmEdge installation.

Compile Sources

After the installation of WasmEdge, the following guide can help you to test for the availability of the WasmEdge C API.

  1. Prepare the test C file (and assumed saved as test.c):

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      printf("WasmEdge version: %s\n", WasmEdge_VersionGet());
      return 0;
    }
    
  2. Compile the file with gcc or clang.

    gcc test.c -lwasmedge_c
    
  3. Run and get the expected output.

    $ ./a.out
    WasmEdge version: 0.10.0
    

WasmEdge Basics

In this part, we will introduce the utilities and concepts of WasmEdge shared library.

Version

The Version related APIs provide developers to check for the WasmEdge shared library version.

#include <wasmedge/wasmedge.h>
printf("WasmEdge version: %s\n", WasmEdge_VersionGet());
printf("WasmEdge version major: %u\n", WasmEdge_VersionGetMajor());
printf("WasmEdge version minor: %u\n", WasmEdge_VersionGetMinor());
printf("WasmEdge version patch: %u\n", WasmEdge_VersionGetPatch());

Logging Settings

The WasmEdge_LogSetErrorLevel() and WasmEdge_LogSetDebugLevel() APIs can set the logging system to debug level or error level. By default, the error level is set, and the debug info is hidden.

Value Types

In WasmEdge, developers should convert the values to WasmEdge_Value objects through APIs for matching to the WASM value types.

  1. Number types: i32, i64, f32, f64, and v128 for the SIMD proposal

    WasmEdge_Value Val;
    Val = WasmEdge_ValueGenI32(123456);
    printf("%d\n", WasmEdge_ValueGetI32(Val));
    /* Will print "123456" */
    Val = WasmEdge_ValueGenI64(1234567890123LL);
    printf("%ld\n", WasmEdge_ValueGetI64(Val));
    /* Will print "1234567890123" */
    Val = WasmEdge_ValueGenF32(123.456f);
    printf("%f\n", WasmEdge_ValueGetF32(Val));
    /* Will print "123.456001" */
    Val = WasmEdge_ValueGenF64(123456.123456789);
    printf("%.10f\n", WasmEdge_ValueGetF64(Val));
    /* Will print "123456.1234567890" */
    
  2. Reference types: funcref and externref for the Reference-Types proposal

    WasmEdge_Value Val;
    void *Ptr;
    bool IsNull;
    uint32_t Num = 10;
    /* Genreate a externref to NULL. */
    Val = WasmEdge_ValueGenNullRef(WasmEdge_RefType_ExternRef);
    IsNull = WasmEdge_ValueIsNullRef(Val);
    /* The `IsNull` will be `TRUE`. */
    Ptr = WasmEdge_ValueGetExternRef(Val);
    /* The `Ptr` will be `NULL`. */
    
    /* Get the function instance by creation or from module instance. */
    const WasmEdge_FunctionInstanceContext *FuncCxt = ...;
    /* Genreate a funcref with the given function instance context. */
    Val = WasmEdge_ValueGenFuncRef(FuncCxt);
    const WasmEdge_FunctionInstanceContext *GotFuncCxt = WasmEdge_ValueGetFuncRef(Val);
    /* The `GotFuncCxt` will be the same as `FuncCxt`. */
    
    /* Genreate a externref to `Num`. */
    Val = WasmEdge_ValueGenExternRef(&Num);
    Ptr = WasmEdge_ValueGetExternRef(Val);
    /* The `Ptr` will be `&Num`. */
    printf("%u\n", *(uint32_t *)Ptr);
    /* Will print "10" */
    Num += 55;
    printf("%u\n", *(uint32_t *)Ptr);
    /* Will print "65" */
    

Strings

The WasmEdge_String object is for the instance names when invoking a WASM function or finding the contexts of instances.

  1. Create a WasmEdge_String from a C string (const char * with NULL termination) or a buffer with length.

    The content of the C string or buffer will be copied into the WasmEdge_String object.

    char Buf[4] = {50, 55, 60, 65};
    WasmEdge_String Str1 = WasmEdge_StringCreateByCString("test");
    WasmEdge_String Str2 = WasmEdge_StringCreateByBuffer(Buf, 4);
    /* The objects should be deleted by `WasmEdge_StringDelete()`. */
    WasmEdge_StringDelete(Str1);
    WasmEdge_StringDelete(Str2);
    
  2. Wrap a WasmEdge_String to a buffer with length.

    The content will not be copied, and the caller should guarantee the life cycle of the input buffer.

    const char CStr[] = "test";
    WasmEdge_String Str = WasmEdge_StringWrap(CStr, 4);
    /* The object should __NOT__ be deleted by `WasmEdge_StringDelete()`. */
    
  3. String comparison

    const char CStr[] = "abcd";
    char Buf[4] = {0x61, 0x62, 0x63, 0x64};
    WasmEdge_String Str1 = WasmEdge_StringWrap(CStr, 4);
    WasmEdge_String Str2 = WasmEdge_StringCreateByBuffer(Buf, 4);
    bool IsEq = WasmEdge_StringIsEqual(Str1, Str2);
    /* The `IsEq` will be `TRUE`. */
    WasmEdge_StringDelete(Str2);
    
  4. Convert to C string

    char Buf[256];
    WasmEdge_String Str = WasmEdge_StringCreateByCString("test_wasmedge_string");
    uint32_t StrLength = WasmEdge_StringCopy(Str, Buf, sizeof(Buf));
    /* StrLength will be 20 */
    printf("String: %s\n", Buf);
    /* Will print "test_wasmedge_string". */
    

Results

The WasmEdge_Result object specifies the execution status. APIs about WASM execution will return the WasmEdge_Result to denote the status.

WasmEdge_Result Res = WasmEdge_Result_Success;
bool IsSucceeded = WasmEdge_ResultOK(Res);
/* The `IsSucceeded` will be `TRUE`. */
uint32_t Code = WasmEdge_ResultGetCode(Res);
/* The `Code` will be 0. */
const char *Msg = WasmEdge_ResultGetMessage(Res);
/* The `Msg` will be "success". */

Contexts

The objects, such as VM, Store, and Function, are composed of Contexts. All of the contexts can be created by calling the corresponding creation APIs and should be destroyed by calling the corresponding deletion APIs. Developers have responsibilities to manage the contexts for memory management.

/* Create the configure context. */
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
/* Delete the configure context. */
WasmEdge_ConfigureDelete(ConfCxt);

The details of other contexts will be introduced later.

WASM Data Structures

The WASM data structures are used for creating instances or can be queried from instance contexts. The details of instances creation will be introduced in the Instances.

  1. Limit

    The WasmEdge_Limit struct is defined in the header:

    /// Struct of WASM limit.
    typedef struct WasmEdge_Limit {
      /// Boolean to describe has max value or not.
      bool HasMax;
      /// Minimum value.
      uint32_t Min;
      /// Maximum value. Will be ignored if the `HasMax` is false.
      uint32_t Max;
    } WasmEdge_Limit;
    

    Developers can initialize the struct by assigning it's value, and the Max value is needed to be larger or equal to the Min value. The API WasmEdge_LimitIsEqual() is provided to compare with 2 WasmEdge_Limit structs.

  2. Function type context

    The Function Type context is used for the Function creation, checking the value types of a Function instance, or getting the function type with function name from VM. Developers can use the Function Type context APIs to get the parameter or return value types information.

    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I64 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_FuncRef };
    WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    
    enum WasmEdge_ValType Buf[16];
    uint32_t ParamLen = WasmEdge_FunctionTypeGetParametersLength(FuncTypeCxt);
    /* `ParamLen` will be 2. */
    uint32_t GotParamLen = WasmEdge_FunctionTypeGetParameters(FuncTypeCxt, Buf, 16);
    /* `GotParamLen` will be 2, and `Buf[0]` and `Buf[1]` will be the same as `ParamList`. */
    uint32_t ReturnLen = WasmEdge_FunctionTypeGetReturnsLength(FuncTypeCxt);
    /* `ReturnLen` will be 1. */
    uint32_t GotReturnLen = WasmEdge_FunctionTypeGetReturns(FuncTypeCxt, Buf, 16);
    /* `GotReturnLen` will be 1, and `Buf[0]` will be the same as `ReturnList`. */
    
    WasmEdge_FunctionTypeDelete(FuncTypeCxt);
    
  3. Table type context

    The Table Type context is used for Table instance creation or getting information from Table instances.

    WasmEdge_Limit TabLim = {.HasMax = true, .Shared = false, .Min = 10, .Max = 20};
    WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_TableTypeCreate(WasmEdge_RefType_ExternRef, TabLim);
    
    enum WasmEdge_RefType GotRefType = WasmEdge_TableTypeGetRefType(TabTypeCxt);
    /* `GotRefType` will be WasmEdge_RefType_ExternRef. */
    WasmEdge_Limit GotTabLim = WasmEdge_TableTypeGetLimit(TabTypeCxt);
    /* `GotTabLim` will be the same value as `TabLim`. */
    
    WasmEdge_TableTypeDelete(TabTypeCxt);
    
  4. Memory type context

    The Memory Type context is used for Memory instance creation or getting information from Memory instances.

    WasmEdge_Limit MemLim = {.HasMax = true, .Shared = false, .Min = 10, .Max = 20};
    WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_MemoryTypeCreate(MemLim);
    
    WasmEdge_Limit GotMemLim = WasmEdge_MemoryTypeGetLimit(MemTypeCxt);
    /* `GotMemLim` will be the same value as `MemLim`. */
    
    WasmEdge_MemoryTypeDelete(MemTypeCxt)
    
  5. Global type context

    The Global Type context is used for Global instance creation or getting information from Global instances.

    WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_GlobalTypeCreate(WasmEdge_ValType_F64, WasmEdge_Mutability_Var);
    
    WasmEdge_ValType GotValType = WasmEdge_GlobalTypeGetValType(GlobTypeCxt);
    /* `GotValType` will be WasmEdge_ValType_F64. */
    WasmEdge_Mutability GotValMut = WasmEdge_GlobalTypeGetMutability(GlobTypeCxt);
    /* `GotValMut` will be WasmEdge_Mutability_Var. */
    
    WasmEdge_GlobalTypeDelete(GlobTypeCxt);
    
  6. Import type context

    The Import Type context is used for getting the imports information from a AST Module. Developers can get the external type (function, table, memory, or global), import module name, and external name from an Import Type context. The details about querying Import Type contexts will be introduced in the AST Module.

    WasmEdge_ASTModuleContext *ASTCxt = ...;
    /* Assume that `ASTCxt` is returned by the `WasmEdge_LoaderContext` for the result of loading a WASM file. */
    const WasmEdge_ImportTypeContext *ImpType = ...;
    /* Assume that `ImpType` is queried from the `ASTCxt` for the import. */
    
    enum WasmEdge_ExternalType ExtType = WasmEdge_ImportTypeGetExternalType(ImpType);
    /*
     * The `ExtType` can be one of `WasmEdge_ExternalType_Function`, `WasmEdge_ExternalType_Table`,
     * `WasmEdge_ExternalType_Memory`, or `WasmEdge_ExternalType_Global`.
     */
    WasmEdge_String ModName = WasmEdge_ImportTypeGetModuleName(ImpType);
    WasmEdge_String ExtName = WasmEdge_ImportTypeGetExternalName(ImpType);
    /* The `ModName` and `ExtName` should not be destroyed and the string buffers are binded into the `ASTCxt`. */
    const WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_ImportTypeGetFunctionType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Function`, the `FuncTypeCxt` will be NULL. */
    const WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_ImportTypeGetTableType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Table`, the `TabTypeCxt` will be NULL. */
    const WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_ImportTypeGetMemoryType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Memory`, the `MemTypeCxt` will be NULL. */
    const WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_ImportTypeGetGlobalType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Global`, the `GlobTypeCxt` will be NULL. */
    
  7. Export type context

    The Export Type context is used for getting the exports information from a AST Module. Developers can get the external type (function, table, memory, or global) and external name from an Export Type context. The details about querying Export Type contexts will be introduced in the AST Module.

    WasmEdge_ASTModuleContext *ASTCxt = ...;
    /* Assume that `ASTCxt` is returned by the `WasmEdge_LoaderContext` for the result of loading a WASM file. */
    const WasmEdge_ExportTypeContext *ExpType = ...;
    /* Assume that `ExpType` is queried from the `ASTCxt` for the export. */
    
    enum WasmEdge_ExternalType ExtType = WasmEdge_ExportTypeGetExternalType(ExpType);
    /*
     * The `ExtType` can be one of `WasmEdge_ExternalType_Function`, `WasmEdge_ExternalType_Table`,
     * `WasmEdge_ExternalType_Memory`, or `WasmEdge_ExternalType_Global`.
     */
    WasmEdge_String ExtName = WasmEdge_ExportTypeGetExternalName(ExpType);
    /* The `ExtName` should not be destroyed and the string buffer is binded into the `ASTCxt`. */
    const WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_ExportTypeGetFunctionType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Function`, the `FuncTypeCxt` will be NULL. */
    const WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_ExportTypeGetTableType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Table`, the `TabTypeCxt` will be NULL. */
    const WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_ExportTypeGetMemoryType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Memory`, the `MemTypeCxt` will be NULL. */
    const WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_ExportTypeGetGlobalType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Global`, the `GlobTypeCxt` will be NULL. */
    

Async

After calling the asynchronous execution APIs, developers will get the WasmEdge_Async object. Developers own the object and should call the WasmEdge_AsyncDelete() API to destroy it.

  1. Wait for the asynchronous execution

    Developers can wait the execution until finished:

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution. */
    WasmEdge_AsyncWait(Async);
    WasmEdge_AsyncDelete(Async);
    

    Or developers can wait for a time limit. If the time limit exceeded, developers can choose to cancel the execution. For the interruptible execution in AOT mode, developers should set TRUE thourgh the WasmEdge_ConfigureCompilerSetInterruptible() API into the configure context for the AOT compiler.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution for 1 second. */
    bool IsEnd = WasmEdge_AsyncWaitFor(Async, 1000);
    if (IsEnd) {
      /* The execution finished. Developers can get the result. */
      WasmEdge_Result Res = WasmEdge_AsyncGet(/* ... Ignored */);
    } else {
      /* The time limit exceeded. Developers can keep waiting or cancel the execution. */
      WasmEdge_AsyncCancel(Async);
      WasmEdge_Result Res = WasmEdge_AsyncGet(Async, 0, NULL);
      /* The result error code will be `WasmEdge_ErrCode_Interrupted`. */
    }
    WasmEdge_AsyncDelete(Async);
    
  2. Get the execution result of the asynchronous execution

    Developers can use the WasmEdge_AsyncGetReturnsLength() API to get the return value list length. This function will block and wait for the execution. If the execution has finished, this function will return the length immediately. If the execution failed, this function will return 0. This function can help the developers to create the buffer to get the return values. If developers have already known the buffer length, they can skip this function and use the WasmEdge_AsyncGet() API to get the result.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution and get the return value list length. */
    uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
    WasmEdge_AsyncDelete(Async);
    

    The WasmEdge_AsyncGet() API will block and wait for the execution. If the execution has finished, this function will fill the return values into the buffer and return the execution result immediately.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution and get the return values. */
    const uint32_t BUF_LEN = 256;
    WasmEdge_Value Buf[BUF_LEN];
    WasmEdge_Result Res = WasmEdge_AsyncGet(Async, Buf, BUF_LEN);
    WasmEdge_AsyncDelete(Async);
    

Configurations

The configuration context, WasmEdge_ConfigureContext, manages the configurations for Loader, Validator, Executor, VM, and Compiler. Developers can adjust the settings about the proposals, VM host pre-registrations (such as WASI), and AOT compiler options, and then apply the Configure context to create other runtime contexts.

  1. Proposals

    WasmEdge supports turning on or off the WebAssembly proposals. This configuration is effective in any contexts created with the Configure context.

    enum WasmEdge_Proposal {
      WasmEdge_Proposal_ImportExportMutGlobals = 0,
      WasmEdge_Proposal_NonTrapFloatToIntConversions,
      WasmEdge_Proposal_SignExtensionOperators,
      WasmEdge_Proposal_MultiValue,
      WasmEdge_Proposal_BulkMemoryOperations,
      WasmEdge_Proposal_ReferenceTypes,
      WasmEdge_Proposal_SIMD,
      WasmEdge_Proposal_TailCall,
      WasmEdge_Proposal_MultiMemories,
      WasmEdge_Proposal_Annotations,
      WasmEdge_Proposal_Memory64,
      WasmEdge_Proposal_ExceptionHandling,
      WasmEdge_Proposal_ExtendedConst,
      WasmEdge_Proposal_Threads,
      WasmEdge_Proposal_FunctionReferences
    };
    

    Developers can add or remove the proposals into the Configure context.

    /* 
     * By default, the following proposals have turned on initially:
     * * Import/Export of mutable globals
     * * Non-trapping float-to-int conversions
     * * Sign-extension operators
     * * Multi-value returns
     * * Bulk memory operations
     * * Reference types
     * * Fixed-width SIMD
     *
     * For the current WasmEdge version, the following proposals are supported
     * (turned of by default) additionally:
     * * Tail-call
     * * Multiple memories
     * * Extended-const
     */
    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddProposal(ConfCxt, WasmEdge_Proposal_MultiMemories);
    WasmEdge_ConfigureRemoveProposal(ConfCxt, WasmEdge_Proposal_ReferenceTypes);
    bool IsBulkMem = WasmEdge_ConfigureHasProposal(ConfCxt, WasmEdge_Proposal_BulkMemoryOperations);
    /* The `IsBulkMem` will be `TRUE`. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  2. Host registrations

    This configuration is used for the VM context to turn on the WASI or wasmedge_process supports and only effective in VM contexts.

    enum WasmEdge_HostRegistration {
      WasmEdge_HostRegistration_Wasi = 0,
      WasmEdge_HostRegistration_WasmEdge_Process
    };
    

    The details will be introduced in the preregistrations of VM context.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    bool IsHostWasi = WasmEdge_ConfigureHasHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    /* The `IsHostWasi` will be `FALSE`. */
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    IsHostWasi = WasmEdge_ConfigureHasHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    /* The `IsHostWasi` will be `TRUE`. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  3. Maximum memory pages

    Developers can limit the page size of memory instances by this configuration. When growing the page size of memory instances in WASM execution and exceeding the limited size, the page growing will fail. This configuration is only effective in the Executor and VM contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    uint32_t PageSize = WasmEdge_ConfigureGetMaxMemoryPage(ConfCxt);
    /* By default, the maximum memory page size is 65536. */
    WasmEdge_ConfigureSetMaxMemoryPage(ConfCxt, 1024);
    /* Limit the memory size of each memory instance with not larger than 1024 pages (64 MiB). */
    PageSize = WasmEdge_ConfigureGetMaxMemoryPage(ConfCxt);
    /* The `PageSize` will be 1024. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  4. AOT compiler options

    The AOT compiler options configure the behavior about optimization level, output format, dump IR, and generic binary.

    enum WasmEdge_CompilerOptimizationLevel {
      // Disable as many optimizations as possible.
      WasmEdge_CompilerOptimizationLevel_O0 = 0,
      // Optimize quickly without destroying debuggability.
      WasmEdge_CompilerOptimizationLevel_O1,
      // Optimize for fast execution as much as possible without triggering
      // significant incremental compile time or code size growth.
      WasmEdge_CompilerOptimizationLevel_O2,
      // Optimize for fast execution as much as possible.
      WasmEdge_CompilerOptimizationLevel_O3,
      // Optimize for small code size as much as possible without triggering
      // significant incremental compile time or execution time slowdowns.
      WasmEdge_CompilerOptimizationLevel_Os,
      // Optimize for small code size as much as possible.
      WasmEdge_CompilerOptimizationLevel_Oz
    };
    
    enum WasmEdge_CompilerOutputFormat {
      // Native dynamic library format.
      WasmEdge_CompilerOutputFormat_Native = 0,
      // WebAssembly with AOT compiled codes in custom section.
      WasmEdge_CompilerOutputFormat_Wasm
    };
    

    These configurations are only effective in Compiler contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    /* By default, the optimization level is O3. */
    WasmEdge_ConfigureCompilerSetOptimizationLevel(ConfCxt, WasmEdge_CompilerOptimizationLevel_O2);
    /* By default, the output format is universal WASM. */
    WasmEdge_ConfigureCompilerSetOutputFormat(ConfCxt, WasmEdge_CompilerOutputFormat_Native);
    /* By default, the dump IR is `FALSE`. */
    WasmEdge_ConfigureCompilerSetDumpIR(ConfCxt, TRUE);
    /* By default, the generic binary is `FALSE`. */
    WasmEdge_ConfigureCompilerSetGenericBinary(ConfCxt, TRUE);
    /* By default, the interruptible is `FALSE`.
    /* Set this option to `TRUE` to support the interruptible execution in AOT mode. */
    WasmEdge_ConfigureCompilerSetInterruptible(ConfCxt, TRUE);
    WasmEdge_ConfigureDelete(ConfCxt);
    
  5. Statistics options

    The statistics options configure the behavior about instruction counting, cost measuring, and time measuring in both runtime and AOT compiler. These configurations are effective in Compiler, VM, and Executor contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    /* By default, the intruction counting is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetInstructionCounting(ConfCxt, TRUE);
    /* By default, the cost measurement is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetCostMeasuring(ConfCxt, TRUE);
    /* By default, the time measurement is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetTimeMeasuring(ConfCxt, TRUE);
    WasmEdge_ConfigureDelete(ConfCxt);
    

Statistics

The statistics context, WasmEdge_StatisticsContext, provides the instruction counter, cost summation, and cost limitation at runtime.

Before using statistics, the statistics configuration must be set. Otherwise, the return values of calling statistics are undefined behaviour.

  1. Instruction counter

    The instruction counter can help developers to profile the performance of WASM running. Developers can retrieve the Statistics context from the VM context, or create a new one for the Executor creation. The details will be introduced in the next partitions.

    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /*
     * ...
     * After running the WASM functions with the `Statistics` context
     */
    uint32_t Count = WasmEdge_StatisticsGetInstrCount(StatCxt);
    double IPS = WasmEdge_StatisticsGetInstrPerSecond(StatCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    
  2. Cost table

    The cost table is to accumulate the cost of instructions with their weights. Developers can set the cost table array (the indices are the byte code value of instructions, and the values are the cost of instructions) into the Statistics context. If the cost limit value is set, the execution will return the cost limit exceeded error immediately when exceeds the cost limit in runtime.

    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    uint64_t CostTable[16] = {
      0, 0,
      10, /* 0x02: Block */
      11, /* 0x03: Loop */
      12, /* 0x04: If */
      12, /* 0x05: Else */
      0, 0, 0, 0, 0, 0, 
      20, /* 0x0C: Br */
      21, /* 0x0D: Br_if */
      22, /* 0x0E: Br_table */
      0
    };
    /* Developers can set the costs of each instruction. The value not covered will be 0. */
    WasmEdge_StatisticsSetCostTable(StatCxt, CostTable, 16);
    WasmEdge_StatisticsSetCostLimit(StatCxt, 5000000);
    /*
     * ...
     * After running the WASM functions with the `Statistics` context
     */
    uint64_t Cost = WasmEdge_StatisticsGetTotalCost(StatCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    

WasmEdge VM

In this partition, we will introduce the functions of WasmEdge_VMContext object and show examples of executing WASM functions.

WASM Execution Example With VM Context

The following shows the example of running the WASM for getting the Fibonacci. This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

(module
  (export "fib" (func $fib))
  (func $fib (param $n i32) (result i32)
    (if
      (i32.lt_s (get_local $n)(i32.const 2))
      (return (i32.const 1))
    )
    (return
      (i32.add
        (call $fib (i32.sub (get_local $n)(i32.const 2)))
        (call $fib (i32.sub (get_local $n)(i32.const 1)))
      )
    )
  )
)
  1. Run WASM functions rapidly

    Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the configure context and add the WASI support. */
      /* This step is not necessary unless you need WASI support. */
      WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
      WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
      /* The configure and store context to the VM creation can be NULL. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(5) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromFile(VMCxt, "fibonacci.wasm", FuncName, Params, 1, Returns, 1);
      /* 
       * Developers can run the WASM binary from buffer with the `WasmEdge_VMRunWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRunWasmFromASTModule()` API.
       */
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_ConfigureDelete(ConfCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 5th Fibonacci number is 8 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 8
    
  2. Instantiate and run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM context APIs:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the configure context and add the WASI support. */
      /* This step is not necessary unless you need the WASI support. */
      WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
      WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
      /* The configure and store context to the VM creation can be NULL. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(10) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
      
      /* Step 1: Load WASM file. */
      Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      /* 
       * Developers can load the WASM binary from buffer with the `WasmEdge_VMLoadWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMLoadWasmFromASTModule()` API.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 2: Validate the WASM module. */
      Res = WasmEdge_VMValidate(VMCxt);
      if (!WasmEdge_ResultOK(Res)) {
        printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 3: Instantiate the WASM module. */
      Res = WasmEdge_VMInstantiate(VMCxt);
      /* 
       * Developers can load, validate, and instantiate another WASM module to replace the
       * instantiated one. In this case, the old module will be cleared, but the registered
       * modules are still kept.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 4: Execute WASM functions. You can execute functions repeatedly after instantiation. */
      Res = WasmEdge_VMExecute(VMCxt, FuncName, Params, 1, Returns, 1);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_ConfigureDelete(ConfCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 10th Fibonacci number is 89 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 89
    

    The following graph explains the status of the VM context.

                           |========================|
                  |------->|      VM: Initiated     |
                  |        |========================|
                  |                    |
                  |                 LoadWasm
                  |                    |
                  |                    v
                  |        |========================|
                  |--------|       VM: Loaded       |<-------|
                  |        |========================|        |
                  |              |            ^              |
                  |         Validate          |              |
              Cleanup            |          LoadWasm         |
                  |              v            |            LoadWasm
                  |        |========================|        |
                  |--------|      VM: Validated     |        |
                  |        |========================|        |
                  |              |            ^              |
                  |      Instantiate          |              |
                  |              |          RegisterModule   |
                  |              v            |              |
                  |        |========================|        |
                  |--------|    VM: Instantiated    |--------|
                           |========================|
                                 |            ^
                                 |            |
                                 --------------
                    Instantiate, Execute, ExecuteRegistered
    

    The status of the VM context would be Inited when created. After loading WASM successfully, the status will be Loaded. After validating WASM successfully, the status will be Validated. After instantiating WASM successfully, the status will be Instantiated, and developers can invoke functions. Developers can register WASM or module instances in any status, but they should instantiate WASM again. Developers can also load WASM in any status, and they should validate and instantiate the WASM module before function invocation. When in the Instantiated status, developers can instantiate the WASM module again to reset the old WASM runtime structures.

VM Creations

The VM creation API accepts the Configure context and the Store context. If developers only need the default settings, just pass NULL to the creation API. The details of the Store context will be introduced in Store.

WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, StoreCxt);
/* The caller should guarantee the life cycle if the store context. */
WasmEdge_StatisticsContext *StatCxt = WasmEdge_VMGetStatisticsContext(VMCxt);
/* The VM context already contains the statistics context and can be retrieved by this API. */
/* 
 * Note that the retrieved store and statistics contexts from the VM contexts by VM APIs
 * should __NOT__ be destroyed and owned by the VM contexts.
 */
WasmEdge_VMDelete(VMCxt);
WasmEdge_StoreDelete(StoreCxt);
WasmEdge_ConfigureDelete(ConfCxt);

Preregistrations

WasmEdge provides the following built-in pre-registrations.

  1. WASI (WebAssembly System Interface)

    Developers can turn on the WASI support for VM in the Configure context.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    /* The following API can retrieve the pre-registration module instances from the VM context. */
    /* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
    WasmEdge_ModuleInstanceContext *WasiModule =
      WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
    /* Initialize the WASI. */
    WasmEdge_ModuleInstanceInitWASI(WasiModule, /* ... ignored */ );
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_ConfigureDelete(ConfCxt);
    

    And also can create the WASI module instance from API. The details will be introduced in the Host Functions and the Host Module Registrations.

  2. WasmEdge_Process

    This pre-registration is for the process interface for WasmEdge on Rust sources. After turning on this pre-registration, the VM will support the wasmedge_process host functions.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_WasmEdge_Process);
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    /* The following API can retrieve the pre-registration module instances from the VM context. */
    /* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
    WasmEdge_ModuleInstanceContext *ProcModule =
      WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_WasmEdge_Process);
    /* Initialize the WasmEdge_Process. */
    WasmEdge_ModuleInstanceInitWasmEdgeProcess(ProcModule, /* ... ignored */ );
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_ConfigureDelete(ConfCxt);
    

    And also can create the WasmEdge_Process module instance from API. The details will be introduced in the Host Functions and the Host Module Registrations.

Host Module Registrations

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, the host functions are composed into host modules as WasmEdge_ModuleInstanceContext objects with module names. Please refer to the Host Functions in WasmEdge Runtime for the details. In this chapter, we show the example for registering the host modules into a VM context.

WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_ModuleInstanceContext *WasiModule =
  WasmEdge_ModuleInstanceCreateWASI( /* ... ignored ... */ );
/* You can also create and register the WASI host modules by this API. */
WasmEdge_Result Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, WasiModule);
/* The result status should be checked. */

/* ... */

WasmEdge_ModuleInstanceDelete(WasiModule);
/* The created module instances should be deleted by the developers when the VM deallocation. */
WasmEdge_VMDelete(VMCxt);

WASM Registrations And Executions

In WebAssembly, the instances in WASM modules can be exported and can be imported by other WASM modules. WasmEdge VM provides APIs for developers to register and export any WASM modules, and execute the functions or host functions in the registered WASM modules.

  1. Register the WASM modules with exported module names

    Unless the module instances have already contained the module names, every WASM module should be named uniquely when registering. Assume that the WASM file fibonacci.wasm is copied into the current directory.

    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    WasmEdge_Result Res = WasmEdge_VMRegisterModuleFromFile(VMCxt, ModName, "fibonacci.wasm");
    /* 
     * Developers can register the WASM module from buffer with the `WasmEdge_VMRegisterModuleFromBuffer()` API,
     * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRegisterModuleFromASTModule()` API.
     */
    /* 
     * The result status should be checked.
     * The error will occur if the WASM module instantiation failed or the module name conflicts.
     */
    WasmEdge_StringDelete(ModName);
    WasmEdge_VMDelete(VMCxt);
    
  2. Execute the functions in registered WASM modules

    Assume that the C file test.c is as follows:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(20) };
      WasmEdge_Value Returns[1];
      /* Names. */
      WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
    
      /* Register the WASM module into VM. */
      Res = WasmEdge_VMRegisterModuleFromFile(VMCxt, ModName, "fibonacci.wasm");
      /* 
      * Developers can register the WASM module from buffer with the `WasmEdge_VMRegisterModuleFromBuffer()` API,
      * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRegisterModuleFromASTModule()` API.
      */
      if (!WasmEdge_ResultOK(Res)) {
        printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* 
      * The function "fib" in the "fibonacci.wasm" was exported with the module name "mod".
      * As the same as host functions, other modules can import the function `"mod" "fib"`.
      */
    
      /* 
      * Execute WASM functions in registered modules.
      * Unlike the execution of functions, the registered functions can be invoked without
      * `WasmEdge_VMInstantiate()` because the WASM module was instantiated when registering.
      * Developers can also invoke the host functions directly with this API.
      */
      Res = WasmEdge_VMExecuteRegistered(VMCxt, ModName, FuncName, Params, 1, Returns, 1);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
      WasmEdge_StringDelete(ModName);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_VMDelete(VMCxt);
      return 0;
    }
    

    Then you can compile and run: (the 20th Fibonacci number is 89 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 10946
    

Asynchronous Execution

  1. Asynchronously run WASM functions rapidly

    Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(20) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Asynchronously run the WASM function from file and get the `WasmEdge_Async` object. */
      WasmEdge_Async *Async = WasmEdge_VMAsyncRunWasmFromFile(VMCxt, "fibonacci.wasm", FuncName, Params, 1);
      /* 
       * Developers can run the WASM binary from buffer with the `WasmEdge_VMAsyncRunWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMAsyncRunWasmFromASTModule()` API.
       */
    
      /* Wait for the execution. */
      WasmEdge_AsyncWait(Async);
      /*
       * Developers can also use the `WasmEdge_AsyncGetReturnsLength()` or `WasmEdge_AsyncGet()` APIs
       * to wait for the asynchronous execution. These APIs will wait until the execution finished.
       */
    
      /* Check the return values length. */
      uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
      /* The `Arity` should be 1. Developers can skip this step if they have known the return arity. */
    
      /* Get the result. */
      WasmEdge_Result Res = WasmEdge_AsyncGet(Async, Returns, Arity);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_AsyncDelete(Async);
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 20th Fibonacci number is 10946 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 10946
    
  2. Instantiate and asynchronously run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM context APIs:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(25) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
      
      /* Step 1: Load WASM file. */
      Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      /* 
       * Developers can load the WASM binary from buffer with the `WasmEdge_VMLoadWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMLoadWasmFromASTModule()` API.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 2: Validate the WASM module. */
      Res = WasmEdge_VMValidate(VMCxt);
      if (!WasmEdge_ResultOK(Res)) {
        printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 3: Instantiate the WASM module. */
      Res = WasmEdge_VMInstantiate(VMCxt);
      /* 
       * Developers can load, validate, and instantiate another WASM module to replace the
       * instantiated one. In this case, the old module will be cleared, but the registered
       * modules are still kept.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 4: Asynchronously execute the WASM function and get the `WasmEdge_Async` object. */
      WasmEdge_Async *Async = WasmEdge_VMAsyncExecute(VMCxt, FuncName, Params, 1);
      /* 
       * Developers can execute functions repeatedly after instantiation.
       * For invoking the registered functions, you can use the `WasmEdge_VMAsyncExecuteRegistered()` API.
       */
    
      /* Wait and check the return values length. */
      uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
      /* The `Arity` should be 1. Developers can skip this step if they have known the return arity. */
    
      /* Get the result. */
      Res = WasmEdge_AsyncGet(Async, Returns, Arity);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_AsyncDelete(Async);
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
    }
    

    Then you can compile and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 121393
    

Instance Tracing

Sometimes the developers may have requirements to get the instances of the WASM runtime. The VM context supplies the APIs to retrieve the instances.

  1. Store

    If the VM context is created without assigning a Store context, the VM context will allocate and own a Store context.

    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    WasmEdge_StoreContext *StoreCxt = WasmEdge_VMGetStoreContext(VMCxt);
    /* The object should __NOT__ be deleted by `WasmEdge_StoreDelete()`. */
    WasmEdge_VMDelete(VMCxt);
    

    Developers can also create the VM context with a Store context. In this case, developers should guarantee the life cycle of the Store context. Please refer to the Store Contexts for the details about the Store context APIs.

    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, StoreCxt);
    WasmEdge_StoreContext *StoreCxtMock = WasmEdge_VMGetStoreContext(VMCxt);
    /* The `StoreCxt` and the `StoreCxtMock` are the same. */
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_StoreDelete(StoreCxt);
    
  2. List exported functions

    After the WASM module instantiation, developers can use the WasmEdge_VMExecute() API to invoke the exported WASM functions. For this purpose, developers may need information about the exported WASM function list. Please refer to the Instances in runtime for the details about the function types. Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, StoreCxt);
    
      WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      WasmEdge_VMValidate(VMCxt);
      WasmEdge_VMInstantiate(VMCxt);
    
      /* List the exported functions. */
      /* Get the number of exported functions. */
      uint32_t FuncNum = WasmEdge_VMGetFunctionListLength(VMCxt);
      /* Create the name buffers and the function type buffers. */
      const uint32_t BUF_LEN = 256;
      WasmEdge_String FuncNames[BUF_LEN];
      WasmEdge_FunctionTypeContext *FuncTypes[BUF_LEN];
      /* 
       * Get the export function list.
       * If the function list length is larger than the buffer length, the overflowed data will be discarded.
       * The `FuncNames` and `FuncTypes` can be NULL if developers don't need them.
       */
      uint32_t RealFuncNum = WasmEdge_VMGetFunctionList(VMCxt, FuncNames, FuncTypes, BUF_LEN);
    
      for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
        char Buf[BUF_LEN];
        uint32_t Size = WasmEdge_StringCopy(FuncNames[I], Buf, sizeof(Buf));
        printf("Get exported function string length: %u, name: %s\n", Size, Buf);
        /* 
         * The function names should be __NOT__ destroyed.
         * The returned function type contexts should __NOT__ be destroyed.
         */
      }
      return 0;
    }
    

    Then you can compile and run: (the only exported function in fibonacci.wasm is fib)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get exported function string length: 3, name: fib
    

    If developers want to get the exported function names in the registered WASM modules, please retrieve the Store context from the VM context and refer to the APIs of Store Contexts to list the registered functions by the module name.

  3. Get function types

    The VM context provides APIs to find the function type by function name. Please refer to the Instances in runtime for the details about the function types.

    /* 
     * ...
     * Assume that a WASM module is instantiated in `VMCxt`.
     */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    const WasmEdge_FunctionTypeContext *FuncType = WasmEdge_VMGetFunctionType(VMCxt, FuncName);
    /* 
     * Developers can get the function types of functions in the registered modules
     * via the `WasmEdge_VMGetFunctionTypeRegistered()` API with the module name.
     * If the function is not found, these APIs will return `NULL`.
     * The returned function type contexts should __NOT__ be destroyed.
     */
    WasmEdge_StringDelete(FuncName);
    
  4. Get the active module

    After the WASM module instantiation, an anonymous module is instantiated and owned by the VM context. Developers may need to retrieve it to get the instances beyond the module. Then developers can use the WasmEdge_VMGetActiveModule() API to get that anonymous module instance. Please refer to the Module instance for the details about the module instance APIs.

    /* 
     * ...
     * Assume that a WASM module is instantiated in `VMCxt`.
     */
    const WasmEdge_ModuleInstanceContext *ModCxt = WasmEdge_VMGetActiveModule(VMCxt);
    /* 
     * If there's no WASM module instantiated, this API will return `NULL`.
     * The returned module instance context should __NOT__ be destroyed.
     */
    

WasmEdge Runtime

In this partition, we will introduce the objects of WasmEdge runtime manually.

WASM Execution Example Step-By-Step

Besides the WASM execution through the VM context, developers can execute the WASM functions or instantiate WASM modules step-by-step with the Loader, Validator, Executor, and Store contexts. Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main() {
  /* Create the configure context. This step is not necessary because we didn't adjust any setting. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  /* Create the statistics context. This step is not necessary if the statistics in runtime is not needed. */
  WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
  /* Create the store context. The store context is the object to link the modules for imports and exports. */
  WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
  /* Result. */
  WasmEdge_Result Res;

  /* Create the loader context. The configure context can be NULL. */
  WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(ConfCxt);
  /* Create the validator context. The configure context can be NULL. */
  WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(ConfCxt);
  /* Create the executor context. The configure context and the statistics context can be NULL. */
  WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);

  /* Load the WASM file or the compiled-WASM file and convert into the AST module context. */
  WasmEdge_ASTModuleContext *ASTCxt = NULL;
  Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
  if (!WasmEdge_ResultOK(Res)) {
    printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }
  /* Validate the WASM module. */
  Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }
  /* Instantiate the WASM module into store context. */
  WasmEdge_ModuleInstanceContext *ModCxt = NULL;
  Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt, StoreCxt, ASTCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }

  /* Try to list the exported functions of the instantiated WASM module. */
  uint32_t FuncNum = WasmEdge_ModuleInstanceListFunctionLength(ModCxt);
  /* Create the name buffers. */
  const uint32_t BUF_LEN = 256;
  WasmEdge_String FuncNames[BUF_LEN];
  /* If the list length is larger than the buffer length, the overflowed data will be discarded. */
  uint32_t RealFuncNum = WasmEdge_ModuleInstanceListFunction(ModCxt, FuncNames, BUF_LEN);
  for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
    char Buf[BUF_LEN];
    uint32_t Size = WasmEdge_StringCopy(FuncNames[I], Buf, sizeof(Buf));
    printf("Get exported function string length: %u, name: %s\n", Size, Buf);
    /* The function names should __NOT__ be destroyed. */
  }

  /* The parameters and returns arrays. */
  WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(18) };
  WasmEdge_Value Returns[1];
  /* Function name. */
  WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
  /* Find the exported function by function name. */
  WasmEdge_FunctionInstanceContext *FuncCxt = WasmEdge_ModuleInstanceFindFunction(ModCxt, FuncName);
  if (FuncCxt == NULL) {
    printf("Function `fib` not found.\n");
    return 1;
  }
  /* Invoke the WASM fnction. */
  Res = WasmEdge_ExecutorInvoke(ExecCxt, FuncCxt, Params, 1, Returns, 1);
  if (WasmEdge_ResultOK(Res)) {
    printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
  } else {
    printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  }

  /* Resources deallocations. */
  WasmEdge_StringDelete(FuncName);
  WasmEdge_ASTModuleDelete(ASTCxt);
  WasmEdge_ModuleInstanceDelete(ModCxt);
  WasmEdge_LoaderDelete(LoadCxt);
  WasmEdge_ValidatorDelete(ValidCxt);
  WasmEdge_ExecutorDelete(ExecCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  WasmEdge_StoreDelete(StoreCxt);
  WasmEdge_StatisticsDelete(StatCxt);
  return 0;
}

Then you can compile and run: (the 18th Fibonacci number is 4181 in 0-based index)

$ gcc test.c -lwasmedge_c
$ ./a.out
Get exported function string length: 3, name: fib
Get the result: 4181

Loader

The Loader context loads the WASM binary from files or buffers. Both the WASM and the compiled-WASM from the WasmEdge AOT Compiler are supported.

uint8_t Buf[4096];
/* ... Read the WASM code to the buffer. */
uint32_t FileSize = ...;
/* The `FileSize` is the length of the WASM code. */

/* Developers can adjust settings in the configure context. */
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
/* Create the loader context. The configure context can be NULL. */
WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(ConfCxt);

WasmEdge_ASTModuleContext *ASTCxt = NULL;
WasmEdge_Result Res;

/* Load WASM or compiled-WASM from the file. */
Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
/* The output AST module context should be destroyed. */
WasmEdge_ASTModuleDelete(ASTCxt);

/* Load WASM or compiled-WASM from the buffer. */
Res = WasmEdge_LoaderParseFromBuffer(LoadCxt, &ASTCxt, Buf, FileSize);
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
/* The output AST module context should be destroyed. */
WasmEdge_ASTModuleDelete(ASTCxt);

WasmEdge_LoaderDelete(LoadCxt);
WasmEdge_ConfigureDelete(ConfCxt);

Validator

The Validator context can validate the WASM module. Every WASM module should be validated before instantiation.

/* 
 * ...
 * Assume that the `ASTCxt` is the output AST module context from the loader context.
 * Assume that the `ConfCxt` is the configure context.
 */
/* Create the validator context. The configure context can be NULL. */
WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(ConfCxt);
WasmEdge_Result Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
WasmEdge_ValidatorDelete(ValidCxt);

Executor

The Executor context is the executor for both WASM and compiled-WASM. This object should work base on the Store context. For the details of the Store context, please refer to the next chapter.

  1. Instantiate and register an AST module as a named Module instance

    As the same of registering host modules or importing WASM modules in VM contexts, developers can instantiate and register an AST module contexts into the Store context as a named Module instance by the Executor APIs. After the registration, the result Module instance is exported with the given module name and can be linked when instantiating another module. For the details about the Module instances APIs, please refer to the Instances.

    /* 
    * ...
    * Assume that the `ASTCxt` is the output AST module context from the loader context
    * and has passed the validation.
    * Assume that the `ConfCxt` is the configure context.
    */
    /* Create the statistics context. This step is not necessary. */
    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* Create the executor context. The configure and the statistics contexts can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);
    /* Create the store context. The store context is the object to link the modules for imports and exports. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* Result. */
    WasmEdge_Result Res;
    
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    /* The output module instance. */
    WasmEdge_ModuleInstanceContext *ModCxt = NULL;
    /* Register the WASM module into the store with the export module name "mod". */
    Res = WasmEdge_ExecutorRegister(ExecCxt, &ModCxt, StoreCxt, ASTCxt, ModName);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    WasmEdge_StringDelete(ModName);
    
    /* ... */
    
    /* After the execution, the resources should be released. */
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    WasmEdge_StoreDelete(StoreCxt);
    WasmEdge_ModuleInstanceDelete(ModCxt);
    
  2. Register an existing Module instance and export the module name

    Besides instantiating and registering an AST module contexts, developers can register an existing Module instance into the store with exporting the module name (which is in the Module instance already). This case occurs when developers create a Module instance for the host functions and want to register it for linking. For the details about the construction of host functions in Module instances, please refer to the Host Functions.

    /* 
    * ...
    * Assume that the `ASTCxt` is the output AST module context from the loader context
    * and has passed the validation.
    * Assume that the `ConfCxt` is the configure context.
    */
    /* Create the statistics context. This step is not necessary. */
    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* Create the executor context. The configure and the statistics contexts can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);
    /* Create the store context. The store context is the object to link the modules for imports and exports. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* Result. */
    WasmEdge_Result Res;
    
    /* Create a module instance for host functions. */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("host-module");
    WasmEdge_ModuleInstanceContext *HostModCxt = WasmEdge_ModuleInstanceCreate(ModName);
    WasmEdge_StringDelete(ModName);
    /*
     * ...
     * Create and add the host functions, tables, memories, and globals into the module instance.
     */
    
    /* Register the module instance into store with the exported module name. */
    /* The export module name is in the module instance already. */
    Res = WasmEdge_ExecutorRegisterImport(ExecCxt, StoreCxt, HostModCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    
    /* ... */
    
    /* After the execution, the resources should be released. */
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    WasmEdge_StoreDelete(StoreCxt);
    WasmEdge_ModuleInstanceDelete(ModCxt);
    
  3. Instantiate an AST module to an anonymous Module instance

    WASM or compiled-WASM modules should be instantiated before the function invocation. Before instantiating a WASM module, please check the import section for ensuring the imports are registered into the Store context for linking.

    /* 
    * ...
    * Assume that the `ASTCxt` is the output AST module context from the loader context
    * and has passed the validation.
    * Assume that the `ConfCxt` is the configure context.
    */
    /* Create the statistics context. This step is not necessary. */
    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* Create the executor context. The configure and the statistics contexts can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);
    /* Create the store context. The store context is the object to link the modules for imports and exports. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    
    /* The output module instance. */
    WasmEdge_ModuleInstanceContext *ModCxt = NULL;
    /* Instantiate the WASM module. */
    WasmEdge_Result Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt, StoreCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM instantiation failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    
    /* ... */
    
    /* After the execution, the resources should be released. */
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    WasmEdge_StoreDelete(StoreCxt);
    WasmEdge_ModuleInstanceDelete(ModCxt);
    
  4. Invoke functions

    After registering or instantiating and get the result Module instance, developers can retrieve the exported Function instances from the Module instance for invocation. For the details about the Module instances APIs, please refer to the Instances. Please refer to the example above for the Function instance invocation with the WasmEdge_ExecutorInvoke() API.

AST Module

The AST Module context presents the loaded structure from a WASM file or buffer. Developer will get this object after loading a WASM file or buffer from Loader. Before instantiation, developers can also query the imports and exports of an AST Module context.

WasmEdge_ASTModuleContext *ASTCxt = ...;
/* Assume that a WASM is loaded into an AST module context. */

/* Create the import type context buffers. */
const uint32_t BUF_LEN = 256;
const WasmEdge_ImportTypeContext *ImpTypes[BUF_LEN];
uint32_t ImportNum = WasmEdge_ASTModuleListImportsLength(ASTCxt);
/* If the list length is larger than the buffer length, the overflowed data will be discarded. */
uint32_t RealImportNum = WasmEdge_ASTModuleListImports(ASTCxt, ImpTypes, BUF_LEN);
for (uint32_t I = 0; I < RealImportNum && I < BUF_LEN; I++) {
  /* Working with the import type `ImpTypes[I]` ... */
}

/* Create the export type context buffers. */
const WasmEdge_ExportTypeContext *ExpTypes[BUF_LEN];
uint32_t ExportNum = WasmEdge_ASTModuleListExportsLength(ASTCxt);
/* If the list length is larger than the buffer length, the overflowed data will be discarded. */
uint32_t RealExportNum = WasmEdge_ASTModuleListExports(ASTCxt, ExpTypes, BUF_LEN);
for (uint32_t I = 0; I < RealExportNum && I < BUF_LEN; I++) {
  /* Working with the export type `ExpTypes[I]` ... */
}

WasmEdge_ASTModuleDelete(ASTCxt);
/* After deletion of `ASTCxt`, all data queried from the `ASTCxt` should not be accessed. */

Store

Store is the runtime structure for the representation of all global state that can be manipulated by WebAssembly programs. The Store context in WasmEdge is an object to provide the instance exporting and importing when instantiating WASM modules. Developers can retrieve the named modules from the Store context.

WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();

/*
 * ...
 * Register a WASM module via the executor context.
 */

/* Try to list the registered WASM modules. */
uint32_t ModNum = WasmEdge_StoreListModuleLength(StoreCxt);
/* Create the name buffers. */
const uint32_t BUF_LEN = 256;
WasmEdge_String ModNames[BUF_LEN];
/* If the list length is larger than the buffer length, the overflowed data will be discarded. */
uint32_t RealModNum = WasmEdge_StoreListModule(StoreCxt, ModNames, BUF_LEN);
for (uint32_t I = 0; I < RealModNum && I < BUF_LEN; I++) {
  /* Working with the module name `ModNames[I]` ... */
  /* The module names should __NOT__ be destroyed. */
}

/* Find named module by name. */
WasmEdge_String ModName = WasmEdge_StringCreateByCString("module");
const WasmEdge_ModuleInstanceContext *ModCxt = WasmEdge_StoreFindModule(StoreCxt, ModName);
/* If the module with name not found, the `ModCxt` will be NULL. */
WasmEdge_StringDelete(ModName);

Instances

The instances are the runtime structures of WASM. Developers can retrieve the Module instances from the Store contexts, and retrieve the other instances from the Module instances. A single instance can be allocated by its creation function. Developers can construct instances into an Module instance for registration. Please refer to the Host Functions for details. The instances created by their creation functions should be destroyed by developers, EXCEPT they are added into an Module instance.

  1. Module instance

    After instantiating or registering an AST module context, developers will get a Module instance as the result, and have the responsibility to destroy it when not in use. A Module instance can also be created for the host module. Please refer to the host function for the details. Module instance provides APIs to list and find the exported instances in the module.

    /*
    * ...
    * Instantiate a WASM module via the executor context and get the `ModCxt` as the output module instance.
    */
    
    /* Try to list the exported instance of the instantiated WASM module. */
    /* Take the function instances for example here. */
    uint32_t FuncNum = WasmEdge_ModuleInstanceListFunctionLength(ModCxt);
    /* Create the name buffers. */
    const uint32_t BUF_LEN = 256;
    WasmEdge_String FuncNames[BUF_LEN];
    /* If the list length is larger than the buffer length, the overflowed data will be discarded. */
    uint32_t RealFuncNum = WasmEdge_ModuleInstanceListFunction(ModCxt, FuncNames, BUF_LEN);
    for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
      /* Working with the function name `FuncNames[I]` ... */
      /* The function names should __NOT__ be destroyed. */
    }
    
    /* Try to find the exported instance of the instantiated WASM module. */
    /* Take the function instances for example here. */
    /* Function name. */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    WasmEdge_FunctionInstanceContext *FuncCxt = WasmEdge_ModuleInstanceFindFunction(ModCxt, FuncName);
    /* `FuncCxt` will be `NULL` if the function not found. */
    /* The returned instance is owned by the module instance context and should __NOT__ be destroyed. */
    WasmEdge_StringDelete(FuncName);
    
  2. Function instance

    Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function contexts for host functions and add them into an Module instance context for registering into a VM or a Store. Developers can retrieve the Function Type from the Function contexts through the API. For the details of the Host Function guide, please refer to the next chapter.

    /* Retrieve the function instance from the module instance context. */
    WasmEdge_FunctionInstanceContext *FuncCxt = ...;
    WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_FunctionInstanceGetFunctionType(FuncCxt);
    /* The `FuncTypeCxt` is owned by the `FuncCxt` and should __NOT__ be destroyed. */
    
    /* For the function instance creation, please refer to the `Host Function` guide. */
    
  3. Table instance

    In WasmEdge, developers can create the Table contexts and add them into an Module instance context for registering into a VM or a Store. The Table contexts supply APIs to control the data in table instances.

    WasmEdge_Limit TabLimit = {.HasMax = true, .Shared = false, .Min = 10, .Max = 20};
    /* Create the table type with limit and the `FuncRef` element type. */
    WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_TableTypeCreate(WasmEdge_RefType_FuncRef, TabLimit);
    /* Create the table instance with table type. */
    WasmEdge_TableInstanceContext *HostTable = WasmEdge_TableInstanceCreate(TabTypeCxt);
    /* Delete the table type. */
    WasmEdge_TableTypeDelete(TabTypeCxt);
    WasmEdge_Result Res;
    WasmEdge_Value Data;
    
    TabTypeCxt = WasmEdge_TableInstanceGetTableType(HostTable);
    /* The `TabTypeCxt` got from table instance is owned by the `HostTable` and should __NOT__ be destroyed. */
    enum WasmEdge_RefType RefType = WasmEdge_TableTypeGetRefType(TabTypeCxt);
    /* `RefType` will be `WasmEdge_RefType_FuncRef`. */
    Data = WasmEdge_ValueGenFuncRef(5);
    Res = WasmEdge_TableInstanceSetData(HostTable, Data, 3);
    /* Set the function index 5 to the table[3]. */
    /*
     * This will get an "out of bounds table access" error
     * because the position (13) is out of the table size (10):
     *   Res = WasmEdge_TableInstanceSetData(HostTable, Data, 13);
     */
    Res = WasmEdge_TableInstanceGetData(HostTable, &Data, 3);
    /* Get the FuncRef value of the table[3]. */
    /*
     * This will get an "out of bounds table access" error
     * because the position (13) is out of the table size (10):
     *   Res = WasmEdge_TableInstanceGetData(HostTable, &Data, 13);
     */
    
    uint32_t Size = WasmEdge_TableInstanceGetSize(HostTable);
    /* `Size` will be 10. */
    Res = WasmEdge_TableInstanceGrow(HostTable, 6);
    /* Grow the table size of 6, the table size will be 16. */
    /*
     * This will get an "out of bounds table access" error because
     * the size (16 + 6) will reach the table limit(20):
     *   Res = WasmEdge_TableInstanceGrow(HostTable, 6);
     */
    
    WasmEdge_TableInstanceDelete(HostTable);
    
  4. Memory instance

    In WasmEdge, developers can create the Memory contexts and add them into an Module instance context for registering into a VM or a Store. The Memory contexts supply APIs to control the data in memory instances.

    WasmEdge_Limit MemLimit = {.HasMax = true, .Shared = false, .Min = 1, .Max = 5};
    /* Create the memory type with limit. The memory page size is 64KiB. */
    WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_MemoryTypeCreate(MemLimit);
    /* Create the memory instance with memory type. */
    WasmEdge_MemoryInstanceContext *HostMemory = WasmEdge_MemoryInstanceCreate(MemTypeCxt);
    /* Delete the memory type. */
    WasmEdge_MemoryTypeDelete(MemTypeCxt);
    WasmEdge_Result Res;
    uint8_t Buf[256];
    
    Buf[0] = 0xAA;
    Buf[1] = 0xBB;
    Buf[2] = 0xCC;
    Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0x1000, 3);
    /* Set the data[0:2] to the memory[4096:4098]. */
    /*
     * This will get an "out of bounds memory access" error
     * because [65535:65537] is out of 1 page size (65536):
     *   Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0xFFFF, 3);
     */
    Buf[0] = 0;
    Buf[1] = 0;
    Buf[2] = 0;
    Res = WasmEdge_MemoryInstanceGetData(HostMemory, Buf, 0x1000, 3);
    /* Get the memory[4096:4098]. Buf[0:2] will be `{0xAA, 0xBB, 0xCC}`. */
    /*
     * This will get an "out of bounds memory access" error
     * because [65535:65537] is out of 1 page size (65536):
     *   Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0xFFFF, 3);
     */
    
    uint32_t PageSize = WasmEdge_MemoryInstanceGetPageSize(HostMemory);
    /* `PageSize` will be 1. */
    Res = WasmEdge_MemoryInstanceGrowPage(HostMemory, 2);
    /* Grow the page size of 2, the page size of the memory instance will be 3. */
    /*
     * This will get an "out of bounds memory access" error because
     * the page size (3 + 3) will reach the memory limit(5):
     *   Res = WasmEdge_MemoryInstanceGrowPage(HostMemory, 3);
     */
    
    WasmEdge_MemoryInstanceDelete(HostMemory);
    
  5. Global instance

    In WasmEdge, developers can create the Global contexts and add them into an Module instance context for registering into a VM or a Store. The Global contexts supply APIs to control the value in global instances.

    WasmEdge_Value Val = WasmEdge_ValueGenI64(1000);
    /* Create the global type with value type and mutation. */
    WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_GlobalTypeCreate(WasmEdge_ValType_I64, WasmEdge_Mutability_Var);
    /* Create the global instance with value and global type. */
    WasmEdge_GlobalInstanceContext *HostGlobal = WasmEdge_GlobalInstanceCreate(GlobTypeCxt, Val);
    /* Delete the global type. */
    WasmEdge_GlobalTypeDelete(GlobTypeCxt);
    WasmEdge_Result Res;
    
    GlobTypeCxt = WasmEdge_GlobalInstanceGetGlobalType(HostGlobal);
    /* The `GlobTypeCxt` got from global instance is owned by the `HostGlobal` and should __NOT__ be destroyed. */
    enum WasmEdge_ValType ValType = WasmEdge_GlobalTypeGetValType(GlobTypeCxt);
    /* `ValType` will be `WasmEdge_ValType_I64`. */
    enum WasmEdge_Mutability ValMut = WasmEdge_GlobalTypeGetMutability(GlobTypeCxt);
    /* `ValMut` will be `WasmEdge_Mutability_Var`. */
    
    WasmEdge_GlobalInstanceSetValue(HostGlobal, WasmEdge_ValueGenI64(888));
    /* 
     * Set the value u64(888) to the global.
     * This function will do nothing if the value type mismatched or
     * the global mutability is `WasmEdge_Mutability_Const`.
     */
    WasmEdge_Value GlobVal = WasmEdge_GlobalInstanceGetValue(HostGlobal);
    /* Get the value (888 now) of the global context. */
    
    WasmEdge_GlobalInstanceDelete(HostGlobal);
    

Host Functions

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function, Memory, Table, and Global contexts and add them into an Module instance context for registering into a VM or a Store.

  1. Host function allocation

    Developers can define C functions with the following function signature as the host function body:

    typedef WasmEdge_Result (*WasmEdge_HostFunc_t)(
      void *Data,
      WasmEdge_MemoryInstanceContext *MemCxt,
      const WasmEdge_Value *Params,
      WasmEdge_Value *Returns);
    

    The example of an add host function to add 2 i32 values:

    WasmEdge_Result Add(void *, WasmEdge_MemoryInstanceContext *,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      /*
      * Params: {i32, i32}
      * Returns: {i32}
      * Developers should take care about the function type.
      */ 
      /* Retrieve the value 1. */
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      /* Retrieve the value 2. */
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      /* Output value 1 is Val1 + Val2. */
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      /* Return the status of success. */
      return WasmEdge_Result_Success;
    }
    

    Then developers can create Function context with the host function body and the function type:

    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
    /* Create a function type: {i32, i32} -> {i32}. */
    HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    /* 
     * Create a function context with the function type and host function body.
     * The `Cost` parameter can be 0 if developers do not need the cost measuring.
     */
    WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
    /*
     * The third parameter is the pointer to the additional data.
     * Developers should guarantee the life cycle of the data, and it can be
     * `NULL` if the external data is not needed.
     */
    
    /* If the function instance is not added into a module instance context, it should be deleted. */
    WasmEdge_FunctionInstanceDelete(HostFunc);
    
  2. Construct a module instance with host instances

    Besides creating a Module instance by registering or instantiating a WASM module, developers can create a Module instance with a module name and add the Function, Memory, Table, and Global instances into it with their exporting names.

    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      return WasmEdge_Result_Success;
    }
    
    /* Create a module instance. */
    WasmEdge_String ExportName = WasmEdge_StringCreateByCString("module");
    WasmEdge_ModuleInstanceContext *HostModCxt = WasmEdge_ModuleInstanceCreate(ExportName);
    WasmEdge_StringDelete(ExportName);
    
    /* Create and add a function instance into the module instance. */
    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
    WasmEdge_FunctionTypeContext *HostFType = 
      WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    WasmEdge_FunctionInstanceContext *HostFunc =
      WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
    /*
     * The third parameter is the pointer to the additional data object.
     * Developers should guarantee the life cycle of the data, and it can be
     * `NULL` if the external data is not needed.
     */
    WasmEdge_FunctionTypeDelete(HostFType);
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("add");
    WasmEdge_ModuleInstanceAddFunction(HostModCxt, FuncName, HostFunc);
    WasmEdge_StringDelete(FuncName);
    
    /* Create and add a table instance into the import object. */
    WasmEdge_Limit TableLimit = {.HasMax = true, .Shared = false, .Min = 10, .Max = 20};
    WasmEdge_TableTypeContext *HostTType = 
      WasmEdge_TableTypeCreate(WasmEdge_RefType_FuncRef, TableLimit);
    WasmEdge_TableInstanceContext *HostTable = WasmEdge_TableInstanceCreate(HostTType);
    WasmEdge_TableTypeDelete(HostTType);
    WasmEdge_String TableName = WasmEdge_StringCreateByCString("table");
    WasmEdge_ModuleInstanceAddTable(HostModCxt, TableName, HostTable);
    WasmEdge_StringDelete(TableName);
    
    /* Create and add a memory instance into the import object. */
    WasmEdge_Limit MemoryLimit = {.HasMax = true, .Shared = false, .Min = 1, .Max = 2};
    WasmEdge_MemoryTypeContext *HostMType = WasmEdge_MemoryTypeCreate(MemoryLimit);
    WasmEdge_MemoryInstanceContext *HostMemory = WasmEdge_MemoryInstanceCreate(HostMType);
    WasmEdge_MemoryTypeDelete(HostMType);
    WasmEdge_String MemoryName = WasmEdge_StringCreateByCString("memory");
    WasmEdge_ModuleInstanceAddMemory(HostModCxt, MemoryName, HostMemory);
    WasmEdge_StringDelete(MemoryName);
    
    /* Create and add a global instance into the module instance. */
    WasmEdge_GlobalTypeContext *HostGType =
      WasmEdge_GlobalTypeCreate(WasmEdge_ValType_I32, WasmEdge_Mutability_Var);
    WasmEdge_GlobalInstanceContext *HostGlobal =
      WasmEdge_GlobalInstanceCreate(HostGType, WasmEdge_ValueGenI32(666));
    WasmEdge_GlobalTypeDelete(HostGType);
    WasmEdge_String GlobalName = WasmEdge_StringCreateByCString("global");
    WasmEdge_ModuleInstanceAddGlobal(HostModCxt, GlobalName, HostGlobal);
    WasmEdge_StringDelete(GlobalName);
    
    /*
     * The module instance should be deleted.
     * Developers should __NOT__ destroy the instances added into the module instance contexts.
     */
    WasmEdge_ModuleInstanceDelete(HostModCxt);
    
  3. Specified module instance

    WasmEdge_ModuleInstanceCreateWASI() API can create and initialize the WASI module instance. WasmEdge_ModuleInstanceCreateWasmEdgeProcess() API can create and initialize the wasmedge_process module instance. Developers can create these module instance contexts and register them into the Store or VM contexts rather than adjust the settings in the Configure contexts.

    WasmEdge_ModuleInstanceContext *WasiModCxt = WasmEdge_ModuleInstanceCreateWASI( /* ... ignored */ );
    WasmEdge_ModuleInstanceContext *ProcModCxt = WasmEdge_ModuleInstanceCreateWasmEdgeProcess( /* ... ignored */ );
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    /* Register the WASI and WasmEdge_Process into the VM context. */
    WasmEdge_VMRegisterModuleFromImport(VMCxt, WasiModCxt);
    WasmEdge_VMRegisterModuleFromImport(VMCxt, ProcModCxt);
    /* Get the WASI exit code. */
    uint32_t ExitCode = WasmEdge_ModuleInstanceWASIGetExitCode(WasiModCxt);
    /*
     * The `ExitCode` will be EXIT_SUCCESS if the execution has no error.
     * Otherwise, it will return with the related exit code.
     */
    WasmEdge_VMDelete(VMCxt);
    /* The module instances should be deleted. */
    WasmEdge_ModuleInstanceDelete(WasiModCxt);
    WasmEdge_ModuleInstanceDelete(ProcModCxt);
    
  4. Example

    Assume that a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    And the test.c as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    
    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      printf("Host function \"Add\": %d + %d\n", Val1, Val2);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      return WasmEdge_Result_Success;
    }
    
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The WASM module buffer. */
      uint8_t WASM[] = {
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B
      };
    
      /* Create the module instance. */
      WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern");
      WasmEdge_ModuleInstanceContext *HostModCxt = WasmEdge_ModuleInstanceCreate(ExportName);
      enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
      enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
      WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
      WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
      WasmEdge_FunctionTypeDelete(HostFType);
      WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("func-add");
      WasmEdge_ModuleInstanceAddFunction(HostModCxt, HostFuncName, HostFunc);
      WasmEdge_StringDelete(HostFuncName);
    
      WasmEdge_VMRegisterModuleFromImport(VMCxt, HostModCxt);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[2] = { WasmEdge_ValueGenI32(1234), WasmEdge_ValueGenI32(5678) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("addTwo");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
        VMCxt, WASM, sizeof(WASM), FuncName, Params, 2, Returns, 1);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_ModuleInstanceDelete(HostModCxt);
      return 0;
    }
    

    Then you can compile and run: (the result of 1234 + 5678 is 6912)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Host function "Add": 1234 + 5678
    Get the result: 6912
    
  5. Host Data Example

    Developers can set a external data object to the Function context, and access to the object in the function body. Assume that a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    And the test.c as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    
    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      printf("Host function \"Add\": %d + %d\n", Val1, Val2);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      /* Also set the result to the data. */
      int32_t *DataPtr = (int32_t *)Data;
      *DataPtr = Val1 + Val2;
      return WasmEdge_Result_Success;
    }
    
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The WASM module buffer. */
      uint8_t WASM[] = {
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B
      };
    
      /* The external data object: an integer. */
      int32_t Data;
    
      /* Create the module instance. */
      WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern");
      WasmEdge_ModuleInstanceContext *HostModCxt = WasmEdge_ModuleInstanceCreate(ExportName);
      enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
      enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
      WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
      WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, &Data, 0);
      WasmEdge_FunctionTypeDelete(HostFType);
      WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("func-add");
      WasmEdge_ModuleInstanceAddFunction(HostModCxt, HostFuncName, HostFunc);
      WasmEdge_StringDelete(HostFuncName);
    
      WasmEdge_VMRegisterModuleFromImport(VMCxt, HostModCxt);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[2] = { WasmEdge_ValueGenI32(1234), WasmEdge_ValueGenI32(5678) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("addTwo");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
        VMCxt, WASM, sizeof(WASM), FuncName, Params, 2, Returns, 1);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
      printf("Data value: %d\n", Data);
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_ModuleInstanceDelete(HostModCxt);
      return 0;
    }
    

    Then you can compile and run: (the result of 1234 + 5678 is 6912)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Host function "Add": 1234 + 5678
    Get the result: 6912
    Data value: 6912
    

WasmEdge AOT Compiler

In this partition, we will introduce the WasmEdge AOT compiler and the options.

WasmEdge runs the WASM files in interpreter mode, and WasmEdge also supports the AOT (ahead-of-time) mode running without modifying any code. The WasmEdge AOT (ahead-of-time) compiler compiles the WASM files for running in AOT mode which is much faster than interpreter mode. Developers can compile the WASM files into the compiled-WASM files in shared library format for universal WASM format for the AOT mode execution.

Compilation Example

Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main() {
  /* Create the configure context. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  /* ... Adjust settings in the configure context. */
  /* Result. */
  WasmEdge_Result Res;

  /* Create the compiler context. The configure context can be NULL. */
  WasmEdge_CompilerContext *CompilerCxt = WasmEdge_CompilerCreate(ConfCxt);
  /* Compile the WASM file with input and output paths. */
  Res = WasmEdge_CompilerCompile(CompilerCxt, "fibonacci.wasm", "fibonacci.wasm.so");
  if (!WasmEdge_ResultOK(Res)) {
      printf("Compilation failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return 1;
  }

  WasmEdge_CompilerDelete(CompilerCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  return 0;
}

Then you can compile and run (the output file is "fibonacci.wasm.so"):

$ gcc test.c -lwasmedge_c
$ ./a.out
[2021-07-02 11:08:08.651] [info] compile start
[2021-07-02 11:08:08.653] [info] verify start
[2021-07-02 11:08:08.653] [info] optimize start
[2021-07-02 11:08:08.670] [info] codegen start
[2021-07-02 11:08:08.706] [info] compile done

Compiler Options

Developers can set options for AOT compilers such as optimization level and output format:

/// AOT compiler optimization level enumeration.
enum WasmEdge_CompilerOptimizationLevel {
  /// Disable as many optimizations as possible.
  WasmEdge_CompilerOptimizationLevel_O0 = 0,
  /// Optimize quickly without destroying debuggability.
  WasmEdge_CompilerOptimizationLevel_O1,
  /// Optimize for fast execution as much as possible without triggering
  /// significant incremental compile time or code size growth.
  WasmEdge_CompilerOptimizationLevel_O2,
  /// Optimize for fast execution as much as possible.
  WasmEdge_CompilerOptimizationLevel_O3,
  /// Optimize for small code size as much as possible without triggering
  /// significant incremental compile time or execution time slowdowns.
  WasmEdge_CompilerOptimizationLevel_Os,
  /// Optimize for small code size as much as possible.
  WasmEdge_CompilerOptimizationLevel_Oz
};

/// AOT compiler output binary format enumeration.
enum WasmEdge_CompilerOutputFormat {
  /// Native dynamic library format.
  WasmEdge_CompilerOutputFormat_Native = 0,
  /// WebAssembly with AOT compiled codes in custom sections.
  WasmEdge_CompilerOutputFormat_Wasm
};

Please refer to the AOT compiler options configuration for details.

WasmEdge 0.9.1 C API Documentation

WasmEdge C API denotes an interface to access the WasmEdge runtime at version 0.9.1. The followings are the guides to working with the C APIs of WasmEdge.

Developers can refer here to upgrade to 0.10.0.

Table of Contents

WasmEdge Installation

Download And Install

The easiest way to install WasmEdge is to run the following command. Your system should have git and wget as prerequisites.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.1

For more details, please refer to the Installation Guide for the WasmEdge installation.

Compile Sources

After the installation of WasmEdge, the following guide can help you to test for the availability of the WasmEdge C API.

  1. Prepare the test C file (and assumed saved as test.c):

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      printf("WasmEdge version: %s\n", WasmEdge_VersionGet());
      return 0;
    }
    
  2. Compile the file with gcc or clang.

    gcc test.c -lwasmedge_c
    
  3. Run and get the expected output.

    $ ./a.out
    WasmEdge version: 0.9.1
    

WasmEdge Basics

In this part, we will introduce the utilities and concepts of WasmEdge shared library.

Version

The Version related APIs provide developers to check for the WasmEdge shared library version.

#include <wasmedge/wasmedge.h>
printf("WasmEdge version: %s\n", WasmEdge_VersionGet());
printf("WasmEdge version major: %u\n", WasmEdge_VersionGetMajor());
printf("WasmEdge version minor: %u\n", WasmEdge_VersionGetMinor());
printf("WasmEdge version patch: %u\n", WasmEdge_VersionGetPatch());

Logging Settings

The WasmEdge_LogSetErrorLevel() and WasmEdge_LogSetDebugLevel() APIs can set the logging system to debug level or error level. By default, the error level is set, and the debug info is hidden.

Value Types

In WasmEdge, developers should convert the values to WasmEdge_Value objects through APIs for matching to the WASM value types.

  1. Number types: i32, i64, f32, f64, and v128 for the SIMD proposal

    WasmEdge_Value Val;
    Val = WasmEdge_ValueGenI32(123456);
    printf("%d\n", WasmEdge_ValueGetI32(Val));
    /* Will print "123456" */
    Val = WasmEdge_ValueGenI64(1234567890123LL);
    printf("%ld\n", WasmEdge_ValueGetI64(Val));
    /* Will print "1234567890123" */
    Val = WasmEdge_ValueGenF32(123.456f);
    printf("%f\n", WasmEdge_ValueGetF32(Val));
    /* Will print "123.456001" */
    Val = WasmEdge_ValueGenF64(123456.123456789);
    printf("%.10f\n", WasmEdge_ValueGetF64(Val));
    /* Will print "123456.1234567890" */
    
  2. Reference types: funcref and externref for the Reference-Types proposal

    WasmEdge_Value Val;
    void *Ptr;
    bool IsNull;
    uint32_t Num = 10;
    /* Genreate a externref to NULL. */
    Val = WasmEdge_ValueGenNullRef(WasmEdge_RefType_ExternRef);
    IsNull = WasmEdge_ValueIsNullRef(Val);
    /* The `IsNull` will be `TRUE`. */
    Ptr = WasmEdge_ValueGetExternRef(Val);
    /* The `Ptr` will be `NULL`. */
    
    /* Genreate a funcref with function index 20. */
    Val = WasmEdge_ValueGenFuncRef(20);
    uint32_t FuncIdx = WasmEdge_ValueGetFuncIdx(Val);
    /* The `FuncIdx` will be 20. */
    
    /* Genreate a externref to `Num`. */
    Val = WasmEdge_ValueGenExternRef(&Num);
    Ptr = WasmEdge_ValueGetExternRef(Val);
    /* The `Ptr` will be `&Num`. */
    printf("%u\n", *(uint32_t *)Ptr);
    /* Will print "10" */
    Num += 55;
    printf("%u\n", *(uint32_t *)Ptr);
    /* Will print "65" */
    

Strings

The WasmEdge_String object is for the instance names when invoking a WASM function or finding the contexts of instances.

  1. Create a WasmEdge_String from a C string (const char * with NULL termination) or a buffer with length.

    The content of the C string or buffer will be copied into the WasmEdge_String object.

    char Buf[4] = {50, 55, 60, 65};
    WasmEdge_String Str1 = WasmEdge_StringCreateByCString("test");
    WasmEdge_String Str2 = WasmEdge_StringCreateByBuffer(Buf, 4);
    /* The objects should be deleted by `WasmEdge_StringDelete()`. */
    WasmEdge_StringDelete(Str1);
    WasmEdge_StringDelete(Str2);
    
  2. Wrap a WasmEdge_String to a buffer with length.

    The content will not be copied, and the caller should guarantee the life cycle of the input buffer.

    const char CStr[] = "test";
    WasmEdge_String Str = WasmEdge_StringWrap(CStr, 4);
    /* The object should __NOT__ be deleted by `WasmEdge_StringDelete()`. */
    
  3. String comparison

    const char CStr[] = "abcd";
    char Buf[4] = {0x61, 0x62, 0x63, 0x64};
    WasmEdge_String Str1 = WasmEdge_StringWrap(CStr, 4);
    WasmEdge_String Str2 = WasmEdge_StringCreateByBuffer(Buf, 4);
    bool IsEq = WasmEdge_StringIsEqual(Str1, Str2);
    /* The `IsEq` will be `TRUE`. */
    WasmEdge_StringDelete(Str2);
    
  4. Convert to C string

    char Buf[256];
    WasmEdge_String Str = WasmEdge_StringCreateByCString("test_wasmedge_string");
    uint32_t StrLength = WasmEdge_StringCopy(Str, Buf, sizeof(Buf));
    /* StrLength will be 20 */
    printf("String: %s\n", Buf);
    /* Will print "test_wasmedge_string". */
    

Results

The WasmEdge_Result object specifies the execution status. APIs about WASM execution will return the WasmEdge_Result to denote the status.

WasmEdge_Result Res = WasmEdge_Result_Success;
bool IsSucceeded = WasmEdge_ResultOK(Res);
/* The `IsSucceeded` will be `TRUE`. */
uint32_t Code = WasmEdge_ResultGetCode(Res);
/* The `Code` will be 0. */
const char *Msg = WasmEdge_ResultGetMessage(Res);
/* The `Msg` will be "success". */

Contexts

The objects, such as VM, Store, and Function, are composed of Contexts. All of the contexts can be created by calling the corresponding creation APIs and should be destroyed by calling the corresponding deletion APIs. Developers have responsibilities to manage the contexts for memory management.

/* Create the configure context. */
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
/* Delete the configure context. */
WasmEdge_ConfigureDelete(ConfCxt);

The details of other contexts will be introduced later.

WASM Data Structures

The WASM data structures are used for creating instances or can be queried from instance contexts. The details of instances creation will be introduced in the Instances.

  1. Limit

    The WasmEdge_Limit struct is defined in the header:

    /// Struct of WASM limit.
    typedef struct WasmEdge_Limit {
      /// Boolean to describe has max value or not.
      bool HasMax;
      /// Minimum value.
      uint32_t Min;
      /// Maximum value. Will be ignored if the `HasMax` is false.
      uint32_t Max;
    } WasmEdge_Limit;
    

    Developers can initialize the struct by assigning it's value, and the Max value is needed to be larger or equal to the Min value. The API WasmEdge_LimitIsEqual() is provided to compare with 2 WasmEdge_Limit structs.

  2. Function type context

    The Function Type context is used for the Function creation, checking the value types of a Function instance, or getting the function type with function name from VM. Developers can use the Function Type context APIs to get the parameter or return value types information.

    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I64 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_FuncRef };
    WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    
    enum WasmEdge_ValType Buf[16];
    uint32_t ParamLen = WasmEdge_FunctionTypeGetParametersLength(FuncTypeCxt);
    /* `ParamLen` will be 2. */
    uint32_t GotParamLen = WasmEdge_FunctionTypeGetParameters(FuncTypeCxt, Buf, 16);
    /* `GotParamLen` will be 2, and `Buf[0]` and `Buf[1]` will be the same as `ParamList`. */
    uint32_t ReturnLen = WasmEdge_FunctionTypeGetReturnsLength(FuncTypeCxt);
    /* `ReturnLen` will be 1. */
    uint32_t GotReturnLen = WasmEdge_FunctionTypeGetReturns(FuncTypeCxt, Buf, 16);
    /* `GotReturnLen` will be 1, and `Buf[0]` will be the same as `ReturnList`. */
    
    WasmEdge_FunctionTypeDelete(FuncTypeCxt);
    
  3. Table type context

    The Table Type context is used for Table instance creation or getting information from Table instances.

    WasmEdge_Limit TabLim = {.HasMax = true, .Min = 10, .Max = 20};
    WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_TableTypeCreate(WasmEdge_RefType_ExternRef, TabLim);
    
    enum WasmEdge_RefType GotRefType = WasmEdge_TableTypeGetRefType(TabTypeCxt);
    /* `GotRefType` will be WasmEdge_RefType_ExternRef. */
    WasmEdge_Limit GotTabLim = WasmEdge_TableTypeGetLimit(TabTypeCxt);
    /* `GotTabLim` will be the same value as `TabLim`. */
    
    WasmEdge_TableTypeDelete(TabTypeCxt);
    
  4. Memory type context

    The Memory Type context is used for Memory instance creation or getting information from Memory instances.

    WasmEdge_Limit MemLim = {.HasMax = true, .Min = 10, .Max = 20};
    WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_MemoryTypeCreate(MemLim);
    
    WasmEdge_Limit GotMemLim = WasmEdge_MemoryTypeGetLimit(MemTypeCxt);
    /* `GotMemLim` will be the same value as `MemLim`. */
    
    WasmEdge_MemoryTypeDelete(MemTypeCxt)
    
  5. Global type context

    The Global Type context is used for Global instance creation or getting information from Global instances.

    WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_GlobalTypeCreate(WasmEdge_ValType_F64, WasmEdge_Mutability_Var);
    
    WasmEdge_ValType GotValType = WasmEdge_GlobalTypeGetValType(GlobTypeCxt);
    /* `GotValType` will be WasmEdge_ValType_F64. */
    WasmEdge_Mutability GotValMut = WasmEdge_GlobalTypeGetMutability(GlobTypeCxt);
    /* `GotValMut` will be WasmEdge_Mutability_Var. */
    
    WasmEdge_GlobalTypeDelete(GlobTypeCxt);
    
  6. Import type context

    The Import Type context is used for getting the imports information from a AST Module. Developers can get the external type (function, table, memory, or global), import module name, and external name from an Import Type context. The details about querying Import Type contexts will be introduced in the AST Module.

    WasmEdge_ASTModuleContext *ASTCxt = ...;
    /* Assume that `ASTCxt` is returned by the `WasmEdge_LoaderContext` for the result of loading a WASM file. */
    const WasmEdge_ImportTypeContext *ImpType = ...;
    /* Assume that `ImpType` is queried from the `ASTCxt` for the import. */
    
    enum WasmEdge_ExternalType ExtType = WasmEdge_ImportTypeGetExternalType(ImpType);
    /*
     * The `ExtType` can be one of `WasmEdge_ExternalType_Function`, `WasmEdge_ExternalType_Table`,
     * `WasmEdge_ExternalType_Memory`, or `WasmEdge_ExternalType_Global`.
     */
    WasmEdge_String ModName = WasmEdge_ImportTypeGetModuleName(ImpType);
    WasmEdge_String ExtName = WasmEdge_ImportTypeGetExternalName(ImpType);
    /* The `ModName` and `ExtName` should not be destroyed and the string buffers are binded into the `ASTCxt`. */
    const WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_ImportTypeGetFunctionType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Function`, the `FuncTypeCxt` will be NULL. */
    const WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_ImportTypeGetTableType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Table`, the `TabTypeCxt` will be NULL. */
    const WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_ImportTypeGetMemoryType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Memory`, the `MemTypeCxt` will be NULL. */
    const WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_ImportTypeGetGlobalType(ASTCxt, ImpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Global`, the `GlobTypeCxt` will be NULL. */
    
  7. Export type context

    The Export Type context is used for getting the exports information from a AST Module. Developers can get the external type (function, table, memory, or global) and external name from an Export Type context. The details about querying Export Type contexts will be introduced in the AST Module.

    WasmEdge_ASTModuleContext *ASTCxt = ...;
    /* Assume that `ASTCxt` is returned by the `WasmEdge_LoaderContext` for the result of loading a WASM file. */
    const WasmEdge_ExportTypeContext *ExpType = ...;
    /* Assume that `ExpType` is queried from the `ASTCxt` for the export. */
    
    enum WasmEdge_ExternalType ExtType = WasmEdge_ExportTypeGetExternalType(ExpType);
    /*
     * The `ExtType` can be one of `WasmEdge_ExternalType_Function`, `WasmEdge_ExternalType_Table`,
     * `WasmEdge_ExternalType_Memory`, or `WasmEdge_ExternalType_Global`.
     */
    WasmEdge_String ExtName = WasmEdge_ExportTypeGetExternalName(ExpType);
    /* The `ExtName` should not be destroyed and the string buffer is binded into the `ASTCxt`. */
    const WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_ExportTypeGetFunctionType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Function`, the `FuncTypeCxt` will be NULL. */
    const WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_ExportTypeGetTableType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Table`, the `TabTypeCxt` will be NULL. */
    const WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_ExportTypeGetMemoryType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Memory`, the `MemTypeCxt` will be NULL. */
    const WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_ExportTypeGetGlobalType(ASTCxt, ExpType);
    /* If the `ExtType` is not `WasmEdge_ExternalType_Global`, the `GlobTypeCxt` will be NULL. */
    

Async

After calling the asynchronous execution APIs, developers will get the WasmEdge_Async object. Developers own the object and should call the WasmEdge_AsyncDelete() API to destroy it.

  1. Wait for the asynchronous execution

    Developers can wait the execution until finished:

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution. */
    WasmEdge_AsyncWait(Async);
    WasmEdge_AsyncDelete(Async);
    

    Or developers can wait for a time limit. If the time limit exceeded, developers can choose to cancel the execution. For the interruptible execution in AOT mode, developers should set TRUE thourgh the WasmEdge_ConfigureCompilerSetInterruptible() API into the configure context for the AOT compiler.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution for 1 second. */
    bool IsEnd = WasmEdge_AsyncWaitFor(Async, 1000);
    if (IsEnd) {
      /* The execution finished. Developers can get the result. */
      WasmEdge_Result Res = WasmEdge_AsyncGet(/* ... Ignored */);
    } else {
      /* The time limit exceeded. Developers can keep waiting or cancel the execution. */
      WasmEdge_AsyncCancel(Async);
      WasmEdge_Result Res = WasmEdge_AsyncGet(Async, 0, NULL);
      /* The result error code will be `WasmEdge_ErrCode_Interrupted`. */
    }
    WasmEdge_AsyncDelete(Async);
    
  2. Get the execution result of the asynchronous execution

    Developers can use the WasmEdge_AsyncGetReturnsLength() API to get the return value list length. This function will block and wait for the execution. If the execution has finished, this function will return the length immediately. If the execution failed, this function will return 0. This function can help the developers to create the buffer to get the return values. If developers have already known the buffer length, they can skip this function and use the WasmEdge_AsyncGet() API to get the result.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution and get the return value list length. */
    uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
    WasmEdge_AsyncDelete(Async);
    

    The WasmEdge_AsyncGet() API will block and wait for the execution. If the execution has finished, this function will fill the return values into the buffer and return the execution result immediately.

    WasmEdge_Async *Async = ...; /* Ignored. Asynchronous execute a function. */
    /* Blocking and waiting for the execution and get the return values. */
    const uint32_t BUF_LEN = 256;
    WasmEdge_Value Buf[BUF_LEN];
    WasmEdge_Result Res = WasmEdge_AsyncGet(Async, Buf, BUF_LEN);
    WasmEdge_AsyncDelete(Async);
    

Configurations

The configuration context, WasmEdge_ConfigureContext, manages the configurations for Loader, Validator, Executor, VM, and Compiler. Developers can adjust the settings about the proposals, VM host pre-registrations (such as WASI), and AOT compiler options, and then apply the Configure context to create other runtime contexts.

  1. Proposals

    WasmEdge supports turning on or off the WebAssembly proposals. This configuration is effective in any contexts created with the Configure context.

    enum WasmEdge_Proposal {
      WasmEdge_Proposal_ImportExportMutGlobals = 0,
      WasmEdge_Proposal_NonTrapFloatToIntConversions,
      WasmEdge_Proposal_SignExtensionOperators,
      WasmEdge_Proposal_MultiValue,
      WasmEdge_Proposal_BulkMemoryOperations,
      WasmEdge_Proposal_ReferenceTypes,
      WasmEdge_Proposal_SIMD,
      WasmEdge_Proposal_TailCall,
      WasmEdge_Proposal_MultiMemories,
      WasmEdge_Proposal_Annotations,
      WasmEdge_Proposal_Memory64,
      WasmEdge_Proposal_ExceptionHandling,
      WasmEdge_Proposal_Threads,
      WasmEdge_Proposal_FunctionReferences
    };
    

    Developers can add or remove the proposals into the Configure context.

    /* 
     * By default, the following proposals have turned on initially:
     * * Import/Export of mutable globals
     * * Non-trapping float-to-int conversions
     * * Sign-extension operators
     * * Multi-value returns
     * * Bulk memory operations
     * * Reference types
     * * Fixed-width SIMD
     */
    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddProposal(ConfCxt, WasmEdge_Proposal_MultiMemories);
    WasmEdge_ConfigureRemoveProposal(ConfCxt, WasmEdge_Proposal_ReferenceTypes);
    bool IsBulkMem = WasmEdge_ConfigureHasProposal(ConfCxt, WasmEdge_Proposal_BulkMemoryOperations);
    /* The `IsBulkMem` will be `TRUE`. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  2. Host registrations

    This configuration is used for the VM context to turn on the WASI or wasmedge_process supports and only effective in VM contexts.

    enum WasmEdge_HostRegistration {
      WasmEdge_HostRegistration_Wasi = 0,
      WasmEdge_HostRegistration_WasmEdge_Process
    };
    

    The details will be introduced in the preregistrations of VM context.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    bool IsHostWasi = WasmEdge_ConfigureHasHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    /* The `IsHostWasi` will be `FALSE`. */
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    IsHostWasi = WasmEdge_ConfigureHasHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    /* The `IsHostWasi` will be `TRUE`. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  3. Maximum memory pages

    Developers can limit the page size of memory instances by this configuration. When growing the page size of memory instances in WASM execution and exceeding the limited size, the page growing will fail. This configuration is only effective in the Executor and VM contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    uint32_t PageSize = WasmEdge_ConfigureGetMaxMemoryPage(ConfCxt);
    /* By default, the maximum memory page size is 65536. */
    WasmEdge_ConfigureSetMaxMemoryPage(ConfCxt, 1024);
    /* Limit the memory size of each memory instance with not larger than 1024 pages (64 MiB). */
    PageSize = WasmEdge_ConfigureGetMaxMemoryPage(ConfCxt);
    /* The `PageSize` will be 1024. */
    WasmEdge_ConfigureDelete(ConfCxt);
    
  4. AOT compiler options

    The AOT compiler options configure the behavior about optimization level, output format, dump IR, and generic binary.

    enum WasmEdge_CompilerOptimizationLevel {
      /// Disable as many optimizations as possible.
      WasmEdge_CompilerOptimizationLevel_O0 = 0,
      /// Optimize quickly without destroying debuggability.
      WasmEdge_CompilerOptimizationLevel_O1,
      /// Optimize for fast execution as much as possible without triggering
      /// significant incremental compile time or code size growth.
      WasmEdge_CompilerOptimizationLevel_O2,
      /// Optimize for fast execution as much as possible.
      WasmEdge_CompilerOptimizationLevel_O3,
      /// Optimize for small code size as much as possible without triggering
      /// significant incremental compile time or execution time slowdowns.
      WasmEdge_CompilerOptimizationLevel_Os,
      /// Optimize for small code size as much as possible.
      WasmEdge_CompilerOptimizationLevel_Oz
    };
    
    enum WasmEdge_CompilerOutputFormat {
      /// Native dynamic library format.
      WasmEdge_CompilerOutputFormat_Native = 0,
      /// WebAssembly with AOT compiled codes in custom section.
      WasmEdge_CompilerOutputFormat_Wasm
    };
    

    These configurations are only effective in Compiler contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    /* By default, the optimization level is O3. */
    WasmEdge_ConfigureCompilerSetOptimizationLevel(ConfCxt, WasmEdge_CompilerOptimizationLevel_O2);
    /* By default, the output format is universal WASM. */
    WasmEdge_ConfigureCompilerSetOutputFormat(ConfCxt, WasmEdge_CompilerOutputFormat_Native);
    /* By default, the dump IR is `FALSE`. */
    WasmEdge_ConfigureCompilerSetDumpIR(ConfCxt, TRUE);
    /* By default, the generic binary is `FALSE`. */
    WasmEdge_ConfigureCompilerSetGenericBinary(ConfCxt, TRUE);
    /* By default, the interruptible is `FALSE`.
    /* Set this option to `TRUE` to support the interruptible execution in AOT mode. */
    WasmEdge_ConfigureCompilerSetInterruptible(ConfCxt, TRUE);
    WasmEdge_ConfigureDelete(ConfCxt);
    
  5. Statistics options

    The statistics options configure the behavior about instruction counting, cost measuring, and time measuring in both runtime and AOT compiler. These configurations are effective in Compiler, VM, and Executor contexts.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    /* By default, the intruction counting is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetInstructionCounting(ConfCxt, TRUE);
    /* By default, the cost measurement is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetCostMeasuring(ConfCxt, TRUE);
    /* By default, the time measurement is `FALSE` when running a compiled-WASM or a pure-WASM. */
    WasmEdge_ConfigureStatisticsSetTimeMeasuring(ConfCxt, TRUE);
    WasmEdge_ConfigureDelete(ConfCxt);
    

Statistics

The statistics context, WasmEdge_StatisticsContext, provides the instruction counter, cost summation, and cost limitation at runtime.

Before using statistics, the statistics configuration must be set. Otherwise, the return values of calling statistics are undefined behaviour.

  1. Instruction counter

    The instruction counter can help developers to profile the performance of WASM running. Developers can retrieve the Statistics context from the VM context, or create a new one for the Executor creation. The details will be introduced in the next partitions.

    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* ....
     * After running the WASM functions with the `Statistics` context
     */
    uint32_t Count = WasmEdge_StatisticsGetInstrCount(StatCxt);
    double IPS = WasmEdge_StatisticsGetInstrPerSecond(StatCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    
  2. Cost table

    The cost table is to accumulate the cost of instructions with their weights. Developers can set the cost table array (the indices are the byte code value of instructions, and the values are the cost of instructions) into the Statistics context. If the cost limit value is set, the execution will return the cost limit exceeded error immediately when exceeds the cost limit in runtime.

    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    uint64_t CostTable[16] = {
      0, 0,
      10, /* 0x02: Block */
      11, /* 0x03: Loop */
      12, /* 0x04: If */
      12, /* 0x05: Else */
      0, 0, 0, 0, 0, 0, 
      20, /* 0x0C: Br */
      21, /* 0x0D: Br_if */
      22, /* 0x0E: Br_table */
      0
    };
    /* Developers can set the costs of each instruction. The value not covered will be 0. */
    WasmEdge_StatisticsSetCostTable(StatCxt, CostTable, 16);
    WasmEdge_StatisticsSetCostLimit(StatCxt, 5000000);
    /* ....
     * After running the WASM functions with the `Statistics` context
     */
    uint64_t Cost = WasmEdge_StatisticsGetTotalCost(StatCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    

WasmEdge VM

In this partition, we will introduce the functions of WasmEdge_VMContext object and show examples of executing WASM functions.

WASM Execution Example With VM Context

The following shows the example of running the WASM for getting the Fibonacci. This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

(module
 (export "fib" (func $fib))
 (func $fib (param $n i32) (result i32)
  (if
   (i32.lt_s (get_local $n)(i32.const 2))
   (return (i32.const 1))
  )
  (return
   (i32.add
    (call $fib (i32.sub (get_local $n)(i32.const 2)))
    (call $fib (i32.sub (get_local $n)(i32.const 1)))
   )
  )
 )
)
  1. Run WASM functions rapidly

    Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the configure context and add the WASI support. */
      /* This step is not necessary unless you need WASI support. */
      WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
      WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
      /* The configure and store context to the VM creation can be NULL. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(5) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromFile(VMCxt, "fibonacci.wasm", FuncName, Params, 1, Returns, 1);
      /* 
       * Developers can run the WASM binary from buffer with the `WasmEdge_VMRunWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRunWasmFromASTModule()` API.
       */
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_ConfigureDelete(ConfCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 5th Fibonacci number is 8 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 8
    
  2. Instantiate and run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM context APIs:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the configure context and add the WASI support. */
      /* This step is not necessary unless you need the WASI support. */
      WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
      WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
      /* The configure and store context to the VM creation can be NULL. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(10) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
      
      /* Step 1: Load WASM file. */
      Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      /* 
       * Developers can load the WASM binary from buffer with the `WasmEdge_VMLoadWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMLoadWasmFromASTModule()` API.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 2: Validate the WASM module. */
      Res = WasmEdge_VMValidate(VMCxt);
      if (!WasmEdge_ResultOK(Res)) {
        printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 3: Instantiate the WASM module. */
      Res = WasmEdge_VMInstantiate(VMCxt);
      /* 
       * Developers can load, validate, and instantiate another WASM module to replace the
       * instantiated one. In this case, the old module will be cleared, but the registered
       * modules are still kept.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 4: Execute WASM functions. You can execute functions repeatedly after instantiation. */
      Res = WasmEdge_VMExecute(VMCxt, FuncName, Params, 1, Returns, 1);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_ConfigureDelete(ConfCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 10th Fibonacci number is 89 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 89
    

    The following graph explains the status of the VM context.

                           |========================|
                  |------->|      VM: Initiated     |
                  |        |========================|
                  |                    |
                  |                 LoadWasm
                  |                    |
                  |                    v
                  |        |========================|
                  |--------|       VM: Loaded       |<-------|
                  |        |========================|        |
                  |              |            ^              |
                  |         Validate          |              |
              Cleanup            |          LoadWasm         |
                  |              v            |            LoadWasm
                  |        |========================|        |
                  |--------|      VM: Validated     |        |
                  |        |========================|        |
                  |              |            ^              |
                  |      Instantiate          |              |
                  |              |          RegisterModule   |
                  |              v            |              |
                  |        |========================|        |
                  |--------|    VM: Instantiated    |--------|
                           |========================|
                                 |            ^
                                 |            |
                                 --------------
                    Instantiate, Execute, ExecuteRegistered
    

    The status of the VM context would be Inited when created. After loading WASM successfully, the status will be Loaded. After validating WASM successfully, the status will be Validated. After instantiating WASM successfully, the status will be Instantiated, and developers can invoke functions. Developers can register WASM or import objects in any status, but they should instantiate WASM again. Developers can also load WASM in any status, and they should validate and instantiate the WASM module before function invocation. When in the Instantiated status, developers can instantiate the WASM module again to reset the old WASM runtime structures.

VM Creations

The VM creation API accepts the Configure context and the Store context. If developers only need the default settings, just pass NULL to the creation API. The details of the Store context will be introduced in Store.

WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, StoreCxt);
/* The caller should guarantee the life cycle if the store context. */
WasmEdge_StatisticsContext *StatCxt = WasmEdge_VMGetStatisticsContext(VMCxt);
/* The VM context already contains the statistics context and can be retrieved by this API. */
/* 
 * Note that the retrieved store and statistics contexts from the VM contexts by VM APIs
 * should __NOT__ be destroyed and owned by the VM contexts.
 */
WasmEdge_VMDelete(VMCxt);
WasmEdge_StoreDelete(StoreCxt);
WasmEdge_ConfigureDelete(ConfCxt);

Preregistrations

WasmEdge provides the following built-in pre-registrations.

  1. WASI (WebAssembly System Interface)

    Developers can turn on the WASI support for VM in the Configure context.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    /* The following API can retrieve the pre-registration import objects from the VM context. */
    /* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
    WasmEdge_ImportObjectContext *WasiObject =
      WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
    /* Initialize the WASI. */
    WasmEdge_ImportObjectInitWASI(WasiObject, /* ... ignored */ );
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_ConfigureDelete(ConfCxt);
    

    And also can create the WASI import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

  2. WasmEdge_Process

    This pre-registration is for the process interface for WasmEdge on Rust sources. After turning on this pre-registration, the VM will support the wasmedge_process host functions.

    WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
    WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_WasmEdge_Process);
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
    /* The following API can retrieve the pre-registration import objects from the VM context. */
    /* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
    WasmEdge_ImportObjectContext *ProcObject =
      WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_WasmEdge_Process);
    /* Initialize the WasmEdge_Process. */
    WasmEdge_ImportObjectInitWasmEdgeProcess(ProcObject, /* ... ignored */ );
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_ConfigureDelete(ConfCxt);
    

    And also can create the WasmEdge_Process import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

Host Module Registrations

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, the host functions are composed into host modules as WasmEdge_ImportObjectContext objects with module names. Please refer to the Host Functions in WasmEdge Runtime for the details. In this chapter, we show the example for registering the host modules into a VM context.

WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_ImportObjectContext *WasiObject =
  WasmEdge_ImportObjectCreateWASI( /* ... ignored ... */ );
/* You can also create and register the WASI host modules by this API. */
WasmEdge_Result Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, WasiObject);
/* The result status should be checked. */
WasmEdge_ImportObjectDelete(WasiObject);
/* The created import objects should be deleted. */
WasmEdge_VMDelete(VMCxt);

WASM Registrations And Executions

In WebAssembly, the instances in WASM modules can be exported and can be imported by other WASM modules. WasmEdge VM provides APIs for developers to register and export any WASM modules, and execute the functions or host functions in the registered WASM modules.

  1. Register the WASM modules with exported module names

    Unless the import objects have already contained the module names, every WASM module should be named uniquely when registering. Assume that the WASM file fibonacci.wasm is copied into the current directory.

    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    WasmEdge_Result Res = WasmEdge_VMRegisterModuleFromFile(VMCxt, ModName, "fibonacci.wasm");
    /* 
     * Developers can register the WASM module from buffer with the `WasmEdge_VMRegisterModuleFromBuffer()` API,
     * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRegisterModuleFromASTModule()` API.
     */
    /* 
     * The result status should be checked.
     * The error will occur if the WASM module instantiation failed or the module name conflicts.
     */
    WasmEdge_StringDelete(ModName);
    WasmEdge_VMDelete(VMCxt);
    
  2. Execute the functions in registered WASM modules

    Assume that the C file test.c is as follows:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(20) };
      WasmEdge_Value Returns[1];
      /* Names. */
      WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
    
      /* Register the WASM module into VM. */
      Res = WasmEdge_VMRegisterModuleFromFile(VMCxt, ModName, "fibonacci.wasm");
      /* 
      * Developers can register the WASM module from buffer with the `WasmEdge_VMRegisterModuleFromBuffer()` API,
      * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMRegisterModuleFromASTModule()` API.
      */
      if (!WasmEdge_ResultOK(Res)) {
        printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* 
      * The function "fib" in the "fibonacci.wasm" was exported with the module name "mod".
      * As the same as host functions, other modules can import the function `"mod" "fib"`.
      */
    
      /* 
      * Execute WASM functions in registered modules.
      * Unlike the execution of functions, the registered functions can be invoked without
      * `WasmEdge_VMInstantiate()` because the WASM module was instantiated when registering.
      * Developers can also invoke the host functions directly with this API.
      */
      Res = WasmEdge_VMExecuteRegistered(VMCxt, ModName, FuncName, Params, 1, Returns, 1);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
      WasmEdge_StringDelete(ModName);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_VMDelete(VMCxt);
      return 0;
    }
    

    Then you can compile and run: (the 20th Fibonacci number is 89 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 10946
    

Asynchronous Execution

  1. Asynchronously run WASM functions rapidly

    Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(20) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Asynchronously run the WASM function from file and get the `WasmEdge_Async` object. */
      WasmEdge_Async *Async = WasmEdge_VMAsyncRunWasmFromFile(VMCxt, "fibonacci.wasm", FuncName, Params, 1);
      /* 
       * Developers can run the WASM binary from buffer with the `WasmEdge_VMAsyncRunWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMAsyncRunWasmFromASTModule()` API.
       */
    
      /* Wait for the execution. */
      WasmEdge_AsyncWait(Async);
      /*
       * Developers can also use the `WasmEdge_AsyncGetReturnsLength()` or `WasmEdge_AsyncGet()` APIs
       * to wait for the asynchronous execution. These APIs will wait until the execution finished.
       */
    
      /* Check the return values length. */
      uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
      /* The `Arity` should be 1. Developers can skip this step if they have known the return arity. */
    
      /* Get the result. */
      WasmEdge_Result Res = WasmEdge_AsyncGet(Async, Returns, Arity);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_AsyncDelete(Async);
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      return 0;
    }
    

    Then you can compile and run: (the 20th Fibonacci number is 10946 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 10946
    
  2. Instantiate and asynchronously run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM context APIs:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(25) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
      /* Result. */
      WasmEdge_Result Res;
      
      /* Step 1: Load WASM file. */
      Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      /* 
       * Developers can load the WASM binary from buffer with the `WasmEdge_VMLoadWasmFromBuffer()` API,
       * or from `WasmEdge_ASTModuleContext` object with the `WasmEdge_VMLoadWasmFromASTModule()` API.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 2: Validate the WASM module. */
      Res = WasmEdge_VMValidate(VMCxt);
      if (!WasmEdge_ResultOK(Res)) {
        printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 3: Instantiate the WASM module. */
      Res = WasmEdge_VMInstantiate(VMCxt);
      /* 
       * Developers can load, validate, and instantiate another WASM module to replace the
       * instantiated one. In this case, the old module will be cleared, but the registered
       * modules are still kept.
       */
      if (!WasmEdge_ResultOK(Res)) {
        printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
        return 1;
      }
      /* Step 4: Asynchronously execute the WASM function and get the `WasmEdge_Async` object. */
      WasmEdge_Async *Async = WasmEdge_VMAsyncExecute(VMCxt, FuncName, Params, 1);
      /* 
       * Developers can execute functions repeatedly after instantiation.
       * For invoking the registered functions, you can use the `WasmEdge_VMAsyncExecuteRegistered()` API.
       */
    
      /* Wait and check the return values length. */
      uint32_t Arity = WasmEdge_AsyncGetReturnsLength(Async);
      /* The `Arity` should be 1. Developers can skip this step if they have known the return arity. */
    
      /* Get the result. */
      Res = WasmEdge_AsyncGet(Async, Returns, Arity);
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_AsyncDelete(Async);
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
    }
    

    Then you can compile and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get the result: 121393
    

Instance Tracing

Sometimes the developers may have requirements to get the instances of the WASM runtime. The VM context supplies the APIs to retrieve the instances.

  1. Store

    If the VM context is created without assigning a Store context, the VM context will allocate and own a Store context.

    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    WasmEdge_StoreContext *StoreCxt = WasmEdge_VMGetStoreContext(VMCxt);
    /* The object should __NOT__ be deleted by `WasmEdge_StoreDelete()`. */
    WasmEdge_VMDelete(VMCxt);
    

    Developers can also create the VM context with a Store context. In this case, developers should guarantee the life cycle of the Store context. Please refer to the Store Contexts for the details about the Store context APIs.

    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, StoreCxt);
    WasmEdge_StoreContext *StoreCxtMock = WasmEdge_VMGetStoreContext(VMCxt);
    /* The `StoreCxt` and the `StoreCxtMock` are the same. */
    WasmEdge_VMDelete(VMCxt);
    WasmEdge_StoreDelete(StoreCxt);
    
  2. List exported functions

    After the WASM module instantiation, developers can use the WasmEdge_VMExecute() API to invoke the exported WASM functions. For this purpose, developers may need information about the exported WASM function list. Please refer to the Instances in runtime for the details about the function types. Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    int main() {
      WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, StoreCxt);
    
      WasmEdge_VMLoadWasmFromFile(VMCxt, "fibonacci.wasm");
      WasmEdge_VMValidate(VMCxt);
      WasmEdge_VMInstantiate(VMCxt);
    
      /* List the exported functions. */
      /* Get the number of exported functions. */
      uint32_t FuncNum = WasmEdge_VMGetFunctionListLength(VMCxt);
      /* Create the name buffers and the function type buffers. */
      const uint32_t BUF_LEN = 256;
      WasmEdge_String FuncNames[BUF_LEN];
      WasmEdge_FunctionTypeContext *FuncTypes[BUF_LEN];
      /* 
       * Get the export function list.
       * If the function list length is larger than the buffer length, the overflowed data will be discarded.
       * The `FuncNames` and `FuncTypes` can be NULL if developers don't need them.
       */
      uint32_t RealFuncNum = WasmEdge_VMGetFunctionList(VMCxt, FuncNames, FuncTypes, BUF_LEN);
    
      for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
        char Buf[BUF_LEN];
        uint32_t Size = WasmEdge_StringCopy(FuncNames[I], Buf, sizeof(Buf));
        printf("Get exported function string length: %u, name: %s\n", Size, Buf);
        /* 
         * The function names should be __NOT__ destroyed.
         * The returned function type contexts should __NOT__ be destroyed.
         */
      }
      return 0;
    }
    

    Then you can compile and run: (the only exported function in fibonacci.wasm is fib)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Get exported function string length: 3, name: fib
    

    If developers want to get the exported function names in the registered WASM modules, please retrieve the Store context from the VM context and refer to the APIs of Store Contexts to list the registered functions by the module name.

  3. Get function types

    The VM context provides APIs to find the function type by function name. Please refer to the Instances in runtime for the details about the function types.

    /* 
     * ...
     * Assume that a WASM module is instantiated in `VMCxt`.
     */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    const WasmEdge_FunctionTypeContext *FuncType = WasmEdge_VMGetFunctionType(VMCxt, FuncName);
    /* 
     * Developers can get the function types of functions in the registered modules
     * via the `WasmEdge_VMGetFunctionTypeRegistered()` API with the module name.
     * If the function is not found, these APIs will return `NULL`.
     * The returned function type contexts should __NOT__ be destroyed.
     */
    WasmEdge_StringDelete(FuncName);
    

WasmEdge Runtime

In this partition, we will introduce the objects of WasmEdge runtime manually.

WASM Execution Example Step-By-Step

Besides the WASM execution through the VM context, developers can execute the WASM functions or instantiate WASM modules step-by-step with the Loader, Validator, Executor, and Store contexts. Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main() {
  /* Create the configure context. This step is not necessary because we didn't adjust any setting. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  /* Create the statistics context. This step is not necessary if the statistics in runtime is not needed. */
  WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
  /* Create the store context. The store context is the WASM runtime structure core. */
  WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
  /* Result. */
  WasmEdge_Result Res;

  /* Create the loader context. The configure context can be NULL. */
  WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(ConfCxt);
  /* Create the validator context. The configure context can be NULL. */
  WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(ConfCxt);
  /* Create the executor context. The configure context and the statistics context can be NULL. */
  WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);

  /* Load the WASM file or the compiled-WASM file and convert into the AST module context. */
  WasmEdge_ASTModuleContext *ASTCxt = NULL;
  Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
  if (!WasmEdge_ResultOK(Res)) {
    printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }
  /* Validate the WASM module. */
  Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }
  /* Instantiate the WASM module into store context. */
  Res = WasmEdge_ExecutorInstantiate(ExecCxt, StoreCxt, ASTCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return 1;
  }

  /* Try to list the exported functions of the instantiated WASM module. */
  uint32_t FuncNum = WasmEdge_StoreListFunctionLength(StoreCxt);
  /* Create the name buffers. */
  const uint32_t BUF_LEN = 256;
  WasmEdge_String FuncNames[BUF_LEN];
  /* If the list length is larger than the buffer length, the overflowed data will be discarded. */
  uint32_t RealFuncNum = WasmEdge_StoreListFunction(StoreCxt, FuncNames, BUF_LEN);
  for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
    char Buf[BUF_LEN];
    uint32_t Size = WasmEdge_StringCopy(FuncNames[I], Buf, sizeof(Buf));
    printf("Get exported function string length: %u, name: %s\n", Size, Buf);
    /* The function names should __NOT__ be destroyed. */
  }

  /* The parameters and returns arrays. */
  WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(18) };
  WasmEdge_Value Returns[1];
  /* Function name. */
  WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
  /* Invoke the WASM fnction. */
  Res = WasmEdge_ExecutorInvoke(ExecCxt, StoreCxt, FuncName, Params, 1, Returns, 1);
  if (WasmEdge_ResultOK(Res)) {
    printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
  } else {
    printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  }

  /* Resources deallocations. */
  WasmEdge_StringDelete(FuncName);
  WasmEdge_ASTModuleDelete(ASTCxt);
  WasmEdge_LoaderDelete(LoadCxt);
  WasmEdge_ValidatorDelete(ValidCxt);
  WasmEdge_ExecutorDelete(ExecCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  WasmEdge_StoreDelete(StoreCxt);
  WasmEdge_StatisticsDelete(StatCxt);
  return 0;
}

Then you can compile and run: (the 18th Fibonacci number is 4181 in 0-based index)

$ gcc test.c -lwasmedge_c
$ ./a.out
Get exported function string length: 3, name: fib
Get the result: 4181

Loader

The Loader context loads the WASM binary from files or buffers. Both the WASM and the compiled-WASM from the WasmEdge AOT Compiler are supported.

uint8_t Buf[4096];
/* ... Read the WASM code to the buffer. */
uint32_t FileSize = ...;
/* The `FileSize` is the length of the WASM code. */

/* Developers can adjust settings in the configure context. */
WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
/* Create the loader context. The configure context can be NULL. */
WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(ConfCxt);

WasmEdge_ASTModuleContext *ASTCxt = NULL;
WasmEdge_Result Res;

/* Load WASM or compiled-WASM from the file. */
Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
/* The output AST module context should be destroyed. */
WasmEdge_ASTModuleDelete(ASTCxt);

/* Load WASM or compiled-WASM from the buffer. */
Res = WasmEdge_LoaderParseFromBuffer(LoadCxt, &ASTCxt, Buf, FileSize);
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
/* The output AST module context should be destroyed. */
WasmEdge_ASTModuleDelete(ASTCxt);

WasmEdge_LoaderDelete(LoadCxt);
WasmEdge_ConfigureDelete(ConfCxt);

Validator

The Validator context can validate the WASM module. Every WASM module should be validated before instantiation.

/* 
 * ...
 * Assume that the `ASTCxt` is the output AST module context from the loader context.
 * Assume that the `ConfCxt` is the configure context.
 */
/* Create the validator context. The configure context can be NULL. */
WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(ConfCxt);
WasmEdge_Result Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
}
WasmEdge_ValidatorDelete(ValidCxt);

Executor

The Executor context is the executor for both WASM and compiled-WASM. This object should work base on the Store context. For the details of the Store context, please refer to the next chapter.

  1. Register modules

    As the same of registering host modules or importing WASM modules in VM context, developers can register Import Object or AST module contexts into the Store context by the Executor APIs. For the details of import objects, please refer to the Host Functions.

    /* 
    * ...
    * Assume that the `ASTCxt` is the output AST module context from the loader context
    * and has passed the validation.
    * Assume that the `ConfCxt` is the configure context.
    */
    /* Create the statistics context. This step is not necessary. */
    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* Create the executor context. The configure and the statistics contexts can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);
    /* Create the store context. The store context is the WASM runtime structure core. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* Result. */
    WasmEdge_Result Res;
    
    /* Register the WASM module into store with the export module name "mod". */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    Res = WasmEdge_ExecutorRegisterModule(ExecCxt, StoreCxt, ASTCxt, ModName);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    WasmEdge_StringDelete(ModName);
    
    /* 
     * Assume that the `ImpCxt` is the import object context for host functions.
     */
    WasmEdge_ImportObjectContext *ImpCxt = ...;
    /* The import module context has already contained the export module name. */
    Res = WasmEdge_ExecutorRegisterImport(ExecCxt, StoreCxt, ImpCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Import object registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    
    WasmEdge_ImportObjectDelete(ImpCxt);
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    WasmEdge_StoreDelete(StoreCxt);
    
  2. Instantiate modules

    WASM or compiled-WASM modules should be instantiated before the function invocation. Note that developers can only instantiate one module into the Store context, and in that case, the old instantiated module will be cleaned. Before instantiating a WASM module, please check the import section for ensuring the imports are registered into the Store context.

    /* 
    * ...
    * Assume that the `ASTCxt` is the output AST module context from the loader context
    * and has passed the validation.
    * Assume that the `ConfCxt` is the configure context.
    */
    /* Create the statistics context. This step is not necessary. */
    WasmEdge_StatisticsContext *StatCxt = WasmEdge_StatisticsCreate();
    /* Create the executor context. The configure and the statistics contexts can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(ConfCxt, StatCxt);
    /* Create the store context. The store context is the WASM runtime structure core. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    
    /* Instantiate the WASM module. */
    WasmEdge_Result Res = WasmEdge_ExecutorInstantiate(ExecCxt, StoreCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM instantiation failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StatisticsDelete(StatCxt);
    WasmEdge_StoreDelete(StoreCxt);
    
  3. Invoke functions

    As the same as function invocation via the VM context, developers can invoke the functions of the instantiated or registered modules. The APIs, WasmEdge_ExecutorInvoke() and WasmEdge_ExecutorInvokeRegistered(), are similar as the APIs of the VM context. Please refer to the VM context workflows for details.

AST Module

The AST Module context presents the loaded structure from a WASM file or buffer. Developer will get this object after loading a WASM file or buffer from Loader. Before instantiation, developers can also query the imports and exports of an AST Module context.

WasmEdge_ASTModuleContext *ASTCxt = ...;
/* Assume that a WASM is loaded into an AST module context. */

/* Create the import type context buffers. */
const uint32_t BUF_LEN = 256;
const WasmEdge_ImportTypeContext *ImpTypes[BUF_LEN];
uint32_t ImportNum = WasmEdge_ASTModuleListImportsLength(ASTCxt);
/* If the list length is larger than the buffer length, the overflowed data will be discarded. */
uint32_t RealImportNum = WasmEdge_ASTModuleListImports(ASTCxt, ImpTypes, BUF_LEN);
for (uint32_t I = 0; I < RealImportNum && I < BUF_LEN; I++) {
  /* Working with the import type `ImpTypes[I]` ... */
}

/* Create the export type context buffers. */
const WasmEdge_ExportTypeContext *ExpTypes[BUF_LEN];
uint32_t ExportNum = WasmEdge_ASTModuleListExportsLength(ASTCxt);
/* If the list length is larger than the buffer length, the overflowed data will be discarded. */
uint32_t RealExportNum = WasmEdge_ASTModuleListExports(ASTCxt, ExpTypes, BUF_LEN);
for (uint32_t I = 0; I < RealExportNum && I < BUF_LEN; I++) {
  /* Working with the export type `ExpTypes[I]` ... */
}

WasmEdge_ASTModuleDelete(ASTCxt);
/* After deletion of `ASTCxt`, all data queried from the `ASTCxt` should not be accessed. */

Store

Store is the runtime structure for the representation of all instances of Functions, Tables, Memorys, and Globals that have been allocated during the lifetime of the abstract machine. The Store context in WasmEdge provides APIs to list the exported instances with their names or find the instances by exported names. For adding instances into Store contexts, please instantiate or register WASM modules or Import Object contexts via the Executor context.

  1. List instances

    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* ... Instantiate a WASM module via the executor context. */
    ...
    
    /* Try to list the exported functions of the instantiated WASM module. */
    /* Take the function instances for example here. */
    uint32_t FuncNum = WasmEdge_StoreListFunctionLength(StoreCxt);
    /* Create the name buffers. */
    const uint32_t BUF_LEN = 256;
    WasmEdge_String FuncNames[BUF_LEN];
    /* If the list length is larger than the buffer length, the overflowed data will be discarded. */
    uint32_t RealFuncNum = WasmEdge_StoreListFunction(StoreCxt, FuncNames, BUF_LEN);
    for (uint32_t I = 0; I < RealFuncNum && I < BUF_LEN; I++) {
      /* Working with the function name `FuncNames[I]` ... */
      /* The function names should __NOT__ be destroyed. */
    }
    

    Developers can list the function instance exported names of the registered modules via the WasmEdge_StoreListFunctionRegisteredLength() and the WasmEdge_StoreListFunctionRegistered() APIs with the module name.

  2. Find instances

    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* ... Instantiate a WASM module via the executor context. */
    ...
    
    /* Try to find the exported instance of the instantiated WASM module. */
    /* Take the function instances for example here. */
    /* Function name. */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    WasmEdge_FunctionInstanceContext *FuncCxt = WasmEdge_StoreFindFunction(StoreCxt, FuncName);
    /* `FuncCxt` will be `NULL` if the function not found. */
    /* The returned instance is owned by the store context and should __NOT__ be destroyed. */
    WasmEdge_StringDelete(FuncName);
    

    Developers can retrieve the exported function instances of the registered modules via the WasmEdge_StoreFindFunctionRegistered() API with the module name.

  3. List registered modules

    With the module names, developers can list the exported instances of the registered modules with their names.

    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* ... Register a WASM module via the executor context. */
    ...
    
    /* Try to list the registered WASM modules. */
    uint32_t ModNum = WasmEdge_StoreListModuleLength(StoreCxt);
    /* Create the name buffers. */
    const uint32_t BUF_LEN = 256;
    WasmEdge_String ModNames[BUF_LEN];
    /* If the list length is larger than the buffer length, the overflowed data will be discarded. */
    uint32_t RealModNum = WasmEdge_StoreListModule(StoreCxt, ModNames, BUF_LEN);
    for (uint32_t I = 0; I < RealModNum && I < BUF_LEN; I++) {
      /* Working with the module name `ModNames[I]` ... */
      /* The module names should __NOT__ be destroyed. */
    }
    

Instances

The instances are the runtime structures of WASM. Developers can retrieve the instances from the Store contexts. The Store contexts will allocate instances when a WASM module or Import Object is registered or instantiated through the Executor. A single instance can be allocated by its creation function. Developers can construct instances into an Import Object for registration. Please refer to the Host Functions for details. The instances created by their creation functions should be destroyed, EXCEPT they are added into an Import Object context.

  1. Function instance

    Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function contexts for host functions and add them into an Import Object context for registering into a VM or a Store. For both host functions and the functions get from Store, developers can retrieve the Function Type from the Function contexts. For the details of the Host Function guide, please refer to the next chapter.

    /* Retrieve the function instance from the store context. */
    WasmEdge_FunctionInstanceContext *FuncCxt = ...;
    WasmEdge_FunctionTypeContext *FuncTypeCxt = WasmEdge_FunctionInstanceGetFunctionType(FuncCxt);
    /* The `FuncTypeCxt` is owned by the `FuncCxt` and should __NOT__ be destroyed. */
    
  2. Table instance

    In WasmEdge, developers can create the Table contexts and add them into an Import Object context for registering into a VM or a Store. The Table contexts supply APIs to control the data in table instances.

    WasmEdge_Limit TabLimit = {.HasMax = true, .Min = 10, .Max = 20};
    /* Create the table type with limit and the `FuncRef` element type. */
    WasmEdge_TableTypeContext *TabTypeCxt = WasmEdge_TableTypeCreate(WasmEdge_RefType_FuncRef, TabLimit);
    /* Create the table instance with table type. */
    WasmEdge_TableInstanceContext *HostTable = WasmEdge_TableInstanceCreate(TabTypeCxt);
    /* Delete the table type. */
    WasmEdge_TableTypeDelete(TabTypeCxt);
    WasmEdge_Result Res;
    WasmEdge_Value Data;
    
    TabTypeCxt = WasmEdge_TableInstanceGetTableType(HostTable);
    /* The `TabTypeCxt` got from table instance is owned by the `HostTable` and should __NOT__ be destroyed. */
    enum WasmEdge_RefType RefType = WasmEdge_TableTypeGetRefType(TabTypeCxt);
    /* `RefType` will be `WasmEdge_RefType_FuncRef`. */
    Data = WasmEdge_ValueGenFuncRef(5);
    Res = WasmEdge_TableInstanceSetData(HostTable, Data, 3);
    /* Set the function index 5 to the table[3]. */
    /*
     * This will get an "out of bounds table access" error
     * because the position (13) is out of the table size (10):
     *   Res = WasmEdge_TableInstanceSetData(HostTable, Data, 13);
     */
    Res = WasmEdge_TableInstanceGetData(HostTable, &Data, 3);
    /* Get the FuncRef value of the table[3]. */
    /*
     * This will get an "out of bounds table access" error
     * because the position (13) is out of the table size (10):
     *   Res = WasmEdge_TableInstanceGetData(HostTable, &Data, 13);
     */
    
    uint32_t Size = WasmEdge_TableInstanceGetSize(HostTable);
    /* `Size` will be 10. */
    Res = WasmEdge_TableInstanceGrow(HostTable, 6);
    /* Grow the table size of 6, the table size will be 16. */
    /*
     * This will get an "out of bounds table access" error because
     * the size (16 + 6) will reach the table limit(20):
     *   Res = WasmEdge_TableInstanceGrow(HostTable, 6);
     */
    
    WasmEdge_TableInstanceDelete(HostTable);
    
  3. Memory instance

    In WasmEdge, developers can create the Memory contexts and add them into an Import Object context for registering into a VM or a Store. The Memory contexts supply APIs to control the data in memory instances.

    WasmEdge_Limit MemLimit = {.HasMax = true, .Min = 1, .Max = 5};
    /* Create the memory type with limit. The memory page size is 64KiB. */
    WasmEdge_MemoryTypeContext *MemTypeCxt = WasmEdge_MemoryTypeCreate(MemLimit);
    /* Create the memory instance with memory type. */
    WasmEdge_MemoryInstanceContext *HostMemory = WasmEdge_MemoryInstanceCreate(MemTypeCxt);
    /* Delete the memory type. */
    WasmEdge_MemoryTypeDelete(MemTypeCxt);
    WasmEdge_Result Res;
    uint8_t Buf[256];
    
    Buf[0] = 0xAA;
    Buf[1] = 0xBB;
    Buf[2] = 0xCC;
    Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0x1000, 3);
    /* Set the data[0:2] to the memory[4096:4098]. */
    /*
     * This will get an "out of bounds memory access" error
     * because [65535:65537] is out of 1 page size (65536):
     *   Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0xFFFF, 3);
     */
    Buf[0] = 0;
    Buf[1] = 0;
    Buf[2] = 0;
    Res = WasmEdge_MemoryInstanceGetData(HostMemory, Buf, 0x1000, 3);
    /* Get the memory[4096:4098]. Buf[0:2] will be `{0xAA, 0xBB, 0xCC}`. */
    /*
     * This will get an "out of bounds memory access" error
     * because [65535:65537] is out of 1 page size (65536):
     *   Res = WasmEdge_MemoryInstanceSetData(HostMemory, Buf, 0xFFFF, 3);
     */
    
    uint32_t PageSize = WasmEdge_MemoryInstanceGetPageSize(HostMemory);
    /* `PageSize` will be 1. */
    Res = WasmEdge_MemoryInstanceGrowPage(HostMemory, 2);
    /* Grow the page size of 2, the page size of the memory instance will be 3. */
    /*
     * This will get an "out of bounds memory access" error because
     * the page size (3 + 3) will reach the memory limit(5):
     *   Res = WasmEdge_MemoryInstanceGrowPage(HostMemory, 3);
     */
    
    WasmEdge_MemoryInstanceDelete(HostMemory);
    
  4. Global instance

    In WasmEdge, developers can create the Global contexts and add them into an Import Object context for registering into a VM or a Store. The Global contexts supply APIs to control the value in global instances.

    WasmEdge_Value Val = WasmEdge_ValueGenI64(1000);
    /* Create the global type with value type and mutation. */
    WasmEdge_GlobalTypeContext *GlobTypeCxt = WasmEdge_GlobalTypeCreate(WasmEdge_ValType_I64, WasmEdge_Mutability_Var);
    /* Create the global instance with value and global type. */
    WasmEdge_GlobalInstanceContext *HostGlobal = WasmEdge_GlobalInstanceCreate(GlobTypeCxt, Val);
    /* Delete the global type. */
    WasmEdge_GlobalTypeDelete(GlobTypeCxt);
    WasmEdge_Result Res;
    
    GlobTypeCxt = WasmEdge_GlobalInstanceGetGlobalType(HostGlobal);
    /* The `GlobTypeCxt` got from global instance is owned by the `HostGlobal` and should __NOT__ be destroyed. */
    enum WasmEdge_ValType ValType = WasmEdge_GlobalTypeGetValType(GlobTypeCxt);
    /* `ValType` will be `WasmEdge_ValType_I64`. */
    enum WasmEdge_Mutability ValMut = WasmEdge_GlobalTypeGetMutability(GlobTypeCxt);
    /* `ValMut` will be `WasmEdge_Mutability_Var`. */
    
    WasmEdge_GlobalInstanceSetValue(HostGlobal, WasmEdge_ValueGenI64(888));
    /* 
     * Set the value u64(888) to the global.
     * This function will do nothing if the value type mismatched or
     * the global mutability is `WasmEdge_Mutability_Const`.
     */
    WasmEdge_Value GlobVal = WasmEdge_GlobalInstanceGetValue(HostGlobal);
    /* Get the value (888 now) of the global context. */
    
    WasmEdge_GlobalInstanceDelete(HostGlobal);
    

Host Functions

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function, Memory, Table, and Global contexts and add them into an Import Object context for registering into a VM or a Store.

  1. Host function allocation

    Developers can define C functions with the following function signature as the host function body:

    typedef WasmEdge_Result (*WasmEdge_HostFunc_t)(
      void *Data,
      WasmEdge_MemoryInstanceContext *MemCxt,
      const WasmEdge_Value *Params,
      WasmEdge_Value *Returns);
    

    The example of an add host function to add 2 i32 values:

    WasmEdge_Result Add(void *, WasmEdge_MemoryInstanceContext *,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      /*
      * Params: {i32, i32}
      * Returns: {i32}
      * Developers should take care about the function type.
      */ 
      /* Retrieve the value 1. */
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      /* Retrieve the value 2. */
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      /* Output value 1 is Val1 + Val2. */
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      /* Return the status of success. */
      return WasmEdge_Result_Success;
    }
    

    Then developers can create Function context with the host function body and function type:

    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
    /* Create a function type: {i32, i32} -> {i32}. */
    HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    /* 
     * Create a function context with the function type and host function body.
     * The `Cost` parameter can be 0 if developers do not need the cost measuring.
     */
    WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
    /*
     * The third parameter is the pointer to the additional data.
     * Developers should guarantee the life cycle of the data, and it can be
     * `NULL` if the external data is not needed.
     */
    
    /* If the function instance is not added into an import object context, it should be deleted. */
    WasmEdge_FunctionInstanceDelete(HostFunc);
    
  2. Import object context

    The Import Object context holds an exporting module name and the instances. Developers can add the Function, Memory, Table, and Global instances with their exporting names.

    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      return WasmEdge_Result_Success;
    }
    
    /* Create the import object. */
    WasmEdge_String ExportName = WasmEdge_StringCreateByCString("module");
    WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName);
    WasmEdge_StringDelete(ExportName);
    
    /* Create and add a function instance into the import object. */
    enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
    enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
    WasmEdge_FunctionTypeContext *HostFType = 
      WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
    WasmEdge_FunctionInstanceContext *HostFunc =
      WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
    /*
     * The third parameter is the pointer to the additional data object.
     * Developers should guarantee the life cycle of the data, and it can be
     * `NULL` if the external data is not needed.
     */
    WasmEdge_FunctionTypeDelete(HostFType);
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("add");
    WasmEdge_ImportObjectAddFunction(ImpObj, FuncName, HostFunc);
    WasmEdge_StringDelete(FuncName);
    
    /* Create and add a table instance into the import object. */
    WasmEdge_Limit TableLimit = {.HasMax = true, .Min = 10, .Max = 20};
    WasmEdge_TableTypeContext *HostTType = 
      WasmEdge_TableTypeCreate(WasmEdge_RefType_FuncRef, TableLimit);
    WasmEdge_TableInstanceContext *HostTable = WasmEdge_TableInstanceCreate(HostTType);
    WasmEdge_TableTypeDelete(HostTType);
    WasmEdge_String TableName = WasmEdge_StringCreateByCString("table");
    WasmEdge_ImportObjectAddTable(ImpObj, TableName, HostTable);
    WasmEdge_StringDelete(TableName);
    
    /* Create and add a memory instance into the import object. */
    WasmEdge_Limit MemoryLimit = {.HasMax = true, .Min = 1, .Max = 2};
    WasmEdge_MemoryTypeContext *HostMType = WasmEdge_MemoryTypeCreate(MemoryLimit);
    WasmEdge_MemoryInstanceContext *HostMemory = WasmEdge_MemoryInstanceCreate(HostMType);
    WasmEdge_MemoryTypeDelete(HostMType);
    WasmEdge_String MemoryName = WasmEdge_StringCreateByCString("memory");
    WasmEdge_ImportObjectAddMemory(ImpObj, MemoryName, HostMemory);
    WasmEdge_StringDelete(MemoryName);
    
    /* Create and add a global instance into the import object. */
    WasmEdge_GlobalTypeContext *HostGType =
      WasmEdge_GlobalTypeCreate(WasmEdge_ValType_I32, WasmEdge_Mutability_Var);
    WasmEdge_GlobalInstanceContext *HostGlobal =
      WasmEdge_GlobalInstanceCreate(HostGType, WasmEdge_ValueGenI32(666));
    WasmEdge_GlobalTypeDelete(HostGType);
    WasmEdge_String GlobalName = WasmEdge_StringCreateByCString("global");
    WasmEdge_ImportObjectAddGlobal(ImpObj, GlobalName, HostGlobal);
    WasmEdge_StringDelete(GlobalName);
    
    /*
     * The import objects should be deleted.
     * Developers should __NOT__ destroy the instances added into the import object contexts.
     */
    WasmEdge_ImportObjectDelete(ImpObj);
    
  3. Specified import object

    WasmEdge_ImportObjectCreateWASI() API can create and initialize the WASI import object. WasmEdge_ImportObjectCreateWasmEdgeProcess() API can create and initialize the wasmedge_process import object. Developers can create these import object contexts and register them into the Store or VM contexts rather than adjust the settings in the Configure contexts.

    WasmEdge_ImportObjectContext *WasiObj = WasmEdge_ImportObjectCreateWASI( /* ... ignored */ );
    WasmEdge_ImportObjectContext *ProcObj = WasmEdge_ImportObjectCreateWasmEdgeProcess( /* ... ignored */ );
    WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    /* Register the WASI and WasmEdge_Process into the VM context. */
    WasmEdge_VMRegisterModuleFromImport(VMCxt, WasiObj);
    WasmEdge_VMRegisterModuleFromImport(VMCxt, ProcObj);
    /* Get the WASI exit code. */
    uint32_t ExitCode = WasmEdge_ImportObjectWASIGetExitCode(WasiObj);
    /*
     * The `ExitCode` will be EXIT_SUCCESS if the execution has no error.
     * Otherwise, it will return with the related exit code.
     */
    WasmEdge_VMDelete(VMCxt);
    /* The import objects should be deleted. */
    WasmEdge_ImportObjectDelete(WasiObj);
    WasmEdge_ImportObjectDelete(ProcObj);
    
  4. Example

    Assume that a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    And the test.c as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    
    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      printf("Host function \"Add\": %d + %d\n", Val1, Val2);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      return WasmEdge_Result_Success;
    }
    
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The WASM module buffer. */
      uint8_t WASM[] = {
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B
      };
    
      /* Create the import object. */
      WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern");
      WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName);
      enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
      enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
      WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
      WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
      WasmEdge_FunctionTypeDelete(HostFType);
      WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("func-add");
      WasmEdge_ImportObjectAddFunction(ImpObj, HostFuncName, HostFunc);
      WasmEdge_StringDelete(HostFuncName);
    
      WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[2] = { WasmEdge_ValueGenI32(1234), WasmEdge_ValueGenI32(5678) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("addTwo");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
        VMCxt, WASM, sizeof(WASM), FuncName, Params, 2, Returns, 1);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_ImportObjectDelete(ImpObj);
      return 0;
    }
    

    Then you can compile and run: (the result of 1234 + 5678 is 6912)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Host function "Add": 1234 + 5678
    Get the result: 6912
    
  5. Host Data Example

    Developers can set a external data object to the function context, and access to the object in the function body. Assume that a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    And the test.c as following:

    #include <wasmedge/wasmedge.h>
    #include <stdio.h>
    
    /* Host function body definition. */
    WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                        const WasmEdge_Value *In, WasmEdge_Value *Out) {
      int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
      int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
      printf("Host function \"Add\": %d + %d\n", Val1, Val2);
      Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
      /* Also set the result to the data. */
      int32_t *DataPtr = (int32_t *)Data;
      *DataPtr = Val1 + Val2;
      return WasmEdge_Result_Success;
    }
    
    int main() {
      /* Create the VM context. */
      WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    
      /* The WASM module buffer. */
      uint8_t WASM[] = {
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B
      };
    
      /* The external data object: an integer. */
      int32_t Data;
    
      /* Create the import object. */
      WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern");
      WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName);
      enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
      enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
      WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
      WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, Add, &Data, 0);
      WasmEdge_FunctionTypeDelete(HostFType);
      WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("func-add");
      WasmEdge_ImportObjectAddFunction(ImpObj, HostFuncName, HostFunc);
      WasmEdge_StringDelete(HostFuncName);
    
      WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
    
      /* The parameters and returns arrays. */
      WasmEdge_Value Params[2] = { WasmEdge_ValueGenI32(1234), WasmEdge_ValueGenI32(5678) };
      WasmEdge_Value Returns[1];
      /* Function name. */
      WasmEdge_String FuncName = WasmEdge_StringCreateByCString("addTwo");
      /* Run the WASM function from file. */
      WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
        VMCxt, WASM, sizeof(WASM), FuncName, Params, 2, Returns, 1);
    
      if (WasmEdge_ResultOK(Res)) {
        printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
      } else {
        printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
      }
      printf("Data value: %d\n", Data);
    
      /* Resources deallocations. */
      WasmEdge_VMDelete(VMCxt);
      WasmEdge_StringDelete(FuncName);
      WasmEdge_ImportObjectDelete(ImpObj);
      return 0;
    }
    

    Then you can compile and run: (the result of 1234 + 5678 is 6912)

    $ gcc test.c -lwasmedge_c
    $ ./a.out
    Host function "Add": 1234 + 5678
    Get the result: 6912
    Data value: 6912
    

WasmEdge AOT Compiler

In this partition, we will introduce the WasmEdge AOT compiler and the options.

WasmEdge runs the WASM files in interpreter mode, and WasmEdge also supports the AOT (ahead-of-time) mode running without modifying any code. The WasmEdge AOT (ahead-of-time) compiler compiles the WASM files for running in AOT mode which is much faster than interpreter mode. Developers can compile the WASM files into the compiled-WASM files in shared library format for universal WASM format for the AOT mode execution.

Compilation Example

Assume that the WASM file fibonacci.wasm is copied into the current directory, and the C file test.c is as following:

#include <wasmedge/wasmedge.h>
#include <stdio.h>
int main() {
  /* Create the configure context. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  /* ... Adjust settings in the configure context. */
  /* Result. */
  WasmEdge_Result Res;

  /* Create the compiler context. The configure context can be NULL. */
  WasmEdge_CompilerContext *CompilerCxt = WasmEdge_CompilerCreate(ConfCxt);
  /* Compile the WASM file with input and output paths. */
  Res = WasmEdge_CompilerCompile(CompilerCxt, "fibonacci.wasm", "fibonacci.wasm.so");
  if (!WasmEdge_ResultOK(Res)) {
      printf("Compilation failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return 1;
  }

  WasmEdge_CompilerDelete(CompilerCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  return 0;
}

Then you can compile and run (the output file is "fibonacci.wasm.so"):

$ gcc test.c -lwasmedge_c
$ ./a.out
[2021-07-02 11:08:08.651] [info] compile start
[2021-07-02 11:08:08.653] [info] verify start
[2021-07-02 11:08:08.653] [info] optimize start
[2021-07-02 11:08:08.670] [info] codegen start
[2021-07-02 11:08:08.706] [info] compile done

Compiler Options

Developers can set options for AOT compilers such as optimization level and output format:

/// AOT compiler optimization level enumeration.
enum WasmEdge_CompilerOptimizationLevel {
  /// Disable as many optimizations as possible.
  WasmEdge_CompilerOptimizationLevel_O0 = 0,
  /// Optimize quickly without destroying debuggability.
  WasmEdge_CompilerOptimizationLevel_O1,
  /// Optimize for fast execution as much as possible without triggering
  /// significant incremental compile time or code size growth.
  WasmEdge_CompilerOptimizationLevel_O2,
  /// Optimize for fast execution as much as possible.
  WasmEdge_CompilerOptimizationLevel_O3,
  /// Optimize for small code size as much as possible without triggering
  /// significant incremental compile time or execution time slowdowns.
  WasmEdge_CompilerOptimizationLevel_Os,
  /// Optimize for small code size as much as possible.
  WasmEdge_CompilerOptimizationLevel_Oz
};

/// AOT compiler output binary format enumeration.
enum WasmEdge_CompilerOutputFormat {
  /// Native dynamic library format.
  WasmEdge_CompilerOutputFormat_Native = 0,
  /// WebAssembly with AOT compiled codes in custom sections.
  WasmEdge_CompilerOutputFormat_Wasm
};

Please refer to the AOT compiler options configuration for details.

Upgrade to WasmEdge 0.10.0

Due to the WasmEdge C API breaking changes, this document shows the guideline of programming with WasmEdge C API to upgrade from the 0.9.1 to the 0.10.0 version.

Concepts

  1. Merged the WasmEdge_ImportObjectContext into the WasmEdge_ModuleInstanceContext.

    The WasmEdge_ImportObjectContext which is for the host functions is merged into WasmEdge_ModuleInstanceContext. Developers can use the related APIs to construct host modules.

    • WasmEdge_ImportObjectCreate() is changed to WasmEdge_ModuleInstanceCreate().
    • WasmEdge_ImportObjectDelete() is changed to WasmEdge_ModuleInstanceDelete().
    • WasmEdge_ImportObjectAddFunction() is changed to WasmEdge_ModuleInstanceAddFunction().
    • WasmEdge_ImportObjectAddTable() is changed to WasmEdge_ModuleInstanceAddTable().
    • WasmEdge_ImportObjectAddMemory() is changed to WasmEdge_ModuleInstanceAddMemory().
    • WasmEdge_ImportObjectAddGlobal() is changed to WasmEdge_ModuleInstanceAddGlobal().
    • WasmEdge_ImportObjectCreateWASI() is changed to WasmEdge_ModuleInstanceCreateWASI().
    • WasmEdge_ImportObjectCreateWasmEdgeProcess() is changed to WasmEdge_ModuleInstanceCreateWasmEdgeProcess().
    • WasmEdge_ImportObjectInitWASI() is changed to WasmEdge_ModuleInstanceInitWASI().
    • WasmEdge_ImportObjectInitWasmEdgeProcess() is changed to WasmEdge_ModuleInstanceInitWasmEdgeProcess().

    For the new host function examples, please refer to the example below.

  2. Used the pointer to WasmEdge_FunctionInstanceContext instead of the index in the FuncRef value type.

    For the better performance and security, the FuncRef related APIs used the const WasmEdge_FunctionInstanceContext * for the parameters and returns.

    • WasmEdge_ValueGenFuncRef() is changed to use the const WasmEdge_FunctionInstanceContext * as it's argument.
    • WasmEdge_ValueGetFuncRef() is changed to return the const WasmEdge_FunctionInstanceContext *.
  3. Supported multiple anonymous WASM module instantiation.

    In the version before 0.9.1, WasmEdge only supports 1 anonymous WASM module to be instantiated at one time. If developers instantiate a new WASM module, the old one will be replaced. After the 0.10.0 version, developers can instantiate multiple anonymous WASM module by Executor and get the Module instance. But for the source code using the VM APIs, the behavior is not changed. For the new examples of instantiating multiple anonymous WASM modules, please refer to the example below.

  4. Behavior changed of WasmEdge_StoreContext.

    The Function, Table, Memory, and Global instances retrievement from the Store is moved to the Module instance. The Store only manage the module linking when instantiation and the named module searching after the 0.10.0 version.

    • WasmEdge_StoreListFunctionLength() and WasmEdge_StoreListFunctionRegisteredLength() is replaced by WasmEdge_ModuleInstanceListFunctionLength().
    • WasmEdge_StoreListTableLength() and WasmEdge_StoreListTableRegisteredLength() is replaced by WasmEdge_ModuleInstanceListTableLength().
    • WasmEdge_StoreListMemoryLength() and WasmEdge_StoreListMemoryRegisteredLength() is replaced by WasmEdge_ModuleInstanceListMemoryLength().
    • WasmEdge_StoreListGlobalLength() and WasmEdge_StoreListGlobalRegisteredLength() is replaced by WasmEdge_ModuleInstanceListGlobalLength().
    • WasmEdge_StoreListFunction() and WasmEdge_StoreListFunctionRegistered() is replaced by WasmEdge_ModuleInstanceListFunction().
    • WasmEdge_StoreListTable() and WasmEdge_StoreListTableRegistered() is replaced by WasmEdge_ModuleInstanceListTable().
    • WasmEdge_StoreListMemory() and WasmEdge_StoreListMemoryRegistered() is replaced by WasmEdge_ModuleInstanceListMemory().
    • WasmEdge_StoreListGlobal() and WasmEdge_StoreListGlobalRegistered() is replaced by WasmEdge_ModuleInstanceListGlobal().
    • WasmEdge_StoreFindFunction() and WasmEdge_StoreFindFunctionRegistered() is replaced by WasmEdge_ModuleInstanceFindFunction().
    • WasmEdge_StoreFindTable() and WasmEdge_StoreFindTableRegistered() is replaced by WasmEdge_ModuleInstanceFindTable().
    • WasmEdge_StoreFindMemory() and WasmEdge_StoreFindMemoryRegistered() is replaced by WasmEdge_ModuleInstanceFindMemory().
    • WasmEdge_StoreFindGlobal() and WasmEdge_StoreFindGlobalRegistered() is replaced by WasmEdge_ModuleInstanceFindGlobal().

    For the new examples of retrieving instances, please refer to the example below.

  5. The WasmEdge_ModuleInstanceContext-based resource management.

    Except the creation of Module instance for the host functons, the Executor will output a Module instance after instantiation. No matter the anonymous or named modules, developers have the responsibility to destroy them by WasmEdge_ModuleInstanceDelete() API. The Store will link to the named Module instance after registering. After the destroyment of a Module instance, the Store will unlink to that automatically; after the destroyment of the Store, the all Module instances the Store linked to will unlink to that Store automatically.

WasmEdge VM changes

The VM APIs are basically not changed, except the ImportObject related APIs.

The following is the example of WASI initialization in WasmEdge 0.9.1 C API:

WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
/* The following API can retrieve the pre-registration import objects from the VM context. */
/* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
WasmEdge_ImportObjectContext *WasiObject =
    WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
/* Initialize the WASI. */
WasmEdge_ImportObjectInitWASI(WasiObject, /* ... ignored */ );

/* ... */

WasmEdge_VMDelete(VMCxt);
WasmEdge_ConfigureDelete(ConfCxt);

Developers can change to use the WasmEdge 0.10.0 C API as follows, with only replacing the WasmEdge_ImportObjectContext into WasmEdge_ModuleInstanceContext:

WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
/* The following API can retrieve the pre-registration module instances from the VM context. */
/* This API will return `NULL` if the corresponding pre-registration is not set into the configuration. */
WasmEdge_ModuleInstanceContext *WasiModule =
    WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
/* Initialize the WASI. */
WasmEdge_ModuleInstanceInitWASI(WasiModule, /* ... ignored */ );

/* ... */

WasmEdge_VMDelete(VMCxt);
WasmEdge_ConfigureDelete(ConfCxt);

The VM provides a new API for getting the current instantiated anonymous Module instance. For example, if developer want to get the exported Global instance:

/* Assume that a WASM module is instantiated in `VMCxt`, and exports the "global_i32". */
WasmEdge_StoreContext *StoreCxt = WasmEdge_VMGetStoreContext(VMCxt);
WasmEdge_String GlobName = WasmEdge_StringCreateByCString("global_i32");
WasmEdge_GlobalInstanceContext *GlobCxt = WasmEdge_StoreFindGlobal(StoreCxt, GlobName);
WasmEdge_StringDelete(GlobName);

After the WasmEdge 0.10.0 C API, developers can use the WasmEdge_VMGetActiveModule() to get the module instance:

/* Assume that a WASM module is instantiated in `VMCxt`, and exports the "global_i32". */
const WasmEdge_ModuleInstanceContext *ModCxt = WasmEdge_VMGetActiveModule(VMCxt);
/* The example of retrieving the global instance. */
WasmEdge_String GlobName = WasmEdge_StringCreateByCString("global_i32");
WasmEdge_GlobalInstanceContext *GlobCxt = WasmEdge_ModuleInstanceFindGlobal(ModCxt, GlobName);
WasmEdge_StringDelete(GlobName);

WasmEdge Executor changes

Executor helps to instantiate a WASM module, register a WASM module into Store with module name, register the host modules with host functions, or invoke functions.

  1. WASM module instantiation

    In WasmEdge 0.9.1 version, developers can instantiate a WASM module by the Executor API:

    WasmEdge_ASTModuleContext *ASTCxt;
    /*
     * Assume that `ASTCxt` is a loaded WASM from file or buffer and has passed the validation.
     * Assume that `ExecCxt` is a `WasmEdge_ExecutorContext`.
     * Assume that `StoreCxt` is a `WasmEdge_StoreContext`.
     */
    WasmEdge_Result Res = WasmEdge_ExecutorInstantiate(ExecCxt, StoreCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    

    Then the WASM module is instantiated into an anonymous module instance and handled by the Store. If a new WASM module is instantiated by this API, the old instantiated module instance will be cleaned. After the WasmEdge 0.10.0 version, the instantiated anonymous module will be outputed and handled by caller, and not only 1 anonymous module instance can be instantiated. Developers have the responsibility to destroy the outputed module instances.

    WasmEdge_ASTModuleContext *ASTCxt1, *ASTCxt2;
    /*
     * Assume that `ASTCxt1` and `ASTCxt2` are loaded WASMs from different files or buffers,
     * and have both passed the validation.
     * Assume that `ExecCxt` is a `WasmEdge_ExecutorContext`.
     * Assume that `StoreCxt` is a `WasmEdge_StoreContext`.
     */
    WasmEdge_ModuleInstanceContext *ModCxt1 = NULL;
    WasmEdge_ModuleInstanceContext *ModCxt2 = NULL;
    WasmEdge_Result Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt1, StoreCxt, ASTCxt1);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt2, StoreCxt, ASTCxt2);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    
  2. WASM module registration with module name

    When instantiating and registering a WASM module with module name, developers can use the WasmEdge_ExecutorRegisterModule() API before WasmEdge 0.9.1.

    WasmEdge_ASTModuleContext *ASTCxt;
    /*
     * Assume that `ASTCxt` is a loaded WASM from file or buffer and has passed the validation.
     * Assume that `ExecCxt` is a `WasmEdge_ExecutorContext`.
     * Assume that `StoreCxt` is a `WasmEdge_StoreContext`.
     */
    
    /* Register the WASM module into store with the export module name "mod". */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    Res = WasmEdge_ExecutorRegisterModule(ExecCxt, StoreCxt, ASTCxt, ModName);
    WasmEdge_StringDelete(ModName);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    

    The same feature is implemented in WasmEdge 0.10.0, but in different API WasmEdge_ExecutorRegister():

    WasmEdge_ASTModuleContext *ASTCxt;
    /*
     * Assume that `ASTCxt` is a loaded WASM from file or buffer and has passed the validation.
     * Assume that `ExecCxt` is a `WasmEdge_ExecutorContext`.
     * Assume that `StoreCxt` is a `WasmEdge_StoreContext`.
     */
    
    /* Register the WASM module into store with the export module name "mod". */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("mod");
    /* The output module instance. */
    WasmEdge_ModuleInstanceContext *ModCxt = NULL;
    Res = WasmEdge_ExecutorRegister(ExecCxt, &ModCxt, StoreCxt, ASTCxt, ModName);
    WasmEdge_StringDelete(ModName);
    if (!WasmEdge_ResultOK(Res)) {
      printf("WASM registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    

    Developers have the responsibility to destroy the outputed module instances.

  3. Host module registration

    In WasmEdge 0.9.1, developers can create a WasmEdge_ImportObjectContext and register into Store.

    /* Create the import object with the export module name. */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("module");
    WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ModName);
    WasmEdge_StringDelete(ModName);
    /*
     * ...
     * Add the host functions, tables, memories, and globals into the import object.
     */
    /* The import module context has already contained the export module name. */
    Res = WasmEdge_ExecutorRegisterImport(ExecCxt, StoreCxt, ImpObj);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Import object registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    

    After WasmEdge 0.10.0, developers should use the WasmEdge_ModuleInstanceContext instead:

    /* Create the module instance with the export module name. */
    WasmEdge_String ModName = WasmEdge_StringCreateByCString("module");
    WasmEdge_ModuleInstanceContext *ModCxt = WasmEdge_ModuleInstanceCreate(ModName);
    WasmEdge_StringDelete(ModName);
    /*
     * ...
     * Add the host functions, tables, memories, and globals into the module instance.
     */
    /* The module instance context has already contained the export module name. */
    Res = WasmEdge_ExecutorRegisterImport(ExecCxt, StoreCxt, ModCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Module instance registration failed: %s\n", WasmEdge_ResultGetMessage(Res));
    }
    

    Developers have the responsibility to destroy the created module instances.

  4. WASM function invokation

    This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat. In WasmEdge 0.9.1 version, developers can invoke a WASM function with the export function name:

    /* Create the store context. The store context holds the instances. */
    WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
    /* Result. */
    WasmEdge_Result Res;
    
    /* Create the loader context. The configure context can be NULL. */
    WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(NULL);
    /* Create the validator context. The configure context can be NULL. */
    WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(NULL);
    /* Create the executor context. The configure context and the statistics context can be NULL. */
    WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(NULL, NULL);
    
    /* Load the WASM file or the compiled-WASM file and convert into the AST module context. */
    WasmEdge_ASTModuleContext *ASTCxt = NULL;
    Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
    if (!WasmEdge_ResultOK(Res)) {
      printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    /* Validate the WASM module. */
    Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    /* Instantiate the WASM module into the store context. */
    Res = WasmEdge_ExecutorInstantiate(ExecCxt, StoreCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    /* Invoke the function which is exported with the function name "fib". */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(18) };
    WasmEdge_Value Returns[1];
    Res = WasmEdge_ExecutorInvoke(ExecCxt, StoreCxt, FuncName, Params, 1, Returns, 1);
    if (WasmEdge_ResultOK(Res)) {
      printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
    } else {
      printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    
    WasmEdge_ASTModuleDelete(ASTCxt);
    WasmEdge_LoaderDelete(LoadCxt);
    WasmEdge_ValidatorDelete(ValidCxt);
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StoreDelete(StoreCxt);
    

    After the WasmEdge 0.10.0, developers should retrieve the Function instance by function name first.

    /*
     * ...
     * Ignore the unchanged steps before validation. Please refer to the sample code above.
     */
    WasmEdge_ModuleInstanceContext *ModCxt = NULL;
    /* Instantiate the WASM module. */
    Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt1, StoreCxt, ASTCxt);
    if (!WasmEdge_ResultOK(Res)) {
      printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    /* Retrieve the function instance by name. */
    WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
    WasmEdge_FunctionInstanceContext *FuncCxt = WasmEdge_ModuleInstanceFindFunction(ModCxt, FuncName);
    WasmEdge_StringDelete(FuncName);
    /* Invoke the function. */
    WasmEdge_Value Params[1] = { WasmEdge_ValueGenI32(18) };
    WasmEdge_Value Returns[1];
    Res = WasmEdge_ExecutorInvoke(ExecCxt, FuncCxt, Params, 1, Returns, 1);
    if (WasmEdge_ResultOK(Res)) {
      printf("Get the result: %d\n", WasmEdge_ValueGetI32(Returns[0]));
    } else {
      printf("Execution phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
      return -1;
    }
    
    WasmEdge_ModuleInstanceDelete(ModCxt);
    WasmEdge_ASTModuleDelete(ASTCxt);
    WasmEdge_LoaderDelete(LoadCxt);
    WasmEdge_ValidatorDelete(ValidCxt);
    WasmEdge_ExecutorDelete(ExecCxt);
    WasmEdge_StoreDelete(StoreCxt);
    

Instances retrievement

This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

Before the WasmEdge 0.9.1 versions, developers can retrieve all exported instances of named or anonymous modules from Store:

/* Create the store context. The store context holds the instances. */
WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
/* Result. */
WasmEdge_Result Res;

/* Create the loader context. The configure context can be NULL. */
WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(NULL);
/* Create the validator context. The configure context can be NULL. */
WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(NULL);
/* Create the executor context. The configure context and the statistics context can be NULL. */
WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(NULL, NULL);

/* Load the WASM file or the compiled-WASM file and convert into the AST module context. */
WasmEdge_ASTModuleContext *ASTCxt = NULL;
Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Validate the WASM module. */
Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Example: register and instantiate the WASM module with the module name "module_fib". */
WasmEdge_String ModName = WasmEdge_StringCreateByCString("module_fib");
Res = WasmEdge_ExecutorRegisterModule(ExecCxt, StoreCxt, ASTCxt, ModName);
if (!WasmEdge_ResultOK(Res)) {
  printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Example: Instantiate the WASM module into the store context. */
Res = WasmEdge_ExecutorInstantiate(ExecCxt, StoreCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
WasmEdge_StringDelete(ModName);

/* Now, developers can retrieve the exported instances from the store. */
/* Take the exported functions as example. This WASM exports the function "fib". */
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
WasmEdge_FunctionInstanceContext *FoundFuncCxt;
/* Find the function "fib" from the instantiated anonymous module. */
FoundFuncCxt = WasmEdge_StoreFindFunction(StoreCxt, FuncName);
/* Find the function "fib" from the registered module "module_fib". */
ModName = WasmEdge_StringCreateByCString("module_fib");
FoundFuncCxt = WasmEdge_StoreFindFunctionRegistered(StoreCxt, ModName, FuncName);
WasmEdge_StringDelete(ModName);
WasmEdge_StringDelete(FuncName);

WasmEdge_ASTModuleDelete(ASTCxt);
WasmEdge_LoaderDelete(LoadCxt);
WasmEdge_ValidatorDelete(ValidCxt);
WasmEdge_ExecutorDelete(ExecCxt);
WasmEdge_StoreDelete(StoreCxt);

After the WasmEdge 0.10.0, developers can instantiate several anonymous Module instances, and should retrieve the exported instances from named or anonymous Module instances:

/* Create the store context. The store context is the object to link the modules for imports and exports. */
WasmEdge_StoreContext *StoreCxt = WasmEdge_StoreCreate();
/* Result. */
WasmEdge_Result Res;

/* Create the loader context. The configure context can be NULL. */
WasmEdge_LoaderContext *LoadCxt = WasmEdge_LoaderCreate(NULL);
/* Create the validator context. The configure context can be NULL. */
WasmEdge_ValidatorContext *ValidCxt = WasmEdge_ValidatorCreate(NULL);
/* Create the executor context. The configure context and the statistics context can be NULL. */
WasmEdge_ExecutorContext *ExecCxt = WasmEdge_ExecutorCreate(NULL, NULL);

/* Load the WASM file or the compiled-WASM file and convert into the AST module context. */
WasmEdge_ASTModuleContext *ASTCxt = NULL;
Res = WasmEdge_LoaderParseFromFile(LoadCxt, &ASTCxt, "fibonacci.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("Loading phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Validate the WASM module. */
Res = WasmEdge_ValidatorValidate(ValidCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Validation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Example: register and instantiate the WASM module with the module name "module_fib". */
WasmEdge_ModuleInstanceContext *NamedModCxt = NULL;
WasmEdge_String ModName = WasmEdge_StringCreateByCString("module_fib");
Res = WasmEdge_ExecutorRegister(ExecCxt, &NamedModCxt, StoreCxt, ASTCxt, ModName);
if (!WasmEdge_ResultOK(Res)) {
  printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
/* Example: Instantiate the WASM module and get the output module instance. */
WasmEdge_ModuleInstanceContext *ModCxt = NULL;
Res = WasmEdge_ExecutorInstantiate(ExecCxt, &ModCxt, StoreCxt, ASTCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("Instantiation phase failed: %s\n", WasmEdge_ResultGetMessage(Res));
  return -1;
}
WasmEdge_StringDelete(ModName);

/* Now, developers can retrieve the exported instances from the module instaces. */
/* Take the exported functions as example. This WASM exports the function "fib". */
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("fib");
WasmEdge_FunctionInstanceContext *FoundFuncCxt;
/* Find the function "fib" from the instantiated anonymous module. */
FoundFuncCxt = WasmEdge_ModuleInstanceFindFunction(ModCxt, FuncName);
/* Find the function "fib" from the registered module "module_fib". */
FoundFuncCxt = WasmEdge_ModuleInstanceFindFunction(NamedModCxt, FuncName);
/* Or developers can get the named module instance from the store: */
ModName = WasmEdge_StringCreateByCString("module_fib");
const WasmEdge_ModuleInstanceContext *ModCxtGot = WasmEdge_StoreFindModule(StoreCxt, ModName);
WasmEdge_StringDelete(ModName);
FoundFuncCxt = WasmEdge_ModuleInstanceFindFunction(ModCxtGot, FuncName);
WasmEdge_StringDelete(FuncName);

WasmEdge_ModuleInstanceDelete(NamedModCxt);
WasmEdge_ModuleInstanceDelete(ModCxt);
WasmEdge_ASTModuleDelete(ASTCxt);
WasmEdge_LoaderDelete(LoadCxt);
WasmEdge_ValidatorDelete(ValidCxt);
WasmEdge_ExecutorDelete(ExecCxt);
WasmEdge_StoreDelete(StoreCxt);

Host functions

The difference of host functions are the replacement of WasmEdge_ImportObjectContext.

/* Host function body definition. */
WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                    const WasmEdge_Value *In, WasmEdge_Value *Out) {
  int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
  int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
  Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
  return WasmEdge_Result_Success;
}

/* Create the import object. */
WasmEdge_String ExportName = WasmEdge_StringCreateByCString("module");
WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName);
WasmEdge_StringDelete(ExportName);

/* Create and add a function instance into the import object. */
enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
WasmEdge_FunctionTypeContext *HostFType = 
    WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
WasmEdge_FunctionInstanceContext *HostFunc =
    WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
/*
 * The third parameter is the pointer to the additional data object.
 * Developers should guarantee the life cycle of the data, and it can be
 * `NULL` if the external data is not needed.
 */
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("add");
WasmEdge_ImportObjectAddFunction(ImpObj, FuncName, HostFunc);
WasmEdge_StringDelete(FuncName);

/*
 * The import objects should be deleted.
 * Developers should __NOT__ destroy the instances added into the import object contexts.
 */
WasmEdge_ImportObjectDelete(ImpObj);

Developers can use the WasmEdge_ModuleInstanceContext to upgrade to WasmEdge 0.10.0 easily.

/* Host function body definition. */
WasmEdge_Result Add(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                    const WasmEdge_Value *In, WasmEdge_Value *Out) {
  int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
  int32_t Val2 = WasmEdge_ValueGetI32(In[1]);
  Out[0] = WasmEdge_ValueGenI32(Val1 + Val2);
  return WasmEdge_Result_Success;
}

/* Create a module instance. */
WasmEdge_String ExportName = WasmEdge_StringCreateByCString("module");
WasmEdge_ModuleInstanceContext *HostModCxt = WasmEdge_ModuleInstanceCreate(ExportName);
WasmEdge_StringDelete(ExportName);

/* Create and add a function instance into the module instance. */
enum WasmEdge_ValType ParamList[2] = { WasmEdge_ValType_I32, WasmEdge_ValType_I32 };
enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
WasmEdge_FunctionTypeContext *HostFType = 
    WasmEdge_FunctionTypeCreate(ParamList, 2, ReturnList, 1);
WasmEdge_FunctionInstanceContext *HostFunc =
    WasmEdge_FunctionInstanceCreate(HostFType, Add, NULL, 0);
/*
 * The third parameter is the pointer to the additional data object.
 * Developers should guarantee the life cycle of the data, and it can be
 * `NULL` if the external data is not needed.
 */
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("add");
WasmEdge_ModuleInstanceAddFunction(HostModCxt, FuncName, HostFunc);
WasmEdge_StringDelete(FuncName);

/*
 * The module instance should be deleted.
 * Developers should __NOT__ destroy the instances added into the module instance contexts.
 */
WasmEdge_ModuleInstanceDelete(HostModCxt);

WasmEdge Go SDK

The followings are the guides to working with the WasmEdge Go API. You can embed the WasmEdge into your go application through the WasmEdge Go API.

Getting Started

The WasmEdge-go requires golang version >= 1.16. Please check your golang version before installation. You can download golang here.

$ go version
go version go1.16.5 linux/amd64

Meantime, please make sure you have installed the WasmEdge shared library with the same WasmEdge-go release version.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.10.0

If you need the TensorFlow or Image extension for WasmEdge-go, please install the WasmEdge with extensions:

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.10.0 -e tensorflow,image

Noticed that the TensorFlow and Image extensions are only for the Linux platforms.

Install the WasmEdge-go package and build in your Go project directory:

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build

WasmEdge-go Extensions

By default, the WasmEdge-go only turns on the basic runtime.

WasmEdge-go has the following extensions:

  • TensorFlow

    • This extension supports the host functions in WasmEdge-tensorflow.

    • To install the tensorflow extension, please use the -e tensorflow flag in the WasmEdge installer command.

    • For using this extension, the tag tensorflow when building is required:

      go build -tags tensorflow
      
  • Image

    • This extension supports the host functions in WasmEdge-image.

    • To install the image extension, please use the -e image flag in the WasmEdge installer command.

    • For using this extension, the tag image when building is required:

      go build -tags image
      

You can also turn on the multiple extensions when building:

go build -tags image,tensorflow

For examples, please refer to the example repository.

WasmEdge AOT Compiler in Go

The go_WasmAOT example demonstrates how to compile a WASM file into a native binary (AOT compile) from within a Go application.

Examples

Embed a standalone WASM app

The WasmEdge Go SDK can embed standalone WebAssembly applications — ie a Rust application with a main() function compiled into WebAssembly.

Our demo Rust application reads from a file. Note that the WebAssembly program's input and output data are now passed by the STDIN and STDOUT.

use std::env;
use std::fs::File;
use std::io::{self, BufRead};

fn main() {
  // Get the argv.
  let args: Vec<String> = env::args().collect();
  if args.len() <= 1 {
    println!("Rust: ERROR - No input file name.");
    return;
  }

  // Open the file.
  println!("Rust: Opening input file \"{}\"...", args[1]);
  let file = match File::open(&args[1]) {
    Err(why) => {
      println!("Rust: ERROR - Open file \"{}\" failed: {}", args[1], why);
      return;
    },
    Ok(file) => file,
  };

  // Read lines.
  let reader = io::BufReader::new(file);
  let mut texts:Vec<String> = Vec::new();
  for line in reader.lines() {
    if let Ok(text) = line {
      texts.push(text);
    }
  }
  println!("Rust: Read input file \"{}\" succeeded.", args[1]);

  // Get stdin to print lines.
  println!("Rust: Please input the line number to print the line of file.");
  let stdin = io::stdin();
  for line in stdin.lock().lines() {
    let input = line.unwrap();
    match input.parse::<usize>() {
      Ok(n) => if n > 0 && n <= texts.len() {
        println!("{}", texts[n - 1]);
      } else {
        println!("Rust: ERROR - Line \"{}\" is out of range.", n);
      },
      Err(e) => println!("Rust: ERROR - Input \"{}\" is not an integer: {}", input, e),
    }
  }
  println!("Rust: Process end.");
}

Compile the application into WebAssembly.

$ cd rust_readfile
$ cargo build --target wasm32-wasi
# The output file will be target/wasm32-wasi/debug/rust_readfile.wasm

The Go source code to run the WebAssembly function in WasmEdge is as follows.

package main

import (
  "os"
  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()

  var conf = wasmedge.NewConfigure(wasmedge.REFERENCE_TYPES)
  conf.AddConfig(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping directories
  )

  // Instantiate wasm. _start refers to the main() function
  vm.RunWasmFile(os.Args[1], "_start")

  vm.Release()
  conf.Release()
}

Next, let's build the Go application with the WasmEdge Go SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build

Run the Golang application.

$ ./read_file rust_readfile/target/wasm32-wasi/debug/rust_readfile.wasm file.txt
Rust: Opening input file "file.txt"...
Rust: Read input file "file.txt" succeeded.
Rust: Please input the line number to print the line of file.
# Input "5" and press Enter.
5
# The output will be the 5th line of `file.txt`:
abcDEF___!@#$%^
# To terminate the program, send the EOF (Ctrl + D).
^D
# The output will print the terminate message:
Rust: Process end.

More examples can be found at the WasmEdge-go-examples GitHub repo.

Embed a Wasm function

The WasmEdge Go SDK allows WebAssembly functions to be embedded into a Go host app. You can use the Go SDK API to pass call parameters to the embedded WebAssembly functions, and then capture the return values. However, the WebAssembly spec only supports a few simple data types out of the box. It does not support types such as string and array. In order to pass rich types in Go to WebAssembly, we could hand-code memory pointers (see here), or use an automated tool to manage the data exchange.

The wasmedge-bindgen project provides Rust macros for functions to accept and return complex data types, and then for Go functions to call such Rust functions running in WasmEdge. The full source code for the demo in this chapter is available here.

Rust function compiled into WebAssembly

In the Rust project, all you need is to annotate your functions with a [wasmedge_bindgen] macro. Those annotated functions will be automatically instrumented by the Rust compiler and turned into WebAssembly functions that can be called from the wasmedge-bindgen GO SDK. In the example below, we have several Rust functions that take complex call parameters and return complex values.


#![allow(unused)]
fn main() {
use wasmedge_bindgen::*;
use wasmedge_bindgen_macro::*;
use num_integer::lcm;
use sha3::{Digest, Sha3_256, Keccak256};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasmedge_bindgen]
pub fn create_line(p1: String, p2: String, desc: String) -> Result<Vec<u8>, String> {
  let point1: Point = serde_json::from_str(p1.as_str()).unwrap();
  let point2: Point = serde_json::from_str(p2.as_str()).unwrap();
  let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();

  let valid = if length == 0.0 { false } else { true };

  let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc };

  return Ok(serde_json::to_vec(&line).unwrap());
}

#[wasmedge_bindgen]
pub fn say(s: String) -> Result<Vec<u8>, String> {
  let r = String::from("hello ");
  return Ok((r + s.as_str()).as_bytes().to_vec());
}

#[wasmedge_bindgen]
pub fn obfusticate(s: String) -> Result<Vec<u8>, String> {
  let r: String = (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect();
  Ok(r.as_bytes().to_vec())
}

#[wasmedge_bindgen]
pub fn lowest_common_multiple(a: i32, b: i32) -> Result<Vec<u8>, String> {
  let r = lcm(a, b);
  return Ok(r.to_string().as_bytes().to_vec());
}

#[wasmedge_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Result<Vec<u8>, String> {
  return Ok(Sha3_256::digest(&v).as_slice().to_vec());
}

#[wasmedge_bindgen]
pub fn keccak_digest(s: Vec<u8>) -> Result<Vec<u8>, String> {
  return Ok(Keccak256::digest(&s).as_slice().to_vec());
}
}

You can build the WebAssembly bytecode file using standard Cargo commands.

cd rust_bindgen_funcs
cargo build --target wasm32-wasi --release
# The output WASM will be target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm.
cp target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm ../
cd ../

Go host application

In the Go host application, you can create and set up the WasmEdge VM using the WasmEdge Go SDK. However, instead of calling vm.Instantiate(), you should now call bindgen.Instantiate(vm) to instantiate the VM and return a bindgen object.

func main() {
  // Expected Args[0]: program name (./bindgen_funcs)
  // Expected Args[1]: wasm file (rust_bindgen_funcs_lib.wasm))

  wasmedge.SetLogErrorLevel()
  var conf = wasmedge.NewConfigure(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping preopens
  )
  vm.LoadWasmFile(os.Args[1])
  vm.Validate()

  // Instantiate the bindgen and vm
  bg := bindgen.Instantiate(vm)

Next, you can call any [wasmedge_bindgen] annotated functions in the VM via the bindgen object.

  // create_line: string, string, string -> string (inputs are JSON stringified) 
  res, err := bg.Execute("create_line", "{\"x\":2.5,\"y\":7.8}", "{\"x\":2.5,\"y\":5.8}", "A thin red line")
  if err == nil {
    fmt.Println("Run bindgen -- create_line:", string(res))
  } else {
    fmt.Println("Run bindgen -- create_line FAILED", err)
  }

  // say: string -> string
  res, err = bg.Execute("say", "bindgen funcs test")
  if err == nil {
    fmt.Println("Run bindgen -- say:", string(res))
  } else {
    fmt.Println("Run bindgen -- say FAILED")
  }

  // obfusticate: string -> string
  res, err = bg.Execute("obfusticate", "A quick brown fox jumps over the lazy dog")
  if err == nil {
    fmt.Println("Run bindgen -- obfusticate:", string(res))
  } else {
    fmt.Println("Run bindgen -- obfusticate FAILED")
  }

  // lowest_common_multiple: i32, i32 -> i32
  res, err = bg.Execute("lowest_common_multiple", int32(123), int32(2))
  if err == nil {
    num, _ := strconv.ParseInt(string(res), 10, 32)
    fmt.Println("Run bindgen -- lowest_common_multiple:", num)
  } else {
    fmt.Println("Run bindgen -- lowest_common_multiple FAILED")
  }

  // sha3_digest: array -> array
  res, err = bg.Execute("sha3_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- sha3_digest:", res)
  } else {
    fmt.Println("Run bindgen -- sha3_digest FAILED")
  }

  // keccak_digest: array -> array
  res, err = bg.Execute("keccak_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- keccak_digest:", res)
  } else {
    fmt.Println("Run bindgen -- keccak_digest FAILED")
  }

  bg.Release()
  vm.Release()
  conf.Release()
}

Finally, you can build and run the Go host application.

go build
./bindgen_funcs rust_bindgen_funcs_lib.wasm

The standard output of this example will be the following.

Run bindgen -- create_line: {"points":[{"x":1.5,"y":3.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.2360682,"desc":"A thin red line"}
Run bindgen -- say: hello bindgen funcs test
Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt
Run bindgen -- lowest_common_multiple: 246
Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203]
Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]

Pass complex parameters to Wasm functions

An issue with the WebAssembly spec is that it only supports a very limited number of data types. If you want to embed a WebAssembly function with complex call parameters or return values, you will need to manage memory pointers both on Go SDK and WebAssembly function sides. Such complex call parameters and return values include dynamic memory structures such as strings and byte arrays. In this section, we will discuss several examples.

Pass strings to Rust functions

In this example, we will demonstrate how to call a Rust-based WebAssembly function from a Go app. Specially, we will discuss how to pass string data.

An alternative approach to pass and return complex values to Rust functions in WebAssembly is to use the wasm-bindgen compiler tool. You can learn more here.

The Rust function takes a memory pointer for the string, and constructs the Rust string itself.


#![allow(unused)]
fn main() {
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_void};

#[no_mangle]
pub extern fn allocate(size: usize) -> *mut c_void {
  let mut buffer = Vec::with_capacity(size);
  let pointer = buffer.as_mut_ptr();
  mem::forget(buffer);

  pointer as *mut c_void
}

#[no_mangle]
pub extern fn deallocate(pointer: *mut c_void, capacity: usize) {
  unsafe {
    let _ = Vec::from_raw_parts(pointer, 0, capacity);
  }
}

#[no_mangle]
pub extern fn greet(subject: *mut c_char) -> *mut c_char {
  let subject = unsafe { CStr::from_ptr(subject).to_bytes().to_vec() };
  let mut output = b"Hello, ".to_vec();
  output.extend(&subject);
  output.extend(&[b'!']);

  unsafe { CString::from_vec_unchecked(output) }.into_raw()
}
}

Use standard Rust compiler tools to compile the Rust source code into a WebAssembly bytecode application.

cd rust_memory_greet
cargo build --target wasm32-wasi
# The output WASM will be `target/wasm32-wasi/debug/rust_memory_greet_lib.wasm`.

The Go SDK application must call allocate from the WasmEdge VM to get a pointer to the string parameter. It will then call the greet function in Rust with the pointer. After the function returns, the Go application will call deallocate to free the memory space.

package main

import (
  "fmt"
  "os"
  "strings"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()
  conf := wasmedge.NewConfigure(wasmedge.WASI)
  store := wasmedge.NewStore()
  vm := wasmedge.NewVMWithConfigAndStore(conf, store)

  wasi := vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],
    os.Environ(),
    []string{".:."},
  )

  err := vm.LoadWasmFile(os.Args[1])
  if err != nil {
    fmt.Println("failed to load wasm")
  }
  vm.Validate()
  vm.Instantiate()

  subject := "WasmEdge"
  lengthOfSubject := len(subject)

  // Allocate memory for the subject, and get a pointer to it.
  // Include a byte for the NULL terminator we add below.
  allocateResult, _ := vm.Execute("allocate", int32(lengthOfSubject+1))
  inputPointer := allocateResult[0].(int32)

  // Write the subject into the memory.
  mem := store.FindMemory("memory")
  memData, _ := mem.GetData(uint(inputPointer), uint(lengthOfSubject+1))
  copy(memData, subject)

  // C-string terminates by NULL.
  memData[lengthOfSubject] = 0

  // Run the `greet` function. Given the pointer to the subject.
  greetResult, _ := vm.Execute("greet", inputPointer)
  outputPointer := greetResult[0].(int32)

  pageSize := mem.GetPageSize()
  // Read the result of the `greet` function.
  memData, _ = mem.GetData(uint(0), uint(pageSize * 65536))
  nth := 0
  var output strings.Builder

  for {
    if memData[int(outputPointer) + nth] == 0 {
      break
    }

    output.WriteByte(memData[int(outputPointer) + nth])
    nth++
  }

  lengthOfOutput := nth

  fmt.Println(output.String())

  // Deallocate the subject, and the output.
  vm.Execute("deallocate", inputPointer, int32(lengthOfSubject+1))
  vm.Execute("deallocate", outputPointer, int32(lengthOfOutput+1))

  vm.Release()
  store.Release()
  conf.Release()
}

To build the Go SDK example, run the following commands.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build greet_memory.go

Now you can use the Go application to run the WebAssembly plug-in compiled from Rust.

$ ./greet_memory rust_memory_greet_lib.wasm
Hello, WasmEdge!

Pass strings to TinyGo functions

In this example, we will demonstrate how to call a TinyGo-based WebAssembly function from a Go app.

The TinyGo function takes a memory pointer for the string, and constructs the TinyGo string itself.

The empty main() is needed to the compiled WebAssembly program to set up WASI properly.

package main

import (
  "strings"
  "unsafe"
)

func main() {}

// export greet
func greet(subject *int32) *byte {
  nth := 0
  var subjectStr strings.Builder
  pointer := uintptr(unsafe.Pointer(subject))
  for {
    s := *(*int32)(unsafe.Pointer(pointer + uintptr(nth)))
    if s == 0 {
      break;
    }

    subjectStr.WriteByte(byte(s))
    nth++
  }

  output := subjectStr.String()
  output = "Hello, " + output + "!"

  return &(([]byte)(output)[0])
}

Use the TinyGo compiler tools to compile the Go source code into a WebAssembly bytecode application.

tinygo build -o greet.wasm -target wasi greet.go

The Go SDK application must call malloc from the WasmEdge VM to get a pointer to the string parameter. It will then call the greet function in TinyGo with the pointer. After the function returns, the Go application will call free to free the memory space.

package main

import (
  "fmt"
  "os"
  "strings"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()
  conf := wasmedge.NewConfigure(wasmedge.WASI)
  store := wasmedge.NewStore()
  vm := wasmedge.NewVMWithConfigAndStore(conf, store)

  wasi := vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],
    os.Environ(),
    []string{".:."},
  )

  err := vm.LoadWasmFile(os.Args[1])
  if err != nil {
    fmt.Println("failed to load wasm")
  }
  vm.Validate()
  vm.Instantiate()

  subject := "WasmEdge"
  lengthOfSubject := len(subject)

  // Allocate memory for the subject, and get a pointer to it.
  // Include a byte for the NULL terminator we add below.
  allocateResult, _ := vm.Execute("malloc", int32(lengthOfSubject+1))
  inputPointer := allocateResult[0].(int32)

  // Write the subject into the memory.
  mem := store.FindMemory("memory")
  memData, _ := mem.GetData(uint(inputPointer), uint(lengthOfSubject+1))
  copy(memData, subject)

  // C-string terminates by NULL.
  memData[lengthOfSubject] = 0

  // Run the `greet` function. Given the pointer to the subject.
  greetResult, _ := vm.Execute("greet", inputPointer)
  outputPointer := greetResult[0].(int32)

  pageSize := mem.GetPageSize()
  // Read the result of the `greet` function.
  memData, _ = mem.GetData(uint(0), uint(pageSize * 65536))
  nth := 0
  var output strings.Builder

  for {
    if memData[int(outputPointer) + nth] == 0 {
      break
    }

    output.WriteByte(memData[int(outputPointer) + nth])
    nth++
  }

  fmt.Println(output.String())

  // Deallocate the subject, and the output.
  vm.Execute("free", inputPointer)
  vm.Execute("free", outputPointer)

  vm.Release()
  store.Release()
  conf.Release()
}

To build the Go SDK example, run the following commands.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build greet_memory.go

Now you can use the Go application to run the WebAssembly plug-in compiled from TinyGo.

$ ./greet_memory greet.wasm
Hello, WasmEdge!

Pass bytes to Rust functions

In this example, we will demonstrate how to call Rust-based WebAssembly functions and pass arrays to and from a Go app.

An alternative approach to pass and return complex values to Rust functions in WebAssembly is to use the wasm-bindgen compiler tool. You can learn more here.

The fib_array() function takes a array as a call parameter and fill it with a fibonacci sequence of numbers. Alternatively, the fib_array_return_memory() function returns a array of fibonacci sequence of numbers.

For the array in the call parameter, the Rust function fib_array() takes a memory pointer and constructs the Rust Vec using from_raw_parts. For the array return value, the Rust function fib_array_return_memory() simply returns the pointer.


#![allow(unused)]
fn main() {
use std::mem;
use std::os::raw::{c_void, c_int};

#[no_mangle]
pub extern fn allocate(size: usize) -> *mut c_void {
  let mut buffer = Vec::with_capacity(size);
  let pointer = buffer.as_mut_ptr();
  mem::forget(buffer);

  pointer as *mut c_void
}

#[no_mangle]
pub extern fn deallocate(pointer: *mut c_void, capacity: usize) {
  unsafe {
    let _ = Vec::from_raw_parts(pointer, 0, capacity);
  }
}

#[no_mangle]
pub extern fn fib_array(n: i32, p: *mut c_int) -> i32 {
  unsafe {
    let mut arr = Vec::<i32>::from_raw_parts(p, 0, (4*n) as usize);
    for i in 0..n {
      if i < 2 {
        arr.push(i);
      } else {
        arr.push(arr[(i - 1) as usize] + arr[(i - 2) as usize]);
      }
    }
    let r = arr[(n - 1) as usize];
    mem::forget(arr);
    r
  }
}

#[no_mangle]
pub extern fn fib_array_return_memory(n: i32) -> *mut c_int {
  let mut arr = Vec::with_capacity((4 * n) as usize);
  let pointer = arr.as_mut_ptr();
  for i in 0..n {
    if i < 2 {
      arr.push(i);
    } else {
      arr.push(arr[(i - 1) as usize] + arr[(i - 2) as usize]);
    }
  }
  mem::forget(arr);
  pointer
}
}

Use standard Rust compiler tools to compile the Rust source code into a WebAssembly bytecode application.

cd rust_access_memory
cargo build --target wasm32-wasi
# The output WASM will be target/wasm32-wasi/debug/rust_access_memory_lib.wasm.

The Go SDK application must call allocate from the WasmEdge VM to get a pointer to the array. It will then call the fib_array() function in Rust and pass in the pointer. After the functions return, the Go application will use the WasmEdge store API to construct an array from the pointer in the call parameter (fib_array()) or in the return value (fib_array_return_memory()). The Go app will eventually call deallocate to free the memory space.

package main

import (
  "fmt"
  "os"
  "unsafe"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()
  conf := wasmedge.NewConfigure(wasmedge.WASI)
  store := wasmedge.NewStore()
  vm := wasmedge.NewVMWithConfigAndStore(conf, store)

  wasi := vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],
    os.Environ(),
    []string{".:."},
  )

  err := vm.LoadWasmFile(os.Args[1])
  if err != nil {
    fmt.Println("failed to load wasm")
  }
  vm.Validate()
  vm.Instantiate()

  n := int32(10)

  p, err := vm.Execute("allocate", 4 * n)
  if err != nil {
    fmt.Println("allocate failed:", err)
  }

  fib, err := vm.Execute("fib_array", n, p[0])
  if err != nil {
    fmt.Println("fib_rray failed:", err)
  } else {
    fmt.Println("fib_array() returned:", fib[0])
    fmt.Printf("fib_array memory at: %p\n", unsafe.Pointer((uintptr)(p[0].(int32))))
    mem := store.FindMemory("memory")
    if mem != nil {
      // int32 occupies 4 bytes
      fibArray, err := mem.GetData(uint(p[0].(int32)), uint(n * 4))
      if err == nil && fibArray != nil {
        fmt.Println("fibArray:", fibArray)
      }
    }
  }

  fibP, err := vm.Execute("fib_array_return_memory", n)
  if err != nil {
    fmt.Println("fib_array_return_memory failed:", err)
  } else {
    fmt.Printf("fib_array_return_memory memory at: %p\n", unsafe.Pointer((uintptr)(fibP[0].(int32))))
    mem := store.FindMemory("memory")
    if mem != nil {
      // int32 occupies 4 bytes
      fibArrayReturnMemory, err := mem.GetData(uint(fibP[0].(int32)), uint(n * 4))
      if err == nil && fibArrayReturnMemory != nil {
        fmt.Println("fibArrayReturnMemory:", fibArrayReturnMemory)
      }
    }
  }

  _, err = vm.Execute("deallocate", p[0].(int32), 4 * n)
  if err != nil {
    fmt.Println("free failed:", err)
  }


  exitcode := wasi.WasiGetExitCode()
  if exitcode != 0 {
    fmt.Println("Go: Running wasm failed, exit code:", exitcode)
  }

  vm.Release()
  store.Release()
  conf.Release()
}

To build the Go SDK example, run the following commands.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build run.go

Now you can use the Go application to run the WebAssembly plug-in compiled from Rust.

$ ./run rust_access_memory_lib.wasm
fib_array() returned: 34
fib_array memory at: 0x102d80
fibArray: [0 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 3 0 0 0 5 0 0 0 8 0 0 0 13 0 0 0 21 0 0 0 34 0 0 0]
fib_array_return_memory memory at: 0x105430
fibArrayReturnMemory: [0 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 3 0 0 0 5 0 0 0 8 0 0 0 13 0 0 0 21 0 0 0 34 0 0 0]

Pass bytes to TinyGo functions

In this example, we will demonstrate how to call TinyGo-based WebAssembly functions and pass arrays to and from a Go app.

The fibArray function takes a array as a call parameter and fill it with a fibonacci sequence of numbers. Alternatively, the fibArrayReturnMemory function returns a array of fibonacci sequence of numbers.

package main

import (
  "fmt"
  "unsafe"
)

func main() {
  println("in main")
  n := int32(10)
  arr := make([]int32, n)
  arrP := &arr[0]
  fmt.Printf("call fibArray(%d, %p) = %d\n", n, arrP, fibArray(n, arrP))
  fmt.Printf("call fibArrayReturnMemory(%d) return %p\n", n, fibArrayReturnMemory(n))
}

// export fibArray
func fibArray(n int32, p *int32) int32 {
  arr := unsafe.Slice(p, n)
  for i := int32(0); i < n; i++ {
    switch {
    case i < 2:
      arr[i] = i
    default:
      arr[i] = arr[i-1] + arr[i-2]
    }
  }
  return arr[n-1]
}

// export fibArrayReturnMemory
func fibArrayReturnMemory(n int32) *int32 {
  arr := make([]int32, n)
  for i := int32(0); i < n; i++ {
    switch {
    case i < 2:
      arr[i] = i
    default:
      arr[i] = arr[i-1] + arr[i-2]
    }
  }
  return &arr[0]
}

Use the TinyGo compiler tools to compile the Go source code into a WebAssembly bytecode application.

tinygo build -o fib.wasm -target wasi fib.go

The Go SDK application must call malloc from the WasmEdge VM to get a pointer to the array. It will then call the fibArray() function in TinyGo with the pointer. After the functions return, the Go app uses the WasmEdge SDK's store API to construct an array from the pointer in the call parameter (fibArray()) or in the return value (fibArrayReturnMemory()). The Go application will eventually call free to free the memory space.

package main

import (
  "fmt"
  "os"
  "unsafe"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()
  conf := wasmedge.NewConfigure(wasmedge.WASI)
  store := wasmedge.NewStore()
  vm := wasmedge.NewVMWithConfigAndStore(conf, store)

  wasi := vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],
    os.Environ(),
    []string{".:."},
  )

  err := vm.LoadWasmFile(os.Args[1])
  if err != nil {
    fmt.Println("failed to load wasm")
  }
  vm.Validate()
  vm.Instantiate()

  n := int32(10)

  p, err := vm.Execute("malloc", n)
  if err != nil {
    fmt.Println("malloc failed:", err)
  }

  fib, err := vm.Execute("fibArray", n, p[0])
  if err != nil {
    fmt.Println("fibArray failed:", err)
  } else {
    fmt.Println("fibArray() returned:", fib[0])
    fmt.Printf("fibArray memory at: %p\n", unsafe.Pointer((uintptr)(p[0].(int32))))
    mem := store.FindMemory("memory")
    if mem != nil {
      // int32 occupies 4 bytes
      fibArray, err := mem.GetData(uint(p[0].(int32)), uint(n * 4))
      if err == nil && fibArray != nil {
        fmt.Println("fibArray:", fibArray)
      }
    }
  }

  fibP, err := vm.Execute("fibArrayReturnMemory", n)
  if err != nil {
    fmt.Println("fibArrayReturnMemory failed:", err)
  } else {
    fmt.Printf("fibArrayReturnMemory memory at: %p\n", unsafe.Pointer((uintptr)(fibP[0].(int32))))
    mem := store.FindMemory("memory")
    if mem != nil {
      // int32 occupies 4 bytes
      fibArrayReturnMemory, err := mem.GetData(uint(fibP[0].(int32)), uint(n * 4))
      if err == nil && fibArrayReturnMemory != nil {
        fmt.Println("fibArrayReturnMemory:", fibArrayReturnMemory)
      }
    }
  }

  _, err = vm.Execute("free", p...)
  if err != nil {
    fmt.Println("free failed:", err)
  }

  exitcode := wasi.WasiGetExitCode()
  if exitcode != 0 {
    fmt.Println("Go: Running wasm failed, exit code:", exitcode)
  }

  vm.Release()
  store.Release()
  conf.Release()
}

To build the Go SDK example, run the following commands.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build run.go

Now you can use the Go application to run the WebAssembly plug-in compiled from TinyGo.

$ ./run fib.wasm
fibArray() returned: 34
fibArray memory at: 0x14d3c
fibArray: [0 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 3 0 0 0 5 0 0 0 8 0 0 0 13 0 0 0 21 0 0 0 34 0 0 0]
fibArrayReturnMemory memory at: 0x14d4c
fibArrayReturnMemory: [0 0 0 0 1 0 0 0 1 0 0 0 2 0 0 0 3 0 0 0 5 0 0 0 8 0 0 0 13 0 0 0 21 0 0 0 34 0 0 0]

Tensorflow

In this section, we will show you how to create a Tensorflow inference function in Rust for image classification, and then embed it into a Go application. The project source code is available here.

Rust function compiled into WebAssembly

The Rust function for image classification is available here. It utilizes the WasmEdge Tensorflow extension API as well as the wasmedge_bindgen for passing call parameters.


#![allow(unused)]
fn main() {
#[wasmedge_bindgen]
fn infer(image_data: Vec<u8>) -> Result<Vec<u8>, String> {
  ... ...
  let flat_img = image::imageops::thumbnail(&img, 192, 192);

  let model_data: &[u8] = include_bytes!("lite-model_aiy_vision_classifier_food_V1_1.tflite");
  let labels = include_str!("aiy_food_V1_labelmap.txt");

  let mut session = wasmedge_tensorflow_interface::Session::new(
    model_data,
    wasmedge_tensorflow_interface::ModelType::TensorFlowLite,
  );
  session
    .add_input("input", &flat_img, &[1, 192, 192, 3])
    .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Softmax");
  ... ...
}
}

You can use the standard Cargo command to build it into a WebAssembly function.

cd rust_tflite_food
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/rust_tflite_food_lib.wasm ../
cd ../

You can use our AOT compiler wasmedgec to instrument the WebAssembly file to make it run much faster. Learn more.

wasmedgec rust_tflite_food_lib.wasm rust_tflite_food_lib.wasm

Go host app

The Go host app source code shows how to instantiate a WasmEdge runtime with the Tensorflow extension, and how to pass the image data to the Rust function in WasmEdge to run the inference.

func main() {
  // Expected Args[0]: program name (./tflite_food)
  // Expected Args[1]: wasm file (rust_tflite_food_lib.wasm)
  // Expected Args[2]: input image name (food.jpg)

  wasmedge.SetLogErrorLevel()

  // Set Tensorflow not to print debug info
  os.Setenv("TF_CPP_MIN_LOG_LEVEL", "3")
  os.Setenv("TF_CPP_MIN_VLOG_LEVEL", "3")

  var conf = wasmedge.NewConfigure(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping preopens
  )

  // Register WasmEdge-tensorflow
  var tfmod = wasmedge.NewTensorflowModule()
  var tflitemod = wasmedge.NewTensorflowLiteModule()
  vm.RegisterModule(tfmod)
  vm.RegisterModule(tflitemod)

  // Load and validate the wasm
  vm.LoadWasmFile(os.Args[1])
  vm.Validate()

  // Instantiate the bindgen and vm
  bg := bindgen.Instantiate(vm)

  img, _ := ioutil.ReadFile(os.Args[2])
  if res, err := bg.Execute("infer", img); err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(res))
  }

  bg.Release()
  vm.Release()
  conf.Release()
  tfmod.Release()
  tflitemod.Release()
}

Build and run

You must have WasmEdge with its tensorflow extension installed on your machine. Checkout the install guide for details.

The following command builds the Go host application with the WasmEdge Go SDK and its tensorflow extension.

go build -tags tensorflow

Now you can run the Go application. It calls the WebAssembly function in WasmEdge to run inference on the input image.

./tflite_food rust_tflite_food_lib.wasm food.jpg

The results are as follows.

Go: Args: [./tflite_food rust_tflite_food_lib.wasm food.jpg]
It is very likely a Hot dog in the picture

Embed a bindgen function

The wasm_bindgen approach discussed in this chapter is deprecated. We encourage you to check out the wasmedge_bindgen approach or to pass memory pointers directly instead.

In this example, we will demonstrate how to call a few simple WebAssembly functions from a Go app. The functions are written in Rust, and require complex call parameters and return values. The #[wasm_bindgen] macro is needed for the compiler tools to auto-generate the correct code to pass call parameters from Go to WebAssembly.

The WebAssembly spec only supports a few simple data types out of the box. It does not support types such as string and array. In order to pass rich types in Go to WebAssembly, the compiler needs to convert them to simple integers. For example, it converts a string into an integer memory address and an integer length. The wasm_bindgen tool, embedded in rustwasmc, does this conversion automatically.

At this time, we require Rust compiler version 1.50 or less in order for WebAssembly functions to work with WasmEdge's Go API. We will catch up to the latest Rust compiler version once the Interface Types spec is finalized and supported.


#![allow(unused)]
fn main() {
use wasm_bindgen::prelude::*;
use num_integer::lcm;
use sha3::{Digest, Sha3_256, Keccak256};

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}

#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasm_bindgen]
pub fn lowest_common_multiple(a: i32, b: i32) -> i32 {
  let r = lcm(a, b);
  return r;
}

#[wasm_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasm_bindgen]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
  return Keccak256::digest(s).as_slice().to_vec();
}
}

First, we use the rustwasmc tool to compile the Rust source code into WebAssembly bytecode functions using Rust 1.50 or less.

$ rustup default 1.50.0
$ cd rust_bindgen_funcs
$ rustwasmc build
# The output WASM will be pkg/rust_bindgen_funcs_lib_bg.wasm

The Go source code to run the WebAssembly function in WasmEdge is as follows. The ExecuteBindgen() function calls the WebAssembly function and passes the call parameters using the #[wasm_bindgen] convention.

package main

import (
  "fmt"
  "os"
  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  // Expected Args[0]: program name (./bindgen_funcs)
  // Expected Args[1]: wasm or wasm-so file (rust_bindgen_funcs_lib_bg.wasm))

  wasmedge.SetLogErrorLevel()

  var conf = wasmedge.NewConfigure(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping directories
  )

  // Instantiate wasm
  vm.LoadWasmFile(os.Args[1])
  vm.Validate()
  vm.Instantiate()

  // Run bindgen functions
  var res interface{}
  var err error
  
  res, err = vm.ExecuteBindgen("say", wasmedge.Bindgen_return_array, []byte("bindgen funcs test"))
  if err == nil {
    fmt.Println("Run bindgen -- say:", string(res.([]byte)))
  } 
  res, err = vm.ExecuteBindgen("obfusticate", wasmedge.Bindgen_return_array, []byte("A quick brown fox jumps over the lazy dog"))
  if err == nil {
    fmt.Println("Run bindgen -- obfusticate:", string(res.([]byte)))
  } 
  res, err = vm.ExecuteBindgen("lowest_common_multiple", wasmedge.Bindgen_return_i32, int32(123), int32(2))
  if err == nil {
    fmt.Println("Run bindgen -- lowest_common_multiple:", res.(int32))
  } 
  res, err = vm.ExecuteBindgen("sha3_digest", wasmedge.Bindgen_return_array, []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- sha3_digest:", res.([]byte))
  } 
  res, err = vm.ExecuteBindgen("keccak_digest", wasmedge.Bindgen_return_array, []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- keccak_digest:", res.([]byte))
  } 

  vm.Release()
  conf.Release()
}

Next, let's build the Go application with the WasmEdge Go SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build

Run the Go application and it will run the WebAssembly functions embedded in the WasmEdge runtime.

$ ./bindgen_funcs rust_bindgen_funcs/pkg/rust_bindgen_funcs_lib_bg.wasm
Run bindgen -- say: hello bindgen funcs test
Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt
Run bindgen -- lowest_common_multiple: 246
Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203]
Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]

WasmEdge Go v0.10.0 API references

The followings are the guides to working with the WasmEdge-Go SDK.

This document is for the newest v0.10.0 version. For the stable v0.9.1 version, please refer to the document here.

Developers can refer here to upgrade to v0.10.0.

Table of Contents

Getting Started

The WasmEdge-go requires golang version >= 1.16. Please check your golang version before installation. Developers can download golang here.

$ go version
go version go1.16.5 linux/amd64

WasmEdge Installation

Developers must install the WasmEdge shared library with the same WasmEdge-go release or pre-release version.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.10.0

For the developers need the TensorFlow or Image extension for WasmEdge-go, please install the WasmEdge with extensions:

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e tf,image -v 0.10.0

Noticed that the TensorFlow and Image extensions are only for the Linux platforms. After installation, developers can use the source command to update the include and linking searching path.

Get WasmEdge-go

After the WasmEdge installation, developers can get the WasmEdge-go package and build it in your Go project directory.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build

WasmEdge-go Extensions

By default, the WasmEdge-go only turns on the basic runtime.

WasmEdge-go has the following extensions (on the Linux platforms only):

  • Tensorflow

    • This extension supports the host functions in WasmEdge-tensorflow.

    • The TensorFlow extension when installing WasmEdge is required. Please install WasmEdge with the -e tensorflow command.

    • For using this extension, the tag tensorflow when building is required:

      go build -tags tensorflow
      
  • Image

    • This extension supports the host functions in WasmEdge-image.

    • The Image extension when installing WasmEdge is required. Please install WasmEdge with the -e image command.

    • For using this extension, the tag image when building is required:

      go build -tags image
      

Users can also turn on the multiple extensions when building:

go build -tags image,tensorflow

Example Repository

Developers can refer to the example repository for the WasmEdge-Go examples.

Example of Embedding A Function with wasmedge-bindgen

In this example, we will demonstrate how to call a few simple WebAssembly functions with wasmedge-bindgen from a Golang app. The functions are written in Rust, and require complex call parameters and return values.

While the WebAssembly only supports a few simple data types out of the box. It does not support types such as string and array. In order to pass rich types in Golang to WebAssembly, the compiler needs to convert them to simple integers. For example, it converts a string into an integer memory address and an integer length. The #[wasmedge_bindgen] macro does this conversion automatically, combining it with Golang's wasmedge-bindgen package to auto-generate the correct code to pass call parameters from Golang to WebAssembly.


#![allow(unused)]
fn main() {
use wasmedge_bindgen::*;
use wasmedge_bindgen_macro::*;
use num_integer::lcm;
use sha3::{Digest, Sha3_256, Keccak256};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasmedge_bindgen]
pub fn create_line(p1: String, p2: String, desc: String) -> String {
  let point1: Point = serde_json::from_str(p1.as_str()).unwrap();
  let point2: Point = serde_json::from_str(p2.as_str()).unwrap();
  let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();

  let valid = if length == 0.0 { false } else { true };

  let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc };

  return serde_json::to_string(&line).unwrap();
}

#[wasmedge_bindgen]
pub fn say(s: String) -> String {
  let r = String::from("hello ");
  return r + s.as_str();
}

#[wasmedge_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasmedge_bindgen]
pub fn lowest_common_multiple(a: i32, b: i32) -> i32 {
  return lcm(a, b);
}

#[wasmedge_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasmedge_bindgen]
pub fn keccak_digest(s: Vec<u8>) -> Vec<u8> {
  return Keccak256::digest(&s).as_slice().to_vec();
}
}

First, compile the Rust source code into WebAssembly bytecode functions.

rustup target add wasm32-wasi
cd rust_bindgen_funcs
cargo build --target wasm32-wasi --release
# The output WASM will be target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm

The Golang source code to run the WebAssembly function in WasmEdge is as follows. The bg.Execute() function calls the WebAssembly function and passes the parameters with the wasmedge-bindgen supporting.

package main

import (
  "fmt"
  "os"

  "github.com/second-state/WasmEdge-go/wasmedge"
  bindgen "github.com/second-state/wasmedge-bindgen/host/go"
)

func main() {
  // Expected Args[0]: program name (./bindgen_funcs)
  // Expected Args[1]: wasm file (rust_bindgen_funcs_lib.wasm))
  
  // Set not to print debug info
  wasmedge.SetLogErrorLevel()

  // Create configure
  var conf = wasmedge.NewConfigure(wasmedge.WASI)

  // Create VM with configure
  var vm = wasmedge.NewVMWithConfig(conf)

  // Init WASI
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping preopens
  )

  // Load and validate the wasm
  vm.LoadWasmFile(os.Args[1])
  vm.Validate()

  // Instantiate the bindgen and vm
  bg := bindgen.Instantiate(vm)

  // create_line: string, string, string -> string (inputs are JSON stringified)  
  res, err := bg.Execute("create_line", "{\"x\":2.5,\"y\":7.8}", "{\"x\":2.5,\"y\":5.8}", "A thin red line")
  if err == nil {
    fmt.Println("Run bindgen -- create_line:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- create_line FAILED", err)
  }

  // say: string -> string
  res, err = bg.Execute("say", "bindgen funcs test")
  if err == nil {
    fmt.Println("Run bindgen -- say:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- say FAILED")
  }

  // obfusticate: string -> string
  res, err = bg.Execute("obfusticate", "A quick brown fox jumps over the lazy dog")
  if err == nil {
    fmt.Println("Run bindgen -- obfusticate:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- obfusticate FAILED")
  }

  // lowest_common_multiple: i32, i32 -> i32
  res, err = bg.Execute("lowest_common_multiple", int32(123), int32(2))
  if err == nil {
    fmt.Println("Run bindgen -- lowest_common_multiple:", res[0].(int32))
  } else {
    fmt.Println("Run bindgen -- lowest_common_multiple FAILED")
  }

  // sha3_digest: array -> array
  res, err = bg.Execute("sha3_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- sha3_digest:", res[0].([]byte))
  } else {
    fmt.Println("Run bindgen -- sha3_digest FAILED")
  }

  // keccak_digest: array -> array
  res, err = bg.Execute("keccak_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- keccak_digest:", res[0].([]byte))
  } else {
    fmt.Println("Run bindgen -- keccak_digest FAILED")
  }

  bg.Release()
  vm.Release()
  conf.Release()
}

Next, build the Golang application with the WasmEdge Golang SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go get github.com/second-state/wasmedge-bindgen@v0.1.12
go build

Run the Golang application and it will run the WebAssembly functions embedded in the WasmEdge runtime.

$ ./bindgen_funcs rust_bindgen_funcs/target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm
Run bindgen -- create_line: {"points":[{"x":2.5,"y":7.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.0,"desc":"A thin red line"}
Run bindgen -- say: hello bindgen funcs test
Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt
Run bindgen -- lowest_common_multiple: 246
Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203]
Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]

Example of Embedding A Full WASI Program

Note: You can use the latest Rust compiler to create a standalone WasmEdge application with a main.rs functions and then embed it into a Golang application.

Besides functions, the WasmEdge Golang SDK can also embed standalone WebAssembly applications — i.e. a Rust application with a main() function compiled into WebAssembly.

Our demo Rust application reads from a file.

use std::env;
use std::fs::File;
use std::io::{self, BufRead};

fn main() {
  // Get the argv.
  let args: Vec<String> = env::args().collect();
  if args.len() <= 1 {
    println!("Rust: ERROR - No input file name.");
    return;
  }

  // Open the file.
  println!("Rust: Opening input file \"{}\"...", args[1]);
  let file = match File::open(&args[1]) {
    Err(why) => {
      println!("Rust: ERROR - Open file \"{}\" failed: {}", args[1], why);
      return;
    },
    Ok(file) => file,
  };

  // Read lines.
  let reader = io::BufReader::new(file);
  let mut texts:Vec<String> = Vec::new();
  for line in reader.lines() {
    if let Ok(text) = line {
      texts.push(text);
    }
  }
  println!("Rust: Read input file \"{}\" succeeded.", args[1]);

  // Get stdin to print lines.
  println!("Rust: Please input the line number to print the line of file.");
  let stdin = io::stdin();
  for line in stdin.lock().lines() {
    let input = line.unwrap();
    match input.parse::<usize>() {
      Ok(n) => if n > 0 && n <= texts.len() {
        println!("{}", texts[n - 1]);
      } else {
        println!("Rust: ERROR - Line \"{}\" is out of range.", n);
      },
      Err(e) => println!("Rust: ERROR - Input \"{}\" is not an integer: {}", input, e),
    }
  }
  println!("Rust: Process end.");
}

Use the rustwasmc tool to compile the application into WebAssembly.

cd rust_readfile
rustwasmc build
# The output file will be at `pkg/rust_readfile.wasm`.

Or you can compile the application into WebAssembly directly by cargo:

cd rust_readfile
# Need to add the `wasm32-wasi` target.
rustup target add wasm32-wasi
cargo build --release --target=wasm32-wasi
# The output wasm will be at `target/wasm32-wasi/release/rust_readfile.wasm`.

The Golang source code to run the WebAssembly function in WasmEdge is as follows.

package main

import (
  "os"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()

  var conf = wasmedge.NewConfigure(wasmedge.REFERENCE_TYPES)
  conf.AddConfig(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportModule(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping directories
  )

  // Instantiate and run WASM "_start" function, which refers to the main() function
  vm.RunWasmFile(os.Args[1], "_start")

  vm.Release()
  conf.Release()
}

Next, build the Golang application with the WasmEdge Golang SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
go build

Run the Golang application.

$ ./read_file rust_readfile/pkg/rust_readfile.wasm file.txt
Rust: Opening input file "file.txt"...
Rust: Read input file "file.txt" succeeded.
Rust: Please input the line number to print the line of file.
# Input "5" and press Enter.
5
# The output will be the 5th line of `file.txt`:
abcDEF___!@#$%^
# To terminate the program, send the EOF (Ctrl + D).
^D
# The output will print the terminate message:
Rust: Process end.

For more examples, please refer to the example repository.

WasmEdge-go Basics

In this partition, we will introduce the utilities and concepts of WasmEdge-go APIs and data structures.

Version

The Version related APIs provide developers to check for the installed WasmEdge shared library version.

import "github.com/second-state/WasmEdge-go/wasmedge"

verstr := wasmedge.GetVersion() // Will be `string` of WasmEdge version.
vermajor := wasmedge.GetVersionMajor() // Will be `uint` of WasmEdge major version number.
verminor := wasmedge.GetVersionMinor() // Will be `uint` of WasmEdge minor version number.
verpatch := wasmedge.GetVersionPatch() // Will be `uint` of WasmEdge patch version number.

Logging Settings

The wasmedge.SetLogErrorLevel() and wasmedge.SetLogDebugLevel() APIs can set the logging system to debug level or error level. By default, the error level is set, and the debug info is hidden.

Value Types

In WasmEdge-go, the APIs will automatically do the conversion for the built-in types, and implement the data structure for the reference types.

  1. Number types: i32, i64, f32, and f64

    • Convert the uint32 and int32 to i32 automatically when passing a value into WASM.
    • Convert the uint64 and int64 to i64 automatically when passing a value into WASM.
    • Convert the uint and int to i32 automatically when passing a value into WASM in 32-bit system.
    • Convert the uint and int to i64 automatically when passing a value into WASM in 64-bit system.
    • Convert the float32 to f32 automatically when passing a value into WASM.
    • Convert the float64 to f64 automatically when passing a value into WASM.
    • Convert the i32 from WASM to int32 when getting a result.
    • Convert the i64 from WASM to int64 when getting a result.
    • Convert the f32 from WASM to float32 when getting a result.
    • Convert the f64 from WASM to float64 when getting a result.
  2. Number type: v128 for the SIMD proposal

    Developers should use the wasmedge.NewV128() to generate a v128 value, and use the wasmedge.GetV128() to get the value.

    val := wasmedge.NewV128(uint64(1234), uint64(5678))
    high, low := val.GetVal()
    // `high` will be uint64(1234), `low` will be uint64(5678)
    
  3. Reference types: FuncRef and ExternRef for the Reference-Types proposal

    var funccxt *wasmedge.Function = ... // Create or get function object.
    funcref := wasmedge.NewFuncRef(funccxt)
    // Create a `FuncRef` with the function object.
    
    num := 1234
    // `num` is a `int`.
    externref := wasmedge.NewExternRef(&num)
    // Create an `ExternRef` which reference to the `num`.
    num = 5678
    // Modify the `num` to 5678.
    numref := externref.GetRef().(*int)
    // Get the original reference from the `ExternRef`.
    fmt.Println(*numref)
    // Will print `5678`.
    numref.Release()
    // Should call the `Release` method.
    

Results

The Result object specifies the execution status. Developers can use the Error() function to get the error message.

// Assume that `vm` is a `wasmedge.VM` object.
res, err = vm.Execute(...) // Ignore the detail of parameters.
// Assume that `res, err` are the return values for executing a function with `vm`.
if err != nil {
  fmt.Println("Error message:", err.Error())
}

Contexts And Their Life Cycles

The objects, such as VM, Store, and Function, etc., are composed of Contexts in the WasmEdge shared library. All of the contexts can be created by calling the corresponding New APIs, developers should also call the corresponding Release functions of the contexts to release the resources. Noticed that it's not necessary to call the Release functions for the contexts which are retrieved from other contexts but not created from the New APIs.

// Create a Configure.
conf := wasmedge.NewConfigure()
// Release the `conf` immediately.
conf.Release()

The details of other contexts will be introduced later.

WASM Data Structures

The WASM data structures are used for creating instances or can be queried from instance contexts. The details of instances creation will be introduced in the Instances.

  1. Limit

    The Limit struct presents the minimum and maximum value data structure.

    lim1 := wasmedge.NewLimit(12)
    fmt.Println(lim1.HasMax())
    // Will print `false`.
    fmt.Println(lim1.GetMin())
    // Will print `12`.
    
    lim2 := wasmedge.NewLimitWithMax(15, 50)
    fmt.Println(lim2.HasMax())
    // Will print `true`.
    fmt.Println(lim2.GetMin())
    // Will print `15`.
    fmt.Println(lim2.GetMax())
    // Will print `50`.
    
  2. Function type context

    The FunctionType is an object holds the function type context and used for the Function creation, checking the value types of a Function instance, or getting the function type with function name from VM. Developers can use the FunctionType APIs to get the parameter or return value types information.

    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{
        wasmedge.ValType_ExternRef,
        wasmedge.ValType_I32,
        wasmedge.ValType_I64,
      }, []wasmedge.ValType{
        wasmedge.ValType_F32,
        wasmedge.ValType_F64,
      })
    
    plen := functype.GetParametersLength()
    // `plen` will be 3.
    rlen := functype.GetReturnsLength()
    // `rlen` will be 2.
    plist := functype.GetParameters()
    // `plist` will be `[]wasmedge.ValType{wasmedge.ValType_ExternRef, wasmedge.ValType_I32, wasmedge.ValType_I64}`.
    rlist := functype.GetReturns()
    // `rlist` will be `[]wasmedge.ValType{wasmedge.ValType_F32, wasmedge.ValType_F64}`.
    
    functype.Release()
    
  3. Table type context

    The TableType is an object holds the table type context and used for Table instance creation or getting information from Table instances.

    lim := wasmedge.NewLimit(12)
    tabtype := wasmedge.NewTableType(wasmedge.RefType_ExternRef, lim)
    
    rtype := tabtype.GetRefType()
    // `rtype` will be `wasmedge.RefType_ExternRef`.
    getlim := tabtype.GetLimit()
    // `getlim` will be the same value as `lim`.
    
    tabtype.Release()
    
  4. Memory type context

    The MemoryType is an object holds the memory type context and used for Memory instance creation or getting information from Memory instances.

    lim := wasmedge.NewLimit(1)
    memtype := wasmedge.NewMemoryType(lim)
    
    getlim := memtype.GetLimit()
    // `getlim` will be the same value as `lim`.
    
    memtype.Release()
    
  5. Global type context

    The GlobalType is an object holds the global type context and used for Global instance creation or getting information from Global instances.

    globtype := wasmedge.NewGlobalType(wasmedge.ValType_F64, wasmedge.ValMut_Var)
    
    vtype := globtype.GetValType()
    // `vtype` will be `wasmedge.ValType_F64`.
    vmut := globtype.GetMutability()
    // `vmut` will be `wasmedge.ValMut_Var`.
    
    globtype.Release()
    
  6. Import type context

    The ImportType is an object holds the import type context and used for getting the imports information from a AST Module. Developers can get the external type (function, table, memory, or global), import module name, and external name from an ImportType object. The details about querying ImportType objects will be introduced in the AST Module.

    var ast *wasmedge.AST = ...
    // Assume that `ast` is returned by the `Loader` for the result of loading a WASM file.
    imptypelist := ast.ListImports()
    // Assume that `imptypelist` is an array listed from the `ast` for the imports.
    
    for i, imptype := range imptypelist {
      exttype := imptype.GetExternalType()
      // The `exttype` must be one of `wasmedge.ExternType_Function`, `wasmedge.ExternType_Table`,
      // wasmedge.ExternType_Memory`, or `wasmedge.ExternType_Global`.
    
      modname := imptype.GetModuleName()
      extname := imptype.GetExternalName()
      // Get the module name and external name of the imports.
    
      extval := imptype.GetExternalValue()
      // The `extval` is the type of `interface{}` which indicates one of `*wasmedge.FunctionType`,
      // `*wasmedge.TableType`, `*wasmedge.MemoryType`, or `*wasmedge.GlobalType`.
    }
    
  7. Export type context

    The ExportType is an object holds the export type context is used for getting the exports information from a AST Module. Developers can get the external type (function, table, memory, or global) and external name from an Export Type context. The details about querying ExportType objects will be introduced in the AST Module.

    var ast *wasmedge.AST = ...
    // Assume that `ast` is returned by the `Loader` for the result of loading a WASM file.
    exptypelist := ast.ListExports()
    // Assume that `exptypelist` is an array listed from the `ast` for the exports.
    
    for i, exptype := range exptypelist {
      exttype := exptype.GetExternalType()
      // The `exttype` must be one of `wasmedge.ExternType_Function`, `wasmedge.ExternType_Table`,
      // wasmedge.ExternType_Memory`, or `wasmedge.ExternType_Global`.
    
      extname := exptype.GetExternalName()
      // Get the external name of the exports.
    
      extval := exptype.GetExternalValue()
      // The `extval` is the type of `interface{}` which indicates one of `*wasmedge.FunctionType`,
      // `*wasmedge.TableType`, `*wasmedge.MemoryType`, or `*wasmedge.GlobalType`.
    }
    

Configurations

The configuration object, wasmedge.Configure, manages the configurations for Loader, Validator, Executor, VM, and Compiler. Developers can adjust the settings about the proposals, VM host pre-registrations (such as WASI), and AOT compiler options, and then apply the Configure object to create other runtime objects.

  1. Proposals

    WasmEdge supports turning on or off the WebAssembly proposals. This configuration is effective in any contexts created with the Configure object.

    const (
      IMPORT_EXPORT_MUT_GLOBALS         = Proposal(C.WasmEdge_Proposal_ImportExportMutGlobals)
      NON_TRAP_FLOAT_TO_INT_CONVERSIONS = Proposal(C.WasmEdge_Proposal_NonTrapFloatToIntConversions)
      SIGN_EXTENSION_OPERATORS          = Proposal(C.WasmEdge_Proposal_SignExtensionOperators)
      MULTI_VALUE                       = Proposal(C.WasmEdge_Proposal_MultiValue)
      BULK_MEMORY_OPERATIONS            = Proposal(C.WasmEdge_Proposal_BulkMemoryOperations)
      REFERENCE_TYPES                   = Proposal(C.WasmEdge_Proposal_ReferenceTypes)
      SIMD                              = Proposal(C.WasmEdge_Proposal_SIMD)
      TAIL_CALL                         = Proposal(C.WasmEdge_Proposal_TailCall)
      ANNOTATIONS                       = Proposal(C.WasmEdge_Proposal_Annotations)
      MEMORY64                          = Proposal(C.WasmEdge_Proposal_Memory64)
      THREADS                           = Proposal(C.WasmEdge_Proposal_Threads)
      EXCEPTION_HANDLING                = Proposal(C.WasmEdge_Proposal_ExceptionHandling)
      FUNCTION_REFERENCES               = Proposal(C.WasmEdge_Proposal_FunctionReferences)
    )
    

    Developers can add or remove the proposals into the Configure object.

    // By default, the following proposals have turned on initially:
    // * IMPORT_EXPORT_MUT_GLOBALS
    // * NON_TRAP_FLOAT_TO_INT_CONVERSIONS
    // * SIGN_EXTENSION_OPERATORS
    // * MULTI_VALUE
    // * BULK_MEMORY_OPERATIONS
    // * REFERENCE_TYPES
    // * SIMD
    // For the current WasmEdge version, the following proposals are supported:
    // * TAIL_CALL
    // * MULTI_MEMORIES
    // * THREADS
    conf := wasmedge.NewConfigure()
    // Developers can also pass the proposals as parameters:
    // conf := wasmedge.NewConfigure(wasmedge.SIMD, wasmedge.BULK_MEMORY_OPERATIONS)
    conf.AddConfig(wasmedge.SIMD)
    conf.RemoveConfig(wasmedge.REFERENCE_TYPES)
    is_bulkmem := conf.HasConfig(wasmedge.BULK_MEMORY_OPERATIONS)
    // The `is_bulkmem` will be `true`.
    conf.Release()
    
  2. Host registrations

    This configuration is used for the VM context to turn on the WASI or wasmedge_process supports and only effective in VM objects.

    const (
      WASI             = HostRegistration(C.WasmEdge_HostRegistration_Wasi)
      WasmEdge_PROCESS = HostRegistration(C.WasmEdge_HostRegistration_WasmEdge_Process)
    )
    

    The details will be introduced in the preregistrations of VM context.

    conf := wasmedge.NewConfigure()
    // Developers can also pass the proposals as parameters:
    // conf := wasmedge.NewConfigure(wasmedge.WASI)
    conf.AddConfig(wasmedge.WASI)
    conf.Release()
    
  3. Maximum memory pages

    Developers can limit the page size of memory instances by this configuration. When growing the page size of memory instances in WASM execution and exceeding the limited size, the page growing will fail. This configuration is only effective in the Executor and VM objects.

    conf := wasmedge.NewConfigure()
    
    pagesize := conf.GetMaxMemoryPage()
    // By default, the maximum memory page size in each memory instances is 65536.
    conf.SetMaxMemoryPage(1234)
    pagesize := conf.GetMaxMemoryPage()
    // `pagesize` will be 1234.
    
    conf.Release()
    
  4. AOT compiler options

    The AOT compiler options configure the behavior about optimization level, output format, dump IR, and generic binary.

    const (
      // Disable as many optimizations as possible.
      CompilerOptLevel_O0 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O0)
      // Optimize quickly without destroying debuggability.
      CompilerOptLevel_O1 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O1)
      // Optimize for fast execution as much as possible without triggering significant incremental compile time or code size growth.
      CompilerOptLevel_O2 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O2)
      // Optimize for fast execution as much as possible.
      CompilerOptLevel_O3 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O3)
      // Optimize for small code size as much as possible without triggering significant incremental compile time or execution time slowdowns.
      CompilerOptLevel_Os = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Os)
      // Optimize for small code size as much as possible.
      CompilerOptLevel_Oz = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Oz)
    )
    
    const (
      // Native dynamic library format.
      CompilerOutputFormat_Native = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Native)
      // WebAssembly with AOT compiled codes in custom section.
      CompilerOutputFormat_Wasm = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Wasm)
    )
    

    These configurations are only effective in Compiler contexts.

    conf := wasmedge.NewConfigure()
    
    // By default, the optimization level is O3.
    conf.SetCompilerOptimizationLevel(wasmedge.CompilerOptLevel_O2)
    // By default, the output format is universal WASM.
    conf.SetCompilerOutputFormat(wasmedge.CompilerOutputFormat_Native)
    // By default, the dump IR is `false`.
    conf.SetCompilerDumpIR(true)
    // By default, the generic binary is `false`.
    conf.SetCompilerGenericBinary(true)
    
    conf.Release()
    
  5. Statistics options

    The statistics options configure the behavior about instruction counting, cost measuring, and time measuring in both runtime and AOT compiler. These configurations are effective in Compiler, VM, and Executor objects.

    conf := wasmedge.NewConfigure()
    
    // By default, the intruction counting is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsInstructionCounting(true)
    // By default, the cost measurement is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsTimeMeasuring(true)
    // By default, the time measurement is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsCostMeasuring(true)
    
    conf.Release()
    

WasmEdge VM

In this partition, we will introduce the functions of wasmedge.VM object and show examples of executing WASM functions.

WASM Execution Example With VM Object

The following shows the example of running the WASM for getting the Fibonacci. This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

(module
  (export "fib" (func $fib))
  (func $fib (param $n i32) (result i32)
    (if
      (i32.lt_s (get_local $n)(i32.const 2))
      (return (i32.const 1))
    )
    (return
      (i32.add
        (call $fib (i32.sub (get_local $n)(i32.const 2)))
        (call $fib (i32.sub (get_local $n)(i32.const 1)))
      )
    )
  )
)
  1. Run WASM functions rapidly

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that the WASM file fibonacci.wasm is copied into the current wasmedge_test directory, and create and edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Set the logging level.
      wasmedge.SetLogErrorLevel()
    
      // Create the configure context and add the WASI support.
      // This step is not necessary unless you need WASI support.
      conf := wasmedge.NewConfigure(wasmedge.WASI)
      // Create VM with the configure.
      vm := wasmedge.NewVMWithConfig(conf)
    
      res, err := vm.RunWasmFile("fibonacci.wasm", "fib", uint32(21))
      if err == nil {
        fmt.Println("Get fibonacci[21]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
      conf.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK: (the 21 Fibonacci number is 17711 in 0-based index)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
    $ go build
    $ ./wasmedge_test
    Get fibonacci[21]: 17711
    
  2. Instantiate and run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM object APIs:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Set the logging level.
      wasmedge.SetLogErrorLevel()
    
      // Create VM.
      vm := wasmedge.NewVM()
      var err error
      var res []interface{}
    
      // Step 1: Load WASM file.
      err = vm.LoadWasmFile("fibonacci.wasm")
      if err != nil {
        fmt.Println("Load WASM from file FAILED:", err.Error())
        return
      }
    
      // Step 2: Validate the WASM module.
      err = vm.Validate()
      if err != nil {
        fmt.Println("Validation FAILED:", err.Error())
        return
      }
    
      // Step 3: Instantiate the WASM module.
      err = vm.Instantiate()
      // Developers can load, validate, and instantiate another WASM module
      // to replace the instantiated one. In this case, the old module will
      // be cleared, but the registered modules are still kept.
      if err != nil {
        fmt.Println("Instantiation FAILED:", err.Error())
        return
      }
    
      // Step 4: Execute WASM functions. Parameters: (funcname, args...)
      res, err = vm.Execute("fib", uint32(25))
      // Developers can execute functions repeatedly after instantiation.
      if err == nil {
        fmt.Println("Get fibonacci[25]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ go build
    $ ./wasmedge_test
    Get fibonacci[25]: 121393
    

    The following graph explains the status of the VM object.

                           |========================|
                  |------->|      VM: Initiated     |
                  |        |========================|
                  |                    |
                  |                 LoadWasm
                  |                    |
                  |                    v
                  |        |========================|
                  |--------|       VM: Loaded       |<-------|
                  |        |========================|        |
                  |              |            ^              |
                  |         Validate          |              |
              Cleanup            |          LoadWasm         |
                  |              v            |            LoadWasm
                  |        |========================|        |
                  |--------|      VM: Validated     |        |
                  |        |========================|        |
                  |              |            ^              |
                  |      Instantiate          |              |
                  |              |          RegisterModule   |
                  |              v            |              |
                  |        |========================|        |
                  |--------|    VM: Instantiated    |--------|
                           |========================|
                                 |            ^
                                 |            |
                                 --------------
                    Instantiate, Execute, ExecuteRegistered,
                    ExecuteBindgen, ExecuteBindgenRegistered
    

    The status of the VM context would be Inited when created. After loading WASM successfully, the status will be Loaded. After validating WASM successfully, the status will be Validated. After instantiating WASM successfully, the status will be Instantiated, and developers can invoke functions. Developers can register WASM or import objects in any status, but they should instantiate WASM again. Developers can also load WASM in any status, and they should validate and instantiate the WASM module before function invocation. When in the Instantiated status, developers can instantiate the WASM module again to reset the old WASM runtime structures.

VM Creations

The VM creation APIs accepts the Configure object and the Store object. Noticed that if the VM created with the outside Store object, the VM will execute WASM on that Store object. If the Store object is set into multiple VM objects, it may causes data conflict when in execution. The details of the Store object will be introduced in Store.

conf := wasmedge.NewConfigure()
store := wasmedge.NewStore()

// Create a VM with default configure and store.
vm := wasmedge.NewVM()
vm.Release()

// Create a VM with the specified configure and default store.
vm = wasmedge.NewVMWithConfig(conf)
vm.Release()

// Create a VM with the default configure and specified store.
vm = wasmedge.NewVMWithStore(store)
vm.Release()

// Create a VM with the specified configure and store.
vm = wasmedge.NewVMWithConfigAndStore(conf, store)
vm.Release()

conf.Release()
store.Release()

Preregistrations

WasmEdge provides the following built-in pre-registrations.

  1. WASI (WebAssembly System Interface)

    Developers can turn on the WASI support for VM in the Configure object.

    conf := wasmedge.NewConfigure(wasmedge.WASI)
    // Or you can set the `wasmedge.WASI` into the configure object through `(*Configure).AddConfig`.
    vm := wasmedge.NewVMWithConfig(conf)
    vm.Release()
    
    // The following API can retrieve the pre-registration import objects from the VM object.
    // This API will return `nil` if the corresponding pre-registration is not set into the configuration.
    wasiconf := conf.GetImportModule(wasmedge.WASI)
    // Initialize the WASI.
    wasiconf.InitWasi(/* ... ignored */)
    
    conf.Release()
    

    And also can create the WASI import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

  2. WasmEdge_Process

    This pre-registration is for the process interface for WasmEdge on Rust sources. After turning on this pre-registration, the VM will support the wasmedge_process host functions.

    conf := wasmedge.NewConfigure(wasmedge.WasmEdge_PROCESS)
    vm := wasmedge.NewVMWithConfig(conf)
    vm.Release()
    
    // The following API can retrieve the pre-registration import objects from the VM object.
    // This API will return `nil` if the corresponding pre-registration is not set into the configuration.
    procconf := conf.GetImportModule(wasmedge.WasmEdge_PROCESS)
    // Initialize the WasmEdge_Process.
    procconf.InitWasmEdgeProcess(/* ... ignored */)
    
    conf.Release()
    

    And also can create the WasmEdge_Process import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

Host Module Registrations

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge-go, the host functions are composed into host modules as Module objects with module names. Please refer to the Host Functions in WasmEdge Runtime for the details. In this chapter, we show the example for registering the host modules into a VM object.

vm := wasmedge.NewVM()
// You can also create and register the WASI host modules by this API.
wasiobj := wasmedge.NewWasiModule(/* ... ignored ... */)

res := vm.RegisterModule(wasiobj)
// The result status should be checked.

vm.Release()
// The created import objects should be released.
wasiobj.Release()

WASM Registrations And Executions

In WebAssembly, the instances in WASM modules can be exported and can be imported by other WASM modules. WasmEdge VM provides APIs for developers to register and export any WASM modules, and execute the functions or host functions in the registered WASM modules.

  1. Register the WASM modules with exported module names

    Unless the import objects have already contained the module names, every WASM module should be named uniquely when registering. The following shows the example.

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that the WASM file fibonacci.wasm is copied into the current directory. Then create and edit the Go file main.go as following:

    package main
    
    import "github.com/second-state/WasmEdge-go/wasmedge"
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      var err error
      err = vm.RegisterWasmFile("module_name", "fibonacci.wasm")
      // Developers can register the WASM module from `[]byte` with the
      // `(*VM).RegisterWasmBuffer` function, or from `AST` object with
      // the `(*VM).RegisterAST` function.
      // The result status should be checked. The error will occur if the
      // WASM module instantiation failed or the module name conflicts.
    
      vm.Release()
    }
    
  2. Execute the functions in registered WASM modules

    Edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      var res []interface{}
      var err error
      // Register the WASM module from file into VM with the module name "mod".
      err = vm.RegisterWasmFile("mod", "fibonacci.wasm")
      // Developers can register the WASM module from `[]byte` with the
      // `(*VM).RegisterWasmBuffer` function, or from `AST` object with
      // the `(*VM).RegisterAST` function.
      if err != nil {
        fmt.Println("WASM registration failed:", err.Error())
        return
      }
      // The function "fib" in the "fibonacci.wasm" was exported with the module
      // name "mod". As the same as host functions, other modules can import the
      // function `"mod" "fib"`.
    
      // Execute WASM functions in registered modules.
      // Unlike the execution of functions, the registered functions can be
      // invoked without `(*VM).Instantiate` because the WASM module was
      // instantiated when registering.
      // Developers can also invoke the host functions directly with this API.
      res, err = vm.ExecuteRegistered("mod", "fib", int32(25))
      if err == nil {
        fmt.Println("Get fibonacci[25]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
    $ go build
    $ ./wasmedge_test
    Get fibonacci[25]: 121393
    

Instance Tracing

Sometimes the developers may have requirements to get the instances of the WASM runtime. The VM object supplies the APIs to retrieve the instances.

  1. Store

    If the VM object is created without assigning a Store object, the VM context will allocate and own a Store.

    vm := wasmedge.NewVM()
    store := vm.GetStore()
    // The object should __NOT__ be deleted by calling `(*Store).Release`.
    vm.Release()
    

    Developers can also create the VM object with a Store object. In this case, developers should guarantee that the Store object cannot be released before the VM object. Please refer to the Store Objects for the details about the Store APIs.

    store := wasmedge.NewStore()
    vm := wasmedge.NewVMWithStore(store)
    
    storemock := vm.GetStore()
    // The internal store context of the `store` and the `storemock` are the same.
    
    vm.Release()
    store.Release()
    
  2. List exported functions

    After the WASM module instantiation, developers can use the (*VM).Execute function to invoke the exported WASM functions. For this purpose, developers may need information about the exported WASM function list. Please refer to the Instances in runtime for the details about the function types.

    Assume that a new Go project is created as following:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Then assume that the WASM file fibonacci.wasm is copied into the current directory, and create and edit a Go file main.go:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      // Step 1: Load WASM file.
      err := vm.LoadWasmFile("fibonacci.wasm")
      if err != nil {
        fmt.Println("Load WASM from file FAILED:", err.Error())
        return
      }
    
      // Step 2: Validate the WASM module.
      err = vm.Validate()
      if err != nil {
        fmt.Println("Validation FAILED:", err.Error())
        return
      }
    
      // Step 3: Instantiate the WASM module.
      err = vm.Instantiate()
      if err != nil {
        fmt.Println("Instantiation FAILED:", err.Error())
        return
      }
    
      // List the exported functions for the names and function types.
      funcnames, functypes := vm.GetFunctionList()
      for _, fname := range funcnames {
        fmt.Println("Exported function name:", fname)
      }
      for _, ftype := range functypes {
        // `ftype` is the `FunctionType` object of the same index in the `funcnames` array.
        // Developers should __NOT__ call the `ftype.Release()`.
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the only exported function in fibonacci.wasm is fib)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
    $ go build
    $ ./wasmedge_test
    Exported function name: fib
    

    If developers want to get the exported function names in the registered WASM modules, please retrieve the Store object from the VM object and refer to the APIs of Store Contexts to list the registered functions by the module name.

  3. Get function types

    The VM object provides APIs to find the function type by function name. Please refer to the Instances in runtime for the details about the function types.

    // Assume that a WASM module is instantiated in `vm` which is a `wasmedge.VM` object.
    functype := vm.GetFunctionType("fib")
    // Developers can get the function types of functions in the registered modules via the 
    // `(*VM).GetFunctionTypeRegistered` API with the functon name and the module name.
    // If the function is not found, these APIs will return `nil`.
    // Developers should __NOT__ call the `(*FunctionType).Release` function of the returned object.
    
  4. Get the active module

    After the WASM module instantiation, an anonymous module is instantiated and owned by the VM object. Developers may need to retrieve it to get the instances beyond the module. Then developers can use the (*VM).GetActiveModule() API to get that anonymous module instance. Please refer to the Module instance for the details about the module instance APIs.

    // Assume that a WASM module is instantiated in `vm` which is a `wasmedge.VM` object.
    mod := vm.GetActiveModule()
    // If there's no WASM module instantiated, this API will return `nil`.
    // Developers should __NOT__ call the `(*Module).Release` function of the returned module instance.
    

WasmEdge Runtime

In this partition, we will introduce the objects of WasmEdge runtime manually.

WASM Execution Example Step-By-Step

Besides the WASM execution through the VM object rapidly, developers can execute the WASM functions or instantiate WASM modules step-by-step with the Loader, Validator, Executor, and Store objects.

Assume that a new Go project is created as following:

mkdir wasmedge_test && cd wasmedge_test
go mod init wasmedge_test

Then assume that the WASM file fibonacci.wasm is copied into the current directory, and create and edit a Go file main.go:

package main

import (
  "fmt"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  // Set the logging level to debug to print the statistics info.
  wasmedge.SetLogDebugLevel()
  // Create the configure object. This is not necessary if developers use the default configuration.
  conf := wasmedge.NewConfigure()
  // Turn on the runtime instruction counting and time measuring.
  conf.SetStatisticsInstructionCounting(true)
  conf.SetStatisticsTimeMeasuring(true)
  // Create the statistics object. This is not necessary if the statistics in runtime is not needed.
  stat := wasmedge.NewStatistics()
  // Create the store object. The store object is the WASM runtime structure core.
  store := wasmedge.NewStore()

  var err error
  var res []interface{}
  var ast *wasmedge.AST
  var mod *wasmedge.Module

  // Create the loader object.
  // For loader creation with default configuration, you can use `wasmedge.NewLoader()` instead.
  loader := wasmedge.NewLoaderWithConfig(conf)
  // Create the validator object.
  // For validator creation with default configuration, you can use `wasmedge.NewValidator()` instead.
  validator := wasmedge.NewValidatorWithConfig(conf)
  // Create the executor object.
  // For executor creation with default configuration and without statistics, you can use `wasmedge.NewExecutor()` instead.
  executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)

  // Load the WASM file or the compiled-WASM file and convert into the AST module object.
  ast, err = loader.LoadFile("fibonacci.wasm")
  if err != nil {
    fmt.Println("Load WASM from file FAILED:", err.Error())
    return
  }
  // Validate the WASM module.
  err = validator.Validate(ast)
  if err != nil {
    fmt.Println("Validation FAILED:", err.Error())
    return
  }
  // Instantiate the WASM module and get the output module instance.
  mod, err = executor.Instantiate(store, ast)
  if err != nil {
    fmt.Println("Instantiation FAILED:", err.Error())
    return
  }

  // Try to list the exported functions of the instantiated WASM module.
  funcnames := mod.ListFunction()
  for _, fname := range funcnames {
    fmt.Println("Exported function name:", fname)
  }

  // Invoke the WASM function.
  funcinst := mod.FindFunction("fib")
  if funcinst == nil {
    fmt.Println("Run FAILED: Function name `fib` not found")
    return
  }
  res, err = executor.Invoke(store, funcinst, int32(30))
  if err == nil {
    fmt.Println("Get fibonacci[30]:", res[0].(int32))
  } else {
    fmt.Println("Run FAILED:", err.Error())
  }

  // Resources deallocations.
  conf.Release()
  stat.Release()
  ast.Release()
  loader.Release()
  validator.Release()
  executor.Release()
  store.Release()
  mod.Release()
}

Then you can build and run: (the 18th Fibonacci number is 1346269 in 30-based index)

$ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
$ go build
$ ./wasmedge_test
Exported function name: fib
[2021-11-24 18:53:01.451] [debug]  Execution succeeded.
[2021-11-24 18:53:01.452] [debug]
 ====================  Statistics  ====================
 Total execution time: 556372295 ns
 Wasm instructions execution time: 556372295 ns
 Host functions execution time: 0 ns
 Executed wasm instructions count: 28271634
 Gas costs: 0
 Instructions per second: 50814237
Get fibonacci[30]: 1346269

Loader

The Loader object loads the WASM binary from files or buffers. Both the WASM and the compiled-WASM from the WasmEdge AOT Compiler are supported.

var buf []byte
// ... Read the WASM code to the `buf`.

// Developers can adjust settings in the configure object.
conf := wasmedge.NewConfigure()
// Create the loader object.
// For loader creation with default configuration, you can use `wasmedge.NewLoader()` instead.
loader := wasmedge.NewLoaderWithConfig(conf)
conf.Release()

// Load WASM or compiled-WASM from the file.
ast, err := loader.LoadFile("fibonacci.wasm")
if err != nil {
  fmt.Println("Load WASM from file FAILED:", err.Error())
} else {
  // The output AST object should be released.
  ast.Release()
}

// Load WASM or compiled-WASM from the buffer
ast, err = loader.LoadBuffer(buf)
if err != nil {
  fmt.Println("Load WASM from buffer FAILED:", err.Error())
} else {
  // The output AST object should be released.
  ast.Release()   
}

loader.Release()

Validator

The Validator object can validate the WASM module. Every WASM module should be validated before instantiation.

// ...
// Assume that the `ast` is the output `*wasmedge.AST` object from the loader context.
// Assume that the `conf` is the `*wasmedge.Configure` object.

// Create the validator context.
// For validator creation with default configuration, you can use `wasmedge.NewValidator()` instead.
validator := wasmedge.NewValidatorWithConfig(conf)

err := validator.Validate(ast)
if err != nil {
  fmt.Println("Validation FAILED:", err.Error())
}

validator.Release()

Executor

The Executor object is the executor for both WASM and compiled-WASM. This object should work base on the Store object. For the details of the Store object, please refer to the next chapter.

  1. Instantiate and register an AST object as a named Module instance

    As the same of registering host modules or importing WASM modules in VM objects, developers can instantiate and register an AST objects into the Store context as a named Module instance by the Executor APIs. After the registration, the result Module instance is exported with the given module name and can be linked when instantiating another module. For the details about the Module instances APIs, please refer to the Instances.

    // ...
    // Assume that the `ast` is the output `*wasmedge.AST` object from the loader
    // and has passed the validation.
    // Assume that the `conf` is the `*wasmedge.Configure` object.
    
    // Create the statistics object. This step is not necessary if the statistics
    // is not needed.
    stat := wasmedge.NewStatistics()
    // Create the executor object.
    // For executor creation with default configuration and without statistics,
    // you can use `wasmedge.NewExecutor()` instead.
    executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)
    // Create the store object. The store is the WASM runtime structure core.
    store := wasmedge.NewStore()
    
    // Register the loaded WASM `ast` into store with the export module name "mod".
    mod, res := executor.Register(store, ast, "mod")
    if err != nil {
      fmt.Println("WASM registration FAILED:", err.Error())
      return
    }
    
    // ...
    
    // Resources deallocations.
    executor.Release()
    stat.Release()
    store.Release()
    mod.Release()
    
  2. Register an existing Module instance and export the module name

    Besides instantiating and registering an AST object, developers can register an existing Module instance into the store with exporting the module name (which is in the Module instance already). This case occurs when developers create a Module instance for the host functions and want to register it for linking. For the details about the construction of host functions in Module instances, please refer to the Host Functions.

    // ...
    // Assume that the `ast` is the output `*wasmedge.AST` object from the loader
    // and has passed the validation.
    // Assume that the `conf` is the `*wasmedge.Configure` object.
    
    // Create the statistics object. This step is not necessary if the statistics
    // is not needed.
    stat := wasmedge.NewStatistics()
    // Create the executor object.
    // For executor creation with default configuration and without statistics,
    // you can use `wasmedge.NewExecutor()` instead.
    executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)
    // Create the store object. The store is the WASM runtime structure core.
    store := wasmedge.NewStore()
    
    // Create a module instance for host functions.
    mod := wasmedge.NewModule("mod")
    // ...
    // Create and add the host functions, tables, memories, and globals into the module instance.
    // ...
    
    // Register the module instance into store with the exported module name.
    // The export module name is in the module instance already.
    res := executor.RegisterImport(store, mod)
    if err != nil {
      fmt.Println("WASM registration FAILED:", err.Error())
      return
    }
    
    // ...
    
    // Resources deallocations.
    executor.Release()
    stat.Release()
    store.Release()
    mod.Release()
    
  3. Instantiate an AST object to an anonymous Module instance

    WASM or compiled-WASM modules should be instantiated before the function invocation. Before instantiating a WASM module, please check the import section for ensuring the imports are registered into the Store object for linking.

    // ...
    // Assume that the `ast` is the output `*wasmedge.AST` object from the loader
    // and has passed the validation.
    // Assume that the `conf` is the `*wasmedge.Configure` object.
    
    // Create the statistics object. This step is not necessary if the statistics
    // is not needed.
    stat := wasmedge.NewStatistics()
    // Create the executor object.
    // For executor creation with default configuration and without statistics,
    // you can use `wasmedge.NewExecutor()` instead.
    executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)
    // Create the store object. The store is the WASM runtime structure core.
    store := wasmedge.NewStore()
    
    // Instantiate the WASM module.
    mod, err := executor.Instantiate(stpre, ast)
    if err != nil {
      fmt.Println("WASM instantiation FAILED:", err.Error())
      return
    }
    
    executor.Release()
    stat.Release()
    store.Release()
    mod.Release()
    
  4. Invoke functions

    After registering or instantiating and get the result Module instance, developers can retrieve the exported Function instances from the Module instance for invocation. For the details about the Module instances APIs, please refer to the Instances. Please refer to the example above for the Function instance invocation with the (*Executor).Invoke API.

AST Module

The AST object presents the loaded structure from a WASM file or buffer. Developer will get this object after loading a WASM file or buffer from Loader. Before instantiation, developers can also query the imports and exports of an AST object.

ast := ...
// Assume that a WASM is loaded into an `*wasmedge.AST` object from loader.

// List the imports.
imports := ast.ListImports()
for _, import := range imports {
  fmt.Println("Import:", import.GetModuleName(), import.GetExternalName())
}

// List the exports.
exports := ast.ListExports()
for _, export := range exports {
  fmt.Println("Export:", export.GetExternalName())
}

ast.Release()

Store

Store is the runtime structure for the representation of all global state that can be manipulated by WebAssembly programs. The Store object in WasmEdge is an object to provide the instance exporting and importing when instantiating WASM modules. Developers can retrieve the named modules from the Store context.

store := wasmedge.NewStore()

// ...
// Register a WASM module via the executor object.
// ...

// Try to list the registered WASM modules.
modnames := store.ListModule()
// ...

// Find named module by name.
mod := store.FindModule("module")
// If the module with name not found, the `mod` will be `nil`.

store.Release()

Instances

The instances are the runtime structures of WASM. Developers can retrieve the Module instances from the Store contexts, and retrieve the other instances from the Module instances. A single instance can be allocated by its creation function. Developers can construct instances into an Module instance for registration. Please refer to the Host Functions for details. The instances created by their creation functions should be destroyed by developers, EXCEPT they are added into an Module instance.

  1. Module instance

    After instantiating or registering an AST object, developers will get a Module instance as the result, and have the responsibility to release it when not in use. A Module instance can also be created for the host module. Please refer to the host function for the details. Module instance provides APIs to list and find the exported instances in the module.

    // ...
    // Instantiate a WASM module via the executor object and get the `mod` as the output module instance.
    // ...
    
    // List the exported instance of the instantiated WASM module.
    // Take the function instances for example here.
    funcnames := mod.ListFunction()
    
    // Try to find the exported instance of the instantiated WASM module.
    // Take the function instances for example here.
    funcinst := mod.FindFunction("fib")
    // `funcinst` will be `nil` if the function not found.
    // The returned instance is owned by the module instance and should __NOT__ be released.
    
  2. Function instance

    Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function objects for host functions and add them into an Module instance for registering into a VM or a Store. Developers can retrieve the Function Type from the Function objects through the API. For the details of the Host Function guide, please refer to the next chapter.

    funcobj := ...
    // `funcobj` is the `*wasmedge.Function` retrieved from the module instance.
    functype := funcobj.GetFunctionType()
    // The `funcobj` retrieved from the module instance should __NOT__ be released.
    // The `functype` retrieved from the `funcobj` should __NOT__ be released.
    
    // For the function object creation, please refer to the `Host Function` guide.
    
  3. Table instance

    In WasmEdge, developers can create the Table objects and add them into an ImportObject object for registering into a VM or a Store. The Table objects supply APIs to control the data in table instances.

    lim := wasmedge.NewLimitWithMax(10, 20)
    // Create the table type with limit and the `FuncRef` element type.
    tabtype := wasmedge.NewTableType(wasmedge.RefType_FuncRef, lim)
    // Create the table instance with table type.
    tabinst := wasmedge.NewTable(tabtype)
    // Delete the table type.
    tabtype.Release()
    
    gottabtype := tabinst.GetTableType()
    // The `gottabtype` got from table instance is owned by the `tabinst`
    // and should __NOT__ be released.
    reftype := gottabtype.GetRefType()
    // The `reftype` will be `wasmedge.RefType_FuncRef`.
    
    var gotdata interface{}
    data := wasmedge.NewFuncRef(5)
    err := tabinst.SetData(data, 3)
    // Set the function index 5 to the table[3].
    
    // The following line will get an "out of bounds table access" error
    // because the position (13) is out of the table size (10):
    //   err = tabinst.SetData(data, 13)
    
    gotdata, err = tabinst.GetData(3)
    // Get the FuncRef value of the table[3].
    
    // The following line will get an "out of bounds table access" error
    // because the position (13) is out of the table size (10):
    //   gotdata, err = tabinst.GetData(13)
    
    tabsize := tabinst.GetSize()
    // `tabsize` will be 10.
    err = tabinst.Grow(6)
    // Grow the table size of 6, the table size will be 16.
    
    // The following line will get an "out of bounds table access" error
    // because the size (16 + 6) will reach the table limit (20):
    //   err = tabinst.Grow(6)
    
    tabinst.Release()
    
  4. Memory instance

    In WasmEdge, developers can create the Memory objects and add them into an ImportObject object for registering into a VM or a Store. The Memory objects supply APIs to control the data in memory instances.

    lim := wasmedge.NewLimitWithMax(1, 5)
    // Create the memory type with limit. The memory page size is 64KiB.
    memtype := wasmedge.NewMemoryType(lim)
    // Create the memory instance with memory type.
    meminst := wasmedge.NewMemory(memtype)
    // Delete the memory type.
    memtype.Release()
    
    data := []byte("A quick brown fox jumps over the lazy dog")
    err := meminst.SetData(data, 0x1000, 10)
    // Set the data[0:9] to the memory[4096:4105].
    
    // The following line will get an "out of bounds memory access" error
    // because [65535:65544] is out of 1 page size (65536):
    //   err = meminst.SetData(data, 0xFFFF, 10)
    
    var gotdata []byte
    gotdata, err = meminst.GetData(0x1000, 10)
    // Get the memory[4096:4105]. The `gotdata` will be `[]byte("A quick br").
    // The following line will get an "out of bounds memory access" error
    // because [65535:65544] is out of 1 page size (65536):
    //   gotdata, err = meminst.Getdata(0xFFFF, 10)
    
    pagesize := meminst.GetPageSize()
    // `pagesize` will be 1.
    err = meminst.GrowPage(2)
    // Grow the page size of 2, the page size of the memory instance will be 3.
    
    // The following line will get an "out of bounds memory access" error
    // because the size (3 + 3) will reach the memory limit (5):
    //   err = meminst.GetPageSize(3)
    
    meminst.Release()
    
  5. Global instance

    In WasmEdge, developers can create the Global objects and add them into an ImportObject object for registering into a VM or a Store. The Global objects supply APIs to control the value in global instances.

    // Create the global type with value type and mutation.
    globtype := wasmedge.NewGlobalType(wasmedge.ValType_I64, wasmedge.ValMut_Var)
    // Create the global instance with value and global type.
    globinst := wasmedge.NewGlobal(globtype, uint64(1000))
    // Delete the global type.
    globtype.Release()
    
    gotglobtype := globinst.GetGlobalType()
    // The `gotglobtype` got from global instance is owned by the `globinst`
    // and should __NOT__ be released.
    valtype := gotglobtype.GetValType()
    // The `valtype` will be `wasmedge.ValType_I64`.
    valmut := gotglobtype.GetMutability()
    // The `valmut` will be `wasmedge.ValMut_Var`.
    
    globinst.SetValue(uint64(888))
    // Set the value u64(888) to the global.
    // This function will do nothing if the value type mismatched or the
    // global mutability is `wasmedge.ValMut_Const`.
    gotval := globinst.GetValue()
    // The `gotbal` will be `interface{}` which the type is `uint64` and
    // the value is 888.
    
    globinst.Release()
    

Host Functions

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge-go, developers can create the Function, Memory, Table, and Global objects and add them into an ImportObject object for registering into a VM or a Store.

  1. Host function allocation

    Developers can define Go functions with the following function signature as the host function body:

    type hostFunctionSignature func(
        data interface{}, mem *Memory, params []interface{}) ([]interface{}, Result)
    

    The example of an add host function to add 2 i32 values:

    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    

    Then developers can create Function object with the host function body and function type:

    // Create a function type: {i32, i32} -> {i32}.
    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
      []wasmedge.ValType{wasmedge.ValType_I32},
    )
    
    // Create a function context with the function type and host function body.
    // The third parameter is the pointer to the additional data.
    // Developers should guarantee the life cycle of the data, and it can be
    // `nil` if the external data is not needed.
    // The last parameter can be 0 if developers do not need the cost measuring.
    func_add := wasmedge.NewFunction(functype, host_add, nil, 0)
    
    // If the function object is not added into an module instance object, it should be released.
    func_add.Release()
    functype.Release()
    
  2. Construct a module instance with host instances

    Besides creating a Module instance by registering or instantiating a WASM module, developers can create a Module instance with a module name and add the Function, Memory, Table, and Global instances into it with their exporting names.

    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    // Create a module instance with the module name "module".
    mod := wasmedge.NewModule("module")
    
    // Create and add a function instance into the module instance with export name "add".
    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
      []wasmedge.ValType{wasmedge.ValType_I32},
    )
    hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
    functype.Release()
    mod.AddFunction("add", hostfunc)
    
    // Create and add a table instance into the module instance with export name "table".
    tabtype := wasmedge.NewTableType(wasmedge.RefType_FuncRef ,wasmedge.NewLimitWithMax(10, 20))
    hosttab := wasmedge.NewTable(tabtype)
    tabtype.Release()
    mod.AddTable("table", hosttab)
    
    // Create and add a memory instance into the module instance with export name "memory".
    memtype := wasmedge.NewMemoryType(wasmedge.NewLimitWithMax(1, 2))
    hostmem := wasmedge.NewMemory(memtype)
    memtype.Release()
    mod.AddMemory("memory", hostmem)
    
    // Create and add a global instance into the module instance with export name "global".
    globtype := wasmedge.NewGlobalType(wasmedge.ValType_I32, wasmedge.ValMut_Var)
    hostglob := wasmedge.NewGlobal(globtype, uint32(666))
    globtype.Release()
    mod.AddGlobal("global", hostglob)
    
    // The module instances should be released.
    // Developers should __NOT__ release the instances added into the module instance objects.
    mod.Release()
    
  3. Specified module instance

    wasmedge.NewWasiModule() API can create and initialize the WASI module instance. wasmedge.NewWasmEdgeProcessModule() API can create and initialize the wasmedge_process module instance. Developers can create these module instance objects and register them into the Store or VM objects rather than adjust the settings in the Configure objects.

    wasiobj := wasmedge.NewWasiModule(
      os.Args[1:],     // The args
      os.Environ(),    // The envs
      []string{".:."}, // The mapping preopens
    )
    procobj := wasmedge.NewWasmEdgeProcessModule(
      []string{"ls", "echo"}, // The allowed commands
      false,                  // Not to allow all commands
    )
    
    // Register the WASI and WasmEdge_Process into the VM object.
    vm := wasmedge.NewVM()
    vm.RegisterImport(wasiobj)
    vm.RegisterImport(procobj)
    
    // ... Execute some WASM functions.
    
    // Get the WASI exit code.
    exitcode := wasiobj.WasiGetExitCode()
    // The `exitcode` will be 0 if the WASI function "_start" execution has no error.
    // Otherwise, it will return with the related exit code.
    
    vm.Release()
    // The import objects should be deleted.
    wasiobj.Release()
    procobj.Release()
    
  4. Example

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that there is a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    Create and edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    func main() {
      // Create the VM object.
      vm := wasmedge.NewVM()
    
      // The WASM module buffer.
      wasmbuf := []byte{
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B,
      }
    
      // Create the module instance with the module name "extern".
      impmod := wasmedge.NewModule("extern")
    
      // Create and add a function instance into the module instance with export name "func-add".
      functype := wasmedge.NewFunctionType(
        []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
        []wasmedge.ValType{wasmedge.ValType_I32},
      )
      hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
      functype.Release()
      impmod.AddFunction("func-add", hostfunc)
    
      // Register the module instance into VM.
      vm.RegisterImport(impmod)
    
      res, err := vm.RunWasmBuffer(wasmbuf, "addTwo", uint32(1234), uint32(5678))
      if err == nil {
        fmt.Println("Get the result:", res[0].(int32))
      } else {
        fmt.Println("Error message:", err.Error())
      }
    
      impmod.Release()
      vm.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK:

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
    $ go build
    $ ./wasmedge_test
    Get the result: 6912
    
  5. Host Data Example

    Developers can set a external data object to the Function object, and access to the object in the function body. Assume that edit the Go file main.go above:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Also set the result to the data.
      *data.(*int32) = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    func main() {
      // Create the VM object.
      vm := wasmedge.NewVM()
    
      // The WASM module buffer.
      wasmbuf := []byte{
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B,
      }
    
      // The additional data to set into the host function.
      var data int32 = 0
    
      // Create the module instance with the module name "extern".
      impmod := wasmedge.NewImportObject("extern")
    
      // Create and add a function instance into the module instance with export name "func-add".
      functype := wasmedge.NewFunctionType(
        []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
        []wasmedge.ValType{wasmedge.ValType_I32},
      )
      hostfunc := wasmedge.NewFunction(functype, host_add, &data, 0)
      functype.Release()
      impmod.AddFunction("func-add", hostfunc)
    
      // Register the module instance into VM.
      vm.RegisterImport(impmod)
    
      res, err := vm.RunWasmBuffer(wasmbuf, "addTwo", uint32(1234), uint32(5678))
      if err == nil {
        fmt.Println("Get the result:", res[0].(int32))
      } else {
        fmt.Println("Error message:", err.Error())
      }
      fmt.Println("Data value:", data)
    
      impmod.Release()
      vm.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK:

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.10.0
    $ go build
    $ ./wasmedge_test
    Get the result: 6912
    Data value: 6912
    

WasmEdge AOT Compiler

In this partition, we will introduce the WasmEdge AOT compiler and the options in Go. WasmEdge runs the WASM files in interpreter mode, and WasmEdge also supports the AOT (ahead-of-time) mode running without modifying any code. The WasmEdge AOT (ahead-of-time) compiler compiles the WASM files for running in AOT mode which is much faster than interpreter mode. Developers can compile the WASM files into the compiled-WASM files in shared library format for universal WASM format for the AOT mode execution.

Compilation Example

The go_WasmAOT example provide a tool for compiling a WASM file.

Compiler Options

Developers can set options for AOT compilers such as optimization level and output format:

const (
  // Disable as many optimizations as possible.
  CompilerOptLevel_O0 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O0)
  // Optimize quickly without destroying debuggability.
  CompilerOptLevel_O1 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O1)
  // Optimize for fast execution as much as possible without triggering significant incremental compile time or code size growth.
  CompilerOptLevel_O2 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O2)
  // Optimize for fast execution as much as possible.
  CompilerOptLevel_O3 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O3)
  // Optimize for small code size as much as possible without triggering significant incremental compile time or execution time slowdowns.
  CompilerOptLevel_Os = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Os)
  // Optimize for small code size as much as possible.
  CompilerOptLevel_Oz = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Oz)
)

const (
  // Native dynamic library format.
  CompilerOutputFormat_Native = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Native)
  // WebAssembly with AOT compiled codes in custom section.
  CompilerOutputFormat_Wasm = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Wasm)
)

Please refer to the AOT compiler options configuration for details.

WasmEdge Go v0.9.1 API Documentation

The followings are the guides to working with the WasmEdge-Go SDK at WasmEdge version 0.9.1 and WasmEdge-Go version v0.9.2.

Please install WasmEdge 0.9.1 to use this Go package.

WasmEdge-Go v0.9.1 is retracted. Please use WasmEdge-Go v0.9.2 instead.

Developers can refer here to upgrade to v0.10.0.

Table of Contents

Getting Started

The WasmEdge-go requires golang version >= 1.15. Please check your golang version before installation. Developers can download golang here.

$ go version
go version go1.16.5 linux/amd64

WasmEdge Installation

Developers must install the WasmEdge shared library with the same WasmEdge-go release or pre-release version.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.1

For the developers need the TensorFlow or Image extension for WasmEdge-go, please install the WasmEdge with extensions:

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e tf,image -v 0.9.1

Noticed that the TensorFlow and Image extensions are only for the Linux platforms. After installation, developers can use the source command to update the include and linking searching path.

Get WasmEdge-go

After the WasmEdge installation, developers can get the WasmEdge-go package and build it in your Go project directory.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
go build

WasmEdge-go Extensions

By default, the WasmEdge-go only turns on the basic runtime.

WasmEdge-go has the following extensions (on the Linux platforms only):

  • Tensorflow

    • This extension supports the host functions in WasmEdge-tensorflow.

    • The TensorFlow extension when installing WasmEdge is required. Please install WasmEdge with the -e tensorflow command.

    • For using this extension, the tag tensorflow when building is required:

      go build -tags tensorflow
      
  • Image

    • This extension supports the host functions in WasmEdge-image.

    • The Image extension when installing WasmEdge is required. Please install WasmEdge with the -e image command.

    • For using this extension, the tag image when building is required:

      go build -tags image
      

Users can also turn on the multiple extensions when building:

go build -tags image,tensorflow

Example of Embedding A Function with wasmedge-bindgen

In this example, we will demonstrate how to call a few simple WebAssembly functions with wasmedge-bindgen from a Golang app. The functions are written in Rust, and require complex call parameters and return values.

While the WebAssembly only supports a few simple data types out of the box. It does not support types such as string and array. In order to pass rich types in Golang to WebAssembly, the compiler needs to convert them to simple integers. For example, it converts a string into an integer memory address and an integer length. The #[wasmedge_bindgen] macro does this conversion automatically, combining it with Golang's wasmedge-bindgen package to auto-generate the correct code to pass call parameters from Golang to WebAssembly.


#![allow(unused)]
fn main() {
use wasmedge_bindgen::*;
use wasmedge_bindgen_macro::*;
use num_integer::lcm;
use sha3::{Digest, Sha3_256, Keccak256};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasmedge_bindgen]
pub fn create_line(p1: String, p2: String, desc: String) -> String {
  let point1: Point = serde_json::from_str(p1.as_str()).unwrap();
  let point2: Point = serde_json::from_str(p2.as_str()).unwrap();
  let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();

  let valid = if length == 0.0 { false } else { true };

  let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc };

  return serde_json::to_string(&line).unwrap();
}

#[wasmedge_bindgen]
pub fn say(s: String) -> String {
  let r = String::from("hello ");
  return r + s.as_str();
}

#[wasmedge_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasmedge_bindgen]
pub fn lowest_common_multiple(a: i32, b: i32) -> i32 {
  return lcm(a, b);
}

#[wasmedge_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasmedge_bindgen]
pub fn keccak_digest(s: Vec<u8>) -> Vec<u8> {
  return Keccak256::digest(&s).as_slice().to_vec();
}
}

First, compile the Rust source code into WebAssembly bytecode functions.

rustup target add wasm32-wasi
cd rust_bindgen_funcs
cargo build --target wasm32-wasi --release
# The output WASM will be target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm

The Golang source code to run the WebAssembly function in WasmEdge is as follows. The bg.Execute() function calls the WebAssembly function and passes the parameters with the wasmedge-bindgen supporting.

package main

import (
  "fmt"
  "os"

  "github.com/second-state/WasmEdge-go/wasmedge"
  bindgen "github.com/second-state/wasmedge-bindgen/host/go"
)

func main() {
  // Expected Args[0]: program name (./bindgen_funcs)
  // Expected Args[1]: wasm file (rust_bindgen_funcs_lib.wasm))
  
  // Set not to print debug info
  wasmedge.SetLogErrorLevel()

  // Create configure
  var conf = wasmedge.NewConfigure(wasmedge.WASI)

  // Create VM with configure
  var vm = wasmedge.NewVMWithConfig(conf)

  // Init WASI
  var wasi = vm.GetImportObject(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping preopens
  )

  // Load and validate the wasm
  vm.LoadWasmFile(os.Args[1])
  vm.Validate()

  // Instantiate the bindgen and vm
  bg := bindgen.Instantiate(vm)

  // create_line: string, string, string -> string (inputs are JSON stringified)  
  res, err := bg.Execute("create_line", "{\"x\":2.5,\"y\":7.8}", "{\"x\":2.5,\"y\":5.8}", "A thin red line")
  if err == nil {
    fmt.Println("Run bindgen -- create_line:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- create_line FAILED", err)
  }

  // say: string -> string
  res, err = bg.Execute("say", "bindgen funcs test")
  if err == nil {
    fmt.Println("Run bindgen -- say:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- say FAILED")
  }

  // obfusticate: string -> string
  res, err = bg.Execute("obfusticate", "A quick brown fox jumps over the lazy dog")
  if err == nil {
    fmt.Println("Run bindgen -- obfusticate:", res[0].(string))
  } else {
    fmt.Println("Run bindgen -- obfusticate FAILED")
  }

  // lowest_common_multiple: i32, i32 -> i32
  res, err = bg.Execute("lowest_common_multiple", int32(123), int32(2))
  if err == nil {
    fmt.Println("Run bindgen -- lowest_common_multiple:", res[0].(int32))
  } else {
    fmt.Println("Run bindgen -- lowest_common_multiple FAILED")
  }

  // sha3_digest: array -> array
  res, err = bg.Execute("sha3_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- sha3_digest:", res[0].([]byte))
  } else {
    fmt.Println("Run bindgen -- sha3_digest FAILED")
  }

  // keccak_digest: array -> array
  res, err = bg.Execute("keccak_digest", []byte("This is an important message"))
  if err == nil {
    fmt.Println("Run bindgen -- keccak_digest:", res[0].([]byte))
  } else {
    fmt.Println("Run bindgen -- keccak_digest FAILED")
  }

  bg.Release()
  vm.Release()
  conf.Release()
}

Next, build the Golang application with the WasmEdge Golang SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
go get github.com/second-state/wasmedge-bindgen@v0.1.12
go build

Run the Golang application and it will run the WebAssembly functions embedded in the WasmEdge runtime.

$ ./bindgen_funcs rust_bindgen_funcs/target/wasm32-wasi/release/rust_bindgen_funcs_lib.wasm
Run bindgen -- create_line: {"points":[{"x":2.5,"y":7.8},{"x":2.5,"y":5.8}],"valid":true,"length":2.0,"desc":"A thin red line"}
Run bindgen -- say: hello bindgen funcs test
Run bindgen -- obfusticate: N dhvpx oebja sbk whzcf bire gur ynml qbt
Run bindgen -- lowest_common_multiple: 246
Run bindgen -- sha3_digest: [87 27 231 209 189 105 251 49 159 10 211 250 15 159 154 181 43 218 26 141 56 199 25 45 60 10 20 163 54 211 195 203]
Run bindgen -- keccak_digest: [126 194 241 200 151 116 227 33 216 99 159 22 107 3 177 169 216 191 114 156 174 193 32 159 246 228 245 133 52 75 55 27]

Example of Embedding A Full WASI Program

Note: You can use the latest Rust compiler to create a standalone WasmEdge application with a main.rs functions and then embed it into a Golang application.

Besides functions, the WasmEdge Golang SDK can also embed standalone WebAssembly applications — i.e. a Rust application with a main() function compiled into WebAssembly.

Our demo Rust application reads from a file.

use std::env;
use std::fs::File;
use std::io::{self, BufRead};

fn main() {
  // Get the argv.
  let args: Vec<String> = env::args().collect();
  if args.len() <= 1 {
    println!("Rust: ERROR - No input file name.");
    return;
  }

  // Open the file.
  println!("Rust: Opening input file \"{}\"...", args[1]);
  let file = match File::open(&args[1]) {
    Err(why) => {
      println!("Rust: ERROR - Open file \"{}\" failed: {}", args[1], why);
      return;
    },
    Ok(file) => file,
  };

  // Read lines.
  let reader = io::BufReader::new(file);
  let mut texts:Vec<String> = Vec::new();
  for line in reader.lines() {
    if let Ok(text) = line {
      texts.push(text);
    }
  }
  println!("Rust: Read input file \"{}\" succeeded.", args[1]);

  // Get stdin to print lines.
  println!("Rust: Please input the line number to print the line of file.");
  let stdin = io::stdin();
  for line in stdin.lock().lines() {
    let input = line.unwrap();
    match input.parse::<usize>() {
      Ok(n) => if n > 0 && n <= texts.len() {
        println!("{}", texts[n - 1]);
      } else {
        println!("Rust: ERROR - Line \"{}\" is out of range.", n);
      },
      Err(e) => println!("Rust: ERROR - Input \"{}\" is not an integer: {}", input, e),
    }
  }
  println!("Rust: Process end.");
}

Use the rustwasmc tool to compile the application into WebAssembly.

cd rust_readfile
rustwasmc build
# The output file will be at `pkg/rust_readfile.wasm`.

Or you can compile the application into WebAssembly directly by cargo:

cd rust_readfile
# Need to add the `wasm32-wasi` target.
rustup target add wasm32-wasi
cargo build --release --target=wasm32-wasi
# The output wasm will be at `target/wasm32-wasi/release/rust_readfile.wasm`.

The Golang source code to run the WebAssembly function in WasmEdge is as follows.

package main

import (
  "os"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  wasmedge.SetLogErrorLevel()

  var conf = wasmedge.NewConfigure(wasmedge.REFERENCE_TYPES)
  conf.AddConfig(wasmedge.WASI)
  var vm = wasmedge.NewVMWithConfig(conf)
  var wasi = vm.GetImportObject(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping directories
  )

  // Instantiate and run WASM "_start" function, which refers to the main() function
  vm.RunWasmFile(os.Args[1], "_start")

  vm.Release()
  conf.Release()
}

Next, build the Golang application with the WasmEdge Golang SDK.

go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
go build

Run the Golang application.

$ ./read_file rust_readfile/pkg/rust_readfile.wasm file.txt
Rust: Opening input file "file.txt"...
Rust: Read input file "file.txt" succeeded.
Rust: Please input the line number to print the line of file.
# Input "5" and press Enter.
5
# The output will be the 5th line of `file.txt`:
abcDEF___!@#$%^
# To terminate the program, send the EOF (Ctrl + D).
^D
# The output will print the terminate message:
Rust: Process end.

For more examples, please refer to the example repository.

WasmEdge-go Basics

In this partition, we will introduce the utilities and concepts of WasmEdge-go APIs and data structures.

Version

The Version related APIs provide developers to check for the installed WasmEdge shared library version.

import "github.com/second-state/WasmEdge-go/wasmedge"

verstr := wasmedge.GetVersion() // Will be `string` of WasmEdge version.
vermajor := wasmedge.GetVersionMajor() // Will be `uint` of WasmEdge major version number.
verminor := wasmedge.GetVersionMinor() // Will be `uint` of WasmEdge minor version number.
verpatch := wasmedge.GetVersionPatch() // Will be `uint` of WasmEdge patch version number.

Logging Settings

The wasmedge.SetLogErrorLevel() and wasmedge.SetLogDebugLevel() APIs can set the logging system to debug level or error level. By default, the error level is set, and the debug info is hidden.

Value Types

In WasmEdge-go, the APIs will automatically do the conversion for the built-in types, and implement the data structure for the reference types.

  1. Number types: i32, i64, f32, and f64

    • Convert the uint32 and int32 to i32 automatically when passing a value into WASM.
    • Convert the uint64 and int64 to i64 automatically when passing a value into WASM.
    • Convert the uint and int to i32 automatically when passing a value into WASM in 32-bit system.
    • Convert the uint and int to i64 automatically when passing a value into WASM in 64-bit system.
    • Convert the float32 to f32 automatically when passing a value into WASM.
    • Convert the float64 to f64 automatically when passing a value into WASM.
    • Convert the i32 from WASM to int32 when getting a result.
    • Convert the i64 from WASM to int64 when getting a result.
    • Convert the f32 from WASM to float32 when getting a result.
    • Convert the f64 from WASM to float64 when getting a result.
  2. Number type: v128 for the SIMD proposal

    Developers should use the wasmedge.NewV128() to generate a v128 value, and use the wasmedge.GetV128() to get the value.

    val := wasmedge.NewV128(uint64(1234), uint64(5678))
    high, low := val.GetVal()
    // `high` will be uint64(1234), `low` will be uint64(5678)
    
  3. Reference types: FuncRef and ExternRef for the Reference-Types proposal

    funcref := wasmedge.NewFuncRef(10)
    // Create a `FuncRef` with function index 10.
    
    num := 1234
    // `num` is a `int`.
    externref := wasmedge.NewExternRef(&num)
    // Create an `ExternRef` which reference to the `num`.
    num = 5678
    // Modify the `num` to 5678.
    numref := externref.GetRef().(*int)
    // Get the original reference from the `ExternRef`.
    fmt.Println(*numref)
    // Will print `5678`.
    numref.Release()
    // Should call the `Release` method.
    

Results

The Result object specifies the execution status. Developers can use the Error() function to get the error message.

// Assume that `vm` is a `wasmedge.VM` object.
res, err = vm.Execute(...) // Ignore the detail of parameters.
// Assume that `res, err` are the return values for executing a function with `vm`.
if err != nil {
  fmt.Println("Error message:", err.Error())
}

Contexts And Their Life Cycles

The objects, such as VM, Store, and Function, etc., are composed of Contexts in the WasmEdge shared library. All of the contexts can be created by calling the corresponding New APIs, developers should also call the corresponding Release functions of the contexts to release the resources. Noticed that it's not necessary to call the Release functions for the contexts which are retrieved from other contexts but not created from the New APIs.

// Create a Configure.
conf := wasmedge.NewConfigure()
// Release the `conf` immediately.
conf.Release()

The details of other contexts will be introduced later.

WASM Data Structures

The WASM data structures are used for creating instances or can be queried from instance contexts. The details of instances creation will be introduced in the Instances.

  1. Limit

    The Limit struct presents the minimum and maximum value data structure.

    lim1 := wasmedge.NewLimit(12)
    fmt.Println(lim1.HasMax())
    // Will print `false`.
    fmt.Println(lim1.GetMin())
    // Will print `12`.
    
    lim2 := wasmedge.NewLimitWithMax(15, 50)
    fmt.Println(lim2.HasMax())
    // Will print `true`.
    fmt.Println(lim2.GetMin())
    // Will print `15`.
    fmt.Println(lim2.GetMax())
    // Will print `50`.
    
  2. Function type context

    The FunctionType is an object holds the function type context and used for the Function creation, checking the value types of a Function instance, or getting the function type with function name from VM. Developers can use the FunctionType APIs to get the parameter or return value types information.

    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{
        wasmedge.ValType_ExternRef,
        wasmedge.ValType_I32,
        wasmedge.ValType_I64,
      }, []wasmedge.ValType{
        wasmedge.ValType_F32,
        wasmedge.ValType_F64,
      })
    
    plen := functype.GetParametersLength()
    // `plen` will be 3.
    rlen := functype.GetReturnsLength()
    // `rlen` will be 2.
    plist := functype.GetParameters()
    // `plist` will be `[]wasmedge.ValType{wasmedge.ValType_ExternRef, wasmedge.ValType_I32, wasmedge.ValType_I64}`.
    rlist := functype.GetReturns()
    // `rlist` will be `[]wasmedge.ValType{wasmedge.ValType_F32, wasmedge.ValType_F64}`.
    
    functype.Release()
    
  3. Table type context

    The TableType is an object holds the table type context and used for Table instance creation or getting information from Table instances.

    lim := wasmedge.NewLimit(12)
    tabtype := wasmedge.NewTableType(wasmedge.RefType_ExternRef, lim)
    
    rtype := tabtype.GetRefType()
    // `rtype` will be `wasmedge.RefType_ExternRef`.
    getlim := tabtype.GetLimit()
    // `getlim` will be the same value as `lim`.
    
    tabtype.Release()
    
  4. Memory type context

    The MemoryType is an object holds the memory type context and used for Memory instance creation or getting information from Memory instances.

    lim := wasmedge.NewLimit(1)
    memtype := wasmedge.NewMemoryType(lim)
    
    getlim := memtype.GetLimit()
    // `getlim` will be the same value as `lim`.
    
    memtype.Release()
    
  5. Global type context

    The GlobalType is an object holds the global type context and used for Global instance creation or getting information from Global instances.

    globtype := wasmedge.NewGlobalType(wasmedge.ValType_F64, wasmedge.ValMut_Var)
    
    vtype := globtype.GetValType()
    // `vtype` will be `wasmedge.ValType_F64`.
    vmut := globtype.GetMutability()
    // `vmut` will be `wasmedge.ValMut_Var`.
    
    globtype.Release()
    
  6. Import type context

    The ImportType is an object holds the import type context and used for getting the imports information from a AST Module. Developers can get the external type (function, table, memory, or global), import module name, and external name from an ImportType object. The details about querying ImportType objects will be introduced in the AST Module.

    var ast *wasmedge.AST = ...
    // Assume that `ast` is returned by the `Loader` for the result of loading a WASM file.
    imptypelist := ast.ListImports()
    // Assume that `imptypelist` is an array listed from the `ast` for the imports.
    
    for i, imptype := range imptypelist {
      exttype := imptype.GetExternalType()
      // The `exttype` must be one of `wasmedge.ExternType_Function`, `wasmedge.ExternType_Table`,
      // wasmedge.ExternType_Memory`, or `wasmedge.ExternType_Global`.
    
      modname := imptype.GetModuleName()
      extname := imptype.GetExternalName()
      // Get the module name and external name of the imports.
    
      extval := imptype.GetExternalValue()
      // The `extval` is the type of `interface{}` which indicates one of `*wasmedge.FunctionType`,
      // `*wasmedge.TableType`, `*wasmedge.MemoryType`, or `*wasmedge.GlobalType`.
    }
    
  7. Export type context

    The ExportType is an object holds the export type context is used for getting the exports information from a AST Module. Developers can get the external type (function, table, memory, or global) and external name from an Export Type context. The details about querying ExportType objects will be introduced in the AST Module.

    var ast *wasmedge.AST = ...
    // Assume that `ast` is returned by the `Loader` for the result of loading a WASM file.
    exptypelist := ast.ListExports()
    // Assume that `exptypelist` is an array listed from the `ast` for the exports.
    
    for i, exptype := range exptypelist {
      exttype := exptype.GetExternalType()
      // The `exttype` must be one of `wasmedge.ExternType_Function`, `wasmedge.ExternType_Table`,
      // wasmedge.ExternType_Memory`, or `wasmedge.ExternType_Global`.
    
      extname := exptype.GetExternalName()
      // Get the external name of the exports.
    
      extval := exptype.GetExternalValue()
      // The `extval` is the type of `interface{}` which indicates one of `*wasmedge.FunctionType`,
      // `*wasmedge.TableType`, `*wasmedge.MemoryType`, or `*wasmedge.GlobalType`.
    }
    

Configurations

The configuration object, wasmedge.Configure, manages the configurations for Loader, Validator, Executor, VM, and Compiler. Developers can adjust the settings about the proposals, VM host pre-registrations (such as WASI), and AOT compiler options, and then apply the Configure object to create other runtime objects.

  1. Proposals

    WasmEdge supports turning on or off the WebAssembly proposals. This configuration is effective in any contexts created with the Configure object.

    const (
      IMPORT_EXPORT_MUT_GLOBALS         = Proposal(C.WasmEdge_Proposal_ImportExportMutGlobals)
      NON_TRAP_FLOAT_TO_INT_CONVERSIONS = Proposal(C.WasmEdge_Proposal_NonTrapFloatToIntConversions)
      SIGN_EXTENSION_OPERATORS          = Proposal(C.WasmEdge_Proposal_SignExtensionOperators)
      MULTI_VALUE                       = Proposal(C.WasmEdge_Proposal_MultiValue)
      BULK_MEMORY_OPERATIONS            = Proposal(C.WasmEdge_Proposal_BulkMemoryOperations)
      REFERENCE_TYPES                   = Proposal(C.WasmEdge_Proposal_ReferenceTypes)
      SIMD                              = Proposal(C.WasmEdge_Proposal_SIMD)
      TAIL_CALL                         = Proposal(C.WasmEdge_Proposal_TailCall)
      ANNOTATIONS                       = Proposal(C.WasmEdge_Proposal_Annotations)
      MEMORY64                          = Proposal(C.WasmEdge_Proposal_Memory64)
      THREADS                           = Proposal(C.WasmEdge_Proposal_Threads)
      EXCEPTION_HANDLING                = Proposal(C.WasmEdge_Proposal_ExceptionHandling)
      FUNCTION_REFERENCES               = Proposal(C.WasmEdge_Proposal_FunctionReferences)
    )
    

    Developers can add or remove the proposals into the Configure object.

    // By default, the following proposals have turned on initially:
    // * IMPORT_EXPORT_MUT_GLOBALS
    // * NON_TRAP_FLOAT_TO_INT_CONVERSIONS
    // * SIGN_EXTENSION_OPERATORS
    // * MULTI_VALUE
    // * BULK_MEMORY_OPERATIONS
    // * REFERENCE_TYPES
    // * SIMD
    conf := wasmedge.NewConfigure()
    // Developers can also pass the proposals as parameters:
    // conf := wasmedge.NewConfigure(wasmedge.SIMD, wasmedge.BULK_MEMORY_OPERATIONS)
    conf.AddConfig(wasmedge.SIMD)
    conf.RemoveConfig(wasmedge.REFERENCE_TYPES)
    is_bulkmem := conf.HasConfig(wasmedge.BULK_MEMORY_OPERATIONS)
    // The `is_bulkmem` will be `true`.
    conf.Release()
    
  2. Host registrations

    This configuration is used for the VM context to turn on the WASI or wasmedge_process supports and only effective in VM objects.

    const (
      WASI             = HostRegistration(C.WasmEdge_HostRegistration_Wasi)
      WasmEdge_PROCESS = HostRegistration(C.WasmEdge_HostRegistration_WasmEdge_Process)
    )
    

    The details will be introduced in the preregistrations of VM context.

    conf := wasmedge.NewConfigure()
    // Developers can also pass the proposals as parameters:
    // conf := wasmedge.NewConfigure(wasmedge.WASI)
    conf.AddConfig(wasmedge.WASI)
    conf.Release()
    
  3. Maximum memory pages

    Developers can limit the page size of memory instances by this configuration. When growing the page size of memory instances in WASM execution and exceeding the limited size, the page growing will fail. This configuration is only effective in the Executor and VM objects.

    conf := wasmedge.NewConfigure()
    
    pagesize := conf.GetMaxMemoryPage()
    // By default, the maximum memory page size in each memory instances is 65536.
    conf.SetMaxMemoryPage(1234)
    pagesize := conf.GetMaxMemoryPage()
    // `pagesize` will be 1234.
    
    conf.Release()
    
  4. AOT compiler options

    The AOT compiler options configure the behavior about optimization level, output format, dump IR, and generic binary.

    const (
      // Disable as many optimizations as possible.
      CompilerOptLevel_O0 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O0)
      // Optimize quickly without destroying debuggability.
      CompilerOptLevel_O1 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O1)
      // Optimize for fast execution as much as possible without triggering significant incremental compile time or code size growth.
      CompilerOptLevel_O2 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O2)
      // Optimize for fast execution as much as possible.
      CompilerOptLevel_O3 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O3)
      // Optimize for small code size as much as possible without triggering significant incremental compile time or execution time slowdowns.
      CompilerOptLevel_Os = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Os)
      // Optimize for small code size as much as possible.
      CompilerOptLevel_Oz = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Oz)
    )
    
    const (
      // Native dynamic library format.
      CompilerOutputFormat_Native = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Native)
      // WebAssembly with AOT compiled codes in custom section.
      CompilerOutputFormat_Wasm = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Wasm)
    )
    

    These configurations are only effective in Compiler contexts.

    conf := wasmedge.NewConfigure()
    
    // By default, the optimization level is O3.
    conf.SetCompilerOptimizationLevel(wasmedge.CompilerOptLevel_O2)
    // By default, the output format is universal WASM.
    conf.SetCompilerOutputFormat(wasmedge.CompilerOutputFormat_Native)
    // By default, the dump IR is `false`.
    conf.SetCompilerDumpIR(true)
    // By default, the generic binary is `false`.
    conf.SetCompilerGenericBinary(true)
    
    conf.Release()
    
  5. Statistics options

    The statistics options configure the behavior about instruction counting, cost measuring, and time measuring in both runtime and AOT compiler. These configurations are effective in Compiler, VM, and Executor objects.

    conf := wasmedge.NewConfigure()
    
    // By default, the intruction counting is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsInstructionCounting(true)
    // By default, the cost measurement is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsTimeMeasuring(true)
    // By default, the time measurement is `false` when running a compiled-WASM or a pure-WASM.
    conf.SetStatisticsCostMeasuring(true)
    
    conf.Release()
    

WasmEdge VM

In this partition, we will introduce the functions of wasmedge.VM object and show examples of executing WASM functions.

WASM Execution Example With VM Object

The following shows the example of running the WASM for getting the Fibonacci. This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

(module
 (export "fib" (func $fib))
 (func $fib (param $n i32) (result i32)
  (if
   (i32.lt_s (get_local $n)(i32.const 2))
   (return (i32.const 1))
  )
  (return
   (i32.add
    (call $fib (i32.sub (get_local $n)(i32.const 2)))
    (call $fib (i32.sub (get_local $n)(i32.const 1)))
   )
  )
 )
)
  1. Run WASM functions rapidly

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that the WASM file fibonacci.wasm is copied into the current wasmedge_test directory, and create and edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Set the logging level.
      wasmedge.SetLogErrorLevel()
    
      // Create the configure context and add the WASI support.
      // This step is not necessary unless you need WASI support.
      conf := wasmedge.NewConfigure(wasmedge.WASI)
      // Create VM with the configure.
      vm := wasmedge.NewVMWithConfig(conf)
    
      res, err := vm.RunWasmFile("fibonacci.wasm", "fib", uint32(21))
      if err == nil {
        fmt.Println("Get fibonacci[21]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
      conf.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK: (the 21 Fibonacci number is 17711 in 0-based index)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
    $ go build
    $ ./wasmedge_test
    Get fibonacci[21]: 17711
    
  2. Instantiate and run WASM functions manually

    Besides the above example, developers can run the WASM functions step-by-step with VM object APIs:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Set the logging level.
      wasmedge.SetLogErrorLevel()
    
      // Create VM.
      vm := wasmedge.NewVM()
      var err error
      var res []interface{}
    
      // Step 1: Load WASM file.
      err = vm.LoadWasmFile("fibonacci.wasm")
      if err != nil {
        fmt.Println("Load WASM from file FAILED:", err.Error())
        return
      }
    
      // Step 2: Validate the WASM module.
      err = vm.Validate()
      if err != nil {
        fmt.Println("Validation FAILED:", err.Error())
        return
      }
    
      // Step 3: Instantiate the WASM module.
      err = vm.Instantiate()
      // Developers can load, validate, and instantiate another WASM module
      // to replace the instantiated one. In this case, the old module will
      // be cleared, but the registered modules are still kept.
      if err != nil {
        fmt.Println("Instantiation FAILED:", err.Error())
        return
      }
    
      // Step 4: Execute WASM functions. Parameters: (funcname, args...)
      res, err = vm.Execute("fib", uint32(25))
      // Developers can execute functions repeatedly after instantiation.
      if err == nil {
        fmt.Println("Get fibonacci[25]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ go build
    $ ./wasmedge_test
    Get fibonacci[25]: 121393
    

    The following graph explains the status of the VM object.

                           |========================|
                  |------->|      VM: Initiated     |
                  |        |========================|
                  |                    |
                  |                 LoadWasm
                  |                    |
                  |                    v
                  |        |========================|
                  |--------|       VM: Loaded       |<-------|
                  |        |========================|        |
                  |              |            ^              |
                  |         Validate          |              |
              Cleanup            |          LoadWasm         |
                  |              v            |            LoadWasm
                  |        |========================|        |
                  |--------|      VM: Validated     |        |
                  |        |========================|        |
                  |              |            ^              |
                  |      Instantiate          |              |
                  |              |          RegisterModule   |
                  |              v            |              |
                  |        |========================|        |
                  |--------|    VM: Instantiated    |--------|
                           |========================|
                                 |            ^
                                 |            |
                                 --------------
                    Instantiate, Execute, ExecuteRegistered,
                    ExecuteBindgen, ExecuteBindgenRegistered
    

    The status of the VM context would be Inited when created. After loading WASM successfully, the status will be Loaded. After validating WASM successfully, the status will be Validated. After instantiating WASM successfully, the status will be Instantiated, and developers can invoke functions. Developers can register WASM or import objects in any status, but they should instantiate WASM again. Developers can also load WASM in any status, and they should validate and instantiate the WASM module before function invocation. When in the Instantiated status, developers can instantiate the WASM module again to reset the old WASM runtime structures.

VM Creations

The VM creation APIs accepts the Configure object and the Store object. Noticed that if the VM created with the outside Store object, the VM will execute WASM on that Store object. If the Store object is set into multiple VM objects, it may causes data conflict when in execution. The details of the Store object will be introduced in Store.

conf := wasmedge.NewConfigure()
store := wasmedge.NewStore()

// Create a VM with default configure and store.
vm := wasmedge.NewVM()
vm.Release()

// Create a VM with the specified configure and default store.
vm = wasmedge.NewVMWithConfig(conf)
vm.Release()

// Create a VM with the default configure and specified store.
vm = wasmedge.NewVMWithStore(store)
vm.Release()

// Create a VM with the specified configure and store.
vm = wasmedge.NewVMWithConfigAndStore(conf, store)
vm.Release()

conf.Release()
store.Release()

Preregistrations

WasmEdge provides the following built-in pre-registrations.

  1. WASI (WebAssembly System Interface)

    Developers can turn on the WASI support for VM in the Configure object.

    conf := wasmedge.NewConfigure(wasmedge.WASI)
    // Or you can set the `wasmedge.WASI` into the configure object through `(*Configure).AddConfig`.
    vm := wasmedge.NewVMWithConfig(conf)
    vm.Release()
    
    // The following API can retrieve the pre-registration import objects from the VM object.
    // This API will return `nil` if the corresponding pre-registration is not set into the configuration.
    wasiconf := conf.GetImportObject(wasmedge.WASI)
    // Initialize the WASI.
    wasiconf.InitWasi(/* ... ignored */)
    
    conf.Release()
    

    And also can create the WASI import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

  2. WasmEdge_Process

    This pre-registration is for the process interface for WasmEdge on Rust sources. After turning on this pre-registration, the VM will support the wasmedge_process host functions.

    conf := wasmedge.NewConfigure(wasmedge.WasmEdge_PROCESS)
    vm := wasmedge.NewVMWithConfig(conf)
    vm.Release()
    
    // The following API can retrieve the pre-registration import objects from the VM object.
    // This API will return `nil` if the corresponding pre-registration is not set into the configuration.
    procconf := conf.GetImportObject(wasmedge.WasmEdge_PROCESS)
    // Initialize the WasmEdge_Process.
    procconf.InitWasmEdgeProcess(/* ... ignored */)
    
    conf.Release()
    

    And also can create the WasmEdge_Process import object from API. The details will be introduced in the Host Functions and the Host Module Registrations.

Host Module Registrations

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge-go, the host functions are composed into host modules as ImportObject objects with module names. Please refer to the Host Functions in WasmEdge Runtime for the details. In this chapter, we show the example for registering the host modules into a VM object.

vm := wasmedge.NewVM()
// You can also create and register the WASI host modules by this API.
wasiobj := wasmedge.NewWasiImportObject(/* ... ignored ... */)

res := vm.RegisterImport(wasiobj)
// The result status should be checked.

vm.Release()
// The created import objects should be released.
wasiobj.Release()

WASM Registrations And Executions

In WebAssembly, the instances in WASM modules can be exported and can be imported by other WASM modules. WasmEdge VM provides APIs for developers to register and export any WASM modules, and execute the functions or host functions in the registered WASM modules.

  1. Register the WASM modules with exported module names

    Unless the import objects have already contained the module names, every WASM module should be named uniquely when registering. The following shows the example.

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that the WASM file fibonacci.wasm is copied into the current directory. Then create and edit the Go file main.go as following:

    package main
    
    import "github.com/second-state/WasmEdge-go/wasmedge"
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      var err error
      err = vm.RegisterWasmFile("module_name", "fibonacci.wasm")
      // Developers can register the WASM module from `[]byte` with the
      // `(*VM).RegisterWasmBuffer` function, or from `AST` object with
      // the `(*VM).RegisterAST` function.
      // The result status should be checked. The error will occur if the
      // WASM module instantiation failed or the module name conflicts.
    
      vm.Release()
    }
    
  2. Execute the functions in registered WASM modules

    Edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      var res []interface{}
      var err error
      // Register the WASM module from file into VM with the module name "mod".
      err = vm.RegisterWasmFile("mod", "fibonacci.wasm")
      // Developers can register the WASM module from `[]byte` with the
      // `(*VM).RegisterWasmBuffer` function, or from `AST` object with
      // the `(*VM).RegisterAST` function.
      if err != nil {
        fmt.Println("WASM registration failed:", err.Error())
        return
      }
      // The function "fib" in the "fibonacci.wasm" was exported with the module
      // name "mod". As the same as host functions, other modules can import the
      // function `"mod" "fib"`.
    
      // Execute WASM functions in registered modules.
      // Unlike the execution of functions, the registered functions can be
      // invoked without `(*VM).Instantiate` because the WASM module was
      // instantiated when registering.
      // Developers can also invoke the host functions directly with this API.
      res, err = vm.ExecuteRegistered("mod", "fib", int32(25))
      if err == nil {
        fmt.Println("Get fibonacci[25]:", res[0].(int32))
      } else {
        fmt.Println("Run failed:", err.Error())
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the 25th Fibonacci number is 121393 in 0-based index)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
    $ go build
    $ ./wasmedge_test
    Get fibonacci[25]: 121393
    

Instance Tracing

Sometimes the developers may have requirements to get the instances of the WASM runtime. The VM object supplies the APIs to retrieve the instances.

  1. Store

    If the VM object is created without assigning a Store object, the VM context will allocate and own a Store.

    vm := wasmedge.NewVM()
    store := vm.GetStore()
    // The object should __NOT__ be deleted by calling `(*Store).Release`.
    vm.Release()
    

    Developers can also create the VM object with a Store object. In this case, developers should guarantee that the Store object cannot be released before the VM object. Please refer to the Store Objects for the details about the Store APIs.

    store := wasmedge.NewStore()
    vm := wasmedge.NewVMWithStore(store)
    
    storemock := vm.GetStore()
    // The internal store context of the `store` and the `storemock` are the same.
    
    vm.Release()
    store.Release()
    
  2. List exported functions

    After the WASM module instantiation, developers can use the (*VM).Execute function to invoke the exported WASM functions. For this purpose, developers may need information about the exported WASM function list. Please refer to the Instances in runtime for the details about the function types.

    Assume that a new Go project is created as following:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Then assume that the WASM file fibonacci.wasm is copied into the current directory, and create and edit a Go file main.go:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    func main() {
      // Create VM.
      vm := wasmedge.NewVM()
    
      // Step 1: Load WASM file.
      err := vm.LoadWasmFile("fibonacci.wasm")
      if err != nil {
        fmt.Println("Load WASM from file FAILED:", err.Error())
        return
      }
    
      // Step 2: Validate the WASM module.
      err = vm.Validate()
      if err != nil {
        fmt.Println("Validation FAILED:", err.Error())
        return
      }
    
      // Step 3: Instantiate the WASM module.
      err = vm.Instantiate()
      if err != nil {
        fmt.Println("Instantiation FAILED:", err.Error())
        return
      }
    
      // List the exported functions for the names and function types.
      funcnames, functypes := vm.GetFunctionList()
      for _, fname := range funcnames {
        fmt.Println("Exported function name:", fname)
      }
      for _, ftype := range functypes {
        // `ftype` is the `FunctionType` object of the same index in the `funcnames` array.
        // Developers should __NOT__ call the `ftype.Release()`.
      }
    
      vm.Release()
    }
    

    Then you can build and run: (the only exported function in fibonacci.wasm is fib)

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
    $ go build
    $ ./wasmedge_test
    Exported function name: fib
    

    If developers want to get the exported function names in the registered WASM modules, please retrieve the Store object from the VM object and refer to the APIs of Store Contexts to list the registered functions by the module name.

  3. Get function types

    The VM object provides APIs to find the function type by function name. Please refer to the Instances in runtime for the details about the function types.

    // Assume that a WASM module is instantiated in `vm` which is a `wasmedge.VM` object.
    functype := vm.GetFunctionType("fib")
    // Developers can get the function types of functions in the registered modules via the 
    // `(*VM).GetFunctionTypeRegistered` API with the functon name and the module name.
    // If the function is not found, these APIs will return `nil`.
    // Developers should __NOT__ call the `(*FunctionType).Release` function of the returned object.
    

WasmEdge Runtime

In this partition, we will introduce the objects of WasmEdge runtime manually.

WASM Execution Example Step-By-Step

Besides the WASM execution through the VM object rapidly, developers can execute the WASM functions or instantiate WASM modules step-by-step with the Loader, Validator, Executor, and Store objects.

Assume that a new Go project is created as following:

mkdir wasmedge_test && cd wasmedge_test
go mod init wasmedge_test

Then assume that the WASM file fibonacci.wasm is copied into the current directory, and create and edit a Go file main.go:

package main

import (
  "fmt"

  "github.com/second-state/WasmEdge-go/wasmedge"
)

func main() {
  // Set the logging level to debug to print the statistics info.
  wasmedge.SetLogDebugLevel()
  // Create the configure object. This is not necessary if developers use the default configuration.
  conf := wasmedge.NewConfigure()
  // Turn on the runtime instruction counting and time measuring.
  conf.SetStatisticsInstructionCounting(true)
  conf.SetStatisticsTimeMeasuring(true)
  // Create the statistics object. This is not necessary if the statistics in runtime is not needed.
  stat := wasmedge.NewStatistics()
  // Create the store object. The store object is the WASM runtime structure core.
  store := wasmedge.NewStore()

  var err error
  var res []interface{}
  var ast *wasmedge.AST

  // Create the loader object.
  // For loader creation with default configuration, you can use `wasmedge.NewLoader()` instead.
  loader := wasmedge.NewLoaderWithConfig(conf)
  // Create the validator object.
  // For validator creation with default configuration, you can use `wasmedge.NewValidator()` instead.
  validator := wasmedge.NewValidatorWithConfig(conf)
  // Create the executor object.
  // For executor creation with default configuration and without statistics, you can use `wasmedge.NewExecutor()` instead.
  executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)

  // Load the WASM file or the compiled-WASM file and convert into the AST module object.
  ast, err = loader.LoadFile("fibonacci.wasm")
  if err != nil {
    fmt.Println("Load WASM from file FAILED:", err.Error())
    return
  }
  // Validate the WASM module.
  err = validator.Validate(ast)
  if err != nil {
    fmt.Println("Validation FAILED:", err.Error())
    return
  }
  // Instantiate the WASM module into the Store object.
  err = executor.Instantiate(store, ast)
  if err != nil {
    fmt.Println("Instantiation FAILED:", err.Error())
    return
  }

  // Try to list the exported functions of the instantiated WASM module.
  funcnames := store.ListFunction()
  for _, fname := range funcnames {
    fmt.Println("Exported function name:", fname)
  }

  // Invoke the WASM function.
  res, err = executor.Invoke(store, "fib", int32(30))
  if err == nil {
    fmt.Println("Get fibonacci[30]:", res[0].(int32))
  } else {
    fmt.Println("Run failed:", err.Error())
  }

  // Resources deallocations.
  conf.Release()
  stat.Release()
  ast.Release()
  loader.Release()
  validator.Release()
  executor.Release()
  store.Release()
}

Then you can build and run: (the 18th Fibonacci number is 1346269 in 30-based index)

$ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
$ go build
$ ./wasmedge_test
Exported function name: fib
[2021-11-24 18:53:01.451] [debug]  Execution succeeded.
[2021-11-24 18:53:01.452] [debug]
 ====================  Statistics  ====================
 Total execution time: 556372295 ns
 Wasm instructions execution time: 556372295 ns
 Host functions execution time: 0 ns
 Executed wasm instructions count: 28271634
 Gas costs: 0
 Instructions per second: 50814237
Get fibonacci[30]: 1346269

Loader

The Loader object loads the WASM binary from files or buffers. Both the WASM and the compiled-WASM from the WasmEdge AOT Compiler are supported.

var buf []byte
// ... Read the WASM code to the `buf`.

// Developers can adjust settings in the configure object.
conf := wasmedge.NewConfigure()
// Create the loader object.
// For loader creation with default configuration, you can use `wasmedge.NewLoader()` instead.
loader := wasmedge.NewLoaderWithConfig(conf)
conf.Release()

// Load WASM or compiled-WASM from the file.
ast, err := loader.LoadFile("fibonacci.wasm")
if err != nil {
  fmt.Println("Load WASM from file FAILED:", err.Error())
} else {
  // The output AST object should be released.
  ast.Release()
}

// Load WASM or compiled-WASM from the buffer
ast, err = loader.LoadBuffer(buf)
if err != nil {
  fmt.Println("Load WASM from buffer FAILED:", err.Error())
} else {
  // The output AST object should be released.
  ast.Release()   
}

loader.Release()

Validator

The Validator object can validate the WASM module. Every WASM module should be validated before instantiation.

// ...
// Assume that the `ast` is the output `*wasmedge.AST` object from the loader context.
// Assume that the `conf` is the `*wasmedge.Configure` object.

// Create the validator context.
// For validator creation with default configuration, you can use `wasmedge.NewValidator()` instead.
validator := wasmedge.NewValidatorWithConfig(conf)

err := validator.Validate(ast)
if err != nil {
  fmt.Println("Validation FAILED:", err.Error())
}

validator.Release()

Executor

The Executor object is the executor for both WASM and compiled-WASM. This object should work base on the Store object. For the details of the Store object, please refer to the next chapter.

  1. Register modules

    As the same of registering host modules or importing WASM modules in VM objects, developers can register ImportObject or AST objects into the Store object by the Executor APIs. For the details of import objects, please refer to the Host Functions.

    // ...
    // Assume that the `ast` is the output `*wasmedge.AST` object from the loader
    // and has passed the validation.
    // Assume that the `conf` is the `*wasmedge.Configure` object.
    
    // Create the statistics object. This step is not necessary if the statistics
    // is not needed.
    stat := wasmedge.NewStatistics()
    // Create the executor object.
    // For executor creation with default configuration and without statistics,
    // you can use `wasmedge.NewExecutor()` instead.
    executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)
    // Create the store object. The store is the WASM runtime structure core.
    store := wasmedge.NewStore()
    
    // Register the loaded WASM `ast` into store with the export module name "mod".
    res := executor.RegisterModule(store, ast, "mod")
    if err != nil {
      fmt.Println("WASM registration FAILED:", err.Error())
      return
    }
    
    // Assume that the `impobj` is the `*wasmedge.ImportObject` for host functions.
    impobj := ...
    err = executor.RegisterImport(store, impobj)
    if err != nil {
      fmt.Println("Import object registration FAILED:", err.Error())
      return
    }
    
    executor.Release()
    stat.Release()
    store.Release()
    impobj.Release()
    
  2. Instantiate modules

    WASM or compiled-WASM modules should be instantiated before the function invocation. Note that developers can only instantiate one module into the Store object, and in that case, the old instantiated module will be cleaned. Before instantiating a WASM module, please check the import section for ensuring the imports are registered into the Store object.

    // ...
    // Assume that the `ast` is the output `*wasmedge.AST` object from the loader
    // and has passed the validation.
    // Assume that the `conf` is the `*wasmedge.Configure` object.
    
    // Create the statistics object. This step is not necessary if the statistics
    // is not needed.
    stat := wasmedge.NewStatistics()
    // Create the executor object.
    // For executor creation with default configuration and without statistics,
    // you can use `wasmedge.NewExecutor()` instead.
    executor := wasmedge.NewExecutorWithConfigAndStatistics(conf, stat)
    // Create the store object. The store is the WASM runtime structure core.
    store := wasmedge.NewStore()
    
    // Instantiate the WASM module.
    err := executor.Instantiate(stpre, ast)
    if err != nil {
      fmt.Println("WASM instantiation FAILED:", err.Error())
      return
    }
    
    executor.Release()
    stat.Release()
    store.Release()
    
  3. Invoke functions

    As the same as function invocation via the VM object, developers can invoke the functions of the instantiated or registered modules. The APIs, (*Executor).Invoke and (*Executor).InvokeRegistered, are similar as the APIs of the VM object. Please refer to the VM context workflows for details.

AST Module

The AST object presents the loaded structure from a WASM file or buffer. Developer will get this object after loading a WASM file or buffer from Loader. Before instantiation, developers can also query the imports and exports of an AST object.

ast := ...
// Assume that a WASM is loaded into an `*wasmedge.AST` object from loader.

// List the imports.
imports := ast.ListImports()
for _, import := range imports {
  fmt.Println("Import:", import.GetModuleName(), import.GetExternalName())
}

// List the exports.
exports := ast.ListExports()
for _, export := range exports {
  fmt.Println("Export:", export.GetExternalName())
}

ast.Release()

Store

Store is the runtime structure for the representation of all instances of Functions, Tables, Memorys, and Globals that have been allocated during the lifetime of the abstract machine. The Store object in WasmEdge-go provides APIs to list the exported instances with their names or find the instances by exported names. For adding instances into Store objects, please instantiate or register WASM modules or ImportObject objects via the Executor APIs.

  1. List instances

    store := wasmedge.NewStore()
    // ...
    // Instantiate a WASM module via the `*wasmedge.Executor` object.
    // ...
    
    // Try to list the exported functions of the instantiated WASM module.
    // Take the function instances for example here.
    funcnames := store.ListFunction()
    for _, name := range funcnames {
      fmt.Println("Exported function name:", name)
    }
    
    store.Release()
    

    Developers can list the function instance exported names of the registered modules via the (*Store).ListFunctionRegistered() API with the module name.

  2. Find instances

    store := wasmedge.NewStore()
    // ...
    // Instantiate a WASM module via the `*wasmedge.Executor` object.
    // ...
    
    // Try to find the exported functions of the instantiated WASM module.
    // Take the function instances for example here.
    funcobj := store.FindFunction("fib")
    // `funcobj` will be `nil` if the function not found.
    
    store.Release()
    

    Developers can retrieve the exported function instances of the registered modules via the (*Store).FindFunctionRegistered API with the module name.

  3. List registered modules

    With the module names, developers can list the exported instances of the registered modules with their names.

    store := wasmedge.NewStore()
    // ...
    // Instantiate a WASM module via the `*wasmedge.Executor` object.
    // ...
    
    // Try to list the registered WASM modules.
    modnames := store.ListModule()
    for _, name := range modnames {
      fmt.Println("Registered module names:", name)
    }
    
    store.Release()
    

Instances

The instances are the runtime structures of WASM. Developers can retrieve the instances from the Store objects. The Store objects will allocate instances when a WASM module or an ImportObject is registered or instantiated through the Executor. A single instance can be allocated by its creation function. Developers can construct instances into an ImportObject for registration. Please refer to the Host Functions for details. The instances created by their creation functions should be destroyed, EXCEPT they are added into an ImportObject object.

  1. Function instance

    Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge, developers can create the Function objects for host functions and add them into an ImportObject object for registering into a VM or a Store. For both host functions and the functions get from Store, developers can retrieve the FunctionType from the Function objects. For the details of the Host Function guide, please refer to the next chapter.

    funcinst := ...
    // `funcobj` is the `*wasmedge.Function` retrieved from the store object.
    functype := funcobj.GetFunctionType()
    // The `funcobj` retrieved from the store object should __NOT__ be released.
    // The `functype` retrieved from the `funcobj` should __NOT__ be released.
    
  2. Table instance

    In WasmEdge, developers can create the Table objects and add them into an ImportObject object for registering into a VM or a Store. The Table objects supply APIs to control the data in table instances.

    lim := wasmedge.NewLimitWithMax(10, 20)
    // Create the table type with limit and the `FuncRef` element type.
    tabtype := wasmedge.NewTableType(wasmedge.RefType_FuncRef, lim)
    // Create the table instance with table type.
    tabinst := wasmedge.NewTable(tabtype)
    // Delete the table type.
    tabtype.Release()
    
    gottabtype := tabinst.GetTableType()
    // The `gottabtype` got from table instance is owned by the `tabinst`
    // and should __NOT__ be released.
    reftype := gottabtype.GetRefType()
    // The `reftype` will be `wasmedge.RefType_FuncRef`.
    
    var gotdata interface{}
    data := wasmedge.NewFuncRef(5)
    err := tabinst.SetData(data, 3)
    // Set the function index 5 to the table[3].
    
    // The following line will get an "out of bounds table access" error
    // because the position (13) is out of the table size (10):
    //   err = tabinst.SetData(data, 13)
    
    gotdata, err = tabinst.GetData(3)
    // Get the FuncRef value of the table[3].
    
    // The following line will get an "out of bounds table access" error
    // because the position (13) is out of the table size (10):
    //   gotdata, err = tabinst.GetData(13)
    
    tabsize := tabinst.GetSize()
    // `tabsize` will be 10.
    err = tabinst.Grow(6)
    // Grow the table size of 6, the table size will be 16.
    
    // The following line will get an "out of bounds table access" error
    // because the size (16 + 6) will reach the table limit (20):
    //   err = tabinst.Grow(6)
    
    tabinst.Release()
    
  3. Memory instance

    In WasmEdge, developers can create the Memory objects and add them into an ImportObject object for registering into a VM or a Store. The Memory objects supply APIs to control the data in memory instances.

    lim := wasmedge.NewLimitWithMax(1, 5)
    // Create the memory type with limit. The memory page size is 64KiB.
    memtype := wasmedge.NewMemoryType(lim)
    // Create the memory instance with memory type.
    meminst := wasmedge.NewMemory(memtype)
    // Delete the memory type.
    memtype.Release()
    
    data := []byte("A quick brown fox jumps over the lazy dog")
    err := meminst.SetData(data, 0x1000, 10)
    // Set the data[0:9] to the memory[4096:4105].
    
    // The following line will get an "out of bounds memory access" error
    // because [65535:65544] is out of 1 page size (65536):
    //   err = meminst.SetData(data, 0xFFFF, 10)
    
    var gotdata []byte
    gotdata, err = meminst.GetData(0x1000, 10)
    // Get the memory[4096:4105]. The `gotdata` will be `[]byte("A quick br").
    // The following line will get an "out of bounds memory access" error
    // because [65535:65544] is out of 1 page size (65536):
    //   gotdata, err = meminst.Getdata(0xFFFF, 10)
    
    pagesize := meminst.GetPageSize()
    // `pagesize` will be 1.
    err = meminst.GrowPage(2)
    // Grow the page size of 2, the page size of the memory instance will be 3.
    
    // The following line will get an "out of bounds memory access" error
    // because the size (3 + 3) will reach the memory limit (5):
    //   err = meminst.GetPageSize(3)
    
    meminst.Release()
    
  4. Global instance

    In WasmEdge, developers can create the Global objects and add them into an ImportObject object for registering into a VM or a Store. The Global objects supply APIs to control the value in global instances.

    // Create the global type with value type and mutation.
    globtype := wasmedge.NewGlobalType(wasmedge.ValType_I64, wasmedge.ValMut_Var)
    // Create the global instance with value and global type.
    globinst := wasmedge.NewGlobal(globtype, uint64(1000))
    // Delete the global type.
    globtype.Release()
    
    gotglobtype := globinst.GetGlobalType()
    // The `gotglobtype` got from global instance is owned by the `globinst`
    // and should __NOT__ be released.
    valtype := gotglobtype.GetValType()
    // The `valtype` will be `wasmedge.ValType_I64`.
    valmut := gotglobtype.GetMutability()
    // The `valmut` will be `wasmedge.ValMut_Var`.
    
    globinst.SetValue(uint64(888))
    // Set the value u64(888) to the global.
    // This function will do nothing if the value type mismatched or the
    // global mutability is `wasmedge.ValMut_Const`.
    gotval := globinst.GetValue()
    // The `gotbal` will be `interface{}` which the type is `uint64` and
    // the value is 888.
    
    globinst.Release()
    

Host Functions

Host functions are functions outside WebAssembly and passed to WASM modules as imports. In WasmEdge-go, developers can create the Function, Memory, Table, and Global objects and add them into an ImportObject object for registering into a VM or a Store.

  1. Host function allocation

    Developers can define Go functions with the following function signature as the host function body:

    type hostFunctionSignature func(
        data interface{}, mem *Memory, params []interface{}) ([]interface{}, Result)
    

    The example of an add host function to add 2 i32 values:

    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    

    Then developers can create Function object with the host function body and function type:

    // Create a function type: {i32, i32} -> {i32}.
    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
      []wasmedge.ValType{wasmedge.ValType_I32},
    )
    
    // Create a function context with the function type and host function body.
    // The third parameter is the pointer to the additional data.
    // Developers should guarantee the life cycle of the data, and it can be
    // `nil` if the external data is not needed.
    // The last parameter can be 0 if developers do not need the cost measuring.
    func_add := wasmedge.NewFunction(functype, host_add, nil, 0)
    
    // If the function object is not added into an import object object, it should be released.
    func_add.Release()
    functype.Release()
    
  2. Import object object

    The ImportObject object holds an exporting module name and the instances. Developers can add the Function, Memory, Table, and Global instances with their exporting names.

    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    // Create the import object with the module name "module".
    impobj := wasmedge.NewImportObject("module")
    
    // Create and add a function instance into the import object with export name "add".
    functype := wasmedge.NewFunctionType(
      []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
      []wasmedge.ValType{wasmedge.ValType_I32},
    )
    hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
    functype.Release()
    impobj.AddFunction("add", hostfunc)
    
    // Create and add a table instance into the import object with export name "table".
    tabtype := wasmedge.NewTableType(wasmedge.RefType_FuncRef ,wasmedge.NewLimitWithMax(10, 20))
    hosttab := wasmedge.NewTable(tabtype)
    tabtype.Release()
    impobj.AddTable("table", hosttab)
    
    // Create and add a memory instance into the import object with export name "memory".
    memtype := wasmedge.NewMemoryType(wasmedge.NewLimitWithMax(1, 2))
    hostmem := wasmedge.NewMemory(memtype)
    memtype.Release()
    impobj.AddMemory("memory", hostmem)
    
    // Create and add a global instance into the import object with export name "global".
    globtype := wasmedge.NewGlobalType(wasmedge.ValType_I32, wasmedge.ValMut_Var)
    hostglob := wasmedge.NewGlobal(globtype, uint32(666))
    globtype.Release()
    impobj.AddGlobal("global", hostglob)
    
    // The import objects should be released.
    // Developers should __NOT__ release the instances added into the import object objects.
    impobj.Release()
    
  3. Specified import object

    wasmedge.NewWasiImportObject() API can create and initialize the WASI import object. wasmedge.NewWasmEdgeProcessImportObject() API can create and initialize the wasmedge_process import object. Developers can create these import object objects and register them into the Store or VM objects rather than adjust the settings in the Configure objects.

    wasiobj := wasmedge.NewWasiImportObject(
      os.Args[1:],     // The args
      os.Environ(),    // The envs
      []string{".:."}, // The mapping preopens
    )
    procobj := wasmedge.NewWasmEdgeProcessImportObject(
      []string{"ls", "echo"}, // The allowed commands
      false,                  // Not to allow all commands
    )
    
    // Register the WASI and WasmEdge_Process into the VM object.
    vm := wasmedge.NewVM()
    vm.RegisterImport(wasiobj)
    vm.RegisterImport(procobj)
    
    // ... Execute some WASM functions.
    
    // Get the WASI exit code.
    exitcode := wasiobj.WasiGetExitCode()
    // The `exitcode` will be 0 if the WASI function "_start" execution has no error.
    // Otherwise, it will return with the related exit code.
    
    vm.Release()
    // The import objects should be deleted.
    wasiobj.Release()
    procobj.Release()
    
  4. Example

    Create a new Go project first:

    mkdir wasmedge_test && cd wasmedge_test
    go mod init wasmedge_test
    

    Assume that there is a simple WASM from the WAT as following:

    (module
      (type $t0 (func (param i32 i32) (result i32)))
      (import "extern" "func-add" (func $f-add (type $t0)))
      (func (export "addTwo") (param i32 i32) (result i32)
        local.get 0
        local.get 1
        call $f-add)
    )
    

    Create and edit the Go file main.go as following:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    func main() {
      // Create the VM object.
      vm := wasmedge.NewVM()
    
      // The WASM module buffer.
      wasmbuf := []byte{
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B,
      }
    
      // Create the import object with the module name "extern".
      impobj := wasmedge.NewImportObject("extern")
    
      // Create and add a function instance into the import object with export name "func-add".
      functype := wasmedge.NewFunctionType(
        []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
        []wasmedge.ValType{wasmedge.ValType_I32},
      )
      hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
      functype.Release()
      impobj.AddFunction("func-add", hostfunc)
    
      // Register the import object into VM.
      vm.RegisterImport(impobj)
    
      res, err := vm.RunWasmBuffer(wasmbuf, "addTwo", uint32(1234), uint32(5678))
      if err == nil {
        fmt.Println("Get the result:", res[0].(int32))
      } else {
        fmt.Println("Error message:", err.Error())
      }
    
      impobj.Release()
      vm.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK:

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
    $ go build
    $ ./wasmedge_test
    Get the result: 6912
    
  5. Host Data Example

    Developers can set a external data object to the function object, and access to the object in the function body. Assume that edit the Go file main.go above:

    package main
    
    import (
      "fmt"
    
      "github.com/second-state/WasmEdge-go/wasmedge"
    )
    
    // Host function body definition.
    func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
      // add: i32, i32 -> i32
      res := params[0].(int32) + params[1].(int32)
    
      // Set the returns
      returns := make([]interface{}, 1)
      returns[0] = res
    
      // Also set the result to the data.
      *data.(*int32) = res
    
      // Return
      return returns, wasmedge.Result_Success
    }
    
    func main() {
      // Create the VM object.
      vm := wasmedge.NewVM()
    
      // The WASM module buffer.
      wasmbuf := []byte{
        /* WASM header */
        0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
        /* Type section */
        0x01, 0x07, 0x01,
        /* function type {i32, i32} -> {i32} */
        0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
        /* Import section */
        0x02, 0x13, 0x01,
        /* module name: "extern" */
        0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
        /* extern name: "func-add" */
        0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
        /* import desc: func 0 */
        0x00, 0x00,
        /* Function section */
        0x03, 0x02, 0x01, 0x00,
        /* Export section */
        0x07, 0x0A, 0x01,
        /* export name: "addTwo" */
        0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
        /* export desc: func 0 */
        0x00, 0x01,
        /* Code section */
        0x0A, 0x0A, 0x01,
        /* code body */
        0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B,
      }
    
      // The additional data to set into the host function.
      var data int32 = 0
    
      // Create the import object with the module name "extern".
      impobj := wasmedge.NewImportObject("extern")
    
      // Create and add a function instance into the import object with export name "func-add".
      functype := wasmedge.NewFunctionType(
        []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
        []wasmedge.ValType{wasmedge.ValType_I32},
      )
      hostfunc := wasmedge.NewFunction(functype, host_add, &data, 0)
      functype.Release()
      impobj.AddFunction("func-add", hostfunc)
    
      // Register the import object into VM.
      vm.RegisterImport(impobj)
    
      res, err := vm.RunWasmBuffer(wasmbuf, "addTwo", uint32(1234), uint32(5678))
      if err == nil {
        fmt.Println("Get the result:", res[0].(int32))
      } else {
        fmt.Println("Error message:", err.Error())
      }
      fmt.Println("Data value:", data)
    
      impobj.Release()
      vm.Release()
    }
    

    Then you can build and run the Golang application with the WasmEdge Golang SDK:

    $ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.2
    $ go build
    $ ./wasmedge_test
    Get the result: 6912
    Data value: 6912
    

WasmEdge AOT Compiler

In this partition, we will introduce the WasmEdge AOT compiler and the options in Go. WasmEdge runs the WASM files in interpreter mode, and WasmEdge also supports the AOT (ahead-of-time) mode running without modifying any code. The WasmEdge AOT (ahead-of-time) compiler compiles the WASM files for running in AOT mode which is much faster than interpreter mode. Developers can compile the WASM files into the compiled-WASM files in shared library format for universal WASM format for the AOT mode execution.

Compilation Example

The go_WasmAOT example provide a tool for compiling a WASM file.

Compiler Options

Developers can set options for AOT compilers such as optimization level and output format:

const (
  // Disable as many optimizations as possible.
  CompilerOptLevel_O0 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O0)
  // Optimize quickly without destroying debuggability.
  CompilerOptLevel_O1 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O1)
  // Optimize for fast execution as much as possible without triggering significant incremental compile time or code size growth.
  CompilerOptLevel_O2 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O2)
  // Optimize for fast execution as much as possible.
  CompilerOptLevel_O3 = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_O3)
  // Optimize for small code size as much as possible without triggering significant incremental compile time or execution time slowdowns.
  CompilerOptLevel_Os = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Os)
  // Optimize for small code size as much as possible.
  CompilerOptLevel_Oz = CompilerOptimizationLevel(C.WasmEdge_CompilerOptimizationLevel_Oz)
)

const (
  // Native dynamic library format.
  CompilerOutputFormat_Native = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Native)
  // WebAssembly with AOT compiled codes in custom section.
  CompilerOutputFormat_Wasm = CompilerOutputFormat(C.WasmEdge_CompilerOutputFormat_Wasm)
)

Please refer to the AOT compiler options configuration for details.

Upgrade to WasmEdge-Go v0.10.0

Due to the WasmEdge-Go API breaking changes, this document shows the guideline of programming with WasmEdge-Go API to upgrade from the v0.9.2 to the v0.10.0 version.

Due to the v0.9.1 is retracted, we use the version v0.9.2 here.

Concepts

  1. Merged the ImportObject into the Module.

    The ImportObject struct which is for the host functions is merged into Module. Developers can use the related APIs to construct host modules.

    • wasmedge.NewImportObject() is changed to wasmedge.NewModule().
    • (*wasmedge.ImportObject).Release() is changed to (*wasmedge.Module).Release().
    • (*wasmedge.ImportObject).AddFunction() is changed to (*wasmedge.Module).AddFunction().
    • (*wasmedge.ImportObject).AddTable() is changed to (*wasmedge.Module).AddTable().
    • (*wasmedge.ImportObject).AddMemory() is changed to (*wasmedge.Module).AddMemory().
    • (*wasmedge.ImportObject).AddGlobal() is changed to (*wasmedge.Module).AddGlobal().
    • (*wasmedge.ImportObject).NewWasiImportObject() is changed to (*wasmedge.Module).NewWasiModule().
    • (*wasmedge.ImportObject).NewWasmEdgeProcessImportObject() is changed to (*wasmedge.Module).NewWasmEdgeProcessModule().
    • (*wasmedge.ImportObject).InitWASI() is changed to (*wasmedge.Module).InitWASI().
    • (*wasmedge.ImportObject).InitWasmEdgeProcess() is changed to (*wasmedge.Module).InitWasmEdgeProcess().
    • (*wasmedge.ImportObject).WasiGetExitCode() is changed to (*wasmedge.Module).WasiGetExitCode.
    • (*wasmedge.VM).RegisterImport() is changed to (*wasmedge.VM).RegisterModule().
    • (*wasmedge.VM).GetImportObject() is changed to (*wasmedge.VM).GetImportModule().

    For the new host function examples, please refer to the example below.

  2. Used the pointer to Function instead of the index in the FuncRef value type.

    For the better performance and security, the FuncRef related APIs used the *wasmedge.Function for the parameters and returns.

    • wasmedge.NewFuncRef() is changed to use the *Function as it's argument.
    • Added (wasmedge.FuncRef).GetRef() to retrieve the *Function.
  3. Supported multiple anonymous WASM module instantiation.

    In the version before v0.9.2, WasmEdge only supports 1 anonymous WASM module to be instantiated at one time. If developers instantiate a new WASM module, the old one will be replaced. After the v0.10.0 version, developers can instantiate multiple anonymous WASM module by Executor and get the Module instance. But for the source code using the VM APIs, the behavior is not changed. For the new examples of instantiating multiple anonymous WASM modules, please refer to the example below.

  4. Behavior changed of Store.

    The Function, Table, Memory, and Global instances retrievement from the Store is moved to the Module instance. The Store only manage the module linking when instantiation and the named module searching after the v0.10.0 version.

    • (*wasmedge.Store).ListFunction() and (*wasmedge.Store).ListFunctionRegistered() is replaced by (*wasmedge.Module).ListFunction().
    • (*wasmedge.Store).ListTable() and (*wasmedge.Store).ListTableRegistered() is replaced by (*wasmedge.Module).ListTable().
    • (*wasmedge.Store).ListMemory() and (*wasmedge.Store).ListMemoryRegistered() is replaced by (*wasmedge.Module).ListMemory().
    • (*wasmedge.Store).ListGlobal() and (*wasmedge.Store).ListGlobalRegistered() is replaced by (*wasmedge.Module).ListGlobal().
    • (*wasmedge.Store).FindFunction() and (*wasmedge.Store).FindFunctionRegistered() is replaced by (*wasmedge.Module).FindFunction().
    • (*wasmedge.Store).FindTable() and (*wasmedge.Store).FindTableRegistered() is replaced by (*wasmedge.Module).FindTable().
    • (*wasmedge.Store).FindMemory() and (*wasmedge.Store).FindMemoryRegistered() is replaced by (*wasmedge.Module).FindMemory().
    • (*wasmedge.Store).FindGlobal() and (*wasmedge.Store).FindGlobalRegistered() is replaced by (*wasmedge.Module).FindGlobal().

    For the new examples of retrieving instances, please refer to the example below.

  5. The Module-based resource management.

    Except the creation of Module instance for the host functons, the Executor will output a Module instance after instantiation. No matter the anonymous or named modules, developers have the responsibility to destroy them by (*wasmedge.Module).Release() API. The Store will link to the named Module instance after registering. After the destroyment of a Module instance, the Store will unlink to that automatically; after the destroyment of the Store, the all Module instances the Store linked to will unlink to that Store automatically.

WasmEdge-Go VM changes

The VM APIs are basically not changed, except the ImportObject related APIs.

The following is the example of WASI initialization in WasmEdge-Go v0.9.2:

conf := wasmedge.NewConfigure(wasmedge.WASI)
vm := wasmedge.NewVMWithConfig(conf)

// The following API can retrieve the pre-registration import objects from the VM object.
// This API will return `nil` if the corresponding pre-registration is not set into the configuration.
wasiobj := vm.GetImportObject(wasmedge.WASI)
// Initialize the WASI.
wasiobj.InitWasi(
  os.Args[1:],     // The args
  os.Environ(),    // The envs
  []string{".:."}, // The mapping preopens
)

// ...

vm.Release()
conf.Release()

Developers can change to use the WasmEdge-Go v0.10.0 as follows, with only replacing the ImportObject into Module:

conf := wasmedge.NewConfigure(wasmedge.WASI)
vm := wasmedge.NewVMWithConfig(conf)

// The following API can retrieve the pre-registration module instances from the VM object.
// This API will return `nil` if the corresponding pre-registration is not set into the configuration.
wasiobj := vm.GetImportModule(wasmedge.WASI)
// Initialize the WASI.
wasiobj.InitWasi(
  os.Args[1:],     // The args
  os.Environ(),    // The envs
  []string{".:."}, // The mapping preopens
)

// ...

vm.Release()
conf.Release()

The VM provides a new API for getting the current instantiated anonymous Module instance. For example, if developer want to get the exported Global instance:

// Assume that a WASM module is instantiated in `vm`, and exports the "global_i32".
store := vm.GetStore()

globinst := store.FindGlobal("global_i32")

After the WasmEdge-Go v0.10.0, developers can use the (*wasmedge.VM).GetActiveModule() to get the module instance:

// Assume that a WASM module is instantiated in `vm`, and exports the "global_i32".
mod := vm.GetActiveModule()

// The example of retrieving the global instance.
globinst := mod.FindGlobal("global_i32")

WasmEdge Executor changes

Executor helps to instantiate a WASM module, register a WASM module into Store with module name, register the host modules with host functions, or invoke functions.

  1. WASM module instantiation

    In WasmEdge-Go v0.9.2 version, developers can instantiate a WASM module by the Executor API:

    var ast *wasmedge.AST
    // Assume that `ast` is a loaded WASM from file or buffer and has passed the validation.
    // Assume that `executor` is a `*wasmedge.Executor`.
    // Assume that `store` is a `*wasmedge.Store`.
    err := executor.Instantiate(store, ast)
    if err != nil {
      fmt.Println("Instantiation FAILED:", err.Error())
    }
    

    Then the WASM module is instantiated into an anonymous module instance and handled by the Store. If a new WASM module is instantiated by this API, the old instantiated module instance will be cleaned. After the WasmEdge-Go v0.10.0 version, the instantiated anonymous module will be outputed and handled by caller, and not only 1 anonymous module instance can be instantiated. Developers have the responsibility to release the outputed module instances.

    var ast1 *wasmedge.AST
    var ast2 *wasmedge.AST
    // Assume that `ast1` and `ast2` are loaded WASMs from different files or buffers,
    // and have both passed the validation.
    // Assume that `executor` is a `*wasmedge.Executor`.
    // Assume that `store` is a `*wasmedge.Store`.
    mod1, err1 := executor.Instantiate(store, ast1)
    if err1 != nil {
      fmt.Println("Instantiation FAILED:", err1.Error())
    }
    mod2, err2 := executor.Instantiate(store, ast2)
    if err2 != nil {
      fmt.Println("Instantiation FAILED:", err2.Error())
    }
    mod1.Release()
    mod2.Release()
    
  2. WASM module registration with module name

    When instantiating and registering a WASM module with module name, developers can use the (*wasmedge.Executor).RegisterModule() API before WasmEdge-Go v0.9.2.

    var ast *wasmedge.AST
    // Assume that `ast` is a loaded WASM from file or buffer and has passed the validation.
    // Assume that `executor` is a `*wasmedge.Executor`.
    // Assume that `store` is a `*wasmedge.Store`.
    
    // Register the WASM module into store with the export module name "mod".
    err := executor.RegisterModule(store, ast, "mod")
    if err != nil {
      fmt.Println("WASM registration FAILED:", err.Error())
    }
    

    The same feature is implemented in WasmEdge-Go v0.10.0, but in different API (*wasmedge.Executor).Register():

    var ast *wasmedge.AST
    // Assume that `ast` is a loaded WASM from file or buffer and has passed the validation.
    // Assume that `executor` is a `*wasmedge.Executor`.
    // Assume that `store` is a `*wasmedge.Store`.
    
    // Register the WASM module into store with the export module name "mod".
    mod, err := executor.Register(store, ast, "mod")
    if err != nil {
      fmt.Println("WASM registration FAILED:", err.Error())
    }
    mod.Release()
    

    Developers have the responsibility to release the outputed module instances.

  3. Host module registration

    In WasmEdge-Go v0.9.2, developers can create an ImportObject and register into Store.

    // Create the import object with the export module name.
    impobj := wasmedge.NewImportObject("module")
    
    // ...
    // Add the host functions, tables, memories, and globals into the import object.
    
    // The import object has already contained the export module name.
    err := executor.RegisterImport(store, impobj)
    if err != nil {
      fmt.Println("Import object registration FAILED:", err.Error())
    }
    

    After WasmEdge-Go v0.10.0, developers should use the Module instance instead:

    // Create the module instance with the export module name.
    impmod := wasmedge.NewModule("module")
    
    // ...
    // Add the host functions, tables, memories, and globals into the module instance.
    
    // The module instance has already contained the export module name.
    err := executor.RegisterImport(store, impmod)
    if err != nil {
      fmt.Println("Module instance registration FAILED:", err.Error())
    }
    

    Developers have the responsibility to release the created module instances.

  4. WASM function invokation

    This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat. In WasmEdge-Go v0.9.2 version, developers can invoke a WASM function with the export function name:

    // Create the store object. The store object holds the instances.
    store := wasmedge.NewStore()
    // Error.
    var err error
    // AST object.
    var ast *wasmedge.AST
    // Return values.
    var res []interface{}
    
    // Create the loader object.
    loader := wasmedge.NewLoader()
    // Create the validator object.
    validator := wasmedge.NewValidator()
    // Create the executor object.
    executor := wasmedge.NewExecutor()
    
    // Load the WASM file or the compiled-WASM file and convert into the AST object.
    ast, err = loader.LoadFile("fibonacci.wasm")
    if err != nil {
      fmt.Println("Load WASM from file FAILED:", err.Error())
      return
    }
    // Validate the WASM module.
    err = validator.Validate(ast)
    if err != nil {
      fmt.Println("Validation FAILED:", err.Error())
      return
    }
    // Instantiate the WASM module into the Store object.
    err = executor.Instantiate(store, ast)
    if err != nil {
      fmt.Println("Instantiation FAILED:", err.Error())
      return
    }
    // Invoke the function which is exported with the function name "fib".
    res, err = executor.Invoke(store, "fib", int32(30))
    if err == nil {
      fmt.Println("Get fibonacci[30]:", res[0].(int32))
    } else {
      fmt.Println("Run failed:", err.Error())
    }
    
    ast.Release()
    loader.Release()
    validator.Release()
    executor.Release()
    store.Release()
    

    After the WasmEdge-Go v0.10.0, developers should retrieve the Function instance by function name first.

    // ...
    // Ignore the unchanged steps before validation. Please refer to the sample code above.
    
    var mod *wasmedge.Module
    // Instantiate the WASM module and get the output module instance.
    mod, err = executor.Instantiate(store, ast)
    if err != nil {
      fmt.Println("Instantiation FAILED:", err.Error())
      return
    }
    // Retrieve the function instance by name.
    funcinst := mod.FindFunction("fib")
    if funcinst == nil {
      fmt.Println("Run FAILED: Function name `fib` not found")
      return
    }
    res, err = executor.Invoke(store, funcinst, int32(30))
    if err == nil {
      fmt.Println("Get fibonacci[30]:", res[0].(int32))
    } else {
      fmt.Println("Run FAILED:", err.Error())
    }
    
    ast.Release()
    mod.Release()
    loader.Release()
    validator.Release()
    executor.Release()
    store.Release()
    

Instances retrievement

This example uses the fibonacci.wasm, and the corresponding WAT file is at fibonacci.wat.

Before the WasmEdge-Go v0.9.2 versions, developers can retrieve all exported instances of named or anonymous modules from Store:

// Create the store object. The store object holds the instances.
store := wasmedge.NewStore()
// Error.
var err error
// AST object.
var ast *wasmedge.AST

// Create the loader object.
loader := wasmedge.NewLoader()
// Create the validator object.
validator := wasmedge.NewValidator()
// Create the executor object.
executor := wasmedge.NewExecutor()

// Load the WASM file or the compiled-WASM file and convert into the AST object.
ast, err = loader.LoadFile("fibonacci.wasm")
if err != nil {
  fmt.Println("Load WASM from file FAILED:", err.Error())
  return
}
// Validate the WASM module.
err = validator.Validate(ast)
if err != nil {
  fmt.Println("Validation FAILED:", err.Error())
  return
}
// Example: register and instantiate the WASM module with the module name "module_fib".
err = executor.RegisterModule(store, ast, "module_fib")
if err != nil {
  fmt.Println("Instantiation FAILED:", err.Error())
  return
}
// Example: Instantiate the WASM module into the Store object.
err = executor.Instantiate(store, ast)
if err != nil {
  fmt.Println("Instantiation FAILED:", err.Error())
  return
}

// Now, developers can retrieve the exported instances from the store.
// Take the exported functions as example. This WASM exports the function "fib".
// Find the function "fib" from the instantiated anonymous module.
func1 := store.FindFunction("fib")
// Find the function "fib" from the registered module "module_fib".
func2 := store.FindFunctionRegistered("module_fib", "fib")

ast.Release()
store.Release()
loader.Release()
validator.Release()
executor.Release()

After the WasmEdge-Go v0.10.0, developers can instantiate several anonymous Module instances, and should retrieve the exported instances from named or anonymous Module instances:

// Create the store object. The store is the object to link the modules for imports and exports.
store := wasmedge.NewStore()
// Error.
var err error
// AST object.
var ast *wasmedge.AST
// Module instances.
var namedmod *wasmedge.Module
var anonymousmod *wasmedge.Module

// Create the loader object.
loader := wasmedge.NewLoader()
// Create the validator object.
validator := wasmedge.NewValidator()
// Create the executor object.
executor := wasmedge.NewExecutor()

// Load the WASM file or the compiled-WASM file and convert into the AST object.
ast, err = loader.LoadFile("fibonacci.wasm")
if err != nil {
  fmt.Println("Load WASM from file FAILED:", err.Error())
  return
}
// Validate the WASM module.
err = validator.Validate(ast)
if err != nil {
  fmt.Println("Validation FAILED:", err.Error())
  return
}
// Example: register and instantiate the WASM module with the module name "module_fib".
namedmod, err = executor.Register(store, ast, "module_fib")
if err != nil {
  fmt.Println("Instantiation FAILED:", err.Error())
  return
}
// Example: Instantiate the WASM module and get the output module instance.
anonymousmod, err = executor.Instantiate(store, ast)
if err != nil {
  fmt.Println("Instantiation FAILED:", err.Error())
  return
}

// Now, developers can retrieve the exported instances from the module instaces.
// Take the exported functions as example. This WASM exports the function "fib".
// Find the function "fib" from the instantiated anonymous module.
func1 := anonymousmod.FindFunction("fib")
// Find the function "fib" from the registered module "module_fib".
func2 := namedmod.FindFunction("fib")
// Or developers can get the named module instance from the store:
gotmod := store.FindModule("module_fib")
func3 := gotmod.FindFunction("fib")

namedmod.Release()
anonymousmod.Release()
ast.Release()
store.Release()
loader.Release()
validator.Release()
executor.Release()

Host functions

The difference of host functions are the replacement of ImportObject struct.

// Host function body definition.
func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
  // add: i32, i32 -> i32
  res := params[0].(int32) + params[1].(int32)

  // Set the returns
  returns := make([]interface{}, 1)
  returns[0] = res

  // Return
  return returns, wasmedge.Result_Success
}

// ...

// Create an import object with the module name "module".
impobj := wasmedge.NewImportObject("module")

// Create and add a function instance into the import object with export name "add".
functype := wasmedge.NewFunctionType(
  []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
  []wasmedge.ValType{wasmedge.ValType_I32},
)
hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
// The third parameter is the pointer to the additional data object.
// Developers should guarantee the life cycle of the data, and it can be `nil`
// if the external data is not needed.
functype.Release()
impobj.AddFunction("add", hostfunc)

// The import object should be released.
// Developers should __NOT__ release the instances added into the import objects.
impobj.Release()

Developers can use the Module struct to upgrade to WasmEdge v0.10.0 easily.

// Host function body definition.
func host_add(data interface{}, mem *wasmedge.Memory, params []interface{}) ([]interface{}, wasmedge.Result) {
  // add: i32, i32 -> i32
  res := params[0].(int32) + params[1].(int32)

  // Set the returns
  returns := make([]interface{}, 1)
  returns[0] = res

  // Return
  return returns, wasmedge.Result_Success
}

// ...

// Create a module instance with the module name "module".
mod := wasmedge.NewModule("module")

// Create and add a function instance into the module instance with export name "add".
functype := wasmedge.NewFunctionType(
  []wasmedge.ValType{wasmedge.ValType_I32, wasmedge.ValType_I32},
  []wasmedge.ValType{wasmedge.ValType_I32},
)
hostfunc := wasmedge.NewFunction(functype, host_add, nil, 0)
// The third parameter is the pointer to the additional data object.
// Developers should guarantee the life cycle of the data, and it can be `nil`
// if the external data is not needed.
functype.Release()
mod.AddFunction("add", hostfunc)

// The module instances should be released.
// Developers should __NOT__ release the instances added into the module instance objects.
mod.Release()

Node.js SDK

In this tutorial, I will show you how to incorporate WebAssembly functions, written in Rust, into Node.js applications on the server via the WasmEdge Node.js SDK. This approach combines Rust's performance, WebAssembly's security and portability, and JavaScript's ease-of-use. A typical application works like this.

  • The host application is a Node.js web application written in JavaScript. It makes WebAssembly function calls.
  • The WebAssembly bytecode program is written in Rust. It runs inside the WasmEdge Runtime, and is called from the Node.js web application.

Fork this Github repository to start coding!

Prerequisites

To set up a high-performance Node.js environment with Rust and WebAssembly, you will need the following:

Docker

The easiest way to get started is to use Docker to build a dev environment. Just clone this template project to your computer and run the following Docker commands.

# Get the code
git clone https://github.com/second-state/wasmedge-nodejs-starter
cd wasmedge-nodejs-starter

# Run Docker container
docker pull wasmedge/appdev_x86_64:0.8.2
docker run -p 3000:3000 --rm -it -v $(pwd):/app wasmedge/appdev_x86_64:0.8.2

# In docker
cd /app

That's it! You are now ready to compile and run the code.

Manual setup without Docker

The commands are as follows.

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup override set 1.50.0

# Install Node.js and npm
curl -sL https://deb.nodesource.com/setup_14.x |  bash
sudo apt-get install -y nodejs npm

# Install rustwasmc toolchain
npm install -g rustwasmc # Append --unsafe-perm if permission denied

# OS dependencies for WasmEdge
sudo apt-get update
sudo apt-get -y upgrade
sudo apt install -y build-essential curl wget git vim libboost-all-dev llvm-dev liblld-10-dev

# Install the nodejs addon for WasmEdge
npm install wasmedge-core
npm install wasmedge-extensions

The WasmEdge Runtime depends on the latest version of libstdc++. Ubuntu 20.04 LTS already has the latest libraries. If you are running an older Linux distribution, you have several options to upgrade.

Next, clone the example source code repository.

git clone https://github.com/second-state/wasmedge-nodejs-starter
cd wasmedge-nodejs-starter

Hello World

The first example is a hello world to show you how various parts of the application fit together.

WebAssembly program in Rust

In this example, our Rust program appends the input string after “hello”. Below is the content of the Rust program src/lib.rs. You can define multiple external functions in this library file, and all of them will be available to the host JavaScript app via WebAssembly. Just remember to annotate each function with #[wasm_bindgen] so that rustwasmc knows to generate the correct JavaScript to Rust interface for it when you build it.


#![allow(unused)]
fn main() {
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn say(s: String) -> String {
  let r = String::from("hello ");
  return r + &s;
}
}

Next, you can compile the Rust source code into WebAssembly bytecode and generate the accompanying JavaScript module for the Node.js host environment.

rustwasmc build

The result are files in the pkg/ directory. the .wasm file is the WebAssembly bytecode program, and the .js files are for the JavaScript module.

The Node.js host application

Next, go to the node folder and examine the JavaScript program app.js. With the generated wasmedge_nodejs_starter_lib.js module, it is very easy to write JavaScript to call WebAssembly functions. Below is the node application app.js. It simply imports the say() function from the generated module. The node application takes the name parameter from incoming an HTTP GET request, and responds with “hello name”.

const { say } = require('../pkg/wasmedge_nodejs_starter_lib.js');

const http = require('http');
const url = require('url');
const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  const queryObject = url.parse(req.url,true).query;
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end(say(queryObject['name']));
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Start the Node.js application server as follows.

$ node node/app.js
Server running at http://127.0.0.1:3000/

Then, you can test it from another terminal window.

$ curl http://127.0.0.1:3000/?name=Wasm
hello Wasm

A complete web application

The next example shows a web application that computes the roots for quadratic equations. Please checkout the full source code here.

The user enters the values for a, b, c on the web form, and the web application calls the web service at /solve to compute the roots for the quadratic equation.

a*X^2 + b*X + c = 0

The roots for X are displayed in the area below the input form.

getting-started-with-rust-function

The HTML file contains the client side JavaScript to submit the web form to /solve, and put result into the #roots HTML element on the page.

$(function() {
  var options = {
    target: '#roots',
    url: "/solve",
    type: "post"
  };
  $('#solve').ajaxForm(options);
});

The Node.js application behind the /solve URL endpoint is as follows. It reads the data from the input form, passes them into the solve function as an array, and puts the return value in the HTTP response.

app.post('/solve', function (req, res) {
  var a = parseFloat(req.body.a);
  var b = parseFloat(req.body.b);
  var c = parseFloat(req.body.c);
  res.send(solve([a, b, c]))
})

The solve function is written in Rust and runs inside the WasmEdge Runtime. While the call arguments in the JavaScript side is an array of values, the Rust function receives a JSON object that encapsulates the array. In the Rust code, we first decode the JSON, perform the computation, and return the result values in a JSON string.


#![allow(unused)]
fn main() {
#[wasm_bindgen]
pub fn solve(params: &str) -> String {
  let ps: (f32, f32, f32) = serde_json::from_str(&params).unwrap();
  let discriminant: f32 = (ps.1 * ps.1) - (4. * ps.0 * ps.2);
  let mut solution: (f32, f32) = (0., 0.);
  if discriminant >= 0. {
    solution.0 = (((-1.) * ps.1) + discriminant.sqrt()) / (2. * ps.0);
    solution.1 = (((-1.) * ps.1) - discriminant.sqrt()) / (2. * ps.0);
    return serde_json::to_string(&solution).unwrap();
  } else {
    return String::from("not real numbers");
  }
}
}

Let's try it.

rustwasmc build
npm install express # The application requires the Express framework in Node.js

node node/server.js

From the web browser, go to http://ip-addr:8080/ to access this application. Note: If you are using Docker, make sure that the Docker container port 8080 is mapped to the host port 8080.

That’s it for the quadratic equation example.

More examples

Besides passing string values between Rust and JavaScript, the rustwasmc tool supports the following data types.

  • Rust call parameters can be any combo of i32, String, &str, Vec<u8>, and &[u8]
  • Return value can be i32 or String or Vec<u8> or void
  • For complex data types, such as structs, you could use JSON strings to pass data.

With JSON support, you can call Rust functions with any number of input parameters and return any number of return values of any type.

The Rust program src/lib.rs in the functions example demonstrates how to pass in call arguments in various supported types, and return values.


#![allow(unused)]
fn main() {
#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasm_bindgen]
pub fn lowest_common_denominator(a: i32, b: i32) -> i32 {
  let r = lcm(a, b);
  return r;
}

#[wasm_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasm_bindgen]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
  return Keccak256::digest(s).as_slice().to_vec();
}
}

Perhaps the most interesting is the create_line() function. It takes two JSON strings, each representing a Point struct, and returns a JSON string representing a Line struct. Notice that both the Point and Line structs are annotated with Serialize and Deserialize so that the Rust compiler automatically generates necessary code to support their conversion to and from JSON strings.


#![allow(unused)]
fn main() {
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32, 
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasm_bindgen]
pub fn create_line (p1: &str, p2: &str, desc: &str) -> String {
  let point1: Point = serde_json::from_str(p1).unwrap();
  let point2: Point = serde_json::from_str(p2).unwrap();
  let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();

  let valid = if length == 0.0 { false } else { true };
  let line = Line { points: vec![point1, point2], valid: valid, length: length, desc: desc.to_string() };
  return serde_json::to_string(&line).unwrap();
}

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}
}

Next, let's examine the JavaScript program app.js. It shows how to call the Rust functions. As you can see String and &str are simply strings in JavaScript, i32 are numbers, and Vec<u8> or &[8] are JavaScript Uint8Array. JavaScript objects need to go through JSON.stringify() or JSON.parse() before being passed into or returned from Rust functions.

const { say, obfusticate, lowest_common_denominator, sha3_digest, keccak_digest, create_line } = require('./functions_lib.js');

var util = require('util');
const encoder = new util.TextEncoder();
console.hex = (d) => console.log((Object(d).buffer instanceof ArrayBuffer ? new Uint8Array(d.buffer) : typeof d === 'string' ? (new util.TextEncoder('utf-8')).encode(d) : new Uint8ClampedArray(d)).reduce((p, c, i, a) => p + (i % 16 === 0 ? i.toString(16).padStart(6, 0) + '  ' : ' ') + c.toString(16).padStart(2, 0) + (i === a.length - 1 || i % 16 === 15 ?  ' '.repeat((15 - i % 16) * 3) + Array.from(a).splice(i - i % 16, 16).reduce((r, v) => r + (v > 31 && v < 127 || v > 159 ? String.fromCharCode(v) : '.'), '  ') + '\n' : ''), ''));

console.log( say("WasmEdge") );
console.log( obfusticate("A quick brown fox jumps over the lazy dog") );
console.log( lowest_common_denominator(123, 2) );
console.hex( sha3_digest(encoder.encode("This is an important message")) );
console.hex( keccak_digest(encoder.encode("This is an important message")) );

var p1 = {x:1.5, y:3.8};
var p2 = {x:2.5, y:5.8};
var line = JSON.parse(create_line(JSON.stringify(p1), JSON.stringify(p2), "A thin red line"));
console.log( line );

After running rustwasmc to build the Rust library, running app.js in Node.js environment produces the following output.

$ rustwasmc build
... Building the wasm file and JS shim file in pkg/ ...

$ node node/app.js
hello WasmEdge
N dhvpx oebja sbk whzcf bire gur ynml qbt
246
000000  57 1b e7 d1 bd 69 fb 31 9f 0a d3 fa 0f 9f 9a b5  W.çѽiû1..Óú...µ
000010  2b da 1a 8d 38 c7 19 2d 3c 0a 14 a3 36 d3 c3 cb  +Ú..8Ç.-<..£6ÓÃË

000000  7e c2 f1 c8 97 74 e3 21 d8 63 9f 16 6b 03 b1 a9  ~ÂñÈ.tã!Øc..k.±©
000010  d8 bf 72 9c ae c1 20 9f f6 e4 f5 85 34 4b 37 1b  Ø¿r.®Á .öäõ.4K7.

{ points: [ { x: 1.5, y: 3.8 }, { x: 2.5, y: 5.8 } ],
  valid: true,
  length: 2.2360682,
  desc: 'A thin red line' }  

WasmEdge Rust SDK

Introduction

WasmEdge supports embedding into Rust applications via WasmEdge Rust SDK. WasmEdge Rust SDK consists of three crates:

  • wasmedge-sdk crate. It defines a group of safe, ergonomic high-level APIs, which are used to build up business applications.

  • wasmedge-sys crate. It defines a group of low-level Rust APIs, which simply wrap WasmEdge C-API and provide the safe counterparts. It is not recommended to use it directly to build up application.

  • wasmedge-types crate. The data structures that are commonly used in wasmedge-sdk and wasmedge-sys are defined in this crate.

Versioning Table

The following table provides the versioning information about each release of wasmedge-sdk crate and its dependencies.

| wasmedge-sdk | WasmEdge lib | wasmedge-sys | wasmedge-types| | :-----------: | :----------------------------------------------------------------: | :-----------: | :-----------: | | 0.1.0 | 0.10.0 | 0.7 | 0.1 |

wasmedge-sdk crate

To use wasmedge-sdk in your project, you should finish the followign two steps before building your project:

  • First, deploy WasmEdge library on your local system.

    You can reference the versiong table and download WasmEdge library from WasmEdge Releases Page. After download the WasmEdge library, you can choose one of the following three ways to specify the locations of the required files:

    • By default location

      For those who do not want to define environment variables, you can put the downloaded WasmEdge binary package in the default location $HOME/.wasmedge/. The directory structure of the default location should looks like below:

      // $HOME/.wasmedge/ on Ubuntu-20.04
      .
      ├── bin
      │   ├── wasmedge
      │   └── wasmedgec
      ├── include
      │   └── wasmedge
      │       ├── dense_enum_map.h
      │       ├── enum.inc
      │       ├── enum_configure.h
      │       ├── enum_errcode.h
      │       ├── enum_types.h
      │       ├── int128.h
      │       ├── spare_enum_map.h
      │       ├── version.h
      │       └── wasmedge.h
      └── lib64
          ├── libwasmedge_c.so
          └── wasmedge
              └── libwasmedgePluginWasmEdgeProcess.so
      
      5 directories, 13 files
      
      // $HOME/.wasmedge/ on macOS-12
      .
      ├── bin
      │   ├── wasmedge
      │   └── wasmedgec
      ├── include
      │   └── wasmedge
      │       ├── dense_enum_map.h
      │       ├── enum.inc
      │       ├── enum_configure.h
      │       ├── enum_errcode.h
      │       ├── enum_types.h
      │       ├── int128.h
      │       ├── spare_enum_map.h
      │       ├── version.h
      │       └── wasmedge.h
      └── lib
          └── libwasmedge_c.dylib
      
      4 directories, 12 files
      
    • By specifying WASMEDGE_INCLUDE_DIR and WASMEDGE_LIB_DIR.

      • WASMEDGE_INCLUDE_DIR is used to specify the directory in which the header files are.

      • WASMEDGE_LIB_DIR is used to specify the directory in which the wasmedge_c library is.

      • For example, suppose that you have already downloaded the Wasmedge-0.10.0 binary package from WasmEdge Releases and placed it into a local directory, for example, ~/workspace/me/. The directory structure of the released package should looks like below:

        root@0a877562f39e:~/workspace/me/WasmEdge-0.10.0-Linux# tree .
        .
        ├── bin
        │   ├── wasmedge
        │   └── wasmedgec
        ├── include
        │   └── wasmedge
        │       ├── dense_enum_map.h
        │       ├── enum.inc
        │       ├── enum_configure.h
        │       ├── enum_errcode.h
        │       ├── enum_types.h
        │       ├── int128.h
        │       ├── spare_enum_map.h
        │       ├── version.h
        │       └── wasmedge.h
        └── lib64
            ├── libwasmedge_c.so
            └── wasmedge
                └── libwasmedgePluginWasmEdgeProcess.so
        
        5 directories, 13 files
        

      Then set WASMEDGE_INCLUDE_DIR and WASMEDGE_LIB_DIR environment variables to specify the include and lib (here is lib64) directories.

      root@0a877562f39e:~/workspace/me/WasmEdge/bindings/rust/wasmedge-sys# export WASMEDGE_INCLUDE_DIR=/root/workspace/me/WasmEdge-0.10.0-Linux/include/wasmedge
      
      root@0a877562f39e:~/workspace/me/WasmEdge/bindings/rust/wasmedge-sys# export WASMEDGE_LIB_DIR=/root/workspace/me/WasmEdge-0.10.0-Linux/lib64
      
    • By specifying WASMEDGE_BUILD_DIR

      You can choose this way if you'd like to use the latest code in the master branch of the WasmEdge github repo. For example,

      • Suppose that you git clone WasmEdge repo in your local directory, for example, ~/workspace/me/WasmEdge, and follow the instructions to build WasmEdge native library. After that, you should find the generated include and lib directories in ~/workspace/me/WasmEdge/build.

      • Then, set WASMEDGE_BUILD_DIR environment variable to specify the build directory.

        root@0a877562f39e:~/workspace/me/WasmEdge# export WASMEDGE_BUILD_DIR=/root/workspace/me/WasmEdge/build
        
  • Second, after deploy the WasmEdge library on your local system, copy/paste the following code into the Cargo.toml file of your project. Now, you can use cargo build command to build your project.

[dependencies]
wasmedge-sdk = "0.1"

wasmedge-sys crate

wasmedge-sys serves as a wraper layer of WasmEdge C-API, and provides a group of safe low-level Rust interfaces. For those who are interested in using wasmedge-sys in their projects, you should also deploy the WasmEdge library on your local system as described in the wasmedge-sdk crate section. Then, copy/paste the following code in the Cargo.toml file of your project.

[dependencies]
wasmedge-sys = "0.7"

For Windows users, duo to issue 1527, the current version of wasmedge-sys does not support standalone mode on Windows platform. Please choose to use our docker image. We'll fix the issue as soon as possible.

For those who want to build wasmedge-sys from source in terminal, just go to the wasmedge-sys directory and type cargo build --features standalone command that will compile the wasmedge-sys crate in standalone mode.

Docker image

For those who would like to dev in Docker environment, you can reference the Use Docker section of this book, which details how to use Docker for WasmEdge application development.

Windows Users

Duo to issue 1527, WasmEdger Rust bindings can not be used directly on Windows platform for now. Please choose to use our docker image. We'll fix the issue as soon as possible.

Examples

For helping you get familiar with WasmEdge Rust bindings, the following quick examples demonstrate how to use the APIs defined in wasmedge-sdk and wasmedge-sys, respectively. In addition, we'll add more examples continuously. Please file an issue here and let us know if you have any problems with the API usage.

wasmedge-sdk Examples

wasmedge-sys Examples

Hello World

In this example, we'll use a wasm module, in which a function run is exported and it will call a function say_hello from an import module named env. The imported function say_hello has no inputs and outputs, and only prints a greeting message out.

N.B. In this example, wasmedge-sdk v0.1.1, wasmedge-sys v0.7.1 and wasmedge-types v0.1.3 are used.

Let's start off by getting all imports right away so you can follow along


#![allow(unused)]
fn main() {
// please add this feature if you're using rust of version < 1.63
// #![feature(explicit_generic_args_with_impl_trait)]

use wasmedge_sdk::{params, Executor, ImportObjectBuilder, Module, Store};
use wasmedge_sys::WasmValue;
use wasmedge_types::wat2wasm;
}

Step 1: Define a native function and Create an ImportObject

First, let's define a native function named say_hello_world that prints out Hello, World!.


#![allow(unused)]
fn main() {
fn say_hello_world(_inputs: Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> {
    println!("Hello, world!");

    Ok(vec![])
}
}

To use the native function as an import function in the WasmEdge runtime, we need an ImportObject. wasmedge-sdk defines a ImportObjectBuilder, which provides a group of chaining methods used to create an ImportObject. Let's see how to do it.


#![allow(unused)]
fn main() {
// create an import module
let import = ImportObjectBuilder::new()
    .with_func::<(), ()>("say_hello", say_hello_world)?
    .build("env")?; 
}

Now, we have an import module named env which holds a host function say_hello. As you may notice, the names we used for the import module and the host function are exactly the same as the ones appearing in the wasm module. You can find the wasm module in Step 2.

Step 2: Load a wasm module

Now, let's load a wasm module. wasmedge-sdk defines two methods in Module:

  • from_file loads a wasm module from a file, and meanwhile, validates the loaded wasm module.

  • from_bytes loads a wasm module from an array of in-memory bytes, and meanwhile, validates the loaded wasm module.

Here we choose Module::from_bytes method to load our wasm module from an array of in-memory bytes.


#![allow(unused)]
fn main() {
let wasm_bytes = wat2wasm(
    br#"
(module
    ;; First we define a type with no parameters and no results.
    (type $no_args_no_rets_t (func (param) (result)))

    ;; Then we declare that we want to import a function named "env" "say_hello" with
    ;; that type signature.
    (import "env" "say_hello" (func $say_hello (type $no_args_no_rets_t)))

    ;; Finally we create an entrypoint that calls our imported function.
    (func $run (type $no_args_no_rets_t)
    (call $say_hello))
    ;; And mark it as an exported function named "run".
    (export "run" (func $run)))
"#,
)?;

// loads a wasm module from the given in-memory bytes and returns a compiled module
let module = Module::from_bytes(None, &wasm_bytes)?;
}

Step 3: Register import module and compiled module

To register a compiled module, we need to check if it has dependency on some import modules. In the wasm module this statement (import "env" "say_hello" (func $say_hello (type $no_args_no_rets_t))) tells us that it depends on an import module named env. Therefore, we need to register the import module first before registering the compiled wasm module.


#![allow(unused)]
fn main() {
// create an executor
let mut executor = Executor::new(None, None)?;

// create a store
let mut store = Store::new()?;

// register the import module into the store
store.register_import_module(&mut executor, &import)?;

// register the compiled module into the store and get an module instance
let extern_instance = store.register_named_module(&mut executor, "extern", &module)?;
}

In the code above we use Executor and Store to register the import module and the compiled module. wasmedge-sdk also provides alternative APIs to do the same thing: Vm::register_import_module and Vm::register_module_from_bytes.

Step 4: Run the exported function

Now we are ready to run the exported function.


#![allow(unused)]
fn main() {
// get the exported function "run"
let run = extern_instance
    .func("run")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'run'."))?;

// run host function
run.call(&mut executor, params!())?;
}

In this example we created an instance of Executor, hence, we have two choices to call a function instance:

Any one of these two methods requires that you have to get a function instance.

In addition, Vm defines a group of methods which can invoke host function in different ways. For details, please reference Vm.

The complete example can be found here.

Memory Manipulation

In this example, we'll present how to manipulate the linear memory with the APIs defined in wasmedge_sdk::Memory.

N.B. In this example, wasmedge-sdk v0.1.1, wasmedge-sys v0.7.1 and wasmedge-types v0.1.3 are used.

Wasm module

Before talk about the code, let's first see the wasm module we use in this example. In the wasm module, a linear memory of 1-page (64KiB) size is defined; in addition, three functions are exported from this module: get_at, set_at, and mem_size.

(module
  (type $mem_size_t (func (result i32)))
  (type $get_at_t (func (param i32) (result i32)))
  (type $set_at_t (func (param i32) (param i32)))

  # A memory with initial size of 1 page
  (memory $mem 1)

  (func $get_at (type $get_at_t) (param $idx i32) (result i32)
    (i32.load (local.get $idx)))

  (func $set_at (type $set_at_t) (param $idx i32) (param $val i32)
    (i32.store (local.get $idx) (local.get $val)))

  (func $mem_size (type $mem_size_t) (result i32)
    (memory.size))
  
  # Exported functions
  (export "get_at" (func $get_at))
  (export "set_at" (func $set_at))
  (export "mem_size" (func $mem_size))
  (export "memory" (memory $mem)))

Next, we'll demonstrate how to manipulate the linear memory by calling the exported functions.

Load and Register Module

Let's start off by getting all imports right away so you can follow along


#![allow(unused)]
fn main() {
// please add this feature if you're using rust of version < 1.63
// #![feature(explicit_generic_args_with_impl_trait)]

use wasmedge_sdk::{params, Executor, ImportObjectBuilder, Module, Store};
use wasmedge_sys::WasmValue;
use wasmedge_types::wat2wasm;
}

To load a Module, wasmedge-sdk defines two methods:

  • from_file loads a wasm module from a file, and meanwhile, validates the loaded wasm module.

  • from_bytes loads a wasm module from an array of in-memory bytes, and meanwhile, validates the loaded wasm module.

Here we use Module::from_bytes method to load our wasm module from an array of in-memory bytes.


#![allow(unused)]
fn main() {
let wasm_bytes = wat2wasm(
        r#"
(module
  (type $mem_size_t (func (result i32)))
  (type $get_at_t (func (param i32) (result i32)))
  (type $set_at_t (func (param i32) (param i32)))

  (memory $mem 1)

  (func $get_at (type $get_at_t) (param $idx i32) (result i32)
    (i32.load (local.get $idx)))

  (func $set_at (type $set_at_t) (param $idx i32) (param $val i32)
    (i32.store (local.get $idx) (local.get $val)))

  (func $mem_size (type $mem_size_t) (result i32)
    (memory.size))

  (export "get_at" (func $get_at))
  (export "set_at" (func $set_at))
  (export "mem_size" (func $mem_size))
  (export "memory" (memory $mem)))
"#
    .as_bytes(),
)?;

// loads a wasm module from the given in-memory bytes
let module = Module::from_bytes(None, &wasm_bytes)?;
}

The module returned by Module::from_bytes is a compiled module, also called AST Module in WasmEdge terminology. To use it in WasmEdge runtime environment, we need to instantiate the AST module. We use Store::register_named_module API to achieve the goal.


#![allow(unused)]
fn main() {
// create an executor
let mut executor = Executor::new(None, None)?;

// create a store
let mut store = Store::new()?;

// register the module into the store
let extern_instance = store.register_named_module(&mut executor, "extern", &module)?;
}

In the code above, we register the AST module into a Store, in which the module is instantiated, and as a result, a module instance named extern is returned.

Memory

In the previous section, we get an instance by registering a compiled module into the runtime environment. Now we retrieve the memory instance from the module instance, and make use of the APIs defined in Memory to manipulate the linear memory.


#![allow(unused)]
fn main() {
// get the exported memory instance
let mut memory = extern_instance
    .memory("memory")
    .ok_or_else(|| anyhow::anyhow!("failed to get memory instance named 'memory'"))?;

// check memory size
assert_eq!(memory.size(), 1);
assert_eq!(memory.data_size(), 65536);

// grow memory size
memory.grow(2)?;
// check the size (in pages) and the data size (in bytes)
assert_eq!(memory.size(), 3);
assert_eq!(memory.data_size(), 3 * 65536);

// get the exported functions: "set_at" and "get_at"
let set_at = extern_instance
    .func("set_at")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'set_at'."))?;
let get_at = extern_instance
    .func("get_at")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'get_at`."))?;

// set val at mem_addr by calling "set_at"
let mem_addr = 0x2220;
let val = 0xFEFEFFE;
set_at.call(&mut executor, params!(mem_addr, val))?;

// get the value at mem_addr by calling "get_at"
let returns = get_at.call(&mut executor, params!(mem_addr))?;
assert_eq!(returns[0].to_i32(), val);

// call the exported function named "set_at"
let page_size = 0x1_0000;
let mem_addr = (page_size * 2) - std::mem::size_of_val(&val) as i32;
let val = 0xFEA09;
set_at.call(&mut executor, params!(mem_addr, val))?;

// call the exported function named "get_at"
let returns = get_at.call(&mut executor, params!(mem_addr))?;
assert_eq!(returns[0].to_i32(), val);
}

The comments in the code explain the meaning of the code sample above, so we don't describe more.

The complete code of this example can be found here.

Table and FuncRef

In this example, we'll present how to use Table and FuncRef stored in a slot of a Table instance to implement indirect function invocation.

Prerequisite

In this example we defines a native function real_add that takes two numbers and returns their sum. This function will be registered as a host function into WasmEdge runtime environment


#![allow(unused)]
fn main() {
fn real_add(input: Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> {
    println!("Rust: Entering Rust function real_add");

    if input.len() != 2 {
        return Err(1);
    }

    let a = if input[0].ty() == ValType::I32 {
        input[0].to_i32()
    } else {
        return Err(2);
    };

    let b = if input[1].ty() == ValType::I32 {
        input[1].to_i32()
    } else {
        return Err(3);
    };

    let c = a + b;
    println!("Rust: calcuating in real_add c: {:?}", c);

    println!("Rust: Leaving Rust function real_add");
    Ok(vec![WasmValue::from_i32(c)])
}
}

In addition, the following imports are used in this example.


#![allow(unused)]
fn main() {
use wasmedge_sdk::{
    config::{CommonConfigOptions, ConfigBuilder},
    params,
    types::Val,
    Executor, Func, ImportObjectBuilder, Store, Table, WasmVal,
};
use wasmedge_sys::WasmValue;
use wasmedge_types::{RefType, TableType, ValType};
}

Register Table instance

The first thing we need to do is to create a Table instance. After that, we register the table instance along with an import module into the WasmEdge runtime environment. Now let's see the code.


#![allow(unused)]
fn main() {
// create an executor
let config = ConfigBuilder::new(CommonConfigOptions::default()).build()?;
let mut executor = Executor::new(Some(&config), None)?;

// create a store
let mut store = Store::new()?;

// create a table instance
let result = Table::new(TableType::new(RefType::FuncRef, 10, Some(20)));
assert!(result.is_ok());
let table = result.unwrap();

// create an import object
let import = ImportObjectBuilder::new()
    .with_table("my-table", table)?
    .build("extern")?;

// register the import object into the store
store.register_import_module(&mut executor, &import)?;
}

In the code snippet above, we create a Table instance with the initial size of 10 and the maximum size of 20. The element type of the Table instance is reference to function.

Store a function reference into Table

In the previous steps, we defined a native function real_add and registered a Table instance named my-table into the runtime environment. Now we'll save a reference to read_add function to a slot of my-table.


#![allow(unused)]
fn main() {
// get the exported table instance
let mut table = instance
    .table("my-table")
    .expect("Not found table instance named 'my-table'");

// create a host function
let host_func = Func::wrap::<(i32, i32), i32>(Box::new(real_add))?;

// store the reference to host_func at the given index of the table instance
table.set(3, Val::FuncRef(Some(host_func.as_ref())))?;
}

We save the reference to host_func into the third slot of my-table. Next, we can retrieve the function reference from the table instance by index and call the function via its reference.

Call native function via FuncRef


#![allow(unused)]
fn main() {
// retrieve the function reference at the given index of the table instance
let value = table.get(3)?;
if let Val::FuncRef(Some(func_ref)) = value {
    // get the function type by func_ref
    let func_ty = func_ref.ty()?;

    // arguments
    assert_eq!(func_ty.args_len(), 2);
    let param_tys = func_ty.args().unwrap();
    assert_eq!(param_tys, [ValType::I32, ValType::I32]);

    // returns
    assert_eq!(func_ty.returns_len(), 1);
    let return_tys = func_ty.returns().unwrap();
    assert_eq!(return_tys, [ValType::I32]);

    // call the function by func_ref
    let returns = func_ref.call(&mut executor, params!(1, 2))?;
    assert_eq!(returns.len(), 1);
    assert_eq!(returns[0].to_i32(), 3);
}
}

The complete code of this example can be found here.

Run a WebAssembly function with WasmEdge low-level Rust APIs

Prerequisites

This example uses the following crates:

  • wasmedge-sys v0.7.0
  • wasmedge-types v0.1.1

Overview

This section demonstrates how to use the Rust APIs of the wasmedge-sys crate to run a host function.

As you may know, several mainstream programming languages, such as C/C++, Rust, Go, and Python, support compiling their programs into WebAssembly binary. In this demo, we'll introduce how to use the APIs defined in Vm of wasmedge-sys crate to call a WebAssembly function which could be coded in any programming language mentioned above.

Example

We use fibonacci.wasm in this demo, and the contents of the WebAssembly file are presented below. The statement, (export "fib" (func $fib)), declares an exported function named fib. This function computes a Fibonacci number with a given i32 number as input. We'll use the function name later to achieve the goal of computing a Fibonacci number.

(module
  (export "fib" (func $fib))
  (func $fib (param $n i32) (result i32)
    (if
      (i32.lt_s
        (get_local $n)
        (i32.const 2)
      )
      (return
        (i32.const 1)
      )
    )
    (return
      (i32.add
        (call $fib
          (i32.sub
            (get_local $n)
            (i32.const 2)
          )
        )
        (call $fib
          (i32.sub
            (get_local $n)
            (i32.const 1)
          )
        )
      )
    )
  )
)

Step 1: Create a WasmEdge AST Module

In this step, we'll create a WasmEdge AST Module instance from a WebAssembly file.

  • First, create a Loader context;

  • Then, load a specified WebAssebly file ("fibonacci.wasm") via the from_file method of the Loader context. If the process is successful, then a WasmEdge AST Module instance is returned.


#![allow(unused)]
fn main() {
use wasmedge_sys::Loader;
use std::path::PathBuf;

// create a Loader context
let loader = Loader::create(None).expect("fail to create a Loader context");

// load a wasm module from a specified wasm file, and return a WasmEdge AST Module instance
let path = PathBuf::from("fibonacci.wasm");
let module = loader.from_file(path).expect("fail to load the WebAssembly file");
}

Step 2: Create a WasmEdge Vm context

In WasmEdge, a Vm defines a running environment, in which all varieties of instances and contexts are stored and maintained. In the demo code below, we explicitly create a WasmEdge Store context, and then use it as one of the inputs in the creation of a Vm context. If not specify a Store context explicitly, then Vm will create a store by itself.


#![allow(unused)]
fn main() {
use wasmedge_sys::{Config, Store, Vm};

// create a Config context
let config = Config::create().expect("fail to create a Config context");

// create a Store context
let mut store = Store::create().expect("fail to create a Store context");

// create a Vm context with the given Config and Store
let mut vm = Vm::create(Some(config), Some(&mut store)).expect("fail to create a Vm context");
}

Step 3: Invoke the fib function

In Step 1, we got a module that hosts the target fib function defined in the WebAssembly. Now, we can call the function via the run_wasm_from_module method of the Vm context by passing the exported function name, fib.


#![allow(unused)]
fn main() {
use wasmedge_sys::WasmValue;

// run a function
let returns = vm.run_wasm_from_module(module, "fib", [WasmValue::from_i32(5)]).expect("fail to run the target function in the module");

println!("The result of fib(5) is {}", returns[0].to_i32());
}

This is the final result printing on the screen:

The result of fib(5) is 8

Compute Fibonacci numbers concurrently

Prerequisites

This example uses the following crates:

  • wasmedge-sys v0.7.0
  • wasmedge-types v0.1.1

Overview

In this example, we will demonstrate how to use the objects and the APIs defined in wasmedge-sys to compute Fibonacci numbers concurrently.

Example

In the following code, we creates two child threads, thread_a and thread_b, which are responsbile for compute Fib(4) and Fib(5) by calling the host function fib, respectively. After that, the main thread computes Fib(6) by adding the numbers returned by thread_a and thread_b.

Step 1: create a Vm context and register the WebAssembly module


#![allow(unused)]
fn main() {
// create a Config context
let mut config = Config::create()?;
config.bulk_memory_operations(true);

// create a Store context
let mut store = Store::create()?;

// create a Vm context with the given Config and Store
let mut vm = Vm::create(Some(config), Some(&mut store))?;

// register a wasm module from a wasm file
let file = std::path::PathBuf::from(env!("WASMEDGE_DIR"))
    .join("bindings/rust/wasmedge-sys/tests/data/fibonacci.wasm");
vm.register_wasm_from_file("extern", file)?;
}

Step 2: create two child threads to compute Fib(4) and Fib(5) respectively


#![allow(unused)]
fn main() {
let vm = Arc::new(Mutex::new(vm));

// compute fib(4) by a child thread
let vm_cloned = Arc::clone(&vm);
let handle_a = thread::spawn(move || {
  let vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
  let returns = vm_child_thread
    .run_registered_function("extern", "fib", [WasmValue::from_i32(4)])
    .expect("fail to compute fib(4)");

  let fib4 = returns[0].to_i32();
  println!("fib(4) by child thread: {}", fib4);

  fib4
});

// compute fib(5) by a child thread
let vm_cloned = Arc::clone(&vm);
let handle_b = thread::spawn(move || {
  let vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
  let returns = vm_child_thread
    .run_registered_function("extern", "fib", [WasmValue::from_i32(5)])
    .expect("fail to compute fib(5)");

  let fib5 = returns[0].to_i32();
  println!("fib(5) by child thread: {}", fib5);

  fib5
});

}

Step3: Get the returns from the two child threads, and compute Fib(6)

let fib4 = handle_a.join().unwrap();
let fib5 = handle_b.join().unwrap();

// compute fib(6)
println!("fib(6) = fib(5) + fib(1) = {}", fib5 + fib4);

The final result of the code above should be printed on the screen like below:

fib(4) by child thread: 5
fib(5) by child thread: 8
fib(6) = fib(5) + fib(1) = 13

The complete code in this demo can be found on WasmEdge Github.

Introduction to WasmEdge module instance

Overview

In this section, we will talk about module instance. In wasmedge-sys crate, four kinds of module instances are defined:

  • Instance

    • An Instance represents a runtime module instance which is held by a WasmEdge Store context. The Store context is either held by a WasmEdge Vm, or related to a WasmEdge Executor.

    • APIs to retrieve an Instance.

      • If a Vm is available, then

        • with the Vm::active_module API, you can get an anonymous module instance from this Vm.

        • with the Vm::store_mut and Store::module APIs, you can get a named module instance from this Vm.

      • If an Executor is available, then

        • with the Executor::register_named_module API, you can get a named module instance from this Executor.

        • with the Executor::register_active_module API, you can get an anonymous module instance from this Executor.

  • ImportModule

    • ImportModule, also called import module, represents a module instance to be registered into a WasmEdge Vm or Executor. ImportModule implements the ImportObject trait, meaning that WebAssembly function, table, memory and global instances can be added to an import module, and then be registered and instantiated together when the import module is registered into a Vm or Executor.
  • WasiModule and WasmEdgeProcessModule

    • WasiModule and WasmEdgeProcessModule are module instances for WASI and WasmEdgeProcess specification, respectively. They also implement the ImportObject trait. Different from ImportModule, these two kinds of module instances can not only be created, but be retrieved from a Vm.

    • APIs to retrieve WasiModule and WasmEdgeProcessModule.

      • If a Vm is available, then

        • with the Vm::wasi_module API, you can get a module instance of WasiModule type.

        • with the Vm::wasmedge_process_module API, you can get a WasmEdgeProcessModule from this Vm.

Examples

Example 1

In this example, we'll demonstrate how to use the APIs of Vm to

  • Create Wasi and WasmEdgeProcess module instances implicitly by using a Config while creating a Vm.

    
    #![allow(unused)]
    
    fn main() {
    // create a Config context
    let mut config = Config::create()?;
    config.bulk_memory_operations(true);
    assert!(config.bulk_memory_operations_enabled());
    config.wasi(true);
    assert!(config.wasi_enabled());
    config.wasmedge_process(true);
    assert!(config.wasmedge_process_enabled());
    
    // create a Vm context with the given Config and Store
    let mut vm = Vm::create(Some(config), None)?;
    
    }
    
  • Retrieve the Wasi and WasmEdgeProcess module instances from the Vm.

    
    #![allow(unused)]
    
    fn main() {
    // get the default Wasi module
    let wasi_instance = vm.wasi_module_mut()?;
    assert_eq!(wasi_instance.name(), "wasi_snapshot_preview1");
    // get the default WasmEdgeProcess module instance
    let wasmedge_process_instance = vm.wasmedge_process_module_mut()?;
    assert_eq!(wasmedge_process_instance.name(), "wasmedge_process");
    
    }
    
  • Register an import module as a named module into the Vm.

    
    #![allow(unused)]
    
    fn main() {
    // create ImportModule instance
    let module_name = "extern_module";
    let mut import = ImportModule::create(module_name)?;
    
    // a function to import
    fn real_add(inputs: Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> {
        if inputs.len() != 2 {
            return Err(1);
        }
    
        let a = if inputs[0].ty() == ValType::I32 {
            inputs[0].to_i32()
        } else {
            return Err(2);
        };
    
        let b = if inputs[1].ty() == ValType::I32 {
            inputs[1].to_i32()
        } else {
            return Err(3);
        };
    
        let c = a + b;
    
        Ok(vec![WasmValue::from_i32(c)])
    }
    
    // add host function
    let func_ty = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32])?;
    let host_func = Function::create(&func_ty, Box::new(real_add), 0)?;
    import.add_func("add", host_func);
    
    // add table
    let table_ty = TableType::create(RefType::FuncRef, 0..=u32::MAX)?;
    let table = Table::create(&table_ty)?;
    import.add_table("table", table);
    
    // add memory
    let mem_ty = MemType::create(0..=u32::MAX)?;
    let memory = Memory::create(&mem_ty)?;
    import.add_memory("mem", memory);
    
    // add global
    let ty = GlobalType::create(ValType::F32, Mutability::Const)?;
    let global = Global::create(&ty, WasmValue::from_f32(3.5))?;
    import.add_global("global", global);
    
    // register the import module as a named module
    vm.register_wasm_from_import(ImportObject::Import(import))?;
    
    }
    
  • Retrieve the internal Store instance from the Vm, and retrieve the named module instance from the Store instance.

    
    #![allow(unused)]
    
    fn main() {
    let mut store = vm.store_mut()?;
    let named_instance = store.module(module_name)?;
    assert!(named_instance.get_func("add").is_ok());
    assert!(named_instance.get_table("table").is_ok());
    assert!(named_instance.get_memory("mem").is_ok());
    assert!(named_instance.get_global("global").is_ok());
    
    }
    
  • Register an active module into the Vm.

    
    #![allow(unused)]
    
    fn main() {
    // read the wasm bytes
    let wasm_bytes = wat2wasm(
        br#"
        (module
            (export "fib" (func $fib))
            (func $fib (param $n i32) (result i32)
            (if
            (i32.lt_s
            (get_local $n)
            (i32.const 2)
            )
            (return
            (i32.const 1)
            )
            )
            (return
            (i32.add
            (call $fib
                (i32.sub
                (get_local $n)
                (i32.const 2)
                )
            )
            (call $fib
                (i32.sub
                (get_local $n)
                (i32.const 1)
                )
            )
            )
            )
            )
        )
    "#,
    )?;
    
    // load a wasm module from a in-memory bytes, and the loaded wasm module works as an anoymous
    // module (aka. active module in WasmEdge terminology)
    vm.load_wasm_from_bytes(&wasm_bytes)?;
    
    // validate the loaded active module
    vm.validate()?;
    
    // instatiate the loaded active module
    vm.instantiate()?;
    
    // get the active module instance
    let active_instance = vm.active_module()?;
    assert!(active_instance.get_func("fib").is_ok());
        
    }
    
  • Retrieve the active module from the Vm.

    
    #![allow(unused)]
    
    fn main() {
    // get the active module instance
    let active_instance = vm.active_module()?;
    assert!(active_instance.get_func("fib").is_ok());
    
    }
    

The complete code in this demo can be found on WasmEdge Github.

Example 2

In this example, we'll demonstrate how to use the APIs of Executor to

  • Create an Executor and a Store.

    
    #![allow(unused)]
    
    fn main() {
    // create an Executor context
    let mut executor = Executor::create(None, None)?;
    
    // create a Store context
    let mut store = Store::create()?;
    
    }
    
  • Register an import module into the Executor.

    
    #![allow(unused)]
    
    fn main() {
    // read the wasm bytes
    let wasm_bytes = wat2wasm(
        br#"
    (module
        (export "fib" (func $fib))
        (func $fib (param $n i32) (result i32)
            (if
            (i32.lt_s
            (get_local $n)
            (i32.const 2)
            )
            (return
            (i32.const 1)
            )
            )
            (return
            (i32.add
            (call $fib
            (i32.sub
                (get_local $n)
                (i32.const 2)
            )
            )
            (call $fib
            (i32.sub
                (get_local $n)
                (i32.const 1)
            )
            )
            )
            )
        )
        )
    "#,
    )?;
    
    // load module from a wasm file
    let config = Config::create()?;
    let loader = Loader::create(Some(config))?;
    let module = loader.from_bytes(&wasm_bytes)?;
    
    // validate module
    let config = Config::create()?;
    let validator = Validator::create(Some(config))?;
    validator.validate(&module)?;
    
    // register a wasm module into the store context
    let module_name = "extern";
    let named_instance = executor.register_named_module(&mut store, &module, module_name)?;
    assert!(named_instance.get_func("fib").is_ok());
    
    }
    
  • Register an active module into the Executor.

    
    #![allow(unused)]
    
    fn main() {
    // read the wasm bytes
    let wasm_bytes = wat2wasm(
        br#"
    (module
        (export "fib" (func $fib))
        (func $fib (param $n i32) (result i32)
            (if
            (i32.lt_s
            (get_local $n)
            (i32.const 2)
            )
            (return
            (i32.const 1)
            )
            )
            (return
            (i32.add
            (call $fib
            (i32.sub
                (get_local $n)
                (i32.const 2)
            )
            )
            (call $fib
            (i32.sub
                (get_local $n)
                (i32.const 1)
            )
            )
            )
            )
        )
        )
    "#,
    )?;
    
    // load module from a wasm file
    let config = Config::create()?;
    let loader = Loader::create(Some(config))?;
    let module = loader.from_bytes(&wasm_bytes)?;
    
    // validate module
    let config = Config::create()?;
    let validator = Validator::create(Some(config))?;
    validator.validate(&module)?;
    
    // register a wasm module as an active module
    let active_instance = executor.register_active_module(&mut store, &module)?;
    assert!(active_instance.get_func("fib").is_ok());
    
    }
    

The complete code in this demo can be found on WasmEdge Github.

Python SDK

Coming soon, or you can help out.

WasmEdge in Kubernetes

Developers can leverage container tools such as Kubernetes, Docker and CRI-O to deploy, manage, and run lightweight WebAssembly applications. In this chapter, we will demonstrate how Kubernetes ecosystem tools work with WasmEdge WebAssembly applications.

Compared with Linux containers, WebAssembly could be 100x faster at startup, have a much smaller memory and disk footprint, and have a better-defined safety sandbox. However, the trade-off is that WebAssembly requires its own language SDKs, and compiler toolchains, making it a more constrained developer environment than Linux containers. WebAssembly is increasingly used in Edge Computing scenarios where it is difficult to deploy Linux containers or when the application performance is vital.

One of the great advantages of Linux application containers is the rich ecosystem of tools. The good news is that you can use the exact same tools to manage WebAssembly applications, enabling Linux containers and WebAssembly apps to run side-by-side in the same system.

kubernetes

The contents of this chapter are organized as follows.

  • Quick start provides simple and scripted tutorials to run WasmEdge-based applications as container images in Kubernetes.
  • Demo apps discusses the two demo WasmEdge applications we will run in Kubernetes clusters. Those applications are compiled from Rust source code, packaged as OCI images, and uploaded to Docker Hub.
  • Container runtimes covers how to configure low level container runtimes, such as crun, to load and run WebAssembly OCI images.
  • CRI runtimes covers how to configure and use high level container runtimes, such as CRI-O and containerd, to load and run WebAssembly OCI images on top of low level container runtimes.
  • Kubernetes covers how to configure and use Kubernetes and Kubernetes variations, such as KubeEdge and SuperEdge, to load and run WebAssembly OCI images on top of CRI runtimes.

The goal is to load and run WebAssembly OCI images side by side with Linux OCI images (e.g., today's Docker containers) across the entire Kubernetes stack.

Quick start

We have created Ubuntu-based scripts for you to quickly get started with the following combination of runtimes in a standard Kubernetes setup.

CRI (high level) runtimeOCI (low level) runtime
CRI-Ocrun + WasmEdgeScript
containerdcrun + WasmEdgeScript

CRI-O and crun

You can use the CRI-O install.sh script to install CRI-O and crun on Ubuntu 20.04.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/install.sh | bash

Next, install Kubernetes using the following script.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/kubernetes_crio/install.sh | bash

The simple_wasi_application.sh script shows how to pull a WebAssembly application from Docker Hub, and then run it as a containerized application in Kubernetes.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/kubernetes_crio/simple_wasi_application.sh | bash

You should see results from the WebAssembly program printed in the console log. Here is an example.

containerd and crun

You can use the containerd install.sh script to install containerd and crun on Ubuntu 20.04.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/containerd/install.sh | bash

Next, install Kubernetes using the following script.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/kubernetes_containerd/install.sh | bash

The simple_wasi_application.sh script shows how to pull a WebAssembly application from Docker Hub, and then run it as a containerized application in Kubernetes.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/kubernetes_containerd/simple_wasi_application.sh | bash

You should see results from the WebAssembly program printed in the console log. Here is an example.

Read on to the rest of this chapter to learn how exactly those runtimes are configured.

Demo apps

In this chapter, we will cover two demo apps. We will build them from Rust source code, build OCI images around them, and then publish the images to Docker Hub.

If you have not done so, please

Next, explore the examples

Since we have already built and published those demo apps on Docker Hub, you could also just go straight to the container runtime sections to use these images.

A simple WebAssembly example

In this article, I will show you how to build a container image for a WebAssembly application. It can then be started and managed by Kubernetes ecosystem tools, such as CRI-O, Docker, crun, and Kubernetes.

Prerequisites

If you simply want a wasm bytecode file to test as a container image, you can skip the building process and just download the wasm file here.

If you have not done so already, follow these simple instructions to install Rust.

Download example code

git clone https://github.com/second-state/wasm-learning
cd wasm-learning/cli/wasi

Build the WASM bytecode

rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

The wasm bytecode application is in the target/wasm32-wasi/release/wasi_example_main.wasm file. You can now publish and use it as a container image.

Apply executable permission on the Wasm bytecode

chmod +x target/wasm32-wasi/release/wasi_example_main.wasm

Create Dockerfile

Create a file called Dockerfile in the target/wasm32-wasi/release/ folder with the following content:

FROM scratch
ADD wasi_example_main.wasm /
CMD ["/wasi_example_main.wasm"]

Create container image with annotations

Please note that adding self-defined annotation is still a new feature in buildah.

The crun container runtime can start the above WebAssembly-based container image. But it requires the module.wasm.image/variant=compat annotation on the container image to indicate that it is a WebAssembly application without a guest OS. You can find the details in Official crun repo.

To add module.wasm.image/variant=compat annotation in the container image, you will need the latest buildah. Currently, Docker does not support this feature. Please follow the install instructions of buildah to build the latest buildah binary.

Build and install the latest buildah on Ubuntu

On Ubuntu zesty and xenial, use these commands to prepare for buildah.

sudo apt-get -y install software-properties-common

export OS="xUbuntu_20.04"
sudo bash -c "echo \"deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /\" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
sudo bash -c "curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -"

sudo add-apt-repository -y ppa:alexlarsson/flatpak
sudo apt-get -y -qq update
sudo apt-get -y install bats git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme-dev libseccomp-dev libselinux1-dev skopeo-containers go-md2man containers-common
sudo apt-get -y install golang-1.16 make

Then, follow these steps to build and install buildah on Ubuntu.

mkdir -p ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/containers/buildah ./src/github.com/containers/buildah
cd ./src/github.com/containers/buildah
PATH=/usr/lib/go-1.16/bin:$PATH make
cp bin/buildah /usr/bin/buildah
buildah --help

Create and publish a container image with buildah

In the target/wasm32-wasi/release/ folder, do the following.

$ sudo buildah build --annotation "module.wasm.image/variant=compat" -t wasm-wasi-example .
# make sure docker is install and running
# systemctl status docker
# to make sure regular user can use docker
# sudo usermod -aG docker $USER
# newgrp docker

# You may need to use docker login to create the `~/.docker/config.json` for auth.
$ sudo buildah push --authfile ~/.docker/config.json wasm-wasi-example docker://docker.io/hydai/wasm-wasi-example:with-wasm-annotation

That's it! Now you can try to run it in CRI-O or Kubernetes!

HTTP server example

Let's build a container image for a WebAssembly HTTP service. The HTTP service application is developed in Rust using the WasmEdge networking socket API. Kubernetes could manage the wasm application lifecycle with CRI-O, Docker and Containerd.

Prerequisites

This is a Rust example, which require you to install Rust and WasmEdge before you can Compile and Run the http service.

Download example code

mkdir http_server
cd http_server
wget -q https://raw.githubusercontent.com/second-state/wasmedge_wasi_socket/main/examples/http_server/Cargo.toml
mkdir src
cd src
wget -q https://raw.githubusercontent.com/second-state/wasmedge_wasi_socket/main/examples/http_server/src/main.rs
cd ../

Build the WASM bytecode

rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

The wasm bytecode application is now should be located in the ./target/wasm32-wasi/release/http_server.wasm You can now test run it with wasmedge and then publish it as a container image.

Apply executable permission on the Wasm bytecode

chmod +x ./target/wasm32-wasi/release/http_server.wasm

Running the http_server application bytecode with wasmedge

When you run the bytecode with wasmedge and see the result as the following, you are ready to package the bytecode into the container.

$ wasmedge ./target/wasm32-wasi/release/http_server.wasm
new connection at 1234

You can test the server from another terminal window.

$ curl -X POST http://127.0.0.1:1234 -d 'name=WasmEdge'
echo: name=WasmEdge

Create Dockerfile

Create a file called Dockerfile in the target/wasm32-wasi/release/ folder with the following content:

FROM scratch
ADD http_server.wasm /
CMD ["/http_server.wasm"]

Create container image with annotations

Please note that adding self-defined annotation is still a new feature in buildah.

The crun container runtime can start the above WebAssembly-based container image. But it requires the module.wasm.image/variant=compat annotation on the container image to indicate that it is a WebAssembly application without a guest OS. You can find the details in Official crun repo.

To add module.wasm.image/variant=compat annotation in the container image, you will need the latest buildah. Currently, Docker does not support this feature. Please follow the install instructions of buildah to build the latest buildah binary.

Build and install the latest buildah on Ubuntu

On Ubuntu zesty and xenial, use these commands to prepare for buildah.

sudo apt-get -y install software-properties-common

export OS="xUbuntu_20.04"
sudo bash -c "echo \"deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /\" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
sudo bash -c "curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -"

sudo add-apt-repository -y ppa:alexlarsson/flatpak
sudo apt-get -y -qq update

sudo apt-get -y install bats git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme-dev libseccomp-dev libselinux1-dev skopeo-containers go-md2man containers-common
sudo apt-get -y install golang-1.16 make

Then, follow these steps to build and install buildah on Ubuntu.

mkdir -p ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/containers/buildah ./src/github.com/containers/buildah
cd ./src/github.com/containers/buildah
PATH=/usr/lib/go-1.16/bin:$PATH make
cp bin/buildah /usr/bin/buildah
buildah --help

Create and publish a container image with buildah

In the target/wasm32-wasi/release/ folder, do the following.

sudo buildah build --annotation "module.wasm.image/variant=compat" -t http_server .

#
# make sure docker is install and running
# systemctl status docker
# to make sure regular user can use docker
# sudo usermod -aG docker $USER#
# newgrp docker

# You may need to use docker login to create the `~/.docker/config.json` for auth.
#
# docker login

sudo buildah push --authfile ~/.docker/config.json http_server docker://docker.io/avengermojo/http_server:with-wasm-annotation

That's it! Now you can try to run it in CRI-O or Kubernetes!

Container runtimes

The container image can be started by any OCI-compliant container runtime, such as

  • crun: a high performance and lightweight container runtime written in C
  • runc: a widely used container runtime written in Go
  • youki: a OCI-compatible container runtime implementation written in Rust

crun

The crun project has WasmEdge support baked in. For now, the easiest approach is just built it yourself from source. First, let's make sure that crun dependencies are installed on your Ubuntu 20.04. For other Linux distributions, please see here.

sudo apt update
sudo apt install -y make git gcc build-essential pkgconf libtool \
    libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev \
    go-md2man libtool autoconf python3 automake

Next, configure, build, and install a crun binary with WasmEdge support.

git clone https://github.com/containers/crun
cd crun
./autogen.sh
./configure --with-wasmedge
make
sudo make install

runc

Coming soon, or you can help out

youki

Coming soon, or you can help out

CRI runtimes

The high-level container runtime, such as CRI-O and containerd, pulls container images from registries (e.g., Docker Hub), manages them on disk, and launches a lower-level runtime to run container processes. From this chapter, you can check out specific tutorials for CRI-O and containerd.

CRI-O

Quick start

The GitHub repo contains scripts and Github Actions for running our example apps on CRI-O.

In the sections below, we will explain the steps in the quick start scripts.

Install CRI-O

Use the following commands to install CRI-O on your system.

export OS="xUbuntu_20.04"
export VERSION="1.21"
apt update
apt install -y libseccomp2 || sudo apt update -y libseccomp2
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -

apt-get update
apt-get install criu libyajl2
apt-get install cri-o cri-o-runc cri-tools containernetworking-plugins
systemctl start crio

Configure CRI-O to use crun

CRI-O uses the runc runtime by default and we need to configure it to use crun instead. That is done by adding to two configuration files.

Make sure that you have built and installed the crun binary with WasmEdge support before starting the following steps.

First, create a /etc/crio/crio.conf file and add the following lines as its content. It tells CRI-O to use crun by default.

[crio.runtime]
default_runtime = "crun"

The crun runtime is in turn defined in the /etc/crio/crio.conf.d/01-crio-runc.conf file.

[crio.runtime.runtimes.runc]
runtime_path = "/usr/lib/cri-o-runc/sbin/runc"
runtime_type = "oci"
runtime_root = "/run/runc"
# The above is the original content

# Add our crunw runtime here
[crio.runtime.runtimes.crun]
runtime_path = "/usr/bin/crun"
runtime_type = "oci"
runtime_root = "/run/crun"

Next, restart CRI-O to apply the configuration changes.

systemctl restart crio

Run a simple WebAssembly app

Now, we can run a simple WebAssembly program using CRI-O. A separate article explains how to compile, package, and publish the WebAssembly program as a container image to Docker hub. In this section, we will start off pulling this WebAssembly-based container image from Docker hub using CRI-O tools.

sudo crictl pull docker.io/hydai/wasm-wasi-example:with-wasm-annotation

Next, we need to create two simple configuration files that specifies how CRI-O should run this WebAssembly image in a sandbox. We already have those two files container_wasi.json and sandbox_config.json. You can just download them to your local directory as follows.

wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/sandbox_config.json
wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/container_wasi.json

Now you can use CRI-O to create a pod and a container using the specified configurations.

# Create the POD. Output will be different from example.
$ sudo crictl runp sandbox_config.json
7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4
# Set a helper variable for later use.
$ POD_ID=7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4

# Create the container instance. Output will be different from example.
$ sudo crictl create $POD_ID container_wasi.json sandbox_config.json
# Set a helper variable for later use.
CONTAINER_ID=1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

Starting the container would execute the WebAssembly program. You can see the output in the console.

# List the container, the state should be `Created`
$ sudo crictl ps -a
CONTAINER           IMAGE                                          CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:with-wasm-annotation   About a minute ago   Created             podsandbox1-wasm-wasi   0                   7992e75df00cc

# Start the container
$ sudo crictl start $CONTAINER_ID

# Check the container status again.
# If the container is not finishing its job, you will see the Running state
# Because this example is very tiny. You may see Exited at this moment.
$ sudo crictl ps -a
CONTAINER           IMAGE                                          CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:with-wasm-annotation   About a minute ago   Running             podsandbox1-wasm-wasi   0                   7992e75df00cc

# When the container is finished. You can see the state becomes Exited.
$ sudo crictl ps -a
CONTAINER           IMAGE                                          CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:with-wasm-annotation   About a minute ago   Exited              podsandbox1-wasm-wasi   0                   7992e75df00cc

# Check the container's logs. It should show outputs from the WebAssembly programs
$ sudo crictl logs $CONTAINER_ID

Test 1: Print Random Number
Random number: 960251471

Test 2: Print Random Bytes
Random bytes: [50, 222, 62, 128, 120, 26, 64, 42, 210, 137, 176, 90, 60, 24, 183, 56, 150, 35, 209, 211, 141, 146, 2, 61, 215, 167, 194, 1, 15, 44, 156, 27, 179, 23, 241, 138, 71, 32, 173, 159, 180, 21, 198, 197, 247, 80, 35, 75, 245, 31, 6, 246, 23, 54, 9, 192, 3, 103, 72, 186, 39, 182, 248, 80, 146, 70, 244, 28, 166, 197, 17, 42, 109, 245, 83, 35, 106, 130, 233, 143, 90, 78, 155, 29, 230, 34, 58, 49, 234, 230, 145, 119, 83, 44, 111, 57, 164, 82, 120, 183, 194, 201, 133, 106, 3, 73, 164, 155, 224, 218, 73, 31, 54, 28, 124, 2, 38, 253, 114, 222, 217, 202, 59, 138, 155, 71, 178, 113]

Test 3: Call an echo function
Printed from wasi: This is from a main function
This is from a main function

Test 4: Print Environment Variables
The env vars are as follows.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TERM: xterm
HOSTNAME: crictl_host
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The args are as follows.
/var/lib/containers/storage/overlay/006e7cf16e82dc7052994232c436991f429109edea14a8437e74f601b5ee1e83/merged/wasi_example_main.wasm
50000000

Test 5: Create a file `/tmp.txt` with content `This is in a file`

Test 6: Read the content from the previous file
File content is This is in a file

Test 7: Delete the previous file

Next, you can try to run the app in Kubernetes!

Run a HTTP server app

Finally, we can run a simple WebAssembly-based HTTP micro-service in CRI-O. A separate article explains how to compile, package, and publish the WebAssembly program as a container image to Docker hub. In this section, we will start off pulling this WebAssembly-based container image from Docker hub using CRI-O tools.

sudo crictl pull docker.io/avengermojo/http_server:with-wasm-annotation

Next, we need to create two simple configuration files that specifies how CRI-O should run this WebAssembly image in a sandbox. We already have those two files container_http_server.json and sandbox_config.json. You can just download them to your local directory as follows.

The sandbox_config.json file is the same for the simple WASI example and the HTTP server example. The other container_*.json file is application specific as it contains the application's Docker Hub URL.

wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/sandbox_config.json
wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/http_server/container_http_server.json

Now you can use CRI-O to create a pod and a container using the specified configurations.

# Create the POD. Output will be different from example.
$ sudo crictl runp sandbox_config.json
7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4
# Set a helper variable for later use.
$ POD_ID=7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4

# Create the container instance. Output will be different from example.
$ sudo crictl create $POD_ID container_http_server.json sandbox_config.json
# Set a helper variable for later use.
CONTAINER_ID=1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

Starting the container would execute the WebAssembly program. You can see the output in the console.

# Start the container
$ sudo crictl start $CONTAINER_ID

# Check the container status. It should be Running. 
# If not, wait a few seconds and check again
$ sudo crictl ps -a
CONTAINER           IMAGE                                          CREATED                  STATE               NAME                ATTEMPT             POD ID
4eeddf8613691       avengermojo/http_server:with-wasm-annotation   Less than a second ago   Running             http_server         0                   1d84f30e7012e

# Check the container's logs to see the HTTP server is listening at port 1234
$ sudo crictl logs $CONTAINER_ID
new connection at 1234

# Get the IP address assigned to the container
$ sudo crictl inspect $CONTAINER_ID | grep IP.0 | cut -d: -f 2 | cut -d'"' -f 2
10.85.0.2

# Test the HTTP service at that IP address
$ curl -d "name=WasmEdge" -X POST http://10.85.0.2:1234
echo: name=WasmEdge

Next, you can try to run it in Kubernetes!

containerd

Quick start

The GitHub repo contains scripts and Github Actions for running our example apps on containerd.

In the sections below, we will explain the steps in the quick start scripts.

Install containerd

Use the following commands to install containerd on your system.

export VERSION="1.5.7"
echo -e "Version: $VERSION"
echo -e "Installing libseccomp2 ..."
sudo apt install -y libseccomp2
echo -e "Installing wget"
sudo apt install -y wget

wget https://github.com/containerd/containerd/releases/download/v${VERSION}/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
wget https://github.com/containerd/containerd/releases/download/v${VERSION}/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz.sha256sum
sha256sum --check cri-containerd-cni-${VERSION}-linux-amd64.tar.gz.sha256sum

sudo tar --no-overwrite-dir -C / -xzf cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
sudo systemctl daemon-reload

Configure containerd to use crun as the underlying OCI runtime. It makes changes to the /etc/containerd/config.toml file.

sudo mkdir -p /etc/containerd/
sudo bash -c "containerd config default > /etc/containerd/config.toml"
wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/containerd/containerd_config.diff
sudo patch -d/ -p0 < containerd_config.diff

Start the containerd service.

sudo systemctl start containerd

Next, make sure that you have built and installed the crun binary with WasmEdge support before running the following examples.

Run a simple WebAssembly app

Now, we can run a simple WebAssembly program using containerd. A separate article explains how to compile, package, and publish the WebAssembly program as a container image to Docker hub. In this section, we will start off pulling this WebAssembly-based container image from Docker hub using containerd tools.

sudo ctr i pull docker.io/hydai/wasm-wasi-example:with-wasm-annotation

Now, you can run the example in just one line with ctr (the containerd cli).

sudo ctr run --rm --runc-binary crun --runtime io.containerd.runc.v2 --label module.wasm.image/variant=compat docker.io/hydai/wasm-wasi-example:with-wasm-annotation wasm-example /wasi_example_main.wasm 50000000

Starting the container would execute the WebAssembly program. You can see the output in the console.

Creating POD ...
Random number: -1678124602
Random bytes: [12, 222, 246, 184, 139, 182, 97, 3, 74, 155, 107, 243, 20, 164, 175, 250, 60, 9, 98, 25, 244, 92, 224, 233, 221, 196, 112, 97, 151, 155, 19, 204, 54, 136, 171, 93, 204, 129, 177, 163, 187, 52, 33, 32, 63, 104, 128, 20, 204, 60, 40, 183, 236, 220, 130, 41, 74, 181, 103, 178, 43, 231, 92, 211, 219, 47, 223, 137, 70, 70, 132, 96, 208, 126, 142, 0, 133, 166, 112, 63, 126, 164, 122, 49, 94, 80, 26, 110, 124, 114, 108, 90, 62, 250, 195, 19, 189, 203, 175, 189, 236, 112, 203, 230, 104, 130, 150, 39, 113, 240, 17, 252, 115, 42, 12, 185, 62, 145, 161, 3, 37, 161, 195, 138, 232, 39, 235, 222]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file

Next, you can try to run it in Kubernetes!

Run a HTTP server app

Finally, we can run a simple WebAssembly-based HTTP micro-service in containerd. A separate article explains how to compile, package, and publish the WebAssembly program as a container image to Docker hub. In this section, we will start off pulling this WebAssembly-based container image from Docker hub using containerd tools.

sudo ctr i pull docker.io/avengermojo/http_server:with-wasm-annotation

Now, you can run the example in just one line with ctr (the containerd cli). Notice that we are running the container with --net-host so that the HTTP server inside the WasmEdge container is accessible from the outside shell.

sudo ctr run --rm --net-host --runc-binary crun --runtime io.containerd.runc.v2 --label module.wasm.image/variant=compat docker.io/avengermojo/http_server:with-wasm-annotation http-server-example /http_server.wasm

Starting the container would execute the WebAssembly program. You can see the output in the console.

new connection at 1234

# Test the HTTP service at that IP address
curl -d "name=WasmEdge" -X POST http://127.0.0.1:1234
echo: name=WasmEdge

Next, you can try to run it in Kubernetes!

Kubernetes

Most high-level container runtimes implement Kubernetes' CRI (Container Runtime Interface) spec so that they can be managed by Kubernetes tools. That means you can use Kubernetes tools to manage the WebAssembly app image in pods and namespaces. Check out specific instructions for different flavors of Kubernetes setup in this chapter.

Kubernetes + CRI-O

Quick start

The GitHub repo contains scripts and Github Actions for running our example apps on Kubernetes + CRI-O.

In the rest of this section, we will explain the steps in detail. We will assume that you have already installed and configured CRI-O to work with WasmEdge container images.

Install and start Kubernetes

Run the following commands from a terminal window. It sets up Kubernetes for local development.

# Install go
$ wget https://golang.org/dl/go1.17.1.linux-amd64.tar.gz
$ sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.17.1.linux-amd64.tar.gz
source /home/${USER}/.profile

# Clone k8s
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
git checkout v1.22.2

# Install etcd with hack script in k8s
sudo CGROUP_DRIVER=systemd CONTAINER_RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT='unix:///var/run/crio/crio.sock' ./hack/install-etcd.sh
export PATH="/home/${USER}/kubernetes/third_party/etcd:${PATH}"
sudo cp third_party/etcd/etcd* /usr/local/bin/

# After run the above command, you can find the following files: /usr/local/bin/etcd  /usr/local/bin/etcdctl  /usr/local/bin/etcdutl

# Build and run k8s with CRI-O
sudo apt-get install -y build-essential
sudo CGROUP_DRIVER=systemd CONTAINER_RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT='unix:///var/run/crio/crio.sock' ./hack/local-up-cluster.sh

... ...
Local Kubernetes cluster is running. Press Ctrl-C to shut it down.

Do NOT close your terminal window. Kubernetes is running!

Run WebAssembly container images in Kubernetes

Finally, we can run WebAssembly programs in Kubernetes as containers in pods. In this section, we will start from another terminal window and start using the cluster.

export KUBERNETES_PROVIDER=local

sudo cluster/kubectl.sh config set-cluster local --server=https://localhost:6443 --certificate-authority=/var/run/kubernetes/server-ca.crt
sudo cluster/kubectl.sh config set-credentials myself --client-key=/var/run/kubernetes/client-admin.key --client-certificate=/var/run/kubernetes/client-admin.crt
sudo cluster/kubectl.sh config set-context local --cluster=local --user=myself
sudo cluster/kubectl.sh config use-context local
sudo cluster/kubectl.sh

Let's check the status to make sure that the cluster is running.

$ sudo cluster/kubectl.sh cluster-info

# Expected output
Cluster "local" set.
User "myself" set.
Context "local" created.
Switched to context "local".
Kubernetes control plane is running at https://localhost:6443
CoreDNS is running at https://localhost:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

A simple WebAssembly app

A separate article explains how to compile, package, and publish a simple WebAssembly WASI program as a container image to Docker hub. Run the WebAssembly-based image from Docker Hub in the Kubernetes cluster as follows.

sudo cluster/kubectl.sh run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" /wasi_example_main.wasm 50000000

The output from the containerized application is printed into the console.

Random number: 401583443
Random bytes: [192, 226, 162, 92, 129, 17, 186, 164, 239, 84, 98, 255, 209, 79, 51, 227, 103, 83, 253, 31, 78, 239, 33, 218, 68, 208, 91, 56, 37, 200, 32, 12, 106, 101, 241, 78, 161, 16, 240, 158, 42, 24, 29, 121, 78, 19, 157, 185, 32, 162, 95, 214, 175, 46, 170, 100, 212, 33, 27, 190, 139, 121, 121, 222, 230, 125, 251, 21, 210, 246, 215, 127, 176, 224, 38, 184, 201, 74, 76, 133, 233, 129, 48, 239, 106, 164, 190, 29, 118, 71, 79, 203, 92, 71, 68, 96, 33, 240, 228, 62, 45, 196, 149, 21, 23, 143, 169, 163, 136, 206, 214, 244, 26, 194, 25, 101, 8, 236, 247, 5, 164, 117, 40, 220, 52, 217, 92, 179]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file
pod "wasi-demo-2" deleted

A WebAssembly-based HTTP service

A separate article explains how to compile, package, and publish a simple WebAssembly HTTP service application as a container image to Docker hub. Since the HTTP service container requires networking support provided by Kubernetes, we will use a k8s-http_server.yaml file to specify its exact configuration.

apiVersion: v1
kind: Pod
metadata:
  name: http-server
  namespace: default
  annotations:
    module.wasm.image/variant: compat
spec:
  hostNetwork: true
  containers:
  - name: http-server
    image: avengermojo/http_server:with-wasm-annotation
    command: [ "/http_server.wasm" ]
    ports:
    - containerPort: 1234
      protocol: TCP
    livenessProbe:
      tcpSocket:
        port: 1234
      initialDelaySeconds: 3
      periodSeconds: 30

Run the WebAssembly-based image from Docker Hub using the above k8s-http_server.yaml file in the Kubernetes cluster as follows.

sudo ./kubernetes/cluster/kubectl.sh apply -f k8s-http_server.yaml

Use the following command to see the running container applications and their IP addresses. Since we are using hostNetwork in the yaml configuration, the HTTP server image is running on the local network with IP address 127.0.0.1.

$ sudo cluster/kubectl.sh get pod --all-namespaces -o wide

NAMESPACE     NAME                       READY   STATUS             RESTARTS      AGE   IP          NODE        NOMINATED NODE   READINESS GATES
default       http-server                1/1     Running            1 (26s ago)     60s     127.0.0.1   127.0.0.1   <none>           <none>

Now, you can use the curl command to access the HTTP service.

$ curl -d "name=WasmEdge" -X POST http://127.0.0.1:1234
echo: name=WasmEdge

That's it!

Kubernetes + containerd

Quick start

The GitHub repo contains scripts and Github Actions for running our example apps on Kubernetes + containerd.

In the rest of this section, we will explain the steps in detail. We will assume that you have already installed and configured containerd to work with WasmEdge container images.

Install and start Kubernetes

Run the following commands from a terminal window. It sets up Kubernetes for local development.

# Install go
$ wget https://golang.org/dl/go1.17.1.linux-amd64.tar.gz
$ sudo rm -rf /usr/local/go
$ sudo tar -C /usr/local -xzf go1.17.1.linux-amd64.tar.gz
$ source /home/${USER}/.profile

# Clone k8s
$ git clone https://github.com/kubernetes/kubernetes.git
$ cd kubernetes
$ git checkout v1.22.2

# Install etcd with hack script in k8s
$ sudo CGROUP_DRIVER=systemd CONTAINER_RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT='unix:///var/run/crio/crio.sock' ./hack/install-etcd.sh
$ export PATH="/home/${USER}/kubernetes/third_party/etcd:${PATH}"
$ sudo cp third_party/etcd/etcd* /usr/local/bin/

# After run the above command, you can find the following files: /usr/local/bin/etcd  /usr/local/bin/etcdctl  /usr/local/bin/etcdutl

# Build and run k8s with containerd
$ sudo apt-get install -y build-essential
$ sudo CGROUP_DRIVER=systemd CONTAINER_RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT='unix:///var/run/crio/crio.sock' ./hack/local-up-cluster.sh

... ...
Local Kubernetes cluster is running. Press Ctrl-C to shut it down.

Do NOT close your terminal window. Kubernetes is running!

Run WebAssembly container images in Kubernetes

Finally, we can run WebAssembly programs in Kubernetes as containers in pods. In this section, we will start from another terminal window and start using the cluster.

export KUBERNETES_PROVIDER=local

sudo cluster/kubectl.sh config set-cluster local --server=https://localhost:6443 --certificate-authority=/var/run/kubernetes/server-ca.crt
sudo cluster/kubectl.sh config set-credentials myself --client-key=/var/run/kubernetes/client-admin.key --client-certificate=/var/run/kubernetes/client-admin.crt
sudo cluster/kubectl.sh config set-context local --cluster=local --user=myself
sudo cluster/kubectl.sh config use-context local
sudo cluster/kubectl.sh

Let's check the status to make sure that the cluster is running.

$ sudo cluster/kubectl.sh cluster-info

# Expected output
Cluster "local" set.
User "myself" set.
Context "local" created.
Switched to context "local".
Kubernetes control plane is running at https://localhost:6443
CoreDNS is running at https://localhost:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

A simple WebAssembly app

A separate article explains how to compile, package, and publish a simple WebAssembly WASI program as a container image to Docker hub. Run the WebAssembly-based image from Docker Hub in the Kubernetes cluster as follows.

sudo cluster/kubectl.sh run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" --overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"hostNetwork": true}}' /wasi_example_main.wasm 50000000

The output from the containerized application is printed into the console.

Random number: 401583443
Random bytes: [192, 226, 162, 92, 129, 17, 186, 164, 239, 84, 98, 255, 209, 79, 51, 227, 103, 83, 253, 31, 78, 239, 33, 218, 68, 208, 91, 56, 37, 200, 32, 12, 106, 101, 241, 78, 161, 16, 240, 158, 42, 24, 29, 121, 78, 19, 157, 185, 32, 162, 95, 214, 175, 46, 170, 100, 212, 33, 27, 190, 139, 121, 121, 222, 230, 125, 251, 21, 210, 246, 215, 127, 176, 224, 38, 184, 201, 74, 76, 133, 233, 129, 48, 239, 106, 164, 190, 29, 118, 71, 79, 203, 92, 71, 68, 96, 33, 240, 228, 62, 45, 196, 149, 21, 23, 143, 169, 163, 136, 206, 214, 244, 26, 194, 25, 101, 8, 236, 247, 5, 164, 117, 40, 220, 52, 217, 92, 179]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file
pod "wasi-demo-2" deleted

A WebAssembly-based HTTP service

A separate article explains how to compile, package, and publish a simple WebAssembly HTTP service application as a container image to Docker hub. Run the WebAssembly-based image from Docker Hub in the Kubernetes cluster as follows.

sudo cluster/kubectl.sh run --restart=Never http-server --image=avengermojo/http_server:with-wasm-annotation --annotations="module.wasm.image/variant=compat" --overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"hostNetwork": true}}'

Since we are using hostNetwork in the kubectl run command, the HTTP server image is running on the local network with IP address 127.0.0.1. Now, you can use the curl command to access the HTTP service.

$ curl -d "name=WasmEdge" -X POST http://127.0.0.1:1234
echo: name=WasmEdge

That's it!

Kubernetes in Docker (KinD)

KinD is a Kubernetes distribution that runs inside Docker and is well suited for local development or integration testing. It runs containerd as CRI and runc as OCI Runtime.

Quick start

As prerequisite we need to install KinD first. To do that the quick start guide and the release page can be used to install the latest version of the KinD CLI.

If KinD is installed we can directly start with the example from here:

# Create a "WASM in KinD" Cluster
kind create cluster --image ghcr.io/liquid-reply/kind-crun-wasm:v1.23.0
# Run the example
kubectl run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" /wasi_example_main.wasm 50000000

In the rest of this section, we will explain how to create a KinD node image with wasmedge support.

Build crun

KinD uses the kindest/node image for the control plane and worker nodes. The image contains containerd as CRI and runc as OCI Runtime. To enable WasmEdge support we replace runc with crun.

For the node image we only need the crun binary and not the entire build toolchain. Therefore we use a multistage dockerfile where we create crun in the first step and only copy the crun binary to the node image.

FROM ubuntu:21.10 AS builder
WORKDIR /data
RUN DEBIAN_FRONTEND=noninteractive apt update \
    && DEBIAN_FRONTEND=noninteractive apt install -y curl make git gcc build-essential pkgconf libtool libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev go-md2man libtool autoconf python3 automake \
    && curl https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -p /usr/local \
    && git clone --single-branch --branch feat/handler_per_container https://github.com/liquid-reply/crun \
    && cd crun \
    && ./autogen.sh \
    && ./configure --with-wasmedge --enable-embedded-yajl\
    && make 

...

Now we have a fresh crun binary with wasmedge enabled under /data/crun/crun that we can copy from this container in the next step.

Replace crun and configure containerd

Both runc and crun implement the OCI runtime spec and they have the same CLI parametes. Therefore we can just replace the runc binary with our crun-wasmedge binary we created before.

Since crun is using some shared libraries we need to install libyajl, wasmedge and criu to make our crun work.

Now we already have a KinD that uses crun instead of runc. Now we just need two config changes. The first one in the /etc/containerd/config.toml where we add the pod_annotationsthat can be passed to the runtime:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  pod_annotations = ["*.wasm.*", "wasm.*", "module.wasm.image/*", "*.module.wasm.image", "module.wasm.image/variant.*"]

And the second one to the /etc/containerd/cri-base.json where we remove a hook that causes some issues.

The resulting dockerfile looks as follows:

...

FROM kindest/node:v1.23.0

COPY config.toml /etc/containerd/config.toml
COPY --from=builder /data/crun/crun /usr/local/sbin/runc
COPY --from=builder /usr/local/lib/libwasmedge_c.so /usr/local/lib/libwasmedge_c.so

RUN echo "Installing Packages ..." \
    && bash -c 'cat <<< $(jq "del(.hooks.createContainer)" /etc/containerd/cri-base.json) > /etc/containerd/cri-base.json' \
    && ldconfig

Build and test

Finally we can build a new node-wasmedge image. To test it, we create a kind cluster from that image and run the simple app example.

docker build -t node-wasmedge .
kind create cluster --image node-wasmedge
# Now you can run the example to validate your cluster
kubectl run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" /wasi_example_main.wasm 50000000

Create a crun demo for KubeEdge

1. Setup Cloud Side (KubeEdge Master Node)

Install Go

$ wget https://golang.org/dl/go1.17.3.linux-amd64.tar.gz
$ tar xzvf go1.17.3.linux-amd64.tar.gz

$ export PATH=/home/${user}/go/bin:$PATH
$ go version
go version go1.17.3 linux/amd64

Install CRI-O

Please see CRI-O Installation Instructions.

# Create the .conf file to load the modules at bootup
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# Set up required sysctl params, these persist across reboots.
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system
export OS="xUbuntu_20.04"
export VERSION="1.21"
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF

curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers-cri-o.gpg add -

sudo apt-get update
sudo apt-get install cri-o cri-o-runc

sudo systemctl daemon-reload
sudo systemctl enable crio --now
sudo systemctl status cri-o

output:

$ sudo systemctl status cri-o
● crio.service - Container Runtime Interface for OCI (CRI-O)
     Loaded: loaded (/lib/systemd/system/crio.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2021-12-06 13:46:29 UTC; 16h ago
       Docs: https://github.com/cri-o/cri-o
   Main PID: 6868 (crio)
      Tasks: 14
     Memory: 133.2M
     CGroup: /system.slice/crio.service
             └─6868 /usr/bin/crio

Dec 07 06:04:13 master crio[6868]: time="2021-12-07 06:04:13.694226800Z" level=info msg="Checking image status: k8s.gcr.io/pause:3.4.1" id=1dbb722e-f031-410c-9f45-5d4b5760163e name=/runtime.v1alpha2.ImageServic>
Dec 07 06:04:13 master crio[6868]: time="2021-12-07 06:04:13.695739507Z" level=info msg="Image status: &{0xc00047fdc0 map[]}" id=1dbb722e-f031-410c-9f45-5d4b5760163e name=/runtime.v1alpha2.ImageService/ImageSta>
Dec 07 06:09:13 master crio[6868]: time="2021-12-07 06:09:13.698823984Z" level=info msg="Checking image status: k8s.gcr.io/pause:3.4.1" id=661b754b-48a4-401b-a03f-7f7a553c7eb6 name=/runtime.v1alpha2.ImageServic>
Dec 07 06:09:13 master crio[6868]: time="2021-12-07 06:09:13.703259157Z" level=info msg="Image status: &{0xc0004d98f0 map[]}" id=661b754b-48a4-401b-a03f-7f7a553c7eb6 name=/runtime.v1alpha2.ImageService/ImageSta>
Dec 07 06:14:13 master crio[6868]: time="2021-12-07 06:14:13.707778419Z" level=info msg="Checking image status: k8s.gcr.io/pause:3.4.1" id=8c7e4d36-871a-452e-ab55-707053604077 name=/runtime.v1alpha2.ImageServic>
Dec 07 06:14:13 master crio[6868]: time="2021-12-07 06:14:13.709379469Z" level=info msg="Image status: &{0xc000035030 map[]}" id=8c7e4d36-871a-452e-ab55-707053604077 name=/runtime.v1alpha2.ImageService/ImageSta>
Dec 07 06:19:13 master crio[6868]: time="2021-12-07 06:19:13.713158978Z" level=info msg="Checking image status: k8s.gcr.io/pause:3.4.1" id=827b6315-f145-4f76-b8da-31653d5892a2 name=/runtime.v1alpha2.ImageServic>
Dec 07 06:19:13 master crio[6868]: time="2021-12-07 06:19:13.714030148Z" level=info msg="Image status: &{0xc000162bd0 map[]}" id=827b6315-f145-4f76-b8da-31653d5892a2 name=/runtime.v1alpha2.ImageService/ImageSta>
Dec 07 06:24:13 master crio[6868]: time="2021-12-07 06:24:13.716746612Z" level=info msg="Checking image status: k8s.gcr.io/pause:3.4.1" id=1d53a917-4d98-4723-9ea8-a2951a472cff name=/runtime.v1alpha2.ImageServic>
Dec 07 06:24:13 master crio[6868]: time="2021-12-07 06:24:13.717381882Z" level=info msg="Image status: &{0xc00042ce00 map[]}" id=1d53a917-4d98-4723-9ea8-a2951a472cff name=/runtime.v1alpha2.ImageService/ImageSta>

Install and Creating a cluster with kubeadm for K8s

Please see Creating a cluster with kubeadm.

Install K8s

sudo apt-get update
sudo apt-get install -y apt-transport-https curl
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt update
K_VER="1.21.0-00"
sudo apt install -y kubelet=${K_VER} kubectl=${K_VER} kubeadm=${K_VER}
sudo apt-mark hold kubelet kubeadm kubectl

Create a cluster with kubeadm

#kubernetes scheduler requires this setting to be done.
$ sudo swapoff -a
$ sudo vim /etc/fstab
mark contain swapfile of row

$ cat /etc/cni/net.d/100-crio-bridge.conf
{
    "cniVersion": "0.3.1",
    "name": "crio",
    "type": "bridge",
    "bridge": "cni0",
    "isGateway": true,
    "ipMasq": true,
    "hairpinMode": true,
    "ipam": {
        "type": "host-local",
        "routes": [
            { "dst": "0.0.0.0/0" },
            { "dst": "1100:200::1/24" }
        ],
        "ranges": [
            [{ "subnet": "10.85.0.0/16" }],
            [{ "subnet": "1100:200::/24" }]
        ]
    }
}
$ export CIDR=10.85.0.0/16
$ sudo kubeadm init --apiserver-advertise-address=192.168.122.160 --pod-network-cidr=$CIDR --cri-socket=/var/run/crio/crio.sock

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

output:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a Pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  /docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>

To make kubectl work for your non-root user, run these commands, which are also part of the kubeadm init output:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Setup KubeEdge Master Node

Please see Deploying using Keadm.

IMPORTANT NOTE:

  1. At least one of kubeconfig or master must be configured correctly, so that it can be used to verify the version and other info of the k8s cluster.
  2. Please make sure edge node can connect cloud node using local IP of cloud node, or you need to specify public IP of cloud node with --advertise-address flag.
  3. --advertise-address(only work since 1.3 release) is the address exposed by the cloud side (will be added to the SANs of the CloudCore certificate), the default value is the local IP.
wget https://github.com/kubeedge/kubeedge/releases/download/v1.8.0/keadm-v1.8.0-linux-amd64.tar.gz
tar xzvf keadm-v1.8.0-linux-amd64.tar.gz
cd keadm-v1.8.0-linux-amd64/keadm/
sudo ./keadm init --advertise-address=192.168.122.160 --kube-config=/home/${user}/.kube/config

output:

Kubernetes version verification passed, KubeEdge installation will start...
...
KubeEdge cloudcore is running, For logs visit:  /var/log/kubeedge/cloudcore.log

2. Setup Edge Side (KubeEdge Worker Node)

You can use the CRI-O install.sh script to install CRI-O and crun on Ubuntu 20.04.

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/crio/install.sh | bash

Install Go on Edge Side

$ wget https://golang.org/dl/go1.17.3.linux-amd64.tar.gz
$ tar xzvf go1.17.3.linux-amd64.tar.gz

$ export PATH=/home/${user}/go/bin:$PATH
$ go version
go version go1.17.3 linux/amd64

Get Token From Cloud Side

Run keadm gettoken in cloud side will return the token, which will be used when joining edge nodes.

$ sudo ./keadm gettoken --kube-config=/home/${user}/.kube/config
27a37ef16159f7d3be8fae95d588b79b3adaaf92727b72659eb89758c66ffda2.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTAyMTYwNzd9.JBj8LLYWXwbbvHKffJBpPd5CyxqapRQYDIXtFZErgYE

Download Kubeedge and join edge nodes

Please see Setting different container runtime with CRI and Deploying using Keadm.

$ wget https://github.com/kubeedge/kubeedge/releases/download/v1.8.0/keadm-v1.8.0-linux-amd64.tar.gz
$ tar xzvf keadm-v1.8.0-linux-amd64.tar.gz
$ cd keadm-v1.8.0-linux-amd64/keadm/

$ sudo ./keadm join \
--cloudcore-ipport=192.168.122.160:10000 \
--edgenode-name=edge \
--token=b4550d45b773c0480446277eed1358dcd8a02a0c214646a8082d775f9c447d81.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Mzg4ODUzNzd9.A9WOYJFrgL2swVGnydpb4gMojyvyoNPCXaA4rXGowqU \
--remote-runtime-endpoint=unix:///var/run/crio/crio.sock \
--runtimetype=remote \
--cgroupdriver=systemd

Output:

Host has mosquit+ already installed and running. Hence skipping the installation steps !!!
...
KubeEdge edgecore is running, For logs visit:  /var/log/kubeedge/edgecore.log

Get Edge Node Status From Cloud Side

Output:

kubectl get node
NAME       STATUS    ROLES                  AGE   VERSION
edge       Ready     agent,edge             10s   v1.19.3-kubeedge-v1.8.2
master     Ready     control-plane,master   68m   v1.21.0

3. Enable kubectl logs Feature

Before metrics-server deployed, kubectl logs feature must be activated, please see here.

4. Run a simple WebAssembly app

We can run the WebAssembly-based image from Docker Hub in the Kubernetes cluster.

Cloud Side

$ kubectl run -it --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" /wasi_example_main.wasm 50000000

Random number: -1694733782
Random bytes: [6, 226, 176, 126, 136, 114, 90, 2, 216, 17, 241, 217, 143, 189, 123, 197, 17, 60, 49, 37, 71, 69, 67, 108, 66, 39, 105, 9, 6, 72, 232, 238, 102, 5, 148, 243, 249, 183, 52, 228, 54, 176, 63, 249, 216, 217, 46, 74, 88, 204, 130, 191, 182, 19, 118, 193, 77, 35, 189, 6, 139, 68, 163, 214, 231, 100, 138, 246, 185, 47, 37, 49, 3, 7, 176, 97, 68, 124, 20, 235, 145, 166, 142, 159, 114, 163, 186, 46, 161, 144, 191, 211, 69, 19, 179, 241, 8, 207, 8, 112, 80, 170, 33, 51, 251, 33, 105, 0, 178, 175, 129, 225, 112, 126, 102, 219, 106, 77, 242, 104, 198, 238, 193, 247, 23, 47, 22, 29]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file

The WebAssembly app of pod successfully deploy to edge node.

$ kubectl describe pod wasi-demo

Name:         wasi-demo
Namespace:    default
Priority:     0
Node:         edge/192.168.122.229
Start Time:   Mon, 06 Dec 2021 15:45:34 +0000
Labels:       run=wasi-demo
Annotations:  module.wasm.image/variant: compat
Status:       Succeeded
IP:           
IPs:          <none>
Containers:
  wasi-demo:
    Container ID:  cri-o://1ae4d0d7f671050331a17e9b61b5436bf97ad35ad0358bef043ab820aed81069
    Image:         hydai/wasm-wasi-example:with-wasm-annotation
    Image ID:      docker.io/hydai/wasm-wasi-example@sha256:525aab8d6ae8a317fd3e83cdac14b7883b92321c7bec72a545edf276bb2100d6
    Port:          <none>
    Host Port:     <none>
    Args:
      /wasi_example_main.wasm
      50000000
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Mon, 06 Dec 2021 15:45:33 +0000
      Finished:     Mon, 06 Dec 2021 15:45:33 +0000
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bhszr (ro)
Conditions:
  Type           Status
  Initialized    True 
  Ready          False 
  PodScheduled   True 
Volumes:
  kube-api-access-bhszr:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------

Edge Side

$ sudo crictl ps -a
CONTAINER           IMAGE                                                                                           CREATED             STATE               NAME                ATTEMPT             POD ID
1ae4d0d7f6710       0423b8eb71e312b8aaa09a0f0b6976381ff567d5b1e5729bf9b9aa87bff1c9f3                                16 minutes ago      Exited              wasi-demo           0                   2bc2ac0c32eda
1e6c7cb6bc731       k8s.gcr.io/kube-proxy@sha256:2a25285ff19f9b4025c8e54dac42bb3cd9aceadc361f2570489b8d723cb77135   18 minutes ago      Running             kube-proxy          0                   8b7e7388ad866

That's it.

5. Demo Run Screen Recording

asciicast

SuperEdge

Install Superedge

One-click install of edge Kubernetes cluster

  • Download the installation package

Choose installation package according to your installation node CPU architecture [amd64, arm64]

arch=amd64 version=v0.6.0 && rm -rf edgeadm-linux-* && wget https://superedge-1253687700.cos.ap-guangzhou.myqcloud.com/$version/$arch/edgeadm-linux-containerd-$arch-$version.tgz && tar -xzvf edgeadm-linux-* && cd edgeadm-linux-$arch-$version && ./edgeadm
  • Install edge Kubernetes master node with containerd runtime
./edgeadm init --kubernetes-version=1.18.2 --image-repository superedge.tencentcloudcr.com/superedge --service-cidr=10.96.0.0/12 --pod-network-cidr=192.168.0.0/16 --install-pkg-path ./kube-linux-*.tar.gz --apiserver-cert-extra-sans=<Master Public IP> --apiserver-advertise-address=<Master Intranet IP> --enable-edge=true --runtime=containerd
  • Join edge node with containerd runtime
./edgeadm join <Master Public/Intranet IP Or Domain>:Port --token xxxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxx --install-pkg-path <edgeadm kube-* install package address path> --enable-edge=true --runtime=containerd

See the detailed processOne-click install of edge Kubernetes cluster

Other installation, deployment, and administration, see our Tutorial.

Install WasmEdge

Use the simple install script to install WasmEdge on your edge node.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

Build And install Crun with WasmEdge

The crun project has WasmEdge support baked in. For now, the easiest approach is just to build it yourself from source. First, let's make sure that crun dependencies are installed on your Ubuntu 20.04. For other Linux distributions, please see here.

sudo apt update
sudo apt install -y make git gcc build-essential pkgconf libtool \
    libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev \
    go-md2man libtool autoconf python3 automake

Next, configure, build, and install a crun binary with WasmEdge support.

git clone https://github.com/containers/crun
cd crun
./autogen.sh
./configure --with-wasmedge
make
sudo make install

Reconfigure containerd with crun runtime

Superedge containerd node has default config, we should modify the configuration file(/etc/containerd/config.toml) according to the following steps.

Firstly, we generate config.toml.diff diff file and patch it.

cat > config.toml.diff << EOF
--- /etc/containerd/config.toml 2022-02-14 15:05:40.061562127 +0800
+++ /etc/containerd/config.toml.crun    2022-02-14 15:03:35.846052853 +0800
@@ -24,17 +24,23 @@
   max_concurrent_downloads = 10

   [plugins.cri.containerd]
-        default_runtime_name = "runc"
-    [plugins.cri.containerd.runtimes.runc]
+        default_runtime_name = "crun"
+    [plugins.cri.containerd.runtimes.crun]
       runtime_type = "io.containerd.runc.v2"
-      pod_annotations = []
+      pod_annotations = ["*.wasm.*", "wasm.*", "module.wasm.image/*", "*.module.wasm.image", "module.wasm.image/variant.*"]
       container_annotations = []
       privileged_without_host_devices = false
-      [plugins.cri.containerd.runtimes.runc.options]
-        BinaryName = "runc"
+      [plugins.cri.containerd.runtimes.crun.options]
+        BinaryName = "crun"
   # cni
   [plugins.cri.cni]
     bin_dir = "/opt/cni/bin"
     conf_dir = "/etc/cni/net.d"
     conf_template = ""

+  [plugins."io.containerd.runtime.v1.linux"]
+    no_shim = false
+    runtime = "crun"
+    runtime_root = ""
+    shim = "containerd-shim"
+    shim_debug = false
EOF
sudo patch -d/ -p0 < config.toml.diff
sudo systemctl restart containerd

Create Wasmedge application in Superedge

We can run a wasm image which has been pushed to dockerhub. If you want to learn how to compile, package, and publish the WebAssembly program as a container image to Docker hub, please refer to WasmEdge Book.

cat > wasmedge-app.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  annotations:
    module.wasm.image/variant: compat
  labels:
    run: wasi-demo
  name: wasi-demo
spec:
  containers:
  - args:
    - /wasi_example_main.wasm
    - "50000000"
    image: hydai/wasm-wasi-example:with-wasm-annotation
    imagePullPolicy: IfNotPresent
    name: wasi-demo
  hostNetwork: true
  restartPolicy: Never
EOF

kubectl create -f wasmedge-app.yaml

The output will show by executing kubectl logs wasi-demo command.

Random number: -1643170076
Random bytes: [15, 223, 242, 238, 69, 114, 217, 106, 80, 214, 44, 225, 20, 182, 2, 189, 226, 184, 97, 40, 154, 6, 56, 202, 45, 89, 184, 80, 5, 89, 73, 222, 143, 132, 17, 79, 145, 64, 33, 17, 250, 102, 91, 94, 26, 200, 28, 161, 46, 93, 123, 36, 100, 167, 43, 159, 82, 112, 255, 165, 37, 232, 17, 139, 97, 14, 28, 169, 225, 156, 147, 22, 174, 148, 209, 57, 82, 213, 19, 215, 11, 18, 32, 217, 188, 142, 54, 127, 237, 237, 230, 137, 86, 162, 185, 66, 88, 95, 226, 53, 174, 76, 226, 25, 151, 186, 156, 16, 62, 63, 230, 148, 133, 102, 33, 138, 20, 83, 31, 60, 246, 90, 167, 189, 103, 238, 106, 51]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file

OpenYurt + containerd + crun

In this article, we will introduce how to run a WasmEdge simple demo app with Containerd over OpenYurt.

Set up an OpenYurt Cluster

Here, we introduce two ways to set up an OpenYurt Cluster. The first one is to set up an OpenYurt Cluster from scratch, use yurtctl convert to realize a K8s Cluster conversion to an OpenYurt Cluster. The second one is to use the ability of OpenYurt Experience Center, which is easy to achieve an OpenYurt Cluster.

Prerequisite

OS/kernelPrivate IP/Public IP
MasterUbuntu 20.04.3 LTS/5.4.0-91-generic192.168.3.169/120.55.126.18
NodeUbuntu 20.04.3 LTS/5.4.0-91-generic192.168.3.170/121.43.113.152

It should be noted that some steps may differ slightly depending on the operating system differences. Please refer to the installation of OpenYurt and crun.

We use yurtctl convert to convert a K8s Cluster to OpenYurt Cluster, so we should set up a K8s Cluster. If you use yurtctl init/join to set up an OpenYurt Cluster, you can skip this step which introduces the process of installing K8s.

Find the difference between yurtctl convert/revert and yurtctl init/join, you can refer to the following two articles.

how use Yurtctl init/join

Conversion between OpenYurt and Kubernetes:yurtctl convert/revert

  • Close the swap space of the master and node firstly.
sudo swapoff -a
//verify
free -m
  • Configure the file /etc/hosts of two nodes as the following.
192.168.3.169  oy-master 
120.55.126.18  oy-master
92.168.3.170   oy-node
121.43.113.152 oy-node
  • Load the br_netfilter Kernel module and modify the Kernel parameter.
//load the module
sudo modprobe br_netfilter
//verify   
lsmod | grep br_netfilter
// create k8s.conf
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system 
  • Setup the value of rp-filter (adjusting the value of two parameters in /etc/sysctl.d/10-network-security.conf from 2 to 1 and setting up the value of /proc/sys/net/ipv4/ip_forward to 1)
sudo vi /etc/sysctl.d/10-network-security.conf
echo 1 > /proc/sys/net/ipv4/ip_forward
sudo sysctl --system

Install containerd and modify the default configure of containerd

Use the following commands to install containerd on your edge node which will run a WasmEdge simple demo.

export VERSION="1.5.7"
echo -e "Version: $VERSION"
echo -e "Installing libseccomp2 ..."
sudo apt install -y libseccomp2
echo -e "Installing wget"
sudo apt install -y wget

wget https://github.com/containerd/containerd/releases/download/v${VERSION}/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
wget https://github.com/containerd/containerd/releases/download/v${VERSION}/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz.sha256sum
sha256sum --check cri-containerd-cni-${VERSION}-linux-amd64.tar.gz.sha256sum

sudo tar --no-overwrite-dir -C / -xzf cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
sudo systemctl daemon-reload

As the crun project support WasmEdge as default, we just need to configure the containerd configuration for runc. So we need to modify the runc parameters in /etc/containerd/config.toml to curn and add pod_annotation.

sudo mkdir -p /etc/containerd/
sudo bash -c "containerd config default > /etc/containerd/config.toml"
wget https://raw.githubusercontent.com/second-state/wasmedge-containers-examples/main/containerd/containerd_config.diff
sudo patch -d/ -p0 < containerd_config.diff

After that, restart containerd to make the configuration take effect.

systemctl start containerd

Install WasmEdge

Use the simple install script to install WasmEdge on your edge node.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

Build and install crun

We need a crun binary that supports WasmEdge on the edge node. For now, the most straightforward approach is to build it yourself from the source. First, let's ensure that crun dependencies are installed on your Ubuntu 20.04. For other Linux distributions, please see here.

  • Dependencies are required for the build
sudo apt update
sudo apt install -y make git gcc build-essential pkgconf libtool \
  libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev \
  go-md2man libtool autoconf python3 automake
  • Configure, build, and install a crun binary with WasmEdge support.
git clone https://github.com/containers/crun
cd crun
./autogen.sh
./configure --with-wasmedge
make
sudo make install

From scratch set up an OpenYurt Cluster

In this demo, we will use two machines to set up an OpenYurt Cluster. One simulated cloud node is called Master, the other one simulated edge node is called Node. These two nodes form the simplest OpenYurt Cluster, where OpenYurt components run on.

Set up a K8s Cluster

Kubernetes version 1.18.9

$ sudo apt-get update && sudo apt-get install -y ca-certificates curl software-properties-common apt-transport-https
// add K8s source
$ curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
$ sudo tee /etc/apt/sources.list.d/kubernetes.list <<EOF
$ deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
// install K8s components 1.18.9
$ sudo apt-get update && sudo apt-get install -y kubelet=1.18.9-00 kubeadm=1.18.9-00 kubectl=1.18.9-00 
// Initialize the master node
$ sudo kubeadm init --pod-network-cidr 172.16.0.0/16 \
--apiserver-advertise-address=192.168.3.167 \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers
// join the work node
$ kubeadm join 192.168.3.167:6443 --token 3zefbt.99e6denc1cxpk9fg \
   --discovery-token-ca-cert-hash sha256:8077d4e7dd6eee64a999d56866ae4336073ed5ffc3f23281d757276b08b9b195

Install yurtctl

Use the following command line to install yurtctl. The yurtctl CLI tool helps install/uninstall OpenYurt and also convert a standard Kubernetes cluster to an OpenYurt cluster.

git clone https://github.com/openyurtio/openyurt.git
cd openyurt
make build WHAT=cmd/yurtctl

Install OpenYurt components

OpenYurt includes several components. YurtHub is the traffic proxy between the components on the node and Kube-apiserver. The YurtHub on the edge will cache the data returned from the cloud. Yurt controller supplements the upstream node controller to support edge computing requirements. TunnelServer connects with the TunnelAgent daemon running in each edge node via a reverse proxy to establish secure network access between the cloud site control plane and the edge nodes that are connected to the intranet. For more detailed information, you could refer to the OpenYurt docs.

yurtctl convert --deploy-yurttunnel --cloud-nodes oy-master --provider kubeadm\
--yurt-controller-manager-image="openyurt/yurt-controller-manager:v0.5.0"\
--yurt-tunnel-agent-image="openyurt/yurt-tunnel-agent:v0.5.0"\
--yurt-tunnel-server-image="openyurt/yurt-tunnel-server:v0.5.0"\
--node-servant-image="openyurt/node-servant:latest"\
--yurthub-image="openyurt/yurthub:v0.5.0"

We need to change the openyurt/node-server-version to latest here: --node-servant-image="openyurt/node-servant:latest"

Actually, OpenYurt components 0.6.0 version is recommended to be installed and proved to be a success to run a WasmEdge demo. How to install OpenYurt:0.6.0, you can see this

Use OpenYurt Experience Center to quickly set up an OpenYurt Cluster

An easier way to set up an OpenYurt Cluster is to use the OpenYurt Experience Center. All you need to do is to sign up for an account for testing, and then you will get an OpenYurt cluster. Next, you could just use yurtctl join command line to join an edge node. See more OpenYurt Experience Center details here.

Run a simple WebAssembly app

Next, let's run a WebAssembly program through the OpenYurt cluster as a container in the pod. This section will start off pulling this WebAssembly-based container image from Docker hub. If you want to learn how to compile, package, and publish the WebAssembly program as a container image to Docker hub, please refer to WasmEdge Book.

One thing is to note that because the kubectl run (version 1.18.9 ) missed annotations parameters, we need to adjust the command line here. If you are using OpenYurt Experience Center with OpenYurt 0.6.0 and Kubernetes 1.20.11 by default, please refer to the Kubernetes sections in the WasmEdge book to run the wasm app.

// kubectl 1.18.9
$ sudo kubectl run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation  --overrides='{"kind":"Pod","metadata":{"annotations":{"module.wasm.image/variant":"compat"}} , "apiVersion":"v1", "spec": {"hostNetwork": true}}' /wasi_example_main.wasm 50000000

// kubectl 1.20.11
$ sudo kubectl run -it --rm --restart=Never wasi-demo --image=hydai/wasm-wasi-example:with-wasm-annotation --annotations="module.wasm.image/variant=compat" --overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"hostNetwork": true}}' /wasi_example_main.wasm 50000000

The output from the containerized application is printed into the console. It is the same for all Kubernetes versions.

Random number: 1123434661
Random bytes: [25, 169, 202, 211, 22, 29, 128, 133, 168, 185, 114, 161, 48, 154, 56, 54, 99, 5, 229, 161, 225, 47, 85, 133, 90, 61, 156, 86, 3, 14, 10, 69, 185, 225, 226, 181, 141, 67, 44, 121, 157, 98, 247, 148, 201, 248, 236, 190, 217, 245, 131, 68, 124, 28, 193, 143, 215, 32, 184, 50, 71, 92, 148, 35, 180, 112, 125, 12, 152, 111, 32, 30, 86, 15, 107, 225, 39, 30, 178, 215, 182, 113, 216, 137, 98, 189, 72, 68, 107, 246, 108, 210, 148, 191, 28, 40, 233, 200, 222, 132, 247, 207, 239, 32, 79, 238, 18, 62, 67, 114, 186, 6, 212, 215, 31, 13, 53, 138, 97, 169, 28, 183, 235, 221, 218, 81, 84, 235]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
The args are as follows.
/wasi_example_main.wasm
50000000
File content is This is in a file
pod "wasi-demo" deleted

You can now check out the pod status through the Kubernetes command line.

crictl ps -a

You can see the events from scheduling to running the WebAssembly workload in the log.

CONTAINER           IMAGE               CREATED             STATE               NAME                 ATTEMPT             POD ID
0c176ed65599a       0423b8eb71e31       8 seconds ago       Exited              wasi-demo  

App frameworks and platforms

WasmEdge applications can be plugged into existing application frameworks or platforms. WasmEdge provides a safe and efficient extension mechanism for those frameworks.

In this chapter, we will cover several such framework and platforms.

Service mesh and distributed runtimes

WasmEdge could be a lightweight runtime for sidecar microservices and the API proxy as the Docker alternative.

Sidecar microservices

For sidecar frameworks that support multiple application runtimes, we could simply embed WasmEdge applications into the sidecar through its C, Go, Rust, or Node.js SDKs. In addition, WasmEdge applications could be managed directly by container tools and act as sidecar microservices.

  • Dapr showcases how to run WasmEdge microservices as Dapr sidecars.
  • Apache EventMesh showcases how to run WasmEdge microservices as Apache EventMesh sidecars

Extension for the API proxy

The API proxy is another crucial component in the service mesh. It manages and directs API requests to sidecars in a manner that keeps the system scalable. Developers need to script those proxies to route traffic according to changing infrastructure and ops requirements. Seeing widespread demand for using WebAssembly instead of the LUA scripting language, the community came together and created the proxy-wasm spec. It defines the host interface that WebAssembly runtimes must support to plug into the proxy. WasmEdge supports proxy-wasm now.

  • MOSN shows how to use WasmEdge as extensions for MOSN.
  • wasm-nginx-module shows how to use WasmEdge run Go/Rust code in OpenResty.

If you have some great ideas on WasmEdge and microservices, feel free to create an issue or PR on the WasmEdge GitHub repo!

Dapr

In this article, I will demonstrate how to use WasmEdge as a sidecar application runtime for Dapr. There are two ways to do this:

  • Standalone WasmEdge is the recommended approach is to write a microservice using Rust or JavaScript, and run it in WasmEdge. The WasmEdge application serves web requests and communicates with the sidecar via sockets using the Dapr API. In this case, we can run WasmEdge as a managed container in k8s.
  • Alternatively, Embedded WasmEdge is to create a simple microservice in Rust or Go to listen for web requests and communicate with the Dapr sidecar. It passes the request data to a WasmEdge runtime for processing. The business logic of the microservice is a WebAssembly function created and deployed by an application developer.

While the first approach (running the entire microservice in WasmEdge) is much preferred, we are still working on a fully fledged Dapr SDKs for WasmEdge. You can track their progress in GitHub issues -- Rust and JavaScript.

Quick start

First you need to install Dapr and WasmEdge. Go and Rust are optional for the standalone WasmEdge approach. However, they are required for the demo app since it showcases both standalone and embedded WasmEdge approaches.

Fork or clone the demo application from Github. You can use this repo as your own application template.

git clone https://github.com/second-state/dapr-wasm

The demo has 4 Dapr sidecar applications. The web-port project provides a public web service for a static HTML page. This is the application’s UI. From the static HTML page, the user can select a microservice to turn an input image into grayscale. All 3 microsoervices below perform the same function. They are just implemented using different appraoches.

  • Standalone WasmEdge approach: The image-api-wasi-socket-rs project provides a standalone WasmEdge sidecar microservice that takes the input image and returns the grayscale image. The microservice is written in Rust and compiled into WebAssembly bytecode to run in WasmEdge.
  • Embedded WasmEdge approach #1: The image-api-rs project provides a simple Rust-based microservice. It embeds a WasmEdge function to turn an input image into a grayscale image.
  • Embedded WasmEdge approach #2: The image-api-go project provides a simple Go-based microservice. It embeds a WasmEdge function to turn an input image into a grayscale image.

You can follow the instructions in the README to start the sidecar services. Here are commands to build the WebAssembly functions and start the sidecar services. The first set of commands deploy the static web page service and the standalone WasmEdge service written in Rust. It forms a complete application to turn an input image into grayscale.

# Build and start the static HTML web page service for the UI and router for sending the uploaded image to the grayscale microservice
cd web-port
go build
./run_web.sh
cd ../

# Build the standalone image grayscale web service for WasmEdge
cd image-api-wasi-socket-rs
cargo build  --target wasm32-wasi
cd ../

# Run the microservice as a Dapr sidecar app
cd image-api-wasi-socket-rs
./run_api_wasi_socket_rs.sh
cd ../

The second set of commands create the alternative microservices for the embedded WasmEdge function.

# Build the grayscale WebAssembly functions, and deploy them to the sidecar projects
cd functions/grayscale
./build.sh
cd ../../

# Build and start the Rust-based microservice for embedding the grayscale WasmEdge function
cd image-api-rs
cargo build --release
./run_api_rs.sh
cd ../

# Build and start the Go-based microservice for embedding the grayscale WasmEdge function
cd image-api-go
go build
./run_api_go.sh
cd ../

Finally, you should be able to see the web UI in your browser.

The standalone WasmEdge microservice starts a non-blocking TCP server inside WasmEdge. The TCP server passes incoming requests to handle_client(), which passes HTTP requests to handle_http(), which calls grayscale() to process the image data in the request.

fn main() -> std::io::Result<()> {
    let port = std::env::var("PORT").unwrap_or(9005.to_string());
    println!("new connection at {}", port);
    let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?;
    loop {
        let _ = handle_client(listener.accept()?.0);
    }
}

fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
  ... ...
}

fn handle_http(req: Request<Vec<u8>>) -> bytecodec::Result<Response<String>> {
  ... ...
}

fn grayscale(image: &[u8]) -> Vec<u8> {
    let detected = image::guess_format(&image);
    let mut buf = vec![];
    if detected.is_err() {
        return buf;
    }
    
    let image_format_detected = detected.unwrap();
    let img = image::load_from_memory(&image).unwrap();
    let filtered = img.grayscale();
    match image_format_detected {
        ImageFormat::Gif => {
            filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
        }
        _ => {
            filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
        }
    };
    return buf;
}

Work in progress: It will soon interact with the Dapr sidecar through the WasmEdge Dapr SDK in Rust.

Now, you can build the microservice. It is a simple matter of compiling from Rust to WebAssembly.

cd image-api-wasi-socket-rs
cargo build  --target wasm32-wasi

Deploy the WasmEdge microservice in Dapr as follows.

dapr run --app-id image-api-wasi-socket-rs \
         --app-protocol http \
         --app-port 9005 \
         --dapr-http-port 3503 \
         --components-path ../config \
         --log-level debug \
         wasmedge ./target/wasm32-wasi/debug/image-api-wasi-socket-rs.wasm

Alternative: The embedded WasmEdge microservices

The embedded WasmEdge approach requires us to create a WebAssembly function for the business logic (image processing) first, and then embed it into simple Dapr microservices.

Rust function for image processing

The Rust function is simple. It uses the wasmedge_bindgen macro to makes it easy to call the function from a Go or Rust host embedding the WebAssembly function. It takes and returns base64 encoded image data for the web.


#![allow(unused)]
fn main() {
#[wasmedge_bindgen]
pub fn grayscale(image_data: String) -> String {
    let image_bytes = image_data.split(",").map(|x| x.parse::<u8>().unwrap()).collect::<Vec<u8>>();
    return grayscale::grayscale_internal(&image_bytes);
}
}

The Rust function that actually performs the task is as follows.


#![allow(unused)]
fn main() {
pub fn grayscale_internal(image_data: &[u8]) -> String {
    let image_format_detected: ImageFormat = image::guess_format(&image_data).unwrap();
    let img = image::load_from_memory(&image_data).unwrap();
    let filtered = img.grayscale();
    let mut buf = vec![];
    match image_format_detected {
        ImageFormat::Gif => {
            filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
        }
        _ => {
            filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
        }
    };
    let mut base64_encoded = String::new();
    base64::encode_config_buf(&buf, base64::STANDARD, &mut base64_encoded);
    return base64_encoded.to_string();
}
}

The Go host wrapper for microservice

The Go-based microservice embeds the above imaging processing function in WasmEdge. The microservice itself is a web server and utilizes the Dapr Go SDK.

func main() {
	s := daprd.NewService(":9003")

	if err := s.AddServiceInvocationHandler("/api/image", imageHandlerWASI); err != nil {
		log.Fatalf("error adding invocation handler: %v", err)
	}

	if err := s.Start(); err != nil && err != http.ErrServerClosed {
		log.Fatalf("error listenning: %v", err)
	}
}

The imageHandlerWASI() function starts a WasmEdge instance and calls the image processing (grayscale) function in it via wasmedge_bindgen.

Build and deploy the Go microservice to Dapr as follows.

cd image-api-go
go build
dapr run --app-id image-api-go \
         --app-protocol http \
         --app-port 9003 \
         --dapr-http-port 3501 \
         --log-level debug \
         --components-path ../config \
         ./image-api-go

The Rust host wrapper for microservice

The Rust-based microservice embeds the above imaging processing function in WasmEdge. The microservice itself is a Tokio and Warp based web server.


#![allow(unused)]
fn main() {
#[tokio::main]
pub async fn run_server(port: u16) {
    pretty_env_logger::init();
    let home = warp::get().map(warp::reply);

    let image = warp::post()
        .and(warp::path("api"))
        .and(warp::path("image"))
        .and(warp::body::bytes())
        .map(|bytes: bytes::Bytes| {
            let v: Vec<u8> = bytes.iter().map(|&x| x).collect();
            let res = image_process_wasmedge_sys(&v);
            let _encoded = base64::encode(&res);
            Response::builder()
                .header("content-type", "image/png")
                .body(res)
        });

    let routes = home.or(image);
    let routes = routes.with(warp::cors().allow_any_origin());

    let log = warp::log("dapr_wasm");
    let routes = routes.with(log);
    warp::serve(routes).run((Ipv4Addr::UNSPECIFIED, port)).await
}
}

The image_process_wasmedge_sys() function starts a WasmEdge instance and calls the image processing (grayscale) function in it via wasmedge_bindgen.

Build and deploy the Rust microservice to Dapr as follows.

cd image-api-rs
cargo build --release
dapr stop image-api-rs

# Change this to your own path for WasmEdge
export LD_LIBRARY_PATH=/home/coder/.wasmedge/lib64/

dapr run --app-id image-api-rs \
         --app-protocol http \
         --app-port 9004 \
         --dapr-http-port 3502 \
         --components-path ../config \
         --log-level debug \
         ./target/release/image-api-rs

That's it! Let us know your cool Dapr microservices in WebAssembly!

MOSN

Coming soon.

wasm-nginx-module

The wasm-nginx-module is an Nginx module built upon OpenResty. By implementing the Proxy Wasm ABI, any Wasm program written with Proxy Wasm SDK can be run inside it. Hence, you can write Go or Rust code, compile them into Wasm, then load & execute it in Nginx.

The wasm-nginx-module is already used in APISIX and allows it to run Wasm plugin like Lua plugin.

In order to follow along the tutorials in this chapter, you will need to first build your Nginx with wasm-nginx-module included and WasmEdge shared library installed in the right path.

Once you have Nginx installed, let me show you a real world example - using Wasm to inject custom responses in Nginx.

Inject Custom Response via Go in Nginx, Step by Step

Step 1: Write code based on proxy-wasm-go-sdk

The implementation code (including go.mod and others) can be found at here.

It should be explained that although the proxy-wasm-go-sdk project carries the Go name, it actually uses tinygo instead of native Go, which has some problems supporting WASI (which you can think of as a non-browser WASM runtime interface), see here for more details.

We also provide a Rust version (including Cargo.toml and others) there.

Step 2: Build the corresponding Wasm file

tinygo build -o ./fault-injection/main.go.wasm -scheduler=none -target=wasi ./fault-injection/main.go

Step 3: Load and execute the Wasm file

Then, start Nginx with the configuration below:

worker_processes  1;

error_log  /tmp/error.log warn;

events {
    worker_connections  10240;
}

http {
    wasm_vm wasmedge;
    init_by_lua_block {
        local wasm = require("resty.proxy-wasm")
        package.loaded.plugin = assert(wasm.load("fault_injection",
            "/path/to/fault-injection/main.go.wasm"))
    }
    server {
        listen 1980;
        location / {
            content_by_lua_block {
                local wasm = require("resty.proxy-wasm")
                local ctx = assert(wasm.on_configure(package.loaded.plugin,
                    '{"http_status": 403, "body": "powered by wasm-nginx-module"}'))
                assert(wasm.on_http_request_headers(ctx))
            }
        }
    }
}

This configuration loads the Wasm file we just built, executes it with the configuration {"http_status": 403, "body": "powered by wasm-nginx-module"}.

Step 4: verify the result

After Nginx starts, we can use curl http://127.0.0.1:1980/ -i to verify the execution result of the Wasm.

It is expected to see the output:

HTTP/1.1 403 Forbidden
...

powered by wasm-nginx-module

Inject Custom Response via Rust in Nginx, Step by Step

Step 1: Write code based on proxy-wasm-rust-sdk

We also provide a Rust version (including Cargo.toml and others) there.

Step 2: Build the corresponding Wasm file

cargo build --target=wasm32-wasi

Step 3: Load and execute the Wasm file

Then, start Nginx with the configuration below:

worker_processes  1;

error_log  /tmp/error.log warn;

events {
    worker_connections  10240;
}

http {
    wasm_vm wasmedge;
    init_by_lua_block {
        local wasm = require("resty.proxy-wasm")
        package.loaded.plugin = assert(wasm.load("fault_injection",
            "/path/to/fault-injection/target/wasm32-wasi/debug/fault_injection.wasm"))
    }
    server {
        listen 1980;
        location / {
            content_by_lua_block {
                local wasm = require("resty.proxy-wasm")
                local ctx = assert(wasm.on_configure(package.loaded.plugin,
                    '{"http_status": 403, "body": "powered by wasm-nginx-module"}'))
                assert(wasm.on_http_request_headers(ctx))
            }
        }
    }
}

This configuration loads the Wasm file we just built, executes it with the configuration {"http_status": 403, "body": "powered by wasm-nginx-module"}.

Step 4: verify the result

After Nginx starts, we can use curl http://127.0.0.1:1980/ -i to verify the execution result of the Wasm.

It is expected to see the output:

HTTP/1.1 403 Forbidden
...

powered by wasm-nginx-module

Apache EventMesh

Coming soon, or you can help out

App frameworks

WasmEdge provides a safe and efficient extension mechanism for applications. Of course, application developers can always use WasmEdge SDKs to embed WebAssembly functions. But some applications and frameworks opt to build their own extension / embedding APIs on top of the WasmEdge SDK, which supports more ergonomic integration with the application's native use cases and programming models.

  • YoMo is a data stream processing framework. WasmEdge functions can be plugged into the framework to process data in-stream.
  • Reactr is a Go language framework for managing and extending WebAssembly functions for the purpose of easy embedding into other Go applications.

YoMo

YoMo is a programming framework enabling developers to build a distributed cloud system (Geo-Distributed Cloud System). YoMo's communication layer is made on top of the QUIC protocol, which brings high-speed data transmission. In addition, it has a built-in Streaming Serverless "streaming function", which significantly improves the development experience of distributed cloud systems. The distributed cloud system built by YoMo provides an ultra-high-speed communication mechanism between near-field computing power and terminals. It has a wide range of use cases in Metaverse, VR/AR, IoT, etc.

YoMo is written in the Go language. For streaming Serverless, Golang plugins and shared libraries are used to load users' code dynamically, which also have certain limitations for developers. Coupled with Serverless architecture's rigid demand for isolation, this makes WebAssembly an excellent choice for running user-defined functions.

For example, in the process of real-time AI inference in AR/VR devices or smart factories, the camera sends real-time unstructured data to the computing node in the near-field MEC (multi-access edge computing) device through YoMo. YoMo sends the AI computing result to the end device in real-time when the AI inference is completed. Thus, the hosted AI inference function will be automatically executed.

However, a challenge for YoMo is to incorporate and manage handler functions written by multiple outside developers in an edge computing node. It requires runtime isolation for those functions without sacrificing performance. Traditional software container solutions, such as Docker, are not up to the task. They are too heavy and slow to handle real-time tasks.

WebAssembly provides a lightweight and high-performance software container. It is ideally suited as a runtime for YoMo’s data processing handler functions.

In this article, we will show you how to create a Rust function for Tensorflow-based image classification, compile it into WebAssembly, and then use YoMo to run it as a stream data handler. We use WasmEdge as our WebAssembly runtime because it offers the highest performance and flexibility compared with other WebAssembly runtimes. It is the only WebAssembly VM that reliably supports Tensorflow. YoMo manages WasmEdge VM instances and the contained WebAssembly bytecode apps through WasmEdge’s Golang API.

Source code: https://github.com/yomorun/yomo-wasmedge-tensorflow

Checkout the WasmEdge image classification function in action in YoMo

Prerequisite

Obviously, you will need to have Golang installed, but I will assume you already did.

Golang version should be newer than 1.15 for our example to work.

You also need to install the YoMo CLI application. It orchestrates and coordinates data streaming and handler function invocations.

$ go install github.com/yomorun/cli/yomo@latest
$ yomo version
YoMo CLI version: v0.1.3

Next, please install the WasmEdge and its Tensorflow shared libraries. WasmEdge is a leading WebAssembly runtime hosted by the CNCF. We will use it to embed and run WebAssembly programs from YoMo.

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

Finally, since our demo WebAssembly functions are written in Rust, you will also need a Rust compiler.

For the rest of the demo, fork and clone the source code repository.

git clone https://github.com/yomorun/yomo-wasmedge-tensorflow.git

The image classification function

The image classification function to process the YoMo image stream is written in Rust. It utilizes the WasmEdge Tensorflow API to process an input image.


#![allow(unused)]
fn main() {
#[wasmedge_bindgen]
pub fn infer(image_data: Vec<u8>) -> Result<Vec<u8>, String> {
  let start = Instant::now();

  // Load the TFLite model and its meta data (the text label for each recognized object number)
  let model_data: &[u8] = include_bytes!("lite-model_aiy_vision_classifier_food_V1_1.tflite");
  let labels = include_str!("aiy_food_V1_labelmap.txt");

  // Pre-process the image to a format that can be used by this model
  let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&image_data[..], 192, 192);
  println!("RUST: Loaded image in ... {:?}", start.elapsed());

  // Run the TFLite model using the WasmEdge Tensorflow API
  let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
  session.add_input("input", &flat_img, &[1, 192, 192, 3])
         .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Softmax");

  // Find the object index in res_vec that has the greatest probability
  // Translate the probability into a confidence level
  // Translate the object index into a label from the model meta data food_name
  let mut i = 0;
  let mut max_index: i32 = -1;
  let mut max_value: u8 = 0;
  while i < res_vec.len() {
    let cur = res_vec[i];
    if cur > max_value {
      max_value = cur;
      max_index = i as i32;
    }
    i += 1;
  }
  println!("RUST: index {}, prob {}", max_index, max_value);

  let confidence: String;
  if max_value > 200 {
    confidence = "is very likely".to_string();
  } else if max_value > 125 {
    confidence = "is likely".to_string();
  } else {
    confidence = "could be".to_string();
  }

  let ret_str: String;
  if max_value > 50 {
    let mut label_lines = labels.lines();
    for _i in 0..max_index {
      label_lines.next();
    }
    let food_name = label_lines.next().unwrap().to_string();
    ret_str = format!(
      "It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture",
      confidence, food_name, food_name
    );
  } else {
    ret_str = "It does not appears to be a food item in the picture.".to_string();
  }

  println!(
    "RUST: Finished post-processing in ... {:?}",
    start.elapsed()
  );
  return Ok(ret_str.as_bytes().to_vec());
}
}

You should add wasm32-wasi target to rust to compile this function into WebAssembly bytecode.

rustup target add wasm32-wasi

cd flow/rust_mobilenet_food
cargo build --target wasm32-wasi --release
# The output WASM will be target/wasm32-wasi/release/rust_mobilenet_food_lib.wasm

# Copy the wasm bytecode file to the flow/ directory
cp target/wasm32-wasi/release/rust_mobilenet_food_lib.wasm ../

To release the best performance of WasmEdge, you should enable the AOT mode by compiling the .wasm file to the .so.

wasmedgec rust_mobilenet_food_lib.wasm rust_mobilenet_food_lib.so

Integration with YoMo

On the YoMo side, we use the WasmEdge Golang API to start and run WasmEdge VM for the image classification function. The app.go file in the source code project is as follows.

package main

import (
  "crypto/sha1"
  "fmt"
  "log"
  "os"
  "sync/atomic"

  "github.com/second-state/WasmEdge-go/wasmedge"
  bindgen "github.com/second-state/wasmedge-bindgen/host/go"
  "github.com/yomorun/yomo"
)

var (
  counter uint64
)

const ImageDataKey = 0x10

func main() {
  // Connect to Zipper service
  sfn := yomo.NewStreamFunction("image-recognition", yomo.WithZipperAddr("localhost:9900"))
  defer sfn.Close()

  // set only monitoring data
  sfn.SetObserveDataID(ImageDataKey)

  // set handler
  sfn.SetHandler(Handler)

  // start
  err := sfn.Connect()
  if err != nil {
    log.Print("❌ Connect to zipper failure: ", err)
    os.Exit(1)
  }

  select {}
}

// Handler process the data in the stream
func Handler(img []byte) (byte, []byte) {
  // Initialize WasmEdge's VM
  vmConf, vm := initVM()
  bg := bindgen.Instantiate(vm)
  defer bg.Release()
  defer vm.Release()
  defer vmConf.Release()

  // recognize the image
  res, err := bg.Execute("infer", img)
  if err == nil {
    fmt.Println("GO: Run bindgen -- infer:", string(res))
  } else {
    fmt.Println("GO: Run bindgen -- infer FAILED")
  }

  // print logs
  hash := genSha1(img)
  log.Printf("✅ received image-%d hash %v, img_size=%d \n", atomic.AddUint64(&counter, 1), hash, len(img))

  return 0x11, nil
}

// genSha1 generate the hash value of the image
func genSha1(buf []byte) string {
  h := sha1.New()
  h.Write(buf)
  return fmt.Sprintf("%x", h.Sum(nil))
}

// initVM initialize WasmEdge's VM
func initVM() (*wasmedge.Configure, *wasmedge.VM) {
  wasmedge.SetLogErrorLevel()
  // Set Tensorflow not to print debug info
  os.Setenv("TF_CPP_MIN_LOG_LEVEL", "3")
  os.Setenv("TF_CPP_MIN_VLOG_LEVEL", "3")

  // Create configure
  vmConf := wasmedge.NewConfigure(wasmedge.WASI)

  // Create VM with configure
  vm := wasmedge.NewVMWithConfig(vmConf)

  // Init WASI
  var wasi = vm.GetImportObject(wasmedge.WASI)
  wasi.InitWasi(
    os.Args[1:],     // The args
    os.Environ(),    // The envs
    []string{".:."}, // The mapping directories
  )

  // Register WasmEdge-tensorflow and WasmEdge-image
  var tfobj = wasmedge.NewTensorflowImportObject()
  var tfliteobj = wasmedge.NewTensorflowLiteImportObject()
  vm.RegisterImport(tfobj)
  vm.RegisterImport(tfliteobj)
  var imgobj = wasmedge.NewImageImportObject()
  vm.RegisterImport(imgobj)

  // Instantiate wasm
  vm.LoadWasmFile("rust_mobilenet_food_lib.so")
  vm.Validate()

  return vmConf, vm
}

In action

Finally, we can start YoMo and see the entire data processing pipeline in action. Start the YoMo CLI application from the project folder. The yaml file defines port YoMo should listen on and the workflow handler to trigger for incoming data. Note that the flow name image-recognition matches the name in the aforementioned data handler app.go.

yomo serve -c ./zipper/workflow.yaml

Start the handler function by running the aforementioned app.go program.

cd flow
go run --tags "tensorflow image" app.go

Start a simulated data source by sending a video to YoMo. The video is a series of image frames. The WasmEdge function in app.go will be invoked against every image frame in the video.

# Download a video file
wget -P source 'https://github.com/yomorun/yomo-wasmedge-tensorflow/releases/download/v0.1.0/hot-dog.mp4'

# Stream the video to YoMo
go run ./source/main.go ./source/hot-dog.mp4

You can see the output from the WasmEdge handler function in the console. It prints the names of the objects detected in each image frame in the video.

What's next

In this article, we have seen how to use the WasmEdge Tensorflow API and Golang SDK in YoMo framework to process an image stream in near real-time.

In collaboration with YoMo, we will soon deploy WasmEdge in production in smart factories for a variety of assembly line tasks. WasmEdge is the software runtime for edge computing!

Reactr

Reactr is a fast, performant function scheduling library written in Go. Reactr is designed to be flexible, with the ability to run embedded in your Go applications and first-class support for WebAssembly. Taking advantage of Go's superior concurrency capabilities, Reactr can manage and execute hundreds of WebAssembly runtime instances all at once, making a great framework for server-side applications.

Reactr allows you to run WebAssembly functions in Go. But so does the WasmEdge Go SDK. The unique feature of Reactr is that it provides a rich set of host functions in Go, which support access to networks and databases etc. Reactr then provides Rust (and Swift / AssemblyScript) APIs to call those host functions from within the WebAssembly function.

In this article, we will show you how to use WasmEdge together with Reactr to take advantage of the best of both worlds. WasmEdge is the fastest and most extensible WebAssembly runtime. It is also the fastest in Reactr's official test suite. We will show you how to run Rust functions compiled to WebAssembly as well as JavaScript programs in WasmEdge and Reactr.

WasmEdge provides advanced support for JavaScript including mixing Rust with JavaScript for improved performance.

Prerequisites

You need have Rust, Go, and WasmEdge installed on your system. The GCC compiler (installed via the build-essential package) is also needed for WasmEdge.

sudo apt-get update
sudo apt-get -y upgrade
sudo apt install build-essential

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustup target add wasm32-wasi

curl -OL https://golang.org/dl/go1.17.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xvf go1.17.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
source $HOME/.wasmedge/env

Hello world

A simple hello world example for Reactr is available here.

Hello world: Rust function compiled to WebAssembly

Let's first create a simple Rust function to echo hello. The Rust function HelloEcho::run() is as follows. It will be exposed to the Go host application through Reactr.


#![allow(unused)]
fn main() {
use suborbital::runnable::*;

struct HelloEcho{}

impl Runnable for HelloEcho {
  fn run(&self, input: Vec<u8>) -> Result<Vec<u8>, RunErr> {
    let in_string = String::from_utf8(input).unwrap();
    Ok(format!("hello {}", in_string).as_bytes().to_vec())
  }
}
}

Let's build the Rust function into a WebAssembly bytecode file.

cd hello-echo
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/hello_echo.wasm ..
cd ..

Hello world: Go host application

Next, lets look into the Go host app that executes the WebAssembly functions. The runBundle() function executes the run() function in the Runnable struct once.

func runBundle() {
  r := rt.New()
  doWasm := r.Register("hello-echo", rwasm.NewRunner("./hello_echo.wasm"))

  res, err := doWasm([]byte("wasmWorker!")).Then()
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println(string(res.([]byte)))
}

The runGroup() function executes the Rust-compiled WebAssembly run() function multiple times asynchronously in a group, and receives the results as they come in.

func runGroup() {
  r := rt.New()

  doWasm := r.Register("hello-echo", rwasm.NewRunner("./hello_echo.wasm"))

  grp := rt.NewGroup()
  for i := 0; i < 100000; i++ {
    grp.Add(doWasm([]byte(fmt.Sprintf("world %d", i))))
  }

  if err := grp.Wait(); err != nil {
    fmt.Println(err)
  }
}

Finally, let's run the Go host application and see the results printed to the console.

You must use the -tags wasmedge flag to take advantage of the performance and extended WebAssembly APIs provided by WasmEdge.

go mod tidy
go run -tags wasmedge main.go

Database query

In this example, we will demonstrate how to use Reactr host functions and APIs to query a PostgreSQL database from your WebAssembly function.

Database query: Install and set up a PostgreSQL database

We will start a PostgreSQL instance through Docker.

docker pull postgres
docker run --name reactr-postgres -p 5432:5432 -e POSTGRES_PASSWORD=12345 -d postgres

Next, let's create a database and populate it with some sample data.

$ docker run -it --rm --network host postgres psql -h 127.0.0.1 -U postgres
postgres=# CREATE DATABASE reactr;
postgres=# \c reactr;

# Create a table:
postgres=# CREATE TABLE users (
    uuid        varchar(100) CONSTRAINT firstkey PRIMARY KEY,
    email       varchar(50) NOT NULL,
    created_at  date,
    state       char(1),
    identifier  integer
);

Leave this running and start another terminal window to interact with this PostgreSQL server.

Database query: Rust function compiled to WebAssembly

Let's create a Rust function to access the PostgreSQL database. The Rust function RsDbtest::run() is as follows. It will be exposed to the Go host application through Reactr. It uses named queries such as PGInsertUser and PGSelectUserWithUUID to operate the database. Those queries are defined in the Go host application, and we will see them later.


#![allow(unused)]
fn main() {
use suborbital::runnable::*;
use suborbital::db;
use suborbital::util;
use suborbital::db::query;
use suborbital::log;
use uuid::Uuid;

struct RsDbtest{}

impl Runnable for RsDbtest {
  fn run(&self, _: Vec<u8>) -> Result<Vec<u8>, RunErr> {
    let uuid = Uuid::new_v4().to_string();

    let mut args: Vec<query::QueryArg> = Vec::new();
    args.push(query::QueryArg::new("uuid", uuid.as_str()));
    args.push(query::QueryArg::new("email", "connor@suborbital.dev"));

    match db::insert("PGInsertUser", args) {
      Ok(_) => log::info("insert successful"),
      Err(e) => {
        return Err(RunErr::new(500, e.message.as_str()))
      }
    };

    let mut args2: Vec<query::QueryArg> = Vec::new();
    args2.push(query::QueryArg::new("uuid", uuid.as_str()));

    match db::update("PGUpdateUserWithUUID", args2.clone()) {
      Ok(rows) => log::info(format!("update: {}", util::to_string(rows).as_str()).as_str()),
      Err(e) => {
        return Err(RunErr::new(500, e.message.as_str()))
      }
    }

    match db::select("PGSelectUserWithUUID", args2.clone()) {
      Ok(result) => log::info(format!("select: {}", util::to_string(result).as_str()).as_str()),
      Err(e) => {
        return Err(RunErr::new(500, e.message.as_str()))
      }
    }

    match db::delete("PGDeleteUserWithUUID", args2.clone()) {
      Ok(rows) => log::info(format!("delete: {}", util::to_string(rows).as_str()).as_str()),
      Err(e) => {
        return Err(RunErr::new(500, e.message.as_str()))
      }
    }

    ... ...
  }
}
}

Let's build the Rust function into a WebAssembly bytecode file.

cd rs-db
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/rs_db.wasm ..
cd ..

Database query: Go host application

The Go host app first defines the SQL queries and gives each of them a name. We will then pass those queries to the Reactr runtime as a configuration.

func main() {
  dbConnString, exists := os.LookupEnv("REACTR_DB_CONN_STRING")
  if !exists {
    fmt.Println("skipping as conn string env var not set")
    return
  }

  q1 := rcap.Query{
    Type:     rcap.QueryTypeInsert,
    Name:     "PGInsertUser",
    VarCount: 2,
    Query: `
    INSERT INTO users (uuid, email, created_at, state, identifier)
    VALUES ($1, $2, NOW(), 'A', 12345)`,
  }

  q2 := rcap.Query{
    Type:     rcap.QueryTypeSelect,
    Name:     "PGSelectUserWithUUID",
    VarCount: 1,
    Query: `
    SELECT * FROM users
    WHERE uuid = $1`,
  }

  q3 := rcap.Query{
    Type:     rcap.QueryTypeUpdate,
    Name:     "PGUpdateUserWithUUID",
    VarCount: 1,
    Query: `
    UPDATE users SET state='B' WHERE uuid = $1`,
  }

  q4 := rcap.Query{
    Type:     rcap.QueryTypeDelete,
    Name:     "PGDeleteUserWithUUID",
    VarCount: 1,
    Query: `
    DELETE FROM users WHERE uuid = $1`,
  }

  config := rcap.DefaultConfigWithDB(vlog.Default(), rcap.DBTypePostgres, dbConnString, []rcap.Query{q1, q2, q3, q4})

  r, err := rt.NewWithConfig(config)
  if err != nil {
    fmt.Println(err)
    return
  }

  ... ...
}

Then, we can run the WebAssembly function from Reactr.

func main() {
  ... ...

  doWasm := r.Register("rs-db", rwasm.NewRunner("./rs_db.wasm"))

  res, err := doWasm(nil).Then()
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println(string(res.([]byte)))
}

Finally, let's run the Go host application and see the results printed to the console.

You must use the -tags wasmedge flag to take advantage of the performance and extended WebAssembly APIs provided by WasmEdge.

export REACTR_DB_CONN_STRING='postgresql://postgres:12345@127.0.0.1:5432/reactr'
go mod tidy
go run -tags wasmedge main.go

Embed JavaScript in Go

As we mentioned, a key feature of the WasmEdge Runtime is its advanced JavaScript support, which allows JavaScript programs to run in lightweight, high-performance, safe, multi-language, and Kubernetes-managed WasmEdge containers. A simple example of embedded JavaScript function in Reactr is available here.

JavaScript example

The JavaScript example function is very simple. It just returns a string value.

let h = 'hello';
let w = 'wasmedge';
`${h} ${w}`;

JavaScript example: Go host application

The Go host app uses the Reactr API to run WasmEdge's standard JavaScript interpreter rs_embed_js.wasm. You can build your own version of JavaScript interpreter by modifying this Rust project.

Learn more about how to embed JavaScript code in Rust, and how to use Rust to implement JavaScript APIs in WasmEdge.

The Go host application just need to start the job for rs_embed_js.wasm and pass the JavaScript content to it. The Go application can then capture and print the return value from JavaScript.

func main() {
  r := rt.New()
  doWasm := r.Register("hello-quickjs", rwasm.NewRunner("./rs_embed_js.wasm"))

  code, err := ioutil.ReadFile(os.Args[1])
  if err != nil {
    fmt.Print(err)
  }
  res, err := doWasm(code).Then()
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println(string(res.([]byte)))
}

Run the Go host application as follows.

$ cd quickjs
$ go mod tidy
$ go run -tags wasmedge main.go hello.js
String(JsString(hello wasmedge))

The printed result shows the type information of the string in Rust and Go APIs. You can strip out this information by changing the Rust or Go applications.

JavaScript example: Feature examples

WasmEdge supports many advanced JavaScript features. For the next step, you could try our React SSR example to generate an HTML UI from a Reactr function! You can just build the dist/main.js from the React SSR example, and copy it over to this example folder to see it in action!

$ cd quickjs
# copy over the dist/main.js file from the react ssr example
$ go mod tidy
$ go run -tags wasmedge main.go main.js
<div data-reactroot=""><div>This is home</div><div><div>This is page</div></div></div>
UnDefined

Serverless platforms

Our vision for the future is to run WebAssembly as an alternative lightweight runtime side-by-side with Docker and microVMs in cloud native infrastructure. WebAssembly offers much higher performance and consumes much less resources than Docker-like containers or microVMs. However, the public cloud only supports running WebAssembly inside a microVM. Nonetheless, running WebAssembly functions inside a microVM still offers many advantages over running containerized NaCl programs.

Running WebAssembly functions inside Docker-like containers offer advantages over running NaCl programs directly in Docker.

For starters, WebAssembly provides fine-grained runtime isolation for individual functions. A microservice could have multiple functions and support services running inside a Docker-like container. WebAssembly can make the microservice more secure and more stable.

Second, the WebAssembly bytecode is portable. Developers only need to build it once and do not need to worry about changes or updates to the underlying Vercel serverless container (OS and hardware). It also allows developers to reuse the same WebAssembly functions in other cloud environments.

Third, WebAssembly apps are easy to deploy and manage. They have much less platform dependencies and complexities compared with NaCl dynamic libraries and executables.

Finally, the WasmEdge Tensorflow API provides the most ergonomic way to execute Tensorflow models in the Rust programming language. WasmEdge installs the correct combination of Tensorflow dependency libraries, and provides a unified API for developers.

In this section, we will show you how to run WebAssembly serverless functions in public clouds. Each platform has its own code template and contains two examples in Rust, one is the normal image processing, The other one is TensorFlow inference using the WasmEdge TensorFlow SDK.

  • Vercel discuss how to leverage WasmEdge to accelerate the Jamstack application deployed on Vercel.
  • Netlify discuss how to leverage WasmEdge to accelerate the Jamstack application deployed on Netlify.
  • AWS Lambda discuss how to leverage WasmEdge to accelerate the serverless functions deployed on AWS Lambda.
  • Tencent discuss how to leverage WasmEdge to accelerate the serverless functions deployed on Tencent cloud.

If you would like to add more WasmEdge examples on public cloud platform,like Google Cloud Functions, feel free to create a PR for WasmEdge and let the community know what you did.

serverless-wasmedge.png

Running WasmEdge from Docker containers deployed on public cloud is an easy way to add high-performance functions to web applications. Going forward an even better approach is to use WasmEdge as the container itself. There will be no Docker and no Node.js to bootstrap WasmEdge. This way, we can reach much higher efficiency for running serverless functions.

  • Second State Functions will discuss how to use WasmEdge ad the container itself, since Second State Functions is a serverless platform with pure WebAssembly/WasmEdge.

Rust and WebAssembly Serverless functions in Vercel

In this article, we will show you two serverless functions in Rust and WasmEdge deployed on Vercel. One is the image processing function, the other one is the TensorFlow inference function.

For more insights on why WasmEdge on Vercel, please refer to the article Rust and WebAssembly Serverless Functions in Vercel.

Prerequisite

Since our demo WebAssembly functions are written in Rust, you will need a Rust compiler. Make sure that you install the wasm32-wasi compiler target as follows, in order to generate WebAssembly bytecode.

rustup target add wasm32-wasi

The demo application front end is written in Next.js, and deployed on Vercel. We will assume that you already have the basic knowledge of how to work with Vercel.

Example 1: Image processing

Our first demo application allows users to upload an image and then invoke a serverless function to turn it into black and white. A live demo deployed on Vercel is available.

Fork the demo application’s GitHub repo to get started. To deploy the application on Vercel, just import the Github repo from Vercel for Github web page.

This repo is a standard Next.js application for the Vercel platform. The backend serverless function is in the api/functions/image_grayscale folder. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the black-white image to the STDOUT.

use hex;
use std::io::{self, Read};
use image::{ImageOutputFormat, ImageFormat};

fn main() {
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap();
  let img = image::load_from_memory(&buf).unwrap();
  let filtered = img.grayscale();
  let mut buf = vec![];
  match image_format_detected {
    ImageFormat::Gif => {
      filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
    },
    _ => {
      filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
    },
  };
  io::stdout().write_all(&buf).unwrap();
  io::stdout().flush().unwrap();
}

You can use Rust’s cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-grayscale/
cargo build --release --target wasm32-wasi 

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/grayscale.wasm ../../

Vercel runs api/pre.sh upon setting up the serverless environment. It installs the WasmEdge runtime, and then compiles each WebAssembly bytecode program into a native so library for faster execution.

The api/hello.js file conforms Vercel serverless specification. It loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice api/hello.js runs the compiled grayscale.so file generated by api/pre.sh for better performance.

const fs = require('fs');
const { spawn } = require('child_process');
const path = require('path');

module.exports = (req, res) => {
  const wasmedge = spawn(
      path.join(__dirname, 'wasmedge'), 
      [path.join(__dirname, 'grayscale.so')]);

  let d = [];
  wasmedge.stdout.on('data', (data) => {
    d.push(data);
  });

  wasmedge.on('close', (code) => {
    let buf = Buffer.concat(d);

    res.setHeader('Content-Type', req.headers['image-type']);
    res.send(buf);
  });

  wasmedge.stdin.write(req.body);
  wasmedge.stdin.end('');
}

That's it. Deploy the repo to Vercel and you now have a Vercel Jamstack app with a high-performance Rust and WebAssembly based serverless backend.

Example 2: AI inference

The second demo application allows users to upload an image and then invoke a serverless function to classify the main subject on the image.

It is in the same GitHub repo as the previous example but in the tensorflow branch. Note: when you import this GitHub repo on the Vercel website, it will create a preview URL for each branch. The tensorflow branch would have its own deployment URL.

The backend serverless function for image classification is in the api/functions/image-classification folder in the tensorflow branch. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the text output to the STDOUT. It utilizes the WasmEdge Tensorflow API to run the AI inference.

pub fn main() {
  // Step 1: Load the TFLite model
  let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
  let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");

  // Step 2: Read image from STDIN
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  // Step 3: Resize the input image for the tensorflow model
  let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);

  // Step 4: AI inference
  let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
  session.add_input("input", &flat_img, &[1, 224, 224, 3])
         .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");

  // Step 5: Find the food label that responds to the highest probability in res_vec
  // ... ...
  let mut label_lines = labels.lines();
  for _i in 0..max_index {
    label_lines.next();
  }

  // Step 6: Generate the output text
  let class_name = label_lines.next().unwrap().to_string();
  if max_value > 50 {
    println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
  } else {
    println!("It does not appears to be any food item in the picture.");
  }
}

You can use the cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-classification/
cargo build --release --target wasm32-wasi

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/classify.wasm ../../

Again, the api/pre.sh script installs WasmEdge runtime and its Tensorflow dependencies in this application. It also compiles the classify.wasm bytecode program to the classify.so native shared library at the time of deployment.

The api/hello.js file conforms Vercel serverless specification. It loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice api/hello.js runs the compiled classify.so file generated by api/pre.sh for better performance.

const fs = require('fs');
const { spawn } = require('child_process');
const path = require('path');

module.exports = (req, res) => {
  const wasmedge = spawn(
    path.join(__dirname, 'wasmedge-tensorflow-lite'),
    [path.join(__dirname, 'classify.so')],
    {env: {'LD_LIBRARY_PATH': __dirname}}
  );

  let d = [];
  wasmedge.stdout.on('data', (data) => {
    d.push(data);
  });

  wasmedge.on('close', (code) => {
    res.setHeader('Content-Type', `text/plain`);
    res.send(d.join(''));
  });

  wasmedge.stdin.write(req.body);
  wasmedge.stdin.end('');
}

You can now deploy your forked repo to Vercel and have a web app for subject classification.

Next, it's your turn to use the vercel-wasm-runtime repo as a template to develop your own Rust serverless functions in Vercel. Looking forward to your great work.

WebAssembly Serverless Functions in Netlify

In this article we will show you two serverless functions in Rust and WasmEdge deployed on Netlify. One is the image processing function, the other one is the TensorFlow inference function.

For more insights on why WasmEdge on Netlify, please refer to the article WebAssembly Serverless Functions in Netlify.

Prerequisite

Since our demo WebAssembly functions are written in Rust, you will need a Rust compiler. Make sure that you install the wasm32-wasi compiler target as follows, in order to generate WebAssembly bytecode.

rustup target add wasm32-wasi

The demo application front end is written in Next.js, and deployed on Netlify. We will assume that you already have the basic knowledge of how to work with Next.js and Netlify.

Example 1: Image processing

Our first demo application allows users to upload an image and then invoke a serverless function to turn it into black and white. A live demo deployed on Netlify is available.

Fork the demo application’s GitHub repo to get started. To deploy the application on Netlify, just add your github repo to Netlify.

This repo is a standard Next.js application for the Netlify platform. The backend serverless function is in the api/functions/image_grayscale folder. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the black-white image to the STDOUT.

use hex;
use std::io::{self, Read};
use image::{ImageOutputFormat, ImageFormat};

fn main() {
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap();
  let img = image::load_from_memory(&buf).unwrap();
  let filtered = img.grayscale();
  let mut buf = vec![];
  match image_format_detected {
    ImageFormat::Gif => {
      filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
    },
    _ => {
      filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
    },
  };
  io::stdout().write_all(&buf).unwrap();
  io::stdout().flush().unwrap();
}

You can use Rust’s cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-grayscale/
cargo build --release --target wasm32-wasi 

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/grayscale.wasm ../../

The Netlify function runs api/pre.sh upon setting up the serverless environment. It installs the WasmEdge runtime, and then compiles each WebAssembly bytecode program into a native so library for faster execution.

The api/hello.js script loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice api/hello.js runs the compiled grayscale.so file generated by api/pre.sh for better performance.

const fs = require('fs');
const { spawn } = require('child_process');
const path = require('path');

module.exports = (req, res) => {
  const wasmedge = spawn(
      path.join(__dirname, 'wasmedge'), 
      [path.join(__dirname, 'grayscale.so')]);

  let d = [];
  wasmedge.stdout.on('data', (data) => {
    d.push(data);
  });

  wasmedge.on('close', (code) => {
    let buf = Buffer.concat(d);

    res.setHeader('Content-Type', req.headers['image-type']);
    res.send(buf);
  });

  wasmedge.stdin.write(req.body);
  wasmedge.stdin.end('');
}

That's it. Deploy the repo to Netlify and you now have a Netlify Jamstack app with a high-performance Rust and WebAssembly based serverless backend.

Example 2: AI inference

The second demo application allows users to upload an image and then invoke a serverless function to classify the main subject on the image.

It is in the same GitHub repo as the previous example but in the tensorflow branch. The backend serverless function for image classification is in the api/functions/image-classification folder in the tensorflow branch. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the text output to the STDOUT. It utilizes the WasmEdge Tensorflow API to run the AI inference.

pub fn main() {
  // Step 1: Load the TFLite model
  let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
  let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");

  // Step 2: Read image from STDIN
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  // Step 3: Resize the input image for the tensorflow model
  let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);

  // Step 4: AI inference
  let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
  session.add_input("input", &flat_img, &[1, 224, 224, 3])
         .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");

  // Step 5: Find the food label that responds to the highest probability in res_vec
  // ... ...
  let mut label_lines = labels.lines();
  for _i in 0..max_index {
    label_lines.next();
  }

  // Step 6: Generate the output text
  let class_name = label_lines.next().unwrap().to_string();
  if max_value > 50 {
    println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
  } else {
    println!("It does not appears to be any food item in the picture.");
  }
}

You can use the cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-classification/
cargo build --release --target wasm32-wasi

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/classify.wasm ../../

Again, the api/pre.sh script installs WasmEdge runtime and its Tensorflow dependencies in this application. It also compiles the classify.wasm bytecode program to the classify.so native shared library at the time of deployment.

The api/hello.js script loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice api/hello.js runs the compiled classify.so file generated by api/pre.sh for better performance.

const fs = require('fs');
const { spawn } = require('child_process');
const path = require('path');

module.exports = (req, res) => {
  const wasmedge = spawn(
    path.join(__dirname, 'wasmedge-tensorflow-lite'),
    [path.join(__dirname, 'classify.so')],
    {env: {'LD_LIBRARY_PATH': __dirname}}
  );

  let d = [];
  wasmedge.stdout.on('data', (data) => {
    d.push(data);
  });

  wasmedge.on('close', (code) => {
    res.setHeader('Content-Type', `text/plain`);
    res.send(d.join(''));
  });

  wasmedge.stdin.write(req.body);
  wasmedge.stdin.end('');
}

You can now deploy your forked repo to Netlify and have a web app for subject classification.

Next, it's your turn to develop Rust serverless functions in Netlify using the netlify-wasm-runtime repo as a template. Looking forward to your great work.

WebAssembly Serverless Functions in AWS Lambda

In this article, we will show you two serverless functions in Rust and WasmEdge deployed on AWS Lambda. One is the image processing function, the other one is the TensorFlow inference function.

For the insight on why WasmEdge on AWS Lambda, please refer to the article WebAssembly Serverless Functions in AWS Lambda

Prerequisites

Since our demo WebAssembly functions are written in Rust, you will need a Rust compiler. Make sure that you install the wasm32-wasi compiler target as follows, in order to generate WebAssembly bytecode.

rustup target add wasm32-wasi

The demo application front end is written in Next.js, and deployed on AWS Lambda. We will assume that you already have the basic knowledge of how to work with Next.js and Lambda.

Example 1: Image processing

Our first demo application allows users to upload an image and then invoke a serverless function to turn it into black and white. A live demo deployed through GitHub Pages is available.

Fork the demo application’s GitHub repo to get started. To deploy the application on AWS Lambda, follow the guide in the repository README.

Create the function

This repo is a standard Next.js application. The backend serverless function is in the api/functions/image_grayscale folder. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the black-white image to the STDOUT.

use hex;
use std::io::{self, Read};
use image::{ImageOutputFormat, ImageFormat};

fn main() {
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap();
  let img = image::load_from_memory(&buf).unwrap();
  let filtered = img.grayscale();
  let mut buf = vec![];
  match image_format_detected {
    ImageFormat::Gif => {
      filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
    },
    _ => {
      filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
    },
  };
  io::stdout().write_all(&buf).unwrap();
  io::stdout().flush().unwrap();
}

You can use Rust’s cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-grayscale/
cargo build --release --target wasm32-wasi 

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/grayscale.wasm ../../

When we build the docker image, api/pre.sh is executed. pre.sh installs the WasmEdge runtime, and then compiles each WebAssembly bytecode program into a native so library for faster execution.

Create the service script to load the function

The api/hello.js script loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice that api/hello.js runs the compiled grayscale.so file generated by api/pre.sh for better performance.

const { spawn } = require('child_process');
const path = require('path');

function _runWasm(reqBody) {
  return new Promise(resolve => {
    const wasmedge = spawn(path.join(__dirname, 'wasmedge'), [path.join(__dirname, 'grayscale.so')]);

    let d = [];
    wasmedge.stdout.on('data', (data) => {
      d.push(data);
    });

    wasmedge.on('close', (code) => {
      let buf = Buffer.concat(d);
      resolve(buf);
    });

    wasmedge.stdin.write(reqBody);
    wasmedge.stdin.end('');
  });
}

The exports.handler part of hello.js exports an async function handler, used to handle different events every time the serverless function is called. In this example, we simply process the image by calling the function above and return the result, but more complicated event-handling behavior may be defined based on your need. We also need to return some Access-Control-Allow headers to avoid Cross-Origin Resource Sharing (CORS) errors when calling the serverless function from a browser. You can read more about CORS errors here if you encounter them when replicating our example.

exports.handler = async function(event, context) {
  var typedArray = new Uint8Array(event.body.match(/[\da-f]{2}/gi).map(function (h) {
    return parseInt(h, 16);
  }));
  let buf = await _runWasm(typedArray);
  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"
    },
    body: buf.toString('hex')
  };
}

Build the Docker image for Lambda deployment

Now we have the WebAssembly bytecode function and the script to load and connect to the web request. In order to deploy them as a function service on AWS Lambda, you still need to package the whole thing into a Docker image.

We are not going to cover in detail about how to build the Docker image and deploy on AWS Lambda, as there are detailed steps in the Deploy section of the repository README. However, we will highlight some lines in the Dockerfile for you to avoid some pitfalls.

FROM public.ecr.aws/lambda/nodejs:14

# Change directory to /var/task
WORKDIR /var/task

RUN yum update -y && yum install -y curl tar gzip

# Bundle and pre-compile the wasm files
COPY *.wasm ./
COPY pre.sh ./
RUN chmod +x pre.sh
RUN ./pre.sh

# Bundle the JS files
COPY *.js ./

CMD [ "hello.handler" ]

First, we are building the image from AWS Lambda's Node.js base image. The advantage of using AWS Lambda's base image is that it includes the Lambda Runtime Interface Client (RIC), which we need to implement in our Docker image as it is required by AWS Lambda. The Amazon Linux uses yum as the package manager.

These base images contain the Amazon Linux Base operating system, the runtime for a given language, dependencies and the Lambda Runtime Interface Client (RIC), which implements the Lambda Runtime API. The Lambda Runtime Interface Client allows your runtime to receive requests from and send requests to the Lambda service.

Second, we need to put our function and all its dependencies in the /var/task directory. Files in other folders will not be executed by AWS Lambda.

Third, we need to define the default command when we start our container. CMD [ "hello.handler" ] means that we will call the handler function in hello.js whenever our serverless function is called. Recall that we have defined and exported the handler function in the previous steps through exports.handler = ... in hello.js.

Optional: test the Docker image locally

Docker images built from AWS Lambda's base images can be tested locally following this guide. Local testing requires AWS Lambda Runtime Interface Emulator (RIE), which is already installed in all of AWS Lambda's base images. To test your image, first, start the Docker container by running:

docker run -p 9000:8080  myfunction:latest 

This command sets a function endpoint on your local machine at http://localhost:9000/2015-03-31/functions/function/invocations.

Then, from a separate terminal window, run:

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

And you should get your expected output in the terminal.

If you don't want to use a base image from AWS Lambda, you can also use your own base image and install RIC and/or RIE while building your Docker image. Just follow Create an image from an alternative base image section from this guide.

That's it! After building your Docker image, you can deploy it to AWS Lambda following steps outlined in the repository README. Now your serverless function is ready to rock!

Example 2: AI inference

The second demo application allows users to upload an image and then invoke a serverless function to classify the main subject on the image.

It is in the same GitHub repo as the previous example but in the tensorflow branch. The backend serverless function for image classification is in the api/functions/image-classification folder in the tensorflow branch. The src/main.rs file contains the Rust program’s source code. The Rust program reads image data from the STDIN, and then outputs the text output to the STDOUT. It utilizes the WasmEdge Tensorflow API to run the AI inference.

pub fn main() {
  // Step 1: Load the TFLite model
  let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
  let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");

  // Step 2: Read image from STDIN
  let mut buf = Vec::new();
  io::stdin().read_to_end(&mut buf).unwrap();

  // Step 3: Resize the input image for the tensorflow model
  let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);

  // Step 4: AI inference
  let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
  session.add_input("input", &flat_img, &[1, 224, 224, 3])
         .run();
  let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");

  // Step 5: Find the food label that responds to the highest probability in res_vec
  // ... ...
  let mut label_lines = labels.lines();
  for _i in 0..max_index {
    label_lines.next();
  }

  // Step 6: Generate the output text
  let class_name = label_lines.next().unwrap().to_string();
  if max_value > 50 {
    println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
  } else {
    println!("It does not appears to be any food item in the picture.");
  }
}

You can use the cargo tool to build the Rust program into WebAssembly bytecode or native code.

cd api/functions/image-classification/
cargo build --release --target wasm32-wasi

Copy the build artifacts to the api folder.

cp target/wasm32-wasi/release/classify.wasm ../../

Again, the api/pre.sh script installs WasmEdge runtime and its Tensorflow dependencies in this application. It also compiles the classify.wasm bytecode program to the classify.so native shared library at the time of deployment.

The api/hello.js script loads the WasmEdge runtime, starts the compiled WebAssembly program in WasmEdge, and passes the uploaded image data via STDIN. Notice api/hello.js runs the compiled classify.so file generated by api/pre.sh for better performance. The handler function is similar to our previous example, and is omitted here.

const { spawn } = require('child_process');
const path = require('path');

function _runWasm(reqBody) {
  return new Promise(resolve => {
    const wasmedge = spawn(
      path.join(__dirname, 'wasmedge-tensorflow-lite'),
      [path.join(__dirname, 'classify.so')],
      {env: {'LD_LIBRARY_PATH': __dirname}}
    );

    let d = [];
    wasmedge.stdout.on('data', (data) => {
      d.push(data);
    });

    wasmedge.on('close', (code) => {
      resolve(d.join(''));
    });

    wasmedge.stdin.write(reqBody);
    wasmedge.stdin.end('');
  });
}

exports.handler = ... // _runWasm(reqBody) is called in the handler

You can build your Docker image and deploy the function in the same way as outlined in the previous example. Now you have created a web app for subject classification!

Next, it's your turn to use the aws-lambda-wasm-runtime repo as a template to develop Rust serverless function on AWS Lambda. Looking forward to your great work.

WebAssembly serverless functions on Tencent Cloud

As the main users of Tencent Cloud are from China, so the tutorial is written in Chinese.

We also provide a code template for deploying serverless WebAssembly functions on Tencent Cloud, please check out the tencent-scf-wasm-runtime repo.

Fork the repo and start writing your own rust functions.

Second State Functions

Second State Functions, powered by WasmEdge, supports the Rust language as a first class citizen.

It could

Check out the Second State Functions website for more tutorials.

Operating systems

WasmEdge supports a wide range of operating systems and hardware platforms. It allows WebAssembly bytecode applications to be truly portable across platforms. It runs not only on Linux-like systems, but also on microkernels such as the seL4 system.

WasmEdge now supports:

  • Linux
  • Windows
  • macOS
  • seL4
  • OpenHarmony(ongoing)

Linux

WasmEdge supports a wide range of Linux distributions dated back to 2007. The official release contains statically linked binaries and libraries for older Linux systems. The table below shows build targets in WasmEdge's official release packages.

tag namearchbased operating systemLLVM versionENVscompatibilitycomments
latestx86_64Ubuntu 20.04 LTS12.0.0CC=clang, CXX=clang++Ubuntu 20.04+This is for CI, will always use the latest Ubuntu release
ubuntu-build-gccx86_64Ubuntu 20.04 LTS12.0.0CC=gcc, CXX=g++Ubuntu 20.04+This is for CI, will always use the latest Ubuntu release
ubuntu-build-clangx86_64Ubuntu 20.04 LTS12.0.0CC=clang, CXX=clang++Ubuntu 20.04+This is for CI, will always use the latest Ubuntu release
ubuntu2004_x86_64x86_64Ubuntu 20.04 LTS10.0.0CC=gcc, CXX=g++Ubuntu 20.04+This is for developers who familiar with Ubuntu 20.04 LTS release
ubuntu2104_armv7larmhfUbuntu 21.0412.0.0CC=gcc, CXX=g++Ubuntu 21.04+This is for armhf release
manylinux2014_x86_64x86_64CentOS 7, 7.9.200912.0.0CC=gcc, CXX=g++Ubuntu 16.04+, CentOS 7+This is for developers who familiar with CentOS on x86_64 architecture
manylinux2014_aarch64aarch64CentOS 7, 7.9.200912.0.0CC=gcc, CXX=g++Ubuntu 16.04+, CentOS 7+This is for developers who familiar with CentOS on aarch64 architecture
manylinux2010_x86_64x86_64CentOS 6, 6.1012.0.0CC=gcc, CXX=g++Ubuntu 14.04+, CentOS 6+This is for developers who familiar with legacy system on x86_64 architecture, EOL
manylinux1_x86_64x86_64CentOS 5, 5.1112.0.0CC=gcc, CXX=g++Ubuntu 14.04+, CentOS 5+This is for developers who familiar with legacy system on x86_64 architecture, EOL

Windows

WasmEdge provides a standard Windows PowerShell binary build as part of its official release package. You can install it via our standard install script.

Mac

WasmEdge provides both x86 and m1 binary builds for Mac OS X.

Android

The WasmEdge Runtime releases come with pre-built binaries for the Android OS. Why WasmEdge on Android?

However, the WasmEdge installer does not support Android. The user must download the release files to a computer, and then use the adb tool to transfer the files to an Android device or simulator. We will show you how to do that.

If you prefer to build WasmEdge Runtime binary on Android for yourself, you can refer to the build WasmEdge for Android document.

WasmEdge CLI tools for Android

In this section, we will show you how to use WasmEdge CLI tools on Android devices. We will showcase a full WasmEdge demo to perform image classification (Tensorflow-based AI inference) on an Android device.

Install Android version of WasmEdge-TensorFlow-Tools

First, install WasmEdge-TensorFlow-Tools pre-release on your Android device. It works with the Android version of TensorFlow-Lite dynamic shared library.

Preparation

Android developer options

Currently, WasmEdge only supports the arm64-v8a architecture on Android devices. You need an arm64-v8a Android simulator or a physical device with developer options turned on. WasmEdge requires Android 6.0 and above.

Android development CLI

In Ubuntu Linux, you can use the apt-get command to install Android debugging and testing tool adb. Using the adb shell command on the Ubuntu dev machine, you can open a CLI shell to execute commands on the connected Android device.

$ sudo apt-get install adb
$ adb devices
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
c657c643 device
$ adb shell
sirius:/ $

Install WasmEdge-TensorFlow-Tools packages

Use the following commands on your Ubuntu dev machine to download the WasmEdge-TensorFlow-Tools pre-release packages.

$ wget https://github.com/second-state/WasmEdge-tensorflow-tools/releases/download/0.10.0/WasmEdge-tensorflow-tools-0.10.0-android_aarch64.tar.gz
$ mkdir WasmEdge-tensorflow-tools && tar zxvf WasmEdge-tensorflow-tools-0.10.0-android_aarch64.tar.gz -C WasmEdge-tensorflow-tools
show-tflite-tensor
wasmedge-tensorflow-lite

Install Android version of the TensorFlow-Lite shared library

We provide an Android compatible version of TensorFlow-Lite dynamic shared library in the WasmEdge-Tensorflow-deps package. Download the package to your Ubuntu dev machine as follows.

$ wget https://github.com/second-state/WasmEdge-tensorflow-deps/releases/download/0.10.0/WasmEdge-tensorflow-deps-TFLite-0.10.0-android_aarch64.tar.gz
$ tar zxvf WasmEdge-tensorflow-deps-TFLite-0.10.0-android_aarch64.tar.gz -C WasmEdge-tensorflow-tools
libtensorflowlite_c.so

Next use the adb tool to push the downloaded WasmEdge-TensorFlow packages onto a connected Android device.

adb push WasmEdge-tensorflow-tools /data/local/tmp

Try it out

Sample application

In this example, we will demonstrate a standard WasmEdge Tensorflow-Lite sample application. It can recognize and classify the bird type from a JPG or PNG picture of a bird. The explanation of the source code can be found here.

git clone https://github.com/second-state/wasm-learning.git
cd wasm-learning/rust/birds_v1

Use the cargo command to build a Wasm bytecode file from the Rust source code. The Wasm file is located at target/wasm32-wasi/release/birds_v1.wasm.

rustup target add wasm32-wasi
cargo build --release --target=wasm32-wasi

Push the Wasm bytecode file, tensorflow lite model file, and the test bird picture file onto the Android device using adb.

adb push target/wasm32-wasi/release/birds_v1.wasm /data/local/tmp/WasmEdge-tensorflow-tools
adb push lite-model_aiy_vision_classifier_birds_V1_3.tflite /data/local/tmp/WasmEdge-tensorflow-tools
adb push bird.jpg /data/local/tmp/WasmEdge-tensorflow-tools

Run the WasmEdge-TensorFlow-Tools

Type adb shell from the Ubuntu CLI to open a command shell for the connected Android device. Confirm that the tools, programs, and test image are all available on the Android device under the /data/local/tmp/WasmEdge-tensorflow-tools folder.

$ adb shell
sirius:/ $ cd /data/local/tmp/WasmEdge-tensorflow-tools
sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ ls
bird.jpg               lite-model_aiy_vision_classifier_birds_V1_3.tflite 
birds_v1.wasm          show-tflite-tensor                                 
libtensorflowlite_c.so wasmedge-tensorflow-lite

Load the TensorFlow-Lite dynamic shared library, and use the show-tflite-tensor CLI tool to examine the Tensorflow Lite model file.

sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ chmod 777 show-tflite-tensor
sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ ./show-tflite-tensor lite-model_aiy_vision_classifier_birds_V1_3.tflite
INFO: Initialized TensorFlow Lite runtime.
Input tensor nums: 1
    Input tensor name: module/hub_input/images_uint8
        dimensions: [1 , 224 , 224 , 3]
        data type: UInt8
        tensor byte size: 150528
Output tensor nums: 1
    Output tensor name: module/prediction
        dimensions: [1 , 965]
        data type: UInt8
        tensor byte size: 965

Use the extended WasmEdge Runtime in wasmedge-tensorflow-lite to execute the compiled Wasm program on the Android device. It loads the Tensorflow Lite model and bird image, and outputs the bird classification and its confidence.

sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ chmod 777 wasmedge-tensorflow-lite
sirius:/data/local/tmp/WasmEdge-tensorflow-tools $ ./wasmedge-tensorflow-lite --dir .:. birds_v1.wasm lite-model_aiy_vision_classifier_birds_V1_3.tflite bird.jpg
INFO: Initialized TensorFlow Lite runtime.
166 : 0.84705883

The result shows that the bird type is in line 166 of the label file (Sicalis flaveola) and the confidence level is 84.7%.

Call WasmEdge functions from an NDK native app

In this section, we will demonstrate how to build an Android native application using C and the Android SDK. The native application uses the WasmEdge C SDK to embed the WasmEdge Runtime, and call WASM functions through WasmEdge.

Prerequisite

Android

Currently, WasmEdge only supports the arm64-v8a architecture on Android devices. You need an arm64-v8a Android simulator or a physical device with developer options turned on. WasmEdge requires Android 6.0 and above.

Android development CLI

In Ubuntu Linux, you can use the apt-get command to install Android debugging and testing tool adb. Using the adb shell command on the Ubuntu dev machine, you can open a CLI shell to execute commands on the connected Android device.

sudo apt-get install adb

Android NDK

To compile programs with the wasmedge-tensorflow c api, you need to install the Android NDK. In this example, we use the latest LTS version (r23b).

Review of source code

The test.c uses the wasmedge-tensorflow c api to run a WebAssembly function. The WebAssembly file birds_v1.wasm is compiled from Rust source code and explained here.

#include <wasmedge/wasmedge.h>
#include <wasmedge/wasmedge-image.h>
#include <wasmedge/wasmedge-tensorflowlite.h>

#include <stdio.h>

int main(int argc, char *argv[]) {
  /*
   * argv[0]: ./a.out
   * argv[1]: WASM file
   * argv[2]: tflite model file
   * argv[3]: image file
   * Usage: ./a.out birds_v1.wasm lite-model_aiy_vision_classifier_birds_V1_3.tflite bird.jpg
   */

  /* Create the VM context. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
  WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
  WasmEdge_ConfigureDelete(ConfCxt);
  
  /* Create the image and TFLite import objects. */
  WasmEdge_ImportObjectContext *ImageImpObj = WasmEdge_Image_ImportObjectCreate();
  WasmEdge_ImportObjectContext *TFLiteImpObj = WasmEdge_TensorflowLite_ImportObjectCreate();
  WasmEdge_ImportObjectContext *TFDummyImpObj = WasmEdge_Tensorflow_ImportObjectCreateDummy();

  /* Register into VM. */
  WasmEdge_VMRegisterModuleFromImport(VMCxt, ImageImpObj);
  WasmEdge_VMRegisterModuleFromImport(VMCxt, TFLiteImpObj);
  WasmEdge_VMRegisterModuleFromImport(VMCxt, TFDummyImpObj);

  /* Init WASI. */
  const char *Preopens[] = {".:."};
  const char *Args[] = {argv[1], argv[2], argv[3]};
  WasmEdge_ImportObjectContext *WASIImpObj = WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
  WasmEdge_ImportObjectInitWASI(WASIImpObj, Args, 3, NULL, 0, Preopens, 1);

  /* Run WASM file. */
  WasmEdge_String FuncName = WasmEdge_StringCreateByCString("_start");
  WasmEdge_Result Res = WasmEdge_VMRunWasmFromFile(VMCxt, argv[1], FuncName, NULL, 0, NULL, 0);
  WasmEdge_StringDelete(FuncName);

  /* Check the result. */
  if (!WasmEdge_ResultOK(Res)) {
    printf("Run WASM failed: %s\n", WasmEdge_ResultGetMessage(Res));
    return -1;
  }

  WasmEdge_ImportObjectDelete(ImageImpObj);
  WasmEdge_ImportObjectDelete(TFLiteImpObj);
  WasmEdge_ImportObjectDelete(TFDummyImpObj);
  WasmEdge_VMDelete(VMCxt);
  return 0;
}

Build

Install dependencies

Use the following commands to download WasmEdge for Android on your Ubuntu dev machine.

wget https://github.com/WasmEdge/WasmEdge/releases/download/0.10.0/WasmEdge-0.10.0-android_aarch64.tar.gz
wget https://github.com/second-state/WasmEdge-image/releases/download/0.10.0/WasmEdge-image-0.10.0-android_aarch64.tar.gz
wget https://github.com/second-state/WasmEdge-tensorflow/releases/download/0.10.0/WasmEdge-tensorflowlite-0.10.0-android_aarch64.tar.gz
wget https://github.com/second-state/WasmEdge-tensorflow-deps/releases/download/0.10.0/WasmEdge-tensorflow-deps-TFLite-0.10.0-android_aarch64.tar.gz
tar -zxf WasmEdge-0.10.0-android_aarch64.tar.gz
tar -zxf WasmEdge-image-0.10.0-android_aarch64.tar.gz -C WasmEdge-0.10.0-Android/
tar -zxf WasmEdge-tensorflowlite-0.10.0-android_aarch64.tar.gz -C WasmEdge-0.10.0-Android/
tar -zxf WasmEdge-tensorflow-deps-TFLite-0.10.0-android_aarch64.tar.gz -C WasmEdge-0.10.0-Android/lib/

Compile

The following command compiles the C program to a.out on your Ubunu dev machine.

(/path/to/ndk)/toolchains/llvm/prebuilt/(HostPlatform)/bin/aarch64-linux-(AndroidApiVersion)-clang test.c -I./WasmEdge-0.10.0-Android/include -L./WasmEdge-0.10.0-Android/lib -lwasmedge-image_c -lwasmedge-tensorflowlite_c -ltensorflowlite_c -lwasmedge_c

Run

Push files onto Android

Install the compiled program, Tensorflow Lite model file, test image file, as well as WasmEdge shared library files for Android, onto the Android device using adb from your Ubuntu dev machine.

adb push a.out /data/local/tmp
adb push birds_v1.wasm /data/local/tmp
adb push lite-model_aiy_vision_classifier_birds_V1_3.tflite /data/local/tmp
adb push bird.jpg /data/local/tmp
adb push ./WasmEdge-0.10.0-Android/lib /data/local/tmp

Run the example

Now you can run the compiled C program on the Android device via a remote shell command. Run adb shell from your Ubuntu dev machine.

$ adb shell
sirius:/ $ cd /data/local/tmp
sirius:/data/local/tmp $ export LD_LIBRARY_PATH=/data/local/tmp/lib:$LD_LIBRARY_PATH
sirius:/data/local/tmp $ ./a.out birds_v1.wasm lite-model_aiy_vision_classifier_birds_V1_3.tflite bird.jpg
INFO: Initialized TensorFlow Lite runtime.
166 : 0.84705883

Call WasmEdge functions from an Android APK app

In this section, we will show you how to build a "regular" Android app (i.e., an APK file that can be installed on an Android device). The APK app embeds a WasmEdge Runtime. It can call WebAssembly functions through the embedded WasmEdge. The benefit is that developers can safely embed high-performance functions written in several different languages (e.g., Rust, JS, Grain, TinyGo etc) into a Kotlin application.

Quickstart

The demo project is available here. You can build the project using the Gradle tool or using the Android Stuido IDE.

Building Project with Gradle

  1. Setup environment variable ANDROID_HOME=path/to/your/android/sdk
  2. Run Command ./gradlew assembleRelease
  3. Sign your APK file with apksigner. The apk file is at ./app/build/outputs/apk/release. The apksigner utility is at $ANDROID_HOME/build-tools/$VERSION/apksigner.

Building Project with Android Studio

Open this folder with Android Studio 2020.3.1 or later.

For Release APK, click Menu -> Build -> Generate Signed Bundle/APK, select APK, setup keystore configuration and wait for build finished.

Review of the source code

The Android UI app is written in Kotlin, and it uses JNI (Java Native Interface) to load a C shared library, which in turn embeds WasmEdge.

Android UI

The Android UI application is located here. It is written in Kotlin using the Android SDK.

class MainActivity : AppCompatActivity() {
  lateinit var lib: NativeLib

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val tv = findViewById<TextView>(R.id.tv_text)

    lib = NativeLib(this)

    Thread {
      val lines = Vector<String>()
      val idxArr = arrayOf(20, 25, 28, 30, 32)
      for (idx: Int in idxArr) {
        lines.add("running fib(${idx}) ...")
        runOnUiThread {
          tv.text = lines.joinToString("\n")
        }
        val begin = System.currentTimeMillis()
        val retVal = lib.wasmFibonacci(idx)
        val end = System.currentTimeMillis()
        lines.removeLast()
        lines.add("fib(${idx}) -> ${retVal}, ${end - begin}ms")
        runOnUiThread {
          tv.text = lines.joinToString("\n")
        }
      }
    }.start()
  }
}

The native library

The Android UI app calls a NativeLib Kotlin object to access WasmEdge functions. The NativeLib source code is available here. It uses JNI (Java Native Interface) to load a C shared library called wasmedge_lib. It then calls the nativeWasmFibonacci function in wasmedge_lib to execute the fibonacci.wasm WebAssembly bytecode.

class NativeLib(ctx : Context) {
  private external fun nativeWasmFibonacci(imageBytes : ByteArray, idx : Int ) : Int

  companion object {
    init {
      System.loadLibrary("wasmedge_lib")
    }
  }

  private var fibonacciWasmImageBytes : ByteArray = ctx.assets.open("fibonacci.wasm").readBytes()

  fun wasmFibonacci(idx : Int) : Int{
    return nativeWasmFibonacci(fibonacciWasmImageBytes, idx)
  }
}

The C shared library

The C shared library source code wasmedge_lib.cpp is available here. It uses the WasmEdge C SDK to embed a WasmEdge VM and execute the WebAssembly function.

extern "C" JNIEXPORT jint JNICALL
Java_org_wasmedge_native_1lib_NativeLib_nativeWasmFibonacci(
    JNIEnv *env, jobject, jbyteArray image_bytes, jint idx) {
  jsize buffer_size = env->GetArrayLength(image_bytes);
  jbyte *buffer = env->GetByteArrayElements(image_bytes, nullptr);

  WasmEdge_ConfigureContext *conf = WasmEdge_ConfigureCreate();
  WasmEdge_ConfigureAddHostRegistration(conf, WasmEdge_HostRegistration_Wasi);

  WasmEdge_VMContext *vm_ctx = WasmEdge_VMCreate(conf, nullptr);

  const WasmEdge_String &func_name = WasmEdge_StringCreateByCString("fib");
  std::array<WasmEdge_Value, 1> params{WasmEdge_ValueGenI32(idx)};
  std::array<WasmEdge_Value, 1> ret_val{};

  const WasmEdge_Result &res = WasmEdge_VMRunWasmFromBuffer(
      vm_ctx, (uint8_t *)buffer, buffer_size, func_name, params.data(),
      params.size(), ret_val.data(), ret_val.size());

  WasmEdge_VMDelete(vm_ctx);
  WasmEdge_ConfigureDelete(conf);
  WasmEdge_StringDelete(func_name);

  env->ReleaseByteArrayElements(image_bytes, buffer, 0);
  if (!WasmEdge_ResultOK(res)) {
    return -1;
  }
  return WasmEdge_ValueGetI32(ret_val[0]);
}

The WebAssembly function

The factorial.wat is a handwritten WebAssembly script to compute factorial numbers. It is compiled into WebAssembly using the WABT tool.

Build dependencies

Android Studio and Gradle use CMake to build the C shared library. The CMakeLists.txt file builds the WasmEdge source into Android shared library files and embeds them into the final APK application. In this case, there is no seperate step to install WasmEdge share libraries onto the Android device.

WasmEdge on seL4

Video demo | Build logs | Build artifact

In this article, we demonstrate how to run WasmEdge on the seL4 RTOS, there are two parts:

  1. Guest Linux OS on seL4: This is the controller of WasmEdge runtime, which will send wasm program to WasmEdge runner that is a agent on seL4 to execute.
  2. WasmEdge runner on seL4: This is the wasm program runtime, which will execute the given wasm program from Guest Linux OS.

The figure below illustrates the architecture of the system.

wasmedge-sel4

This demo is based on the seL4 simulator on Linux.<