Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wasm2cstruct #250

Merged
merged 19 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,20 @@ jobs:
./examples/wasm2wasm/build/wasm2wasm out.wasm out2.wasm
cmp out.wasm out2.wasm
# Note: the generated file (module.c) will be used by the next step
- name: Test "wasm2cstruct" example with the library we built
if: matrix.arch == 'native'
run: |
./test/build-example.sh wasm2cstruct ${{env.builddir}}/toywasm-v*.tgz build
wat2wasm --debug-names wat/wasi/recursive_hello_arg.wat
./examples/wasm2cstruct/build/wasm2cstruct g_wasm_module $(pwd)/recursive_hello_arg.wasm > ./examples/runwasi_cstruct/module.c
- name: Test "runwasi_cstruct" example with the library we built
if: matrix.arch == 'native'
run: |
./test/build-example.sh runwasi_cstruct ${{env.builddir}}/toywasm-v*.tgz build
./examples/runwasi_cstruct/build/runwasi_cstruct -- foo hello
- name: Upload artifacts
if: matrix.name != 'noname'
uses: actions/upload-artifact@v3
Expand Down
68 changes: 42 additions & 26 deletions examples/runwasi/runwasi.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,17 @@
#include "runwasi.h"

int
runwasi(struct mem_context *mctx, const char *filename, unsigned int ndirs,
char **dirs, unsigned int nenvs, const char *const *envs, int argc,
const char *const *argv, const int stdio_fds[3],
struct import_object *base_imports, uint32_t *wasi_exit_code_p)
runwasi_module(struct mem_context *mctx, const struct module *m,
unsigned int ndirs, char **dirs, unsigned int nenvs,
const char *const *envs, int argc, const char *const *argv,
const int stdio_fds[3], struct import_object *base_imports,
uint32_t *wasi_exit_code_p)
{
struct module *m = NULL;
struct wasi_instance *wasi = NULL;
struct import_object *wasi_import_object = NULL;
struct instance *inst = NULL;
int ret;

/*
* load a module
*/
uint8_t *p;
size_t sz;
ret = map_file(filename, (void **)&p, &sz);
if (ret != 0) {
xlog_error("map_file failed with %d", ret);
goto fail;
}
struct load_context lctx;
load_context_init(&lctx, mctx);
ret = module_create(&m, p, p + sz, &lctx);
if (ret != 0) {
xlog_error("module_load failed with %d: %s", ret,
report_getmessage(&lctx.report));
load_context_clear(&lctx);
goto fail;
}
load_context_clear(&lctx);

/*
* find the entry point
*/
Expand Down Expand Up @@ -154,6 +133,43 @@ runwasi(struct mem_context *mctx, const char *filename, unsigned int ndirs,
if (wasi != NULL) {
wasi_instance_destroy(wasi);
}
return ret;
}

int
runwasi(struct mem_context *mctx, const char *filename, unsigned int ndirs,
char **dirs, unsigned int nenvs, const char *const *envs, int argc,
const char *const *argv, const int stdio_fds[3],
struct import_object *base_imports, uint32_t *wasi_exit_code_p)
{
struct module *m = NULL;
int ret;

/*
* load a module
*/
uint8_t *p;
size_t sz;
ret = map_file(filename, (void **)&p, &sz);
if (ret != 0) {
xlog_error("map_file failed with %d", ret);
goto fail;
}
struct load_context lctx;
load_context_init(&lctx, mctx);
ret = module_create(&m, p, p + sz, &lctx);
if (ret != 0) {
xlog_error("module_load failed with %d: %s", ret,
report_getmessage(&lctx.report));
load_context_clear(&lctx);
goto fail;
}
load_context_clear(&lctx);

ret = runwasi_module(mctx, m, ndirs, dirs, nenvs, envs, argc, argv,
stdio_fds, base_imports, wasi_exit_code_p);

fail:
if (m != NULL) {
module_destroy(mctx, m);
}
Expand Down
7 changes: 7 additions & 0 deletions examples/runwasi/runwasi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

struct mem_context;
struct import_object;
struct module;

int runwasi_module(struct mem_context *mctx, const struct module *m,
unsigned int ndirs, char **dirs, unsigned int nenvs,
const char *const *envs, int argc, const char *const *argv,
const int stdio_fds[3], struct import_object *base_imports,
uint32_t *wasi_exit_code_p);

int runwasi(struct mem_context *mctx, const char *filename, unsigned int ndirs,
char **dirs, unsigned int nenvs, const char *const *envs, int argc,
Expand Down
23 changes: 23 additions & 0 deletions examples/runwasi_cstruct/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.16)

include(../../cmake/LLVM.cmake)

project(runwasi_cstruct LANGUAGES C)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wvla -Werror")

find_package(toywasm-lib-core REQUIRED)
find_package(toywasm-lib-wasi REQUIRED)

# REVISIT: make runwasi.[ch] a library?

set(sources
"../runwasi/runwasi.c"
"../runwasi/runwasi_cli_args.c"
"main.c"
"module.c"
)

add_executable(runwasi_cstruct ${sources})
target_link_libraries(runwasi_cstruct toywasm-lib-core toywasm-lib-wasi m)
target_include_directories(runwasi_cstruct PRIVATE "../runwasi")
8 changes: 8 additions & 0 deletions examples/runwasi_cstruct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# What's this

A sample embedder to use a module preloaded by [wasm2cstruct].

Before building this sample, place `module.c` which provides
`const sturct module g_wasm_module` is this directory.

[wasm2cstruct]: ../wasm2cstruct
9 changes: 9 additions & 0 deletions examples/runwasi_cstruct/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/sh

set -e

TOPDIR=$(cd $(dirname $0) && pwd -P)/../..
. ${TOPDIR}/all_features.sh

TOYWASM_EXTRA_CMAKE_OPTIONS="${EXTRA_CMAKE_OPTIONS}" \
../build-toywasm-and-app.sh
51 changes: 51 additions & 0 deletions examples/runwasi_cstruct/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* an example app to run a wasi command.
*
* usage:
* % runwasi --dir=. --env=a=b -- foo.wasm -x -y
*/

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

#include <toywasm/mem.h>
#include <toywasm/xlog.h>

#include "runwasi.h"
#include "runwasi_cli_args.h"

extern const struct module g_wasm_module;

int
main(int argc, char **argv)
{
struct runwasi_cli_args a0;
struct runwasi_cli_args *a = &a0;
uint32_t wasi_exit_code;
int ret;
const int stdio_fds[3] = {
STDIN_FILENO,
STDOUT_FILENO,
STDERR_FILENO,
};
ret = runwasi_cli_args_parse(argc, argv, a);
if (ret != 0) {
xlog_error("failed to process cli arguments");
exit(1);
}
struct mem_context mctx;
mem_context_init(&mctx);
ret = runwasi_module(&mctx, &g_wasm_module, a->ndirs, a->dirs, a->nenvs,
(const char *const *)a->envs, a->argc,
(const char *const *)a->argv, stdio_fds, NULL,
&wasi_exit_code);
mem_context_clear(&mctx);
free(a->dirs);
free(a->envs);
if (ret != 0) {
exit(1);
}
exit(wasi_exit_code);
}
17 changes: 17 additions & 0 deletions examples/wasm2cstruct/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.16)

include(../../cmake/LLVM.cmake)

project(wasm2cstruct LANGUAGES C)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wvla -Werror")

find_package(toywasm-lib-core REQUIRED)

set(app_sources
"main.c"
"cstruct.c"
)

add_executable(wasm2cstruct ${app_sources})
target_link_libraries(wasm2cstruct toywasm-lib-core m)
52 changes: 52 additions & 0 deletions examples/wasm2cstruct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# What's this

This program loads and validates a module and generates a C source code
which provides a structure which represents the loaded module.
(`struct module`)

```shell
% wasm2cstruct g_wasm_module foo.wasm | clang-format > module.c
```

Note: the first argument is the C symbol to use.

Note: `clang-format` here is just for possible human-readers of the module.

The generated C source file contains a single exported symbol for the
`struct module`.

You can compile and link it to you program, which can look like:

```c
extern struct module g_wasm_module;

/* instantiate the preloaded module */
instance_create(..., &g_wasm_module, ...);
```

See [runwasi_cstruct] for a complete example to consume
the generated C source file.

[runwasi_cstruct]: ../runwasi_cstruct

## Notes

* This effectively preloads a module at the build-time of the embedder.

* The generated structure and its all dependencies are `const` qualified.
The compiler likely places them into read-only sections.

* While it's less flexible than dynamically loading modules using
the `module_create` API, it's likely more memory-efficient
especially when you want to save the malloc'ed memory.
More importantly, this can allow placing the loaded modules into
the ROM directly.

* You can't use `module_destroy` on the generated structure.
Note that `module_destroy` is the only operation in toywasm which
modifies a loaded module.

* Maybe it makes sense to use this for modules like `libc.so`.

* A compiler for a modern language might be able to do something similar
via compile-time code execution. Unfortunately C is not such a language.
11 changes: 11 additions & 0 deletions examples/wasm2cstruct/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /bin/sh

set -e

TOPDIR=$(cd $(dirname $0) && pwd -P)/../..
. ${TOPDIR}/all_features.sh

# use a debug build to enable assertions for now
TOYWASM_EXTRA_CMAKE_OPTIONS="${EXTRA_CMAKE_OPTIONS} -DCMAKE_BUILD_TYPE=Debug" \
APP_EXTRA_CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Debug" \
../build-toywasm-and-app.sh
Loading
Loading