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

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 tools/wasmedge/examples
# 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 wget as prerequisites.

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

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.9.0-rc.5

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.

  • 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 and wasmedgec-tensorflow tools are runtimes and compilers 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.

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.

Cloud-native runtime (as a lightweight Docker alternative)

WasmEdge can be embedded into cloud-native infrastructure via its SDKs in C, Go, Rust, and JavaScript. It is also an OCI compliant runtime that can be directly managed by CRI-O and Docker tools as a lightweight and high-performance alternative to Docker.

Dapr (Distributed Application Runtime)

Service mesh (work in progress):

  • Linkerd
  • MOSN
  • Envoy

Orchestration and management:

JavaScript or DSL runtime

In order for WebAssembly to be widely adopted by developers as a runtime, it must support "easy" languages like JavaScript. Or, better yet, through its advanced compiler toolchain, WasmEdge could support high performance DSLs (Domain Specifc Languages), which are low code solutions designed for specific tasks.

JavaScript

WasmEdge can act as a cloud-native JavaScript runtime by embedding a JS execution engine or interpreter. It is faster and lighter than running a JS engine inside Docker. WasmEdge supports JS APIs to access native extension libraries such as network sockets, tensorflow, and user-defined shared libraries. It also allows embedding JS into other high-performance languages (eg, Rust) or using Rust / C to implement JS functions.

DSL for image classification

The image classification DSL is a YAML format that allows the user to specify a tensorflow model and its parameters. WasmEdge takes an image as the input of the DSL and outputs the detected item name / label.

DSL for chatbots

A chatbot DSL function takes an input string and responds with a reply string. The DSL specifies the internal state transtions of the chatbot, as well as AI models for language understanding. This work is in progress.

Serverless function-as-a-service in public clouds

WasmEdge works with existing serverless or Jamstack platforms to provide a high-performance, portable and secure runtime for functions. It offers significant benefits even when it runs inside Docker or microVMs on those platforms.

AWS Lambda

Tencent Serverless Functions

Vercel Serverless Functions

Netlify Functions

Second State Functions

Edge computing

WasmEdge is ideally suited to run on mission-critical edge devices or edge networks.

YoMo Flow

YoMo is a high-performance data streaming framework for far edge networks. WasmEdge is integrated into YoMo to run user-defined workloads, such as image classification along a factory assembly line.

seL4 microkernel OS

seL4 is a highly secure real-time operating system. WasmEdge is the only WebAssembly runtime that can run on seL4, and it runs at native speed. We also provide a management tool to support the OTA deployment of wasm modules.

Reactive 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.

Slack

Lark

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

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

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

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

  • WASI (WebAssembly Systems Interface) spec. WasmEdge has supported the WASI spec for WebAssembly programs to interact with the host Linux operating system securely.
  • Reference Types. It allows WebAssembly programs to exchange data with host applications and operating systems.
  • Bulk memory operations. The WebAssembly program sees faster memory access and performs better with bulk memory operations.
  • SIMD (Single instruction, multiple data). For modern devices with multiple CPU cores, the SIMD allows data processing programs to take advantage of the CPUs fully. SIMD could significantly enhance the performance of data applications.

Meanwhile, the WasmEdge team is exploring the wasi-socket proposal to support network access in WebAssembly programs.

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);
  }
}

Build the WASM bytecode

$ cargo build --target wasm32-wasi

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;
}
}

Build the WASM bytecode

$ cargo build --target wasm32-wasi

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.
  • Networking socket shows how to create networking applications for WebAssembly using the WasmEdge networking socket Rust SDK.
  • Command interface shows how to create native command applications for WebAssembly using the Wasmedge command interface Rust SDK.
  • Bindgen and rustwasmc shows how to use the rustwasmc toolchain to compile Rust functions into WebAssembly, and then pass complex call parameters to the function from an external host application.

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.

Networking sockets

The wasmedge_wasi_socket crate enables Rust developers to create networking applications and compile them into WebAssembly for WasmEdge Runtime.

The 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));
}

To compile this source code, you must have the following in your Cargo.toml.

[dependencies]
wasmedge_http_req  = "0.8.1"

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

The 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);
    }
}

To compile this source code, you must have the following in your Cargo.toml.

[dependencies]
wasmedge_http_req  = "0.8.1"

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

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. The Second State WebAssembly VM 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.

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.

Bindgen and rustwasmc

The rustwasmc tool is inspired by the wasm-pack project but is optimized for edge cloud and device applications. Specifically, it supports the WasmEdge WebAssembly runtime.

One of the key features of rustwasmc over the standard wasm32-wasi compiler target is that rustwasmc processes compiled Rust functions using the wasm-bindgen tool. By default, WebAssembly functions only support a few simple data types as input call arguments. Tools like wasm-bindgen turn WebAssembly function arguments into memory pointers, and allow host applications to pass complex arguments, such as strings and arrays, to WebAssembly functions. WasmEdge's Node.js SDK and Go SDK both support wasm-bindgen, allowing JavaScript and Go programs to call WebAssembly function with complex call arguments.

At this time, we require Rust compiler version 1.50 or less in order for WebAssembly functions to work with wasm-bindgen and rustwasmc. We will catch up to the latest Rust compiler version once the Interface Types spec is finalized and supported.

Prerequisites

The rustwasmc depends on the Rust cargo toolchain to compile Rust source code to WebAssembly. You must have Rust installed on your machine.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ source $HOME/.cargo/env
$ rustup override set 1.50.0

Install

The easiest way to install rustwasmc is to use its installer.

$ curl https://raw.githubusercontent.com/second-state/rustwasmc/master/installer/init.sh -sSf | sh

Alternatively, you can install using the NPM if you'd like.

Usage

To build Rust functions for Node.js applications, use the following command. See a template application.

$ rustwasmc build

Use the --enable-ext flag to compile Rust programs that use WASI extensions, such as WasmEdge's storage and Tensorflow APIs. The rustwasmc will generates the compiled WebAssembly bytecode program for the wasmedge-extensions Node.js module instead of the wasmedge-core module in this case.

$ rustwasmc build --enable-ext

Support AOT

A key feature of the WasmEdge runtime is its support for Ahead-of-Time (AOT) compilers. When you run WebAssembly programs in Node.js wasmedge-core and wasmedge-extensions add-ons, you typically do not need to worry about it as the add-on handles AOT compilation transparently. However, in some cases, you do want the rustwasmc to compile and generate native code for the program.

Then, use the commands below to bring your operating system up to date with the latest developer tools. The commands here are tested on Ubuntu 20.04.

$ sudo apt-get update
$ sudo apt-get -y upgrade
$ sudo apt install build-essential curl wget git vim libboost-all-dev llvm-dev liblld-10-dev

Now, you can build the .so files for the AOT native target like the following.

$ rustwasmc build --enable-aot

Enjoy coding!

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.

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.
  • ES6 module shows how to run ES6 modules in WasmEdge.
  • CommonJS module shows how to run CommonJS modules in WasmEdge.
  • NodeJS and NPM module shows how to run NPM modules in WasmEdge.
  • React SSR shows an example React SSR application in WasmEdge.
  • TensorFlow shows how to use WasmEdge's TensorFlow extension from its JavaScript API.
  • Networking sockets shows how to create HTTP client and server applications using the WasmEdge networking extension and its JavaScript API.
  • Async networking shows how to improve HTTP server application performance by supporting asynchronous and non-blocking I/O.
  • Use Rust to implement JS API discusses how to use Rust to implement and support a JavaScript API in WasmEdge.

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.

args = args.slice(1)
print("Hello", ...args)

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.

ES6 module

The WasmEdge QuickJS runtime supports ES6 modules. 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.

CommonJS module

The WasmEdge QuickJS runtime supports CommonJS (CJS) modules. This article will show you how to use CJS modules in WasmEdge.

The example_js/simple_common_js_demo folder in the GitHub repo contains several examples for your reference.

The other_module/main.js file defines and exports a simple CJS module.

print('hello other_module')
module.exports = ['other module exports']

The one_module/main.js file uses the CJS module.

print('hello one_module');
print('dirname:',__dirname);
let other_module_exports = require('../other_module/main.js')
print('other_module_exports=',other_module_exports)

Then the file_module.js file imports the module and runs it.

import * as one from './one_module/main.js'
print('hello file_module')

To run the example, you need to build a WasmEdge QuickJS runtime with CJS support.

$ cargo build --target wasm32-wasi --release --features=cjs

Finally, do the following on the CLI.

$ cd example_js/simple_common_js_demo
$ wasmedge --dir .:. ../../target/wasm32-wasi/release/wasmedge_quickjs.wasm file_module.js
hello one_module
dirname: one_module
hello other_module
other_module_exports= other module exports
hello file_module

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

NodeJS and NPM module

With CommonJS support, we can run NodeJS 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.

import * as std from 'std'

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

print('write file')
let f = std.open('hello.txt','w')
let x = f.puts("hello wasm")
f.flush()
f.close()

In order to run it, we must first use the vercel ncc tool to build all dependencies into a single file. The build script is package.json.

{
  "dependencies": {
    "mathjs": "^9.5.1",
    "md5": "^2.3.0"
  },
  "devDependencies": {
    "@vercel/ncc": "^0.28.6"
  },
  "scripts": {
    "ncc_build": "ncc build npm_main.js"
  }
}

Now, install ncc and npm_main.js dependencies via NPM, and then build the single JS file in dist/index.js.

$ npm install
$ npm run ncc_build
ncc: Version 0.28.6
ncc: Compiling file index.js

To run the example, you need to build a WasmEdge QuickJS runtime with CJS support.

$ cargo build --target wasm32-wasi --release --features=cjs

Run the JS file with NodeJS imports in WasmEdge CLI as follows.

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

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

Example: React SSR

React Server-Side Rendering (SSR) is a common use of JavaScript in BFF (backend for frontend) functions. Instead of rending HTML DOM elements in the browser, it uses the React framework to render HTML elements from the server side to speed up the application. It is an ideal use case for serverless functions in Jamstack applications.

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.

The example_js/react_ssr folder in the GitHub repo contains the example's source code.

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 Home from './component/Home.jsx';
import {renderToString} from 'react-dom/server';
import React from 'react';

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

The rollup.config.js and package.json files are to build the React SSR dependencies into a single 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, you need to build a WasmEdge QuickJS runtime with CJS support.

$ cargo build --target wasm32-wasi --release --features=cjs

Finally, do the following on the CLI.

$ 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.

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 {TensorflowLiteSession} from 'tensorflow_lite'
import {Image} from 'image'let img = new Image('./example_js/tensorflow_lite_demo/food.jpg')

let img_rgb = img.to_rgb().resize(192,192)
let rgb_pix = img_rgb.pixels()let session = new TensorflowLiteSession('./example_js/tensorflow_lite_demo/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;
    }
}
print(max,max_idx)

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!

Networking sockets

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.

A JavaScript networking client example

Below is an example of JavaScript running a HTTP client. You could find the code in example_js/http_demo.js.

let r = GET("http://18.235.124.214/get?a=123",{"a":"b","c":[1,2,3]})
print(r.status)
    
let headers = r.headers
print(JSON.stringify(headers))let body = r.body;
let body_str = new Uint8Array(body)
print(String.fromCharCode.apply(null,body_str))

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 http_demo.js

You should now see the HTTP GET result printed on the console.

A JavaScript networking server example

Below is an example of JavaScript running a HTTP server listening at port 3000. You could find the code in example_js/http_server_demo.js.

import {HttpServer} from 'http'

let http_server = new HttpServer('0.0.0.0:8000')
print('listen on 0.0.0.0:8000')

while(true){
    http_server.accept((request)=>{
        let body = request.body
        let body_str = String.fromCharCode.apply(null,new Uint8Array(body))
        print(JSON.stringify(request),'\n body_str:',body_str)

        return {
            status:200,
            header:{'Content-Type':'application/json'},
            body:'echo:'+body_str
        }
    });
}

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 http_server_demo.js &

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

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

You should now see the HTTP POST body printed on the console.

Async networking

Coming soon.

Check out the WasmEdge-QuickJS proejct to get the latest updates.

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.

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

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.

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.

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.

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.

    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.

    ctx.get_global().set("test_obj", obj.into());

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

    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.

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

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.

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.

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]
}

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.

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

For the details of APIs, please refer to the API header file.

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

For the details of APIs, please refer to the API header file.

WasmEdge C 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.

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.

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.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.9.0
    

WasmEdge Basics

In this partition, 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. */
    

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_Annotations,
      WasmEdge_Proposal_Memory64,
      WasmEdge_Proposal_Threads,
      WasmEdge_Proposal_ExceptionHandling,
      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_SIMD);
    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);
    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));
      }
      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
    

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.

uint32_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.

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.15. Please check your golang version before installation. Developers can download golang here.

$ go version
go version go1.16.5 linux/amd64

Developers must install the WasmEdge shared library with the same WasmEdge-go release version.

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.0-rc.4

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

For the developers need the TensorFlow or Image extension for WasmEdge-go, please install the WasmEdge with extensions:

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.0-rc.4

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.9.0-rc3
$ 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.
    • 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

For examples, please refer to the example repository.

WasmEdge AOT Compiler in Go

The go_WasmAOT example provide a tool for compiling a WASM file into compiled-WASM for AOT mode.

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.GetImportObject(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.9.0-rc3
$ 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.GetImportObject(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.GetImportObject(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.9.0-rc5
$ 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.GetImportObject(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.9.0-rc5
$ 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.GetImportObject(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.9.0-rc5
$ 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.GetImportObject(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.9.0-rc5
$ 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.GetImportObject(wasmedge.WASI)
	wasi.InitWasi(
		os.Args[1:],     /// The args
		os.Environ(),    /// The envs
		[]string{".:."}, /// The mapping preopens
	)

	/// Register WasmEdge-tensorflow
	var tfobj = wasmedge.NewTensorflowImportObject()
	var tfliteobj = wasmedge.NewTensorflowLiteImportObject()
	vm.RegisterImport(tfobj)
	vm.RegisterImport(tfliteobj)

	/// 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()
	tfobj.Release()
	tfliteobj.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.GetImportObject(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.9.0-rc3
$ 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 API references

The followings are the guides to working with the WasmEdge-Go SDK.

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.

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.9.0

For the developers need the TensorFlow or Image extension for WasmEdge-go, please install the WasmEdge with extensions:

wget -qO- https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e tf,image -v 0.9.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.9.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 of Embedding A Function with wasm-bindgen

In this example, we will demonstrate how to call a few simple WebAssembly functions with wasm-bindgen from a Golang 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 Golang to WebAssembly.

Note: At this time, we require Rust compiler version 1.50 or less in order for WebAssembly functions to work with WasmEdge’s Golang API. We will catch up to the latest Rust compiler version once the Interface Types specification is finalized and supported.

Note: 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 wasm-bindgen tool, embedded in rustwasmc, does this conversion automatically.


#![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 Golang source code to run the WebAssembly function in WasmEdge is as follows. The ExecuteBindgen() function calls the WebAssembly function and passes the parameters with the wasm-bindgen supporting.

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.GetImportObject(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, build the Golang application with the WasmEdge Golang SDK.

$ go get github.com/second-state/WasmEdge-go/wasmedge@v0.9.0
$ go build

Run the Golang 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]

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. Note that there is no need for #[wasm_bindgen] here, as the WebAssembly program’s WASI supporting for the argv input and exit code output of the main() function.

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.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

    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.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.

  3. Wasm-bindgen Supporting

    The (*VM).ExecuteBindgen and the (*VM).ExecuteBindgenRegistered are the special functions for supporting the wasm-bindgen functions execution. In WasmEdge-go, we support the following return types of the wasm-bindgen functions:

    type bindgen int
    
    const (
        Bindgen_return_void  bindgen = iota
        Bindgen_return_i32   bindgen = iota
        Bindgen_return_i64   bindgen = iota
        Bindgen_return_array bindgen = iota
    )
    

    And only the int32, uint32, int64, uint64, and []byte parameters are accepted. Each wasm-bindgen function has at most only 1 return value.

    // Take the wasm-bindgen case for example.
    var res interface{}
    var err error
    res, err = vm.ExecuteBindgen(
        "lowest_common_multiple",       // Function name
        wasmedge.Bindgen_return_i32,    // Return type: int32
        int32(123), int32(2)            // Parameters: int32, int32
    )
    if err == nil {
        fmt.Println("Run bindgen -- lowest_common_multiple:", res.(int32))
    } 
    res, err = vm.ExecuteBindgen(
        "sha3_digest",                          // Function name
        wasmedge.Bindgen_return_array,          // Return type: []byte
        []byte("This is an important message")  // Parameter: []byte
    )
    if err == nil {
        fmt.Println("Run bindgen -- sha3_digest:", res.([]byte))
    } 
    

    For the full example, please refer to the example above.

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.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.9.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.
    

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.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. 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.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 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.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.

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
(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.

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' }  

Rust SDK

You can also embed WasmEdge into your Rust application via the WasmEdge Rust SDK.

The WasmEdge Rust SDK includes two Rust crates, wasmedge-sys and wasmedge-rs, for the Rust API.

  • The wasmedge-sys is a low-level API generated from the WasmEdge C API.
  • The wasmedge-sdk is an idiomatic Rust API wrapped around the low-level wasmedge-sys to make it safer and more developer-friendly. The full wasmedge-sdk crate is still under active development. Check out the source code on GitHub. Feedbacks and contributions are welcome.

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.

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

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 node.

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

Coming soon, or you can help out

OpenYurt

Coming soon, or you can help out

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.

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. We use a simple NaCl written in Rust or Go to listen for API requests to the microservice. It passes the request data to a WebAssembly runtime for processing. The business logic of the microservice is a WebAssembly function created and deployed by an application developer. You can also watch a walk-through video.

For more insights on WasmEdge on Dapr, please refer to the article A Lightweight, Safe, Portable, and High-performance Runtime for Dapr

Quick start

First you need to install Go, Rust, Dapr, WasmEdge, and the rustwasmc compiler tool.

Next, 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 3 Dapr sidecar applications.

  • The web-port project provides a public web service for a static HTML page. This is the application’s UI.
  • The image-api-rs project provides a WasmEdge microservice to turn an input image into a grayscale image using the grayscale function. It demonstrates the use of Rust SDKs for Dapr and WasmEdge.
  • The image-api-go project provides a WasmEdge microservice to recognize and classify the object on an input image using the classify function. It demonstrates the use of Go SDKs for Dapr and WasmEdge.

dapr-wasmedge

Dapr sidecar microservices in the demo application.

You can follow the instructions in the README to start the sidecar services. Here are commands to build the WebAssembly functions and start the 3 sidecar services.

# Build the classify and grayscale WebAssembly functions, and deploy them to the sidecar projects
$ cd functions/grayscale
$ ./build.sh
$ cd ../../
$ cd functions/classify
$ ./build.sh
$ cd ../../

# Build and start the web service for the application UI
$ cd web-port
$ go build
$ ./run_web.sh
$ cd ../

# Build and start the microservice for image processing (grayscale)
$ cd image-api-rs
$ cargo build
$ ./run_api_rs.sh
$ cd ../

# Build and start the microservice for tensorflow-based image classification
$ cd image-api-go
$ go build --tags "tensorflow image"
$ ./run_api_go.sh
$ cd ../

Finally, you should be able to see the web UI in your browser.

dapr-wasmedge

The demo application in action.

The two WebAssembly functions

We have two functions written in Rust and compiled into WebAssembly. They are deployed in the sidecar microservices to perform the actual work of image processing and classification.

While our example WebAssembly functions are written in Rust, you can compile functions written in C/C++, Swift, Kotlin, and AssemblyScript to WebAssembly. WasmEdge also provides support for functions written in JavaScript and DSLs.

The grayscale function is a Rust program that reads image data from STDIN and writes the grayscale image into STDOUT.

use image::{ImageFormat, ImageOutputFormat};
use std::io::{self, Read, Write};

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();
}

We use rustwasmc to build it and then copy it to the image-api-rs sidecar.

$ cd functions/grayscale
$ rustup override set 1.50.0
$ rustwasmc  build --enable-ext
$ cp ./pkg/grayscale.wasm ../../image-api-rs/lib

The classify function is a Rust function that takes a byte array for image data as input and returns a string for the classification. It uses the WasmEdge TensorFlow API.

use wasmedge_tensorflow_interface;

pub fn infer_internal(image_data: &[u8]) -> String {
   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");

   let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(image_data, 224, 224);

   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");

   // ... Map the probabilities in res_vec to text labels in the labels file ...
  
   if max_value > 50 {
       format!(
           "It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture",
           confidence.to_string(),
           class_name,
           class_name
       )
   } else {
       format!("It does not appears to be any food item in the picture.")
   }
}

We use rustwasmc to build it and then copy it to the image-api-go sidecar.

$ cd functions/classify
$ rustup override set 1.50.0
$ rustwasmc  build --enable-ext
$ cp ./pkg/classify_bg.wasm ../../image-api-go/lib/classify_bg.wasm

In the next three sections, we will look into those three sidecar services.

The image processing sidecar

The image-api-rs sidecar application is written in Rust. It should already have the WebAssembly function lib/grayscale.wasm installed from the previous step. Please refer to the functions/bin/install.sh script to install the WasmEdge Runtime binary lib/wasmedge-tensorflow-lite and its dependencies.

The sidecar microservice runs a Tokio-based event loop that listens for incoming HTTP requests at the path /api/image.

#[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(&v);
           Ok(Box::new(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
}

Once it receives an image file in the HTTP POST request, it invokes a WebAssembly function in WasmEdge to perform the image processing task. It creates a WasmEdge instance to interact with the WebAssembly program.

pub fn image_process(buf: &Vec<u8>) -> Vec<u8> {
   let mut child = Command::new("./lib/wasmedge-tensorflow-lite")
       .arg("./lib/grayscale.wasm")
       .stdin(Stdio::piped())
       .stdout(Stdio::piped())
       .spawn()
       .expect("failed to execute child");
   {
       // limited borrow of stdin
       let stdin = child.stdin.as_mut().expect("failed to get stdin");
       stdin.write_all(buf).expect("failed to write to stdin");
   }
   let output = child.wait_with_output().expect("failed to wait on child");
   output.stdout
}

The following Dapr CLI command starts the microservice in the Dapr runtime environment.

$ cd image-api-rs
$ sudo dapr run --app-id image-api-rs \
        --app-protocol http \
        --app-port 9004 \
        --dapr-http-port 3502 \
        --components-path ../config \
        --log-level debug \
        ./target/debug/image-api-rs
$ cd ../

The Tensorflow sidecar

The image-api-go sidecar application is written in Go. It should already have the WebAssembly function lib/classify\_bg.wasm installed from the previous step. Please refer to the functions/bin/install.sh script to install the WasmEdge Runtime Go SDK.

The sidecar microservice runs an event loop that listens for incoming HTTP requests at the path /api/image.

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)
   }
}

Once it receives an image file in the HTTP POST request, it invokes a WebAssembly function in WasmEdge to perform the Tensorflow-based image classification task. It utilizes the Go API for WasmEdge to interact with the WebAssembly program.

func imageHandlerWASI(_ context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
   image := in.Data

   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
       []string{},      /// The preopens will be empty
   )

   /// 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)

   vm.LoadWasmFile("./lib/classify_bg.wasm")
   vm.Validate()
   vm.Instantiate()

   res, err := vm.ExecuteBindgen("infer", wasmedge.Bindgen_return_array, image)
   ans := string(res.([]byte))
  
   vm.Delete()
   conf.Delete()

   out = &common.Content{
       Data:        []byte(ans),
       ContentType: in.ContentType,
       DataTypeURL: in.DataTypeURL,
   }
   return out, nil
}

The following Dapr CLI command starts the microservice in the Dapr runtime environment.

$ cd image-api-go
$ sudo 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
$ cd ../

The web UI sidecar

The web UI service web-port is a simple web server written in Go. It serves static HTML and JavaScript files from the static folder and sends images uploaded to /api/hello to the grayscale or classify sidecars’ /api/image endpoints.

func main() {
   http.HandleFunc("/static/", staticHandler)
   http.HandleFunc("/api/hello", imageHandler)
   println("listen to 8080 ...")
   log.Fatal(http.ListenAndServe(":8080", nil))
}

func staticHandler(w http.ResponseWriter, r *http.Request) {
   // ... read and return the contents of HTML CSS and JS files ...
}

func imageHandler(w http.ResponseWriter, r *http.Request) {
   // ... ...
   api := r.Header.Get("api")
   if api == "go" {
       daprClientSend(body, w)
   } else {
       httpClientSend(body, w)
   }
}

// Send to the image-api-go sidecar (classify) via the Dapr API
func daprClientSend(image []byte, w http.ResponseWriter) {
   // ... ...
   resp, err := client.InvokeMethodWithContent(ctx, "image-api-go", "/api/image", "post", content)
   // ... ...
}

// Send to the image-api-rs sidecar (grayscale) via the HTTP API
func httpClientSend(image []byte, w http.ResponseWriter) {
   // ... ...
   req, err := http.NewRequest("POST", "http://localhost:3502/v1.0/invoke/image-api-rs/method/api/image", bytes.NewBuffer(image))
   // ... ...
}

The JavaScript in page.js simply uploads images to the web-port sidecar’s /api/hello endpoint and the web-port will request the classify or grayscale microservice based on the request header api.

function runWasm(e) {
   const reader = new FileReader();
   reader.onload = function (e) {
       setLoading(true);
       var req = new XMLHttpRequest();
       req.open("POST", '/api/hello', true);
       req.setRequestHeader('api', getApi());
       req.onload = function () {
           // ...  display results ...
       };
       const blob = new Blob([e.target.result], {
           type: 'application/octet-stream'
       });
       req.send(blob);
   };
   console.log(image.file)
   reader.readAsArrayBuffer(image.file);
}

The following Dapr CLI command starts the web service for the static UI files.

$ cd web-port
$ sudo dapr run --app-id go-web-port \
        --app-protocol http \
        --app-port 8080 \
        --dapr-http-port 3500 \
        --components-path ../config \
        --log-level debug \
        ./web-port
$ cd ../

That's it. You now have a three part distributed application written in two languages!

MOSN

Coming soon.

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.

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

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.

#[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.

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 ..

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.

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.

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 ..

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}`;

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.

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.

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.

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.

This demo is based on the seL4 simulator on Linux.

Getting Started

System requirements

Hardware:

  • at least 4GB of RAM
  • at least 20GB of disk storage (the wasmedge_sel4 directory will contain over 11 GB of data after the following installation completes)

Software: Ubuntu 20.04 with dev tools packages (ep. Python) installed. We recommend the GitHub Actions Ubuntu 20.04 VM (See a list of installed apt packages). Or, you could use our Docker image (see the Dockerfile).

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

If you do not want to build the seL4 system simulator yourself, you can download the build artifact from our GitHub Actions, and skip directly to Boot wasmedge-seL4

Automatic installation: all-in-one script

Use our all-in-one build script:

wget -qO- https://raw.githubusercontent.com/second-state/wasmedge-seL4/main/build.sh | bash

And this will clone and build our wasmedge on seL4 to an image.

After finishing the build script, you will have a folder sel4_wasmedge.

If this automatic installation completed successfully, skip over the manual installation information and proceed to boot wasmedge-sel4

Manual installation: managing memory usage

The above all-in-one script will work in most cases. However, if your system resources were stressed and you encountered an error such as ninja: build stopped: subcommand failed please note that you can decrease the parallelization of the install by explicitly passing in a -j parameter to the ninja command (on the last line of the build.sh file). You see, Ninja runs the most amount of parallel processes by default and so the following procedure is a way to explicitly set/reduce parallelization.

Manually fetch the `wasmedge-sel4 repository.

cd ~
git clone https://github.com/second-state/wasmedge-seL4.git
cd wasmedge-seL4

Manually edit the build.sh file.

vi build.sh

Add the following -j parameter to the last line of the file i.e.

ninja -j 2

Make the build.sh file executable.

sudo chmod a+x build.sh

Run the edited `build.sh file.

./build.sh

Once this manual installation is complete, follow along with the following steps; boot wasmedge-sel4

Boot wasmedge-seL4

cd sel4_wasmedge/build
./simulate

Expected output:

./simulate: qemu-system-aarch64 -machine virt,virtualization=on,highmem=off,secure=off -cpu cortex-a53 -nographic  -m size=2048  -kernel images/capdl-loader-image-arm-qemu-arm-virt
ELF-loader started on CPU: ARM Ltd. Cortex-A53 r0p4
  paddr=[6abd8000..750cf0af]
No DTB passed in from boot loader.
Looking for DTB in CPIO archive...found at 6ad18f58.
Loaded DTB from 6ad18f58.
   paddr=[60243000..60244fff]
ELF-loading image 'kernel' to 60000000
  paddr=[60000000..60242fff]
  vaddr=[ff8060000000..ff8060242fff]
  virt_entry=ff8060000000
ELF-loading image 'capdl-loader' to 60245000
  paddr=[60245000..6a7ddfff]
  vaddr=[400000..a998fff]
  virt_entry=408f38
Enabling hypervisor MMU and paging
Jumping to kernel-image entry point...

Bootstrapping kernel
Warning: Could not infer GIC interrupt target ID, assuming 0.
Booting all finished, dropped to user space
<<seL4(CPU 0) [decodeUntypedInvocation/205 T0xff80bf85d400 "rootserver" @4006f8]: Untyped Retype: Insufficient memory (1 * 2097152 bytes needed, 0 bytes available).>>
Loading Linux: 'linux' dtb: 'linux-dtb'

...(omitted)...

Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Initializing random number generator... [    3.512482] random: dd: uninitialized urandom read (512 bytes read)
done.
Starting network: OK
[    4.086059] connection: loading out-of-tree module taints kernel.
[    4.114686] Event Bar (dev-0) initalised
[    4.123771] 2 Dataports (dev-0) initalised
[    4.130626] Event Bar (dev-1) initalised
[    4.136096] 2 Dataports (dev-1) initalised

Welcome to Buildroot
buildroot login:

Login on guest linux

Enter root to login

buildroot login: root

Expected output:

buildroot login: root
#

Execute wasm examples

Example A: nbody-c.wasm

Run nbody simulation.

wasmedge_emit /usr/bin/nbody-c.wasm 10

Expected output:

[1900-01-00 00:00:00.000] [info] executing wasm file
-0.169075164
-0.169073022
[1900-01-00 00:00:00.000] [info] execution success, exit code:0

Example B: hello.wasm

Run an easy application to print hello, sel4 and a simple calculation.

wasmedge_emit /usr/bin/hello.wasm

Expected output:

[1900-01-00 00:00:00.000] [info] executing wasm file
hello, sel4
1+2-3*4 = -9
[1900-01-00 00:00:00.000] [info] execution success, exit code:0

Open Harmony

WIP. For Chinese speakers, please check out this instruction.

Raspberry Pi 3/4

Raspberry Pi uses 64-bit processors starting from the 3 Model B. So WasmEdge can be executed on Raspberry Pi as well. You can choose any 64-bit Linux distribution, such as Raspbian, Ubuntu or Manjaro for ARM. This document has been tested on the Manjaro for ARM distribution and the hardware is the Raspberry Pi 3 Model B.

The installation steps are no different from the installation document, and the execution is the same. Here's a video about installing WasmEdge and running a simple WebAssembly module to add two numbers up.

asciicast

Contribute to WasmEdge

One of the most important features of WasmEdge is its extensibility. Through extensions, WasmEdge applications can use languages like Rust, C, and JavaScript to access features and functionalities provided by the operating systems or shared libraries.

In this chapter, we will cover how to build WasmEdge from source code for different operating systems and platforms. We will then discuss the WasmEdge plug-in API, which allows developers to create extensions that can be distributed with official WasmEdge releases.

Build WasmEdge from source

Please follow this guide to build and test WasmEdge from source code.

The following guide is based on Linux distributions. For MacOS, please refer to Build for macOS. For Windows, please refer to Build for Windows

If you just want the latest builds from the HEAD of the master branch, and do not want to build it yourself, you can download the release package directly from our Github Action's CI artifact. Here is an example.

Get Source Code

$ git clone https://github.com/WasmEdge/WasmEdge.git
$ cd WasmEdge

Check Dependencies

WasmEdge will try to use the latest LLVM release to create our nightly build. If you want to build from source, you may need to install these dependencies by yourself or using our docker images which provides several Linux distribution support.

  • LLVM 12.0.0 (>= 10.0.0)
  • GCC 11.1.0 (>= 9.4.0)

Prepare the Environment

Docker Images

Repository on dockerhub wasmedge/wasmedge

You can use the following commands to get our latest docker image:

$ docker pull wasmedge/wasmedge # Equals to wasmedge/wasmedge:latest

Available Tags

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

Install dependencies on Ubuntu 20.04 manually

# Tools and libraries
$ sudo apt install -y \
	software-properties-common \
	cmake \
	libboost-all-dev

# And you will need to install llvm for wasmedgec tool
$ sudo apt install -y \
	llvm-12-dev \
	liblld-12-dev

# WasmEdge supports both clang++ and g++ compilers
# You can choose one of them for building this project
# If you prefer GCC
$ sudo apt install -y gcc g++
# Else you can choose clang
$ sudo apt install -y clang-12

Support for legacy operating systems

Our development environment requires libLLVM-12 and >=GLIBCXX_3.4.33.

If users are using the older operating system than Ubuntu 20.04, please use our special docker image to build WasmEdge. If you are looking for the pre-built binaries for the older operating system, we also provide several pre-built binaries based on manylinux* distribution.

Portable Linux Built Distributions TagsBase ImageProvided RequirementsDocker Image
manylinux1CentOS 5.11GLIBC <= 2.5
CXXABI <= 3.4.8
GLIBCXX <= 3.4.9
GCC <= 4.2.0
wasmedge/wasmedge:manylinux1_x86_64
manylinux2010CentOS 6.10GLIBC <= 2.12
CXXABI <= 1.3.3
GLIBCXX <= 3.4.13
GCC <= 4.5.0
wasmedge/wasmedge:manylinux2010_x86_64
manylinux2014CentOS 7.9GLIBC <= 2.17
CXXABI <= 1.3.7
GLIBCXX <= 3.4.19
GCC <= 4.8.0
wasmedge/wasmedge:manylinux2014_x86_64
manylinux2014CentOS 7.9GLIBC <= 2.17
CXXABI <= 1.3.7
GLIBCXX <= 3.4.19
GCC <= 4.8.0
wasmedge/wasmedge:manylinux2014_aarch64

If you don't want to build Ahead-of-Time runtime/compiler

If users don't need Ahead-of-Time runtime/compiler support, they can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.

$ cmake -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_AOT_RUNTIME=OFF ..

Build WasmEdge

WasmEdge provides various tools for enabling different runtime environments for optimal performance. After the build is finished, you can find there are several wasmedge related tools:

  1. wasmedge is for general wasm runtime.
    • wasmedge executes a WASM file in interpreter mode or a compiled WASM so file in ahead-of-time compilation mode.
    • To disable building all tools, you can set the CMake option WASMEDGE_BUILD_TOOLS to OFF.
  2. wasmedgec is for ahead-of-time WASM compiler.
    • wasmedgec compiles a general WASM file into a so file.
    • To disable building the ahead-of-time compiler only, you can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.
  3. libwasmedge_c.so is the WasmEdge C API shared library.
    • libwasmedge_c.so provides C API for the ahead-of-time compiler and the WASM runtime.
    • The APIs about the ahead-of-time compiler will always return failed if the CMake option WASMEDGE_BUILD_AOT_RUNTIME is set as OFF.
    • To disable building the shared library only, you can set the CMake option WASMEDGE_BUILD_SHARED_LIB to OFF.
  4. ssvm-qitc is for AI application, supporting ONNC runtime for AI model in ONNX format.
# After pulling our wasmedge docker image
$ docker run -it --rm \
    -v <path/to/your/wasmedge/source/folder>:/root/wasmedge \
    wasmedge/wasmedge:latest
(docker)$ cd /root/wasmedge
(docker)$ mkdir -p build && cd build
(docker)$ cmake -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_TESTS=ON .. && make -j

Run built-in tests

The following built-in tests are only available when the build flag WASMEDGE_BUILD_TESTS sets to ON.

Users can use these tests to verify the correctness of WasmEdge binaries.

$ cd <path/to/wasmedge/build_folder>
$ LD_LIBRARY_PATH=$(pwd)/lib/api ctest

Run applications

Next, follow this guide to run WebAssembly bytecode programs in wasmedge.

Build from source on MacOS

Currently, WasmEdge project on MacOS supports both Intel and M1 models. However, we only test and develop on Big Sur and Catalina.

  • Model:
    • Intel
    • M1
  • Operating System
    • Big Sur
    • Catalina

If you would like to develop WasmEdge on MacOS, please follow this guide to build and test from source code.

Get Source Code

$ git clone https://github.com/WasmEdge/WasmEdge.git
$ cd WasmEdge

Requirements and Dependencies

WasmEdge will try to use the latest LLVM release to create our nightly build. If you want to build from source, you may need to install these dependencies by yourself.

  • LLVM 12.0.0 (>= 10.0.0), installed via brew, please don't use the built-in one.
  • Because the default version of LLVM on the latest brew is 13. Please use llvm@12 to fix the LLVM version.

Prepare the environment

# Tools and libraries
brew install boost cmake ninja llvm@12
# Use brew version of llvm, not the built-in one.
export PATH="/usr/local/opt/llvm@12/bin:$PATH"
export LDFLAGS="-L/usr/local/opt/llvm@12/lib -Wl,-rpath,/usr/local/opt/llvm@12/lib"
export CPPFLAGS="-I/usr/local/opt/llvm@12/include"

If you don't want to build Ahead-of-Time runtime/compiler

If users don't need Ahead-of-Time runtime/compiler support, they can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.

$ cmake -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_AOT_RUNTIME=OFF ..

Build WasmEdge

WasmEdge provides various tools for enabling different runtime environments for optimal performance. After the build is finished, you can find there are several wasmedge related tools:

  1. wasmedge is for general wasm runtime.
    • wasmedge executes a WASM file in interpreter mode or a compiled WASM dyld file in ahead-of-time compilation mode.
    • To disable building all tools, you can set the CMake option WASMEDGE_BUILD_TOOLS to OFF.
  2. wasmedgec is for ahead-of-time WASM compiler.
    • wasmedgec compiles a general WASM file into a dyld file.
    • To disable building the ahead-of-time compiler only, you can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.
  3. libwasmedge_c.dyld is the WasmEdge C API shared library.
    • libwasmedge_c.dyld provides C API for the ahead-of-time compiler and the WASM runtime.
    • The APIs about the ahead-of-time compiler will always return failed if the CMake option WASMEDGE_BUILD_AOT_RUNTIME is set as OFF.
    • To disable building the shared library only, you can set the CMake option WASMEDGE_BUILD_SHARED_LIB to OFF.
cmake -Bbuild -GNinja -DWASMEDGE_BUILD_PACKAGE="TGZ" -DWASMEDGE_BUILD_TESTS=ON .
cmake --build build

Run built-in tests

The following built-in tests are only available when the build flag WASMEDGE_BUILD_TESTS sets to ON.

Users can use these tests to verify the correctness of WasmEdge binaries.

export DYLD_LIBRARY_PATH="$(pwd)/build/lib/api:$DYLD_LIBRARY_PATH"
cmake --build build --target test

Run applications

Next, follow this guide to run WebAssembly bytecode programs in wasmedge.

Known issues

The following tests can not pass on macos, we are investigating these issues:

  • wasmedgeAPIVMCoreTests
  • wasmedgeAPIStepsCoreTests
  • wasmedgeAPIAOTCoreTests
  • wasmedgeProcessTests

Build from source on Windows 10

WasmEdge supports Windows 10. Currently, we also provide pre-built binaries and libraries for Windows.

You can find the details here

If you would like to develop WasmEdge on Windows 10, please follow this guide to build and test from source code.

Get Source Code

$ git clone https://github.com/WasmEdge/WasmEdge.git
$ cd WasmEdge

Requirements and Dependencies

WasmEdge will try to use the latest LLVM release to create our nightly build. If you want to build from source, you may need to install these dependencies by yourself.

  • Chocolatey, we use it to install cmake, ninja, and vswhere
  • Windows SDK 19041
  • LLVM 13.0.0, you can find the pre-built files in the following section.

Prepare the environment

# Instal tools
choco install cmake ninja vswhere

$vsPath = (vswhere -latest -property installationPath)
Import-Module (Join-Path $vsPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64 -winsdk=10.0.19041.0"

# Download our pre-built LLVM 13
$llvm = "LLVM-13.0.0-win64.zip"
curl -sLO https://github.com/WasmEdge/llvm-windows/releases/download/llvmorg-13.0.0/LLVM-13.0.0-win64.zip -o $llvm
Expand-Archive -Path $llvm

# Set LLVM environment
$llvm_dir = "$pwd\\LLVM-13.0.0-win64\\LLVM-13.0.0-win64\\lib\\cmake\\llvm"
$Env:CC = "clang-cl"
$Env:CXX = "clang-cl"

If you don't want to build Ahead-of-Time runtime/compiler

If users don't need Ahead-of-Time runtime/compiler support, they can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.

$ cmake -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_AOT_RUNTIME=OFF ..

Build WasmEdge

WasmEdge provides various tools for enabling different runtime environments for optimal performance. After the build is finished, you can find there are several wasmedge related tools:

  1. wasmedge is for general wasm runtime.
    • wasmedge executes a WASM file in interpreter mode or a compiled WASM dyld file in ahead-of-time compilation mode.
    • To disable building all tools, you can set the CMake option WASMEDGE_BUILD_TOOLS to OFF.
  2. wasmedgec is for ahead-of-time WASM compiler.
    • wasmedgec compiles a general WASM file into a dyld file.
    • To disable building the ahead-of-time compiler only, you can set the CMake option WASMEDGE_BUILD_AOT_RUNTIME to OFF.
  3. libwasmedge_c.dyld is the WasmEdge C API shared library.
    • libwasmedge_c.dyld provides C API for the ahead-of-time compiler and the WASM runtime.
    • The APIs about the ahead-of-time compiler will always return failed if the CMake option WASMEDGE_BUILD_AOT_RUNTIME is set as OFF.
    • To disable building the shared library only, you can set the CMake option WASMEDGE_BUILD_SHARED_LIB to OFF.
$vsPath = (vswhere -latest -property installationPath)
Import-Module (Join-Path $vsPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64 -winsdk=10.0.19041.0"

cmake -Bbuild -GNinja -DCMAKE_SYSTEM_VERSION=10.0.19041.0 -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL "-DLLVM_DIR=$llvm_dir" -DWASMEDGE_BUILD_TESTS=ON -DWASMEDGE_BUILD_PACKAGE="ZIP" .
cmake --build build

Run built-in tests

The following built-in tests are only available when the build flag WASMEDGE_BUILD_TESTS sets to ON.

Users can use these tests to verify the correctness of WasmEdge binaries.

$vsPath = (vswhere -latest -property installationPath)
Import-Module (Join-Path $vsPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64 -winsdk=10.0.19041.0"

$Env:PATH += ";$pwd\\build\\lib\\api"
cd build
ctest --output-on-failure
cd -

Run applications

Next, follow this guide to run WebAssembly bytecode programs in wasmedge.

WasmEdge plug-in API

WasmEdge provides a C++ based API for registering extensions and host functions. While the WasmEdge language SDKs allow registering host functions from a host (wrapping) application, the plugin API allows such extensions to be incorporated into WasmEdge's own building and releasing process.

In fact, the WasmEdge extensions for Tensorflow, image processing, key-value storage etc are all implemented using the plugin API. The plugin API is how you could contribute new functions to the WasmEdge Runtime itself.

Host Functions

Host functions are functions outside WebAssembly and passed to WASM modules as imports. The following steps give an example of registering a host module into WasmEdge runtime.

This example is for the sources compile with the WasmEdge project in C++. If developers want to implement the host functions in C/C++ with WasmEdge C API and without compiling with the WasmEdge project, please refer to the C API Documentation.

Definitions of Host Instances

WasmEdge supports registering host function, memory, table, and global instances as imports. For more details, samples can be found in include/host/wasi/ and test/core/spectest.h.

Functions

A simple host function class can be declared as follows:

#include "common/errcode.h"
#include "runtime/hostfunc.h"
#include "runtime/instance/memory.h"

namespace WasmEdge {
namespace Host {

class TestHost : public Runtime::HostFunction<TestHost> {
public:
  Expect<uint32_t> body(Runtime::Instance::MemoryInstance *MemInst, uint32_t Param1, float Param2);
};

} // namespace Host
} // namespace WasmEdge

According to example, return type Expect<T> presents the expected return number type T of this host function. Types of Param1 and Param2 presents argument types of this host function. Only WASM built-in types (aka. uint32_t, uint64_t, float, and double) are supported in host functions. When instantiating, the function signature of vec(valtype) -> resulttype is generated and can be imported by WASM modules.

Note: In the current state, only a single value returning is supported.

Another situation is passing environments or information which need to be accessed by host function body. The following sample shows how to implement host function clusters:

#include "common/errcode.h"
#include "runtime/hostfunc.h"
#include "runtime/instance/memory.h"
#include <vector>

namespace WasmEdge {
namespace Host {

template <typename T> class TestCluster : public Runtime::HostFunction<T> {
public:
  TestCluster(std::vector<uint8_t> &Vec) : Data(Vec) {}

protected:
  std::vector<uint8_t> &Data;
};

class TestHost1 : public TestCluster<TestHost1> {
public:
  TestHost1(std::vector<uint8_t> &Vec) : TestCluster(Vec) {}
  Expect<uint32_t> body(Runtime::Instance::MemoryInstance *MemInst, uint32_t Param1, float Param2) {
    /// Operations to `Data` ...
    return {};
  }
};

class TestHost2 : public TestCluster<TestHost2> {
public:
  TestHost2(std::vector<uint8_t> &Vec) : TestCluster(Vec) {}
  Expect<uint64_t> body(Runtime::Instance::MemoryInstance *MemInst, uint64_t Param1, double Param2) {
    /// Operations to `Data` ...
    return {};
  }
};

} // namespace Host
} // namespace WasmEdge

Tables, Memories, and Globals

To create a host table, memory, and global instance, the only way is to create them with their constructor in the host module. The following chapter about the host module will provide examples.

Host Modules

Host module is an object which can be registered into WasmEdge runtime. Host module contains host functions, tables, memories, globals, and other user-customized data. WasmEdge provides API to register host modules. After registering, these host instances in the host module can be imported by WASM modules.

Declaration

Host module supplies exported module name and can contain customized data. A module name is needed when constructing host modules.

#include "common/errcode.h"
#include "runtime/hostfunc.h"
#include "runtime/importobj.h"

namespace WasmEdge {
namespace Host {

class TestModule : public Runtime::ImportObject {
public:
  TestModule() : ImportObject("test");
  virtual ~TestModule() = default;
};

} // namespace Host
} // namespace WasmEdge

Add Instances

Host module provides addHostFunc(), addHostTable(), addHostMemory(), and addHostGlobal() to insert instances with their unique names. Insertion can be done in constructor. The following example also shows how to create host memories, tables, and globals.

#include "common/errcode.h"
#include "runtime/hostfunc.h"
#include "runtime/importobj.h"
#include <memory>
#include <vector>

namespace WasmEdge {
namespace Host {

template <typename T> class TestCluster : public Runtime::HostFunction<T> {
public:
  TestCluster(std::vector<uint8_t> &Vec) : Data(Vec) {}

protected:
  std::vector<uint8_t> &Data;
};

class TestHost1 : public TestCluster<TestHost1> {
public:
  TestHost1(std::vector<uint8_t> &Vec) : TestCluster(Vec) {}
  Expect<uint32_t> body(Runtime::Instance::MemoryInstance *MemInst, uint32_t Param1, float Param2) {
    /// Operations to `Data` ...
    return {};
  }
};

class TestHost2 : public TestCluster<TestHost2> {
public:
  TestHost2(std::vector<uint8_t> &Vec) : TestCluster(Vec) {}
  Expect<uint64_t> body(Runtime::Instance::MemoryInstance *MemInst, uint64_t Param1, double Param2) {
    /// Operations to `Data` ...
    return {};
  }
};

class TestModule : public Runtime::ImportObject {
public:
  TestModule(std::vector<uint8_t> &Vec) : ImportObject("test"), Data(Vec) {
    /// Add function instances with exporting name
    addHostFunc("test_func1", std::make_unique<TestHost1>(Data));
    addHostFunc("test_func2", std::make_unique<TestHost2>(Data));

    /// Add table instance with exporting name
    addHostTable("table", std::make_unique<Runtime::Instance::TableInstance>(
                              TableType(RefType::FuncRef, 10, 20)));

    /// Add memory instance with exporting name
    addHostMemory("memory", std::make_unique<Runtime::Instance::MemoryInstance>(
                                MemoryType(1, 2)));

    /// Add global instance with exporting name
    addHostGlobal("global_i32",
                  std::make_unique<Runtime::Instance::GlobalInstance>(
                      GlobalType(ValType::I32, ValMut::Const), uint32_t(666)));
    addHostGlobal("global_i64",
                  std::make_unique<Runtime::Instance::GlobalInstance>(
                      GlobalType(ValType::I64, ValMut::Const), uint64_t(666)));
    addHostGlobal("global_f32",
                  std::make_unique<Runtime::Instance::GlobalInstance>(
                      GlobalType(ValType::F32, ValMut::Const), float(666)));
    addHostGlobal("global_f64",
                  std::make_unique<Runtime::Instance::GlobalInstance>(
                      GlobalType(ValType::F64, ValMut::Const), double(666)));
  }
  virtual ~TestModule() = default;

private:
  std::vector<uint8_t> &Data;
};

} // namespace Host
} // namespace WasmEdge

Host module supplies getFuncs(), getTables(), getMems(), and getGlobals() to search registered instances by unique exporting name. For more details, APIs can be found in include/runtime/importobj.h.

Register Host Modules to WasmEdge

Users can register host modules via WasmEdge::VM::registerModule() API.

#include "common/configure.h"
#include "vm/vm.h"
#include <vector>

WasmEdge::Configure Conf;
WasmEdge::VM::VM VM(Conf);
std::vector<uint8_t> Data;
WasmEdge::Host::TestModule TestMod(Data);
VM.registerModule(TestMod);

For finding headers from WasmEdge include directories and linking static libraries, some settings are necessary for CMakeFile:

add_library(wasmedgeHostModuleTest  # Static library name of host modules
  test.cpp  # Path to host modules cpp files
)

target_include_directories(wasmedgeHostModuleTest
  PUBLIC
  ${Boost_INCLUDE_DIRS}
  ${PROJECT_SOURCE_DIR}/include
)

Implementation of Host Function Body

There are some tips about implementing host function bodies.

Checking Memory Instance When Using

Host function can access WASM memory, which passed as MemoryInstance * argument. When a function call occurs, a frame with module which the called function belonging to will be pushed onto the stack. In the host function case, the memory instance of the module of the top frame on the stack will be passed as the host function body's argument. But there can be no memory instance in a WASM module. Therefore, users should check if the memory instance pointer is a nullptr or not when accessing.

Returning Expectation

From our mechanism, Expect<T> declared in include/common/errcode.h is used as the result type of function body. In Expect<void> case, return {}; is needed for an expected situation. In other cases, return Value; is needed, where Value is a variable of type T. If an unexpected situation occurs, users can call return Unexpect(Code); to return an error, which Code is an element of enumeration ErrCode.

Forcing Termination

WasmEdge provides a method for terminating WASM execution in host functions. Developers can return ErrCode::Terminated to trigger the forcing termination of the current execution and pass the ErrCode::Terminated to the caller of the host functions.

Customized External References

External References denotes an opaque and unforgeable reference to a host object. A new externref type can be passed into a Wasm module or return from it. The Wasm module cannot reveal an externref value's bit pattern, nor create a fake host reference by an integer value.

Tutorial

The following tutorial is the summary of the externref example in WasmEdge.

Prepare Your Wasm File

The Wasm file should contain importing host functions that would take the externref. Take the test WASM file (this WAT is the corresponding text format) as an example:

(module
  (type $t0 (func (param externref i32) (result i32)))
  (type $t1 (func (param externref i32 i32) (result i32)))
  (type $t2 (func (param externref externref i32 i32) (result i32)))
  (import "extern_module" "functor_square" (func $functor_square (type $t0)))
  (import "extern_module" "class_add" (func $class_add (type $t1)))
  (import "extern_module" "func_mul" (func $func_mul (type $t1)))
  (func $call_add (export "call_add") (type $t1) (param $p0 externref) (param $p1 i32) (param $p2 i32) (result i32)
    (call $class_add
      (local.get $p0)
      (local.get $p1)
      (local.get $p2)))
  (func $call_mul (export "call_mul") (type $t1) (param $p0 externref) (param $p1 i32) (param $p2 i32) (result i32)
    (call $func_mul
      (local.get $p0)
      (local.get $p1)
      (local.get $p2)))
  (func $call_square (export "call_square") (type $t0) (param $p0 externref) (param $p1 i32) (result i32)
    (call $functor_square
      (local.get $p0)
      (local.get $p1)))
  (func $call_add_square (export "call_add_square") (type $t2) (param $p0 externref) (param $p1 externref) (param $p2 i32) (param $p3 i32) (result i32)
    (call $functor_square
      (local.get $p1)
      (call $class_add
        (local.get $p0)
        (local.get $p2)
        (local.get $p3))))
  (memory $memory (export "memory") 1))

Users can convert wat to wasm through wat2wasm live tool. Noted that reference types checkbox should be checked on this page.

Implement Host Module and Register into WasmEdge

The host module should be implemented and registered into WasmEdge before executing Wasm. Assume that the following code is saved as main.c:

#include <wasmedge/wasmedge.h>

#include <stdio.h>

uint32_t SquareFunc(uint32_t A) { return A * A; }
uint32_t AddFunc(uint32_t A, uint32_t B) { return A + B; }
uint32_t MulFunc(uint32_t A, uint32_t B) { return A * B; }

/// Host function to call `SquareFunc` by external reference
WasmEdge_Result ExternSquare(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                             const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /// Function type: {externref, i32} -> {i32}
  uint32_t (*Func)(uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
  uint32_t C = Func(WasmEdge_ValueGetI32(In[1]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

/// Host function to call `AddFunc` by external reference
WasmEdge_Result ExternAdd(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                          const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /// Function type: {externref, i32, i32} -> {i32}
  uint32_t (*Func)(uint32_t, uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
  uint32_t C = Func(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

/// Host function to call `ExternMul` by external reference
WasmEdge_Result ExternMul(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
                          const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /// Function type: {externref, i32, i32} -> {i32}
  uint32_t (*Func)(uint32_t, uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
  uint32_t C = Func(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

/// Helper function to create the "extern_module" import object.
WasmEdge_ImportObjectContext *CreateExternModule() {
  WasmEdge_String HostName;
  WasmEdge_FunctionTypeContext *HostFType = NULL;
  WasmEdge_FunctionInstanceContext *HostFunc = NULL;
  enum WasmEdge_ValType P[3], R[1];

  HostName = WasmEdge_StringCreateByCString("extern_module");
  WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(HostName);
  WasmEdge_StringDelete(HostName);

  /// Add host function "functor_square": {externref, i32} -> {i32}
  P[0] = WasmEdge_ValType_ExternRef;
  P[1] = WasmEdge_ValType_I32;
  R[0] = WasmEdge_ValType_I32;
  HostFType = WasmEdge_FunctionTypeCreate(P, 2, R, 1);
  HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternSquare, NULL, 0);
  WasmEdge_FunctionTypeDelete(HostFType);
  HostName = WasmEdge_StringCreateByCString("functor_square");
  WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
  WasmEdge_StringDelete(HostName);

  /// Add host function "class_add": {externref, i32, i32} -> {i32}
  P[2] = WasmEdge_ValType_I32;
  HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
  HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternAdd, NULL, 0);
  WasmEdge_FunctionTypeDelete(HostFType);
  HostName = WasmEdge_StringCreateByCString("class_add");
  WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
  WasmEdge_StringDelete(HostName);

  /// Add host function "func_mul": {externref, i32, i32} -> {i32}
  HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
  HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternMul, NULL, 0);
  WasmEdge_FunctionTypeDelete(HostFType);
  HostName = WasmEdge_StringCreateByCString("func_mul");
  WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
  WasmEdge_StringDelete(HostName);

  return ImpObj;
}

int main() {
  WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
  WasmEdge_ImportObjectContext *ImpObj = CreateExternModule();
  WasmEdge_Value P[3], R[1];
  WasmEdge_String FuncName;
  WasmEdge_Result Res;

  Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
  if (!WasmEdge_ResultOK(Res)) {
    printf("Import object registration failed\n");
    return EXIT_FAILURE;
  }
  Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "funcs.wasm");
  if (!WasmEdge_ResultOK(Res)) {
    printf("WASM file loading failed\n");
    return EXIT_FAILURE;
  }
  Res = WasmEdge_VMValidate(VMCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("WASM validation failed\n");
    return EXIT_FAILURE;
  }
  Res = WasmEdge_VMInstantiate(VMCxt);
  if (!WasmEdge_ResultOK(Res)) {
    printf("WASM instantiation failed\n");
    return EXIT_FAILURE;
  }

  /// Test 1: call add -- 1234 + 5678
  P[0] = WasmEdge_ValueGenExternRef(AddFunc);
  P[1] = WasmEdge_ValueGenI32(1234);
  P[2] = WasmEdge_ValueGenI32(5678);
  FuncName = WasmEdge_StringCreateByCString("call_add");
  Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
  WasmEdge_StringDelete(FuncName);
  if (WasmEdge_ResultOK(Res)) {
    printf("Test 1 -- `call_add` -- 1234 + 5678 = %d\n",
           WasmEdge_ValueGetI32(R[0]));
  } else {
    printf("Test 1 -- `call_add` -- 1234 + 5678 -- failed\n");
    return EXIT_FAILURE;
  }

  /// Test 2: call mul -- 789 * 4321
  P[0] = WasmEdge_ValueGenExternRef(MulFunc);
  P[1] = WasmEdge_ValueGenI32(789);
  P[2] = WasmEdge_ValueGenI32(4321);
  FuncName = WasmEdge_StringCreateByCString("call_mul");
  Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
  WasmEdge_StringDelete(FuncName);
  if (WasmEdge_ResultOK(Res)) {
    printf("Test 2 -- `call_mul` -- 789 * 4321 = %d\n",
           WasmEdge_ValueGetI32(R[0]));
  } else {
    printf("Test 2 -- `call_mul` -- 789 * 4321 -- failed\n");
    return EXIT_FAILURE;
  }

  /// Test 3: call square -- 8256^2
  P[0] = WasmEdge_ValueGenExternRef(SquareFunc);
  P[1] = WasmEdge_ValueGenI32(8256);
  FuncName = WasmEdge_StringCreateByCString("call_square");
  Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
  if (WasmEdge_ResultOK(Res)) {
    printf("Test 3 -- `call_mul` -- 8256 ^ 2 = %d\n",
           WasmEdge_ValueGetI32(R[0]));
  } else {
    printf("Test 3 -- `call_mul` -- 8256 ^ 2 -- failed\n");
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

Setup the Environment And Compile

  1. Install the WasmEdge shared library.

    Please refer to the Installation for details.

  2. Prepare the WASM file and the main.c source file as above.

  3. Compile

    $ gcc main.c -lwasmedge_c
    # Or you can use g++ for the C++ case, or use the clang.
    
  4. Run the Test

    $ ./a.out
    Test 1 -- `call_add` -- 1234 + 5678 = 6912
    Test 2 -- `call_mul` -- 789 * 4321 = 3409269
    Test 3 -- `call_mul` -- 8256 ^ 2 = 68161536
    

Wasm module with External References

Take the following wat for example:

(module
  (type $t0 (func (param externref i32) (result i32)))
  ;; Import a host function which type is {externref i32} -> {i32}
  (import "extern_module" "functor_square" (func $functor_square (type $t0)))
  ;; Wasm function which type is {externref i32} -> {i32} and exported as "call_square"
  (func $call_square (export "call_square") (type $t0) (param $p0 externref) (param $p1 i32) (result i32)
    (call $functor_square (local.get $p0) (local.get $p1))
  )
  (memory $memory (export "memory") 1))

The Wasm function "call_square" takes an externref parameter, and calls the imported host function functor_square with that externref. Therefore, the functor_square host function can get the object reference when users call "call_square" Wasm function and pass the object's reference.

WasmEdge ExternRef Example

The following examples are how to use externref in Wasm with WasmEdge C API.

Wasm Code

The Wasm code must pass the externref to host functions that want to access it. Take the following wat for example, which is a part of the test WASM file:

(module
  (type $t0 (func (param externref i32 i32) (result i32)))
  (import "extern_module" "func_mul" (func $func_mul (type $t0)))
  (func $call_mul (export "call_mul") (type $t0) (param $p0 externref) (param $p1 i32) (param $p2 i32) (result i32)
    (call $func_mul (local.get $p0) (local.get $p1) (local.get $p2))
  )
  (memory $memory (export "memory") 1))

The host function "extern_module::func_mul" takes externref as a function pointer to multiply parameters 1 and 2 and then return the result. The exported Wasm function "call_mul" calls "func_mul" and pass the externref and 2 numbers as arguments.

Host Functions

To instantiate the above example Wasm, the host functions must be registered into WasmEdge. See Host Functions for more details. The host functions which take externrefs must know the original objects' types. We take the function pointer case for example.

/* Function to pass as function pointer. */
uint32_t MulFunc(uint32_t A, uint32_t B) { return A * B; }

/* Host function to call function by external reference as a function pointer */
WasmEdge_Result ExternMul(void *, WasmEdge_MemoryInstanceContext *,
                          const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /* Function type: {externref, i32, i32} -> {i32} */
  void *Ptr = WasmEdge_ValueGetExternRef(In[0]);
  uint32_t (*Obj)(uint32_t, uint32_t) = Ptr;
  /*
   * For C++, the `reinterpret_cast` is needed:
   * uint32_t (*Obj)(uint32_t, uint32_t) = 
   *   *reinterpret_cast<uint32_t (*)(uint32_t, uint32_t)>(Ptr);
   */
  uint32_t C = Obj(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

"MulFunc" is a function that will be passed into Wasm as externref. In the "func_mul" host function, users can use "WasmEdge_ValueGetExternRef" API to get the pointer from the WasmEdge_Value which contains a externref.

Developers can add the host functions with names into an import object.

/* Create an import object. */
WasmEdge_String HostName = WasmEdge_StringCreateByCString("extern_module");
WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(HostName);
WasmEdge_StringDelete(HostName);

/* Create a function instance and add into an import object. */
enum WasmEdge_ValType P[3], R[1];
P[0] = WasmEdge_ValType_ExternRef;
P[1] = WasmEdge_ValType_I32;
P[2] = WasmEdge_ValType_I32;
R[0] = WasmEdge_ValType_I32;
WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternFuncMul, NULL, 0);
WasmEdge_FunctionTypeDelete(HostFType);
HostName = WasmEdge_StringCreateByCString("func_mul");
WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
WasmEdge_StringDelete(HostName);

...

Execution

Take the test WASM file (this WAT is the corresponding text format) for example. Assume that the funcs.wasm is copied into current directory. The following is the example to execute WASM with externref through the WasmEdge C API.

/* Create the VM context. */
WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
/* Create the import object context that contains the host functions. */
WasmEdge_ImportObjectContext *ImpObj = /* Ignored ... */;
/* Assume that the host functions are added into the import object above. */
WasmEdge_Value P[3], R[1];
WasmEdge_String FuncName;
WasmEdge_Result Res;

/* Register the import object into VM. */
Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
if (!WasmEdge_ResultOK(Res)) {
  printf("Import object registration failed\n");
  return EXIT_FAILURE;
}
/* Load WASM from file. */
Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "funcs.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM file loading failed\n");
  return EXIT_FAILURE;
}
/* Validate WASM. */
Res = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM validation failed\n");
  return EXIT_FAILURE;
}
/* Instantiate the WASM module. */
Res = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM instantiation failed\n");
  return EXIT_FAILURE;
}

/* Run a WASM function. */
P[0] = WasmEdge_ValueGenExternRef(AddFunc);
P[1] = WasmEdge_ValueGenI32(1234);
P[2] = WasmEdge_ValueGenI32(5678);
/* Run the `call_add` function. */
FuncName = WasmEdge_StringCreateByCString("call_add");
Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
WasmEdge_StringDelete(FuncName);
if (WasmEdge_ResultOK(Res)) {
  printf("Run -- `call_add` -- 1234 + 5678 = %d\n",
          WasmEdge_ValueGetI32(R[0]));
} else {
  printf("Run -- `call_add` -- 1234 + 5678 -- failed\n");
  return EXIT_FAILURE;
}

Passing Objects

The above example is passing a function reference as externref. The following examples are about how to pass an object reference into WASM as externref in C++.

Passing a Class

To pass a class as externref, the object instance is needed.

class AddClass {
public:
  uint32_t add(uint32_t A, uint32_t B) const { return A + B; }
};

AddClass AC;

Then users can pass the object into WasmEdge by using WasmEdge_ValueGenExternRef() API.

WasmEdge_Value P[3], R[1];
P[0] = WasmEdge_ValueGenExternRef(&AC);
P[1] = WasmEdge_ValueGenI32(1234);
P[2] = WasmEdge_ValueGenI32(5678);
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_add");
WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
WasmEdge_StringDelete(FuncName);
if (WasmEdge_ResultOK(Res)) {
  std::cout << "Result : " << WasmEdge_ValueGetI32(R[0]) std::endl;
  /// Will print `6912`.
} else {
  return EXIT_FAILURE;
}

In the host function which would access the object by reference, users can use the WasmEdge_ValueGetExternRef() API to retrieve the reference to the object.

/// Modify the `ExternAdd` in the above tutorial.
WasmEdge_Result ExternAdd(void *, WasmEdge_MemoryInstanceContext *,
                          const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /// Function type: {externref, i32, i32} -> {i32}
  void *Ptr = WasmEdge_ValueGetExternRef(In[0]);
  AddClass &Obj = *reinterpret_cast<AddClass *>(Ptr);
  uint32_t C =
      Obj.add(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

Passing an Object As Functor

As the same as passing a class instance, the functor object instance is needed.

struct SquareStruct {
  uint32_t operator()(uint32_t Val) const { return Val * Val; }
};

SquareStruct SS;

Then users can pass the object into WasmEdge by using the WasmEdge_ValueGenExternRef() API.

WasmEdge_Value P[2], R[1];
P[0] = WasmEdge_ValueGenExternRef(&SS);
P[1] = WasmEdge_ValueGenI32(1024);
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_square");
WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
WasmEdge_StringDelete(FuncName);
if (WasmEdge_ResultOK(Res)) {
  std::cout << "Result : " << WasmEdge_ValueGetI32(R[0]) std::endl;
  /// Will print `1048576`.
} else {
  return EXIT_FAILURE;
}

In the host function which would access the object by reference, users can use the WasmEdge_ValueGetExternRef API to retrieve the reference to the object, and the reference is a functor.

/// Modify the `ExternSquare` in the above tutorial.
WasmEdge_Result ExternSquare(void *, WasmEdge_MemoryInstanceContext *,
                          const WasmEdge_Value *In, WasmEdge_Value *Out) {
  /// Function type: {externref, i32, i32} -> {i32}
  void *Ptr = WasmEdge_ValueGetExternRef(In[0]);
  SquareStruct &Obj = *reinterpret_cast<SquareStruct *>(Ptr);
  uint32_t C = Obj(WasmEdge_ValueGetI32(In[1]));
  Out[0] = WasmEdge_ValueGenI32(C);
  return WasmEdge_Result_Success;
}

Passing STL Objects

The example Wasm binary (this WAT is the corresponding text format) provides functions to interact with host functions which can access C++ STL objects. Assume that the WASM file stl.wasm is copied into the current directory.

Take the std::ostream and std::string objects for example. Assume that there's a host function accesses to a std::ostream and a std::string through externrefs:

/// Host function to output std::string through std::ostream
WasmEdge_Result ExternSTLOStreamStr(void *, WasmEdge_MemoryInstanceContext *,
                                    const WasmEdge_Value *In,
                                    WasmEdge_Value *) {
  /// Function type: {externref, externref} -> {}
  void *Ptr0 = WasmEdge_ValueGetExternRef(In[0]);
  void *Ptr1 = WasmEdge_ValueGetExternRef(In[1]);
  std::ostream &RefOS = *reinterpret_cast<std::ostream *>(Ptr0);
  std::string &RefStr = *reinterpret_cast<std::string *>(Ptr1);
  RefOS << RefStr;
  return WasmEdge_Result_Success;
}

Assume that the above host function is added into an import object ImpObj, and the ImpObj is register into a VM context VMCxt. Then users can instantiate the Wasm module:

WasmEdge_Result Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "stl.wasm");
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM file loading failed\n");
  return EXIT_FAILURE;
}
Res = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM validation failed\n");
  return EXIT_FAILURE;
}
Res = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(Res)) {
  printf("WASM instantiation failed\n");
  return EXIT_FAILURE;
}

Last, pass the std::cout and a std::string object by external references.

std::string PrintStr("Hello world!");
WasmEdge_Value P[2], R[1];
P[0] = WasmEdge_ValueGenExternRef(&std::cout);
P[1] = WasmEdge_ValueGenExternRef(&PrintStr);
WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_ostream_str");
WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
/// Will print "Hello world!" to stdout.
WasmEdge_StringDelete(FuncName);
if (!WasmEdge_ResultOK(Res)) {
  return EXIT_FAILURE;
}

For other C++ STL objects cases, such as std::vector<T>, std::map<T, U>, or std::set<T>, the object can be accessed correctly in host functions if the type in reinterpret_cast is correct.