wasm-micro-runtime/samples/wasm-c-api/src/clone.c
Wenyong Huang a182926a73
Refactor interpreter/AOT module instance layout (#1559)
Refactor the layout of interpreter and AOT module instance:
- Unify the interp/AOT module instance, use the same WASMModuleInstance/
  WASMMemoryInstance/WASMTableInstance data structures for both interpreter
  and AOT
- Make the offset of most fields the same in module instance for both interpreter
  and AOT, append memory instance structure, global data and table instances to
  the end of module instance for interpreter mode (like AOT mode)
- For extra fields in WASM module instance, use WASMModuleInstanceExtra to
  create a field `e` for interpreter
- Change the LLVM JIT module instance creating process, LLVM JIT uses the WASM
  module and module instance same as interpreter/Fast-JIT mode. So that Fast JIT
  and LLVM JIT can access the same data structures, and make it possible to
  implement the Multi-tier JIT (tier-up from Fast JIT to LLVM JIT) in the future
- Unify some APIs: merge some APIs for module instance and memory instance's
  related operations (only implement one copy)

Note that the AOT ABI is same, the AOT file format, AOT relocation types, how AOT
code accesses the AOT module instance and so on are kept unchanged.

Refer to:
https://github.com/bytecodealliance/wasm-micro-runtime/issues/1384
2022-10-18 10:59:28 +08:00

519 lines
12 KiB
C

/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "wasm_c_api.h"
#define WORKER_NUMBER 10
/******************************* VM *******************************/
/* Use wasm_vm_t and vm_xxx to simulate a minimal Wasm VM in Envoy */
typedef struct _vm {
wasm_engine_t *engine;
wasm_store_t *store;
wasm_module_t *module;
wasm_instance_t *instance;
wasm_func_t **function_list;
wasm_memory_t *memory;
wasm_table_t *table;
wasm_extern_vec_t *exports;
} wasm_vm_t;
typedef enum _clone_level {
not_cloneable = 0,
compiled_bytecode,
instantiated_module
} clone_level;
typedef struct _thread_arg_t {
char name[32];
bool *ready_go_flag;
pthread_mutex_t *ready_go_lock;
pthread_cond_t *ready_go_cond;
const wasm_vm_t *base_vm;
} thread_arg_t;
wasm_vm_t *
vm_new()
{
wasm_vm_t *vm = NULL;
vm = malloc(sizeof(struct _vm));
if (!vm)
goto fail;
memset(vm, 0, sizeof(wasm_vm_t));
vm->engine = wasm_engine_new();
if (!vm->engine)
goto fail;
vm->store = wasm_store_new(vm->engine);
if (!vm->store)
goto fail;
return vm;
fail:
if (vm) {
if (vm->engine)
wasm_engine_delete(vm->engine);
free(vm);
}
return NULL;
}
bool
vm_load(wasm_vm_t *vm, const wasm_byte_vec_t *binary)
{
vm->module = wasm_module_new(vm->store, binary);
return vm->module != NULL;
}
bool
vm_link(wasm_vm_t *vm, wasm_extern_vec_t *imports)
{
vm->instance = wasm_instance_new(vm->store, vm->module, imports, NULL);
if (!vm->instance)
goto fail;
vm->exports = malloc(sizeof(wasm_extern_vec_t));
if (!vm->exports)
goto fail;
memset(vm->exports, 0, sizeof(wasm_extern_vec_t));
wasm_instance_exports(vm->instance, vm->exports);
/* an exported memory, and two exported functions */
assert(vm->exports->size == 3);
/* bind memory */
assert(wasm_extern_kind(vm->exports->data[0]) == WASM_EXTERN_MEMORY);
vm->memory = wasm_extern_as_memory(vm->exports->data[0]);
vm->function_list = malloc(2 * sizeof(wasm_func_t *));
if (!vm->function_list)
goto fail;
memset(vm->function_list, 0, sizeof(2 * sizeof(wasm_func_t *)));
/* bind wasm_set_byte(...) */
assert(wasm_extern_kind(vm->exports->data[1]) == WASM_EXTERN_FUNC);
vm->function_list[0] = wasm_extern_as_func(vm->exports->data[1]);
/* bind wasm_get_byte(...) */
assert(wasm_extern_kind(vm->exports->data[2]) == WASM_EXTERN_FUNC);
vm->function_list[1] = wasm_extern_as_func(vm->exports->data[2]);
return true;
fail:
return false;
}
wasm_vm_t *
vm_clone_from_module(const wasm_vm_t *base)
{
printf("Initializing...\n");
wasm_vm_t *secondary = NULL;
secondary = vm_new();
if (secondary) {
printf("Reuse module and bypass vm_load()...");
secondary->module = base->module;
}
return secondary;
}
wasm_vm_t *
vm_clone_from_instance(const wasm_vm_t *base)
{
/**
* if do a clone of the level instantiated_module, need to malloc and
* initialie
* - global. WASMGlobalIntance and global data
* - memory. WAAMMemoryInstance, memory_data and heap
* - table. WASMTableInstance, table_data
* - exports. all global, memory and table
*
* it is almost everything in wasm_instantiate() except funciton.
*/
(void)base;
printf("Unsupported\n");
return NULL;
}
wasm_vm_t *
vm_clone(const wasm_vm_t *base, clone_level level)
{
if (level == not_cloneable)
return NULL;
if (level == compiled_bytecode)
return vm_clone_from_module(base);
else
return vm_clone_from_instance(base);
}
wasm_vm_t *
vm_release(wasm_vm_t *vm)
{
if (!vm)
return NULL;
if (vm->function_list) {
free(vm->function_list);
vm->function_list = NULL;
}
if (vm->exports) {
wasm_extern_vec_delete(vm->exports);
free(vm->exports);
vm->exports = NULL;
}
wasm_instance_delete(vm->instance);
vm->instance = NULL;
wasm_module_delete(vm->module);
vm->module = NULL;
wasm_store_delete(vm->store);
vm->store = NULL;
wasm_engine_delete(vm->engine);
vm->engine = NULL;
free(vm);
return NULL;
}
bool
vm_memory_set_byte(const wasm_vm_t *vm, uint32_t offset, uint8_t byte)
{
byte_t *data = wasm_memory_data(vm->memory);
assert(data);
*(data + offset) = byte;
return true;
}
bool
vm_memory_get_byte(const wasm_vm_t *vm, uint32_t offset, uint8_t *byte)
{
byte_t *data = wasm_memory_data(vm->memory);
assert(data);
*byte = *(data + offset);
return true;
}
bool
vm_function_set_byte(const wasm_vm_t *vm, uint32_t offset, uint8_t byte)
{
wasm_val_t a_v[2] = { WASM_I32_VAL(offset), WASM_I32_VAL(byte) };
wasm_val_vec_t args = WASM_ARRAY_VEC(a_v);
wasm_val_vec_t results = WASM_EMPTY_VEC;
wasm_trap_t *trap = wasm_func_call(vm->function_list[0], &args, &results);
if (trap)
printf("call wasm_set_byte failed");
return trap != NULL;
}
bool
vm_function_get_byte(const wasm_vm_t *vm, uint32_t offset, uint8_t *byte)
{
wasm_val_t a_v[1] = { WASM_I32_VAL(offset) };
wasm_val_vec_t args = WASM_ARRAY_VEC(a_v);
wasm_val_t r_v[1] = { WASM_INIT_VAL };
wasm_val_vec_t results = WASM_ARRAY_VEC(r_v);
wasm_trap_t *trap = wasm_func_call(vm->function_list[1], &args, &results);
if (trap) {
printf("call wasm_get_byte failed");
return false;
}
assert(results.data->kind == WASM_I32);
*byte = results.data->of.i32;
return true;
}
static bool
load_wasm_file_content(const char *file_name, wasm_byte_vec_t *out)
{
bool ret = false;
#if WASM_ENABLE_AOT != 0 && WASM_ENABLE_INTERP == 0
FILE *file = fopen(file_name, "rb");
#else
FILE *file = fopen(file_name, "rb");
#endif
if (!file) {
printf("> Error loading .wasm!\n");
goto quit;
}
int offset = fseek(file, 0L, SEEK_END);
if (offset == -1) {
printf("> Error loading .wasm!\n");
goto close_file;
}
long file_size = ftell(file);
if (file_size == -1) {
printf("> Error loading .wasm!\n");
goto close_file;
}
offset = fseek(file, 0L, SEEK_SET);
if (offset == -1) {
printf("> Error loading .wasm!\n");
goto close_file;
}
wasm_byte_vec_new_uninitialized(out, file_size);
if (fread(out->data, file_size, 1, file) != 1) {
printf("> Error loading content!\n");
goto close_file;
}
ret = true;
close_file:
fclose(file);
quit:
return ret;
}
static pthread_key_t name_key;
wasm_trap_t *
report_cb(const wasm_val_vec_t *args, wasm_val_vec_t *results)
{
(void)results;
assert(args->data[0].kind == WASM_I32);
uint32_t chk_pnt_no = args->data[0].of.i32;
char *name = pthread_getspecific(name_key);
printf("[%s] Pass CHK POINT #%u\n", name, chk_pnt_no);
return NULL;
}
bool
run_code_start(wasm_vm_t **out)
{
bool ret = false;
printf("Initializing...\n");
wasm_vm_t *vm = vm_new();
if (!vm)
goto fail;
printf("Loading binary...\n");
wasm_byte_vec_t binary = { 0 };
#if WASM_ENABLE_AOT != 0 && WASM_ENABLE_INTERP == 0
const char *file_name = "clone.aot";
#else
const char *file_name = "clone.wasm";
#endif
if (!load_wasm_file_content(file_name, &binary))
goto release_vm;
printf("Compiling module...\n");
ret = vm_load(vm, &binary);
wasm_byte_vec_delete(&binary);
if (!ret)
goto release_vm;
printf("Creating callback...\n");
wasm_functype_t *callback_type =
wasm_functype_new_1_0(wasm_valtype_new_i32());
if (!callback_type)
goto release_vm;
wasm_func_t *callback = wasm_func_new(vm->store, callback_type, report_cb);
wasm_functype_delete(callback_type);
if (!callback)
goto release_vm;
printf("Instantiating module...\n");
wasm_extern_t *externs[] = { wasm_func_as_extern(callback) };
wasm_extern_vec_t imports = WASM_ARRAY_VEC(externs);
ret = vm_link(vm, &imports);
wasm_func_delete(callback);
if (!ret)
goto release_vm;
*out = vm;
return true;
release_vm:
vm_release(vm);
fail:
return false;
}
bool
run_warm_start_w_compiled_bytecode(const wasm_vm_t *first, wasm_vm_t **out)
{
bool ret;
wasm_vm_t *secondary = vm_clone(first, compiled_bytecode);
if (!secondary)
goto fail;
printf("Creating callback...\n");
wasm_functype_t *callback_type =
wasm_functype_new_1_0(wasm_valtype_new_i32());
if (!callback_type)
goto release_vm;
wasm_func_t *callback =
wasm_func_new(secondary->store, callback_type, report_cb);
wasm_functype_delete(callback_type);
if (!callback)
goto release_vm;
printf("Instantiating module...\n");
wasm_extern_t *externs[] = { wasm_func_as_extern(callback) };
wasm_extern_vec_t imports = WASM_ARRAY_VEC(externs);
ret = vm_link(secondary, &imports);
wasm_func_delete(callback);
if (!ret)
goto release_vm;
*out = secondary;
return true;
release_vm:
vm_release(secondary);
fail:
return false;
}
bool
run_warm_start_w_instantiated_module(const wasm_vm_t *first, wasm_vm_t **out)
{
wasm_vm_t *secondary = vm_clone(first, instantiated_module);
if (!secondary)
return false;
*out = secondary;
return true;
}
void
run_test(const wasm_vm_t *vm)
{
uint8_t byte = 0xFF;
/* read initialization */
vm_function_get_byte(vm, 10, &byte);
assert(byte == 0x0);
vm_memory_get_byte(vm, 10, &byte);
assert(byte == 0x0);
/* read after writing */
vm_function_set_byte(vm, 16, 0xab);
vm_function_get_byte(vm, 16, &byte);
assert(byte == 0xab);
vm_memory_set_byte(vm, 16, 0xcd);
vm_memory_get_byte(vm, 16, &byte);
assert(byte == 0xcd);
/* reading and writing across */
vm_function_set_byte(vm, 16, 0xef);
vm_memory_get_byte(vm, 16, &byte);
assert(byte == 0xef);
vm_memory_set_byte(vm, 16, 0x67);
vm_function_get_byte(vm, 16, &byte);
assert(byte == 0x67);
printf("All Passed ...\n");
}
static void *
thrd_func(void *arg)
{
thread_arg_t *thrd_arg = (thread_arg_t *)arg;
printf("Running warm start at %s...\n", thrd_arg->name);
pthread_setspecific(name_key, thrd_arg->name);
wasm_vm_t *vm;
if (!run_warm_start_w_compiled_bytecode(thrd_arg->base_vm, &vm))
return NULL;
pthread_mutex_trylock(thrd_arg->ready_go_lock);
while (!(*thrd_arg->ready_go_flag)) {
pthread_cond_wait(thrd_arg->ready_go_cond, thrd_arg->ready_go_lock);
}
pthread_mutex_unlock(thrd_arg->ready_go_lock);
printf("Running test at %s...\n", thrd_arg->name);
run_test(vm);
vm_release(vm);
pthread_exit(NULL);
return NULL;
}
int
main()
{
int ret = EXIT_FAILURE;
bool ready_go_flag = false;
pthread_mutex_t ready_go_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ready_go_cond = PTHREAD_COND_INITIALIZER;
pthread_key_create(&name_key, NULL);
pthread_setspecific(name_key, "Execution Thread");
printf("Running cold start at the execution thread...\n");
wasm_vm_t *base_vm;
if (!run_code_start(&base_vm))
goto quit;
run_test(base_vm);
printf("Running warm start at other threads...\n");
pthread_mutex_trylock(&ready_go_lock);
pthread_t tids[WORKER_NUMBER] = { 0 };
thread_arg_t thrd_args[WORKER_NUMBER] = { 0 };
for (size_t i = 0; i < sizeof(tids) / sizeof(tids[0]); i++) {
thread_arg_t *thrd_arg = thrd_args + i;
snprintf(thrd_arg->name, 32, "Worker#%lu", i);
thrd_arg->ready_go_cond = &ready_go_cond;
thrd_arg->ready_go_lock = &ready_go_lock;
thrd_arg->ready_go_flag = &ready_go_flag;
thrd_arg->base_vm = base_vm;
int ret = pthread_create(&tids[i], NULL, thrd_func, thrd_arg);
if (ret != 0)
break;
}
sleep(5);
ready_go_flag = true;
pthread_mutex_unlock(&ready_go_lock);
pthread_cond_broadcast(&ready_go_cond);
for (size_t i = 0; i < sizeof(tids) / sizeof(tids[0]); i++) {
if (tids[i] != 0)
pthread_join(tids[i], NULL);
}
vm_release(base_vm);
ret = EXIT_SUCCESS;
quit:
return ret;
}