tree: ef8b6c35cf6d1cfcb9a671b80cfaeecaa8494475 [path history] [tgz]
  1. examples/
  2. .gitignore
  3. README.md
  4. wasm-rt-impl.c
  5. wasm-rt-impl.h
  6. wasm-rt-os-unix.c
  7. wasm-rt-os-win.c
  8. wasm-rt-os.h
  9. wasm-rt-runner.c
  10. wasm-rt-shadow.cpp
  11. wasm-rt-unified.c
  12. wasm-rt-wasi.c
  13. wasm-rt.h
wasm2c/README.md

wasm2c: Convert wasm files to C source and header

wasm2c takes a WebAssembly module and produces an equivalent C source and header. Some examples:

# parse binary file test.wasm and write test.c and test.h
$ wasm2c test.wasm -o test.c

# parse test.wasm, write test.c and test.h, but ignore the debug names, if any
$ wasm2c test.wasm --no-debug-names -o test.c

Tutorial: .wat -> .wasm -> .c

Let's look at a simple example of a factorial function.

(func (export "fac") (param $x i32) (result i32)
  (if (result i32) (i32.eq (local.get $x) (i32.const 0))
    (then (i32.const 1))
    (else
      (i32.mul (local.get $x) (call 0 (i32.sub (local.get $x) (i32.const 1))))
    )
  )
)

Save this to fac.wat. We can convert this to a .wasm file by using the wat2wasm tool:

$ wat2wasm fac.wat -o fac.wasm

We can then convert it to a C source and header by using the wasm2c tool:

$ wasm2c fac.wasm -o fac.c

This generates two files, fac.c and fac.h. We‘ll take a closer look at these files below, but first let’s show a simple example of how to use these files.

Using the generated module

To actually use our fac module, we'll use create a new file, main.c, that include fac.h, initializes the module, and calls fac.

wasm2c generates a few symbols for us, get_wasm2c_sandbox_info and w2c_fac. get_wasm2c_sandbox_info gives us a way to create and destory wasm instances initializes the module, and w2c_fac is our exported fac function.

#include <stdio.h>
#include <stdlib.h>

/* Uncomment this to define fac_init and fac_Z_facZ_ii instead. */
/* #define WASM_RT_MODULE_PREFIX fac_ */

#include "fac.h"

int main(int argc, char** argv) {
  /* Make sure there is at least one command-line argument. */
  if (argc < 2) return 1;

  /* Convert the argument from a string to an int. We'll implictly cast the int
  to a `u32`, which is what `fac` expects. */
  u32 x = atoi(argv[1]);

  /* Retrieve sandbox details */
  wasm2c_sandbox_funcs_t sbx_details = get_wasm2c_sandbox_info();

  /* One time initializations of minimum wasi runtime supported by wasm2c */
  sbx_details.wasm_rt_sys_init();

  /* Optional upper limit for number of wasm pages for this module.
  0 means no limit  */
  int max_wasm_page = 0;

  /* Create a sandbox instance */
  wasm2c_sandbox_t* sbx_instance = (wasm2c_sandbox_t*) sbx_details.create_wasm2c_sandbox(max_wasm_page);

  /* Call `fac`, using the mangled name. */
  u32 result = w2c_fac(sbx_instance, x);

  /* Print the result. */
  printf("fac(%u) -> %u\n", x, result);

  /* Destroy the sandbox instance */
  sbx_details.destroy_wasm2c_sandbox(sbx_instance);

  return 0;
}

To compile the executable, we need to use main.c and the generated fac.c. We'll also include wasm-rt-impl.c which has implementations of the various wasm_rt_* functions used by fac.c and fac.h, wasm-rt-os-unix.c and wasm-rt-os-win.c which include some OS specific initialization and wasm-rt-wasi.c which includes a minimum wasi implementation to get things running

$ cc -o fac main.c fac.c wasm-rt-impl.c wasm-rt-os-unix.c wasm-rt-os-win.c wasm-rt-wasi.c

Now let's test it out!

$ ./fac 1
fac(1) -> 1
$ ./fac 5
fac(5) -> 120
$ ./fac 10
fac(10) -> 3628800

You can take a look at the all of these files in wasm2c/examples/fac.

Looking at the generated header, fac.h

The generated header file looks something like this:

#ifndef FAC_H_GENERATED_
#define FAC_H_GENERATED_

/* Automically generated by wasm2c */
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

#include "wasm-rt.h"

#ifndef WASM_RT_MODULE_PREFIX
#define WASM_RT_MODULE_PREFIX
#endif

#define WASM_RT_PASTE_(x, y) x ## y
#define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y)
#define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x)

/* TODO(binji): only use stdint.h types in header */
typedef uint8_t u8;
typedef int8_t s8;
typedef uint16_t u16;
typedef int16_t s16;
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;
typedef float f32;
typedef double f64;

#ifndef WASM_DONT_EXPORT_FUNCS
# if defined(_WIN32)
#   define FUNC_EXPORT __declspec(dllexport)
# else
#   define FUNC_EXPORT
# endif
#else
# define FUNC_EXPORT
#endif

FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)();

struct wasm2c_sandbox_t;
typedef struct wasm2c_sandbox_t wasm2c_sandbox_t;

FUNC_EXPORT u32 w2c_fac(wasm2c_sandbox_t* const, u32);

#ifdef __cplusplus
}
#endif

#endif  /* FAC_H_GENERATED_ */

fac.h defines exported symbols provided by the module. In our example, the only function we exported was fac. An additional function is provided called get_wasm2c_sandbox_info, which helps create wasm instances:

extern void WASM_RT_ADD_PREFIX(init)(void);

/* export: 'fac' */
extern u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32);

A quick look at fac.c

The contents of fac.c are internals, but it is useful to see a little about how it works.

The first few hundred lines define macros that are used to implement the various WebAssembly instructions. Their implementations may be interesting to the curious reader, but are out of scope for this document.

Following those definitions are various initialization functions (init, init_func_types, init_globals, init_memory, init_table, and init_exports.) In our example, most of these functions are empty, since the module doesn't use any globals, memory or tables.

The most interesting part is the definition of the function fac:

static u32 fac(u32 p0) {
  FUNC_PROLOGUE;
  u32 i0, i1, i2;
  i0 = p0;
  i1 = 0u;
  i0 = i0 == i1;
  if (i0) {
    i0 = 1u;
  } else {
    i0 = p0;
    i1 = p0;
    i2 = 1u;
    i1 -= i2;
    i1 = fac(i1);
    i0 *= i1;
  }
  FUNC_EPILOGUE;
  return i0;
}

If you look at the original WebAssembly text in the flat format, you can see that there is a 1-1 mapping in the output:

(func $fac (param $x i32) (result i32)
  local.get $x
  i32.const 0
  i32.eq
  if (result i32)
    i32.const 1
  else
    local.get $x
    local.get $x
    i32.const 1
    i32.sub
    call 0
    i32.mul
  end)

This looks different than the factorial function above because it is using the “flat format” instead of the “folded format”. You can use wat-desugar to convert between the two to be sure:

$ wat-desugar fac-flat.wat --fold -o fac-folded.wat
(module
  (func (;0;) (param i32) (result i32)
    (if (result i32)  ;; label = @1
      (i32.eq
        (local.get 0)
        (i32.const 0))
      (then
        (i32.const 1))
      (else
        (i32.mul
          (local.get 0)
          (call 0
            (i32.sub
              (local.get 0)
              (i32.const 1)))))))
  (export "fac" (func 0))
  (type (;0;) (func (param i32) (result i32))))

The formatting is different and the variable and function names are gone, but the structure is the same.