wasm-micro-runtime/samples/wasm-c-api-imports/README.md
liang.he 3698f2279b
Improve wasm-c-api instantiation-time linking (#1902)
Add APIs to help prepare the imports for the wasm-c-api `wasm_instance_new`:
- wasm_importtype_is_linked
- wasm_runtime_is_import_func_linked
- wasm_runtime_is_import_global_linked
- wasm_extern_new_empty

For wasm-c-api, developer may use `wasm_module_imports` to get the import
types info, check whether an import func/global is linked with the above API,
and ignore the linking of an import func/global with `wasm_extern_new_empty`.

Sample `wasm-c-api-import` is added and document is updated.
2023-02-13 15:06:04 +08:00

6.8 KiB

How to create imports for wasm_instance_new() properly

It's always been asked how to create wasm_extern_vec_t *imports for wasm_instance_new()?

WASM_API_EXTERN own wasm_instance_t* wasm_instance_new(
  wasm_store_t*, const wasm_module_t*, const wasm_extern_vec_t *imports,
  own wasm_trap_t** trap
);

wasm_extern_vec_t *imports is required to match the requirement of the import section of a .wasm.

$ /opt/wabt-1.0.31/bin/wasm-objdump -j Import -x <some_example>.wasm

Section Details:

Import[27]:
 - func[0] sig=2 <pthread_mutex_lock> <- env.pthread_mutex_lock
 - func[1] sig=2 <pthread_mutex_unlock> <- env.pthread_mutex_unlock
 - func[2] sig=2 <pthread_cond_signal> <- env.pthread_cond_signal
 - func[3] sig=3 <host_log> <- env.log
 ...
 - func[11] sig=4 <__imported_wasi_snapshot_preview1_sock_bind> <- wasi_snapshot_preview1.sock_bind
 - func[12] sig=4 <__imported_wasi_snapshot_preview1_sock_connect> <- wasi_snapshot_preview1.sock_connect
 - func[13] sig=4 <__imported_wasi_snapshot_preview1_sock_listen> <- wasi_snapshot_preview1.sock_listen
 - func[14] sig=5 <__imported_wasi_snapshot_preview1_sock_open> <- wasi_snapshot_preview1.sock_open
 - func[15] sig=4 <__imported_wasi_snapshot_preview1_sock_addr_remote> <- wasi_snapshot_preview1.sock_addr_remote
 - func[16] sig=4 <__imported_wasi_snapshot_preview1_args_get> <- wasi_snapshot_preview1.args_get
 - func[17] sig=4 <__imported_wasi_snapshot_preview1_args_sizes_get> <- wasi_snapshot_preview1.args_sizes_get
 ...

Developers should fill in imports with enough host functions and make sure there are no linking problems during instantiation.

TODO: linking warnings

A natural way

One natural answer is "to create a list which matches every item in the import section" of the .wasm. Since developers can see the section details of a .wasm by tools like wasm-objdump, the answer is doable. Most of the time, if they also prepare Wasm modules, developers have full control over import requirements, and they only need to take a look at the order of the import section.

Yes, the order. A proper wasm_extern_vec_t *imports includes two things:

  1. how many wasm_extern_t
  2. and order of those

Because there is no "name information" in a wasm_extern_t. The only way is let wasm_instance_new() to tell which item in the import section of a .wasm should match any item in wasm_extern_vec_t *imports is based on index.

The algorithm is quite straightforward. The first one of the import section matches wasm_extern_vec_t *imports->data[0] . The second one matches wasm_extern_vec_t *imports->data[1]. And so on.

So the order of wasm_extern_vec_t *imports becomes quite a burden. It requires developers always checking the import section visually.

Until here, the natural way is still workable although involving some handy work. Right?

A blocker

Sorry, the situation changes a lot when driving wasm32-wasi Wasm modules with wasm-c-api.

As you know, WASI provides a set of crossing-platform standard libraries for Wasm modules, and leaves some interfaces for native platform-dependent supports. Those interfaces are those import items with the module name wasi_snapshot_preview1 in a Wasm module.

It seems not economical to let developers provide their version of host implementations of the wasi_snapshot_preview1.XXX functions. All those support should be packed into a common library and shared in different Wasm modules. Like a cargo WASI.

WAMR chooses to integrate the WASI support library in the runtime to reduce developers' compilation work. It brings developers a new thing of a proper wasm_extern_vec_t *imports that developers should avoid overwriting those items of the import section of a Wasm module that will be provided by the runtime. It also not economical to code for those functions.

Using module names as a filter seems to be a simple way. But some private additional c/c++ libraries are supported in WAMR. Those supporting will bring more import items that don't use wasi_snapshot_preview1 as module names but are still covered by the WASM runtime. Like env.pthread_. Plus, the native lib registeration provides another possible way to fill in the requirement of the import section.

Let's take summarize. A proper wasm_extern_vec_t *imports should include:

  1. provides all necessary host implementations for items in the import section
  2. should not override runtime provided implementation or covered by native registrations. functinal or econmical.
  3. keep them in a right order

A recommendation

The recommendation is:

  • use wasm_module_imports() to build the order
  • use wasm_importtype_is_linked() to avoid overwriting

wasm-c-api-imports is a simple showcase of how to do that.

First, let's take a look at the Wasm module. send_recv uses both standard WASI and WAMR_BUILD_LIB_PTHREAD supporting. Plus a private native function host_log.

So, wasm_extern_vec_t *imports should only include the host implementation of host_log and avoid WASI related(wasm-c-api-imports.XXX) and pthread related(env.pthread_XXX).

Here is how to do:

  • get import types with wasm_module_imports(0). it contains name information
  wasm_importtype_vec_t importtypes = { 0 };
  wasm_module_imports(module, &importtypes);
  • traversal import types. The final wasm_importvec_t *imports should have the same order with wasm_importtype_vec_t
  for (unsigned i = 0; i < importtypes.num_elems; i++)
  • use wasm_importtype_is_linked() to avoid those covered by the runtime and registered natives. A little tip is use "wasm_extern_new_empty()" to create a placeholder.
    /* use wasm_extern_new_empty() to create a placeholder */
    if (wasm_importtype_is_linked(importtype)) {
        externs[i] = wasm_extern_new_empty(
            store, wasm_externtype_kind(wasm_importtype_type(importtype)));
        continue;
    }
  • use wasm_importtype_module() to get the module name, use wasm_importtype_name() to get the field name.
      const wasm_name_t *module_name =
          wasm_importtype_module(importtypes.data[i]);
      const wasm_name_t *field_name =
          wasm_importtype_name(importtypes.data[i]);
  • fill in wasm_externvec_t *imports dynamically and programmatically.
      if (strncmp(module_name->data, "env", strlen("env")) == 0
          && strncmp(field_name->data, "log", strlen("log")) == 0) {
          wasm_functype_t *log_type = wasm_functype_new_2_0(
              wasm_valtype_new_i64(), wasm_valtype_new_i32());
          wasm_func_t *log_func = wasm_func_new(store, log_type, host_logs);
          wasm_functype_delete(log_type);

          externs[i] = wasm_func_as_extern(log_func);
      }
  }