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

fix nested calls #122

Merged
merged 33 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
215e945
allow to have multiple restart information in an exec context
yamt Oct 12, 2023
ad01e73
do_host_call: keep the stack height while calling the host func
yamt Oct 13, 2023
9c17f48
do_host_call: fix restart
yamt Oct 14, 2023
3fc4dd9
examples/runwasi: add an extra import object argument
yamt Oct 31, 2023
983955f
examples/runwasi: separate cli argument processing for easier reuse
yamt Nov 1, 2023
be13f02
install a few more header files for the convenience of user host funcs
yamt Nov 1, 2023
d5704ea
add examples/hostfunc
yamt Nov 1, 2023
af233a8
add a script to build toywasm and an example app
yamt Nov 1, 2023
d68f65b
add cconv_deref_func_ptr
yamt Nov 2, 2023
ba0e8fa
examples/hostfunc/wasm/test.c: add a way to use a host func for load_…
yamt Nov 2, 2023
9d44b6c
examples/hostfunc: implement a host func version of load_call
yamt Nov 2, 2023
74861cd
comment
yamt Nov 3, 2023
a794bde
examples/hostfunc/wasm: add USE_HOST_LOAD_CALL_ADD
yamt Nov 21, 2023
5983236
examples/hostfunc: reduce code dup a bit
yamt Nov 21, 2023
2e25c2e
examples/hostfunc: clean up a bit
yamt Nov 21, 2023
a46ffc0
examples/hostfunc: wip my_host_inst_load_call_add
yamt Nov 21, 2023
ea5928d
build-toywasm-and-app.sh: honor EXTRA_CMAKE_OPTIONS
yamt Nov 22, 2023
7c6e64c
install restart.h
yamt Nov 22, 2023
c06acf4
implement a mechanism to return to the calling host func
yamt Nov 22, 2023
a159501
examples/hostfunc: implement the rest of my_host_inst_load_call_add
yamt Nov 22, 2023
5c9095d
examples/hostfunc/wasm/build.sh: add a script to build test wasm modules
yamt Nov 22, 2023
b2bc937
examples/hostfunc: add scripts to build and run tests
yamt Nov 22, 2023
c27e97b
examples/hostfunc/wasm/build.sh: build more combinations
yamt Nov 22, 2023
7985e61
test-example.sh -> build-example.sh
yamt Nov 22, 2023
7be1774
examples/hostfunc/run.sh: make the executable overridable
yamt Nov 22, 2023
ba7135e
examples/hostfunc/wasm: add prebuilt modules
yamt Nov 22, 2023
b03253e
ci: run hostfunc example
yamt Nov 22, 2023
a4a79a0
doc/check_interrupt.md: mention hostfunc example
yamt Nov 22, 2023
dcf949d
examples/hostfunc: fix stack adjustment
yamt Nov 22, 2023
63a779c
examples/hostfunc: comment
yamt Nov 22, 2023
9254bee
ci: fix build-example.sh usage
yamt Nov 22, 2023
39a4e1f
make multiple inclusion protection macrs have _TOYWASM prefix
yamt Nov 22, 2023
2fa7755
ci: fix a build dir mismatch
yamt Nov 22, 2023
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
15 changes: 13 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,24 @@ jobs:
- name: Test "app" example with the library we built
if: matrix.BUILD_TYPE == 'Release' && matrix.arch == 'native'
run: |
./test/test-example.sh app ${{env.builddir}}/toywasm-v*.tgz ${{env.builddir}}/spectest.wasm
./test/build-example.sh app ${{env.builddir}}/toywasm-v*.tgz build
./examples/app/build/app ${{env.builddir}}/spectest.wasm

- name: Test "runwasi" example with the library we built
if: matrix.BUILD_TYPE == 'Release' && matrix.arch == 'native'
run: |
wat2wasm wat/wasi/hello.wat
./test/test-example.sh runwasi ${{env.builddir}}/toywasm-v*.tgz $(pwd)/hello.wasm
./test/build-example.sh runwasi ${{env.builddir}}/toywasm-v*.tgz build
./examples/runwasi/build/runwasi $(pwd)/hello.wasm

- name: Test "hostfunc" example with the library we built
if: matrix.BUILD_TYPE == 'Release' && matrix.arch == 'native'
run: |
./test/build-example.sh hostfunc ${{env.builddir}}/toywasm-v*.tgz build
cd examples/hostfunc/wasm
pax -rvzf hostfunc-test-wasm.tgz
cd ..
BIN=./build/hostfunc ./run.sh

- name: Upload artifacts
if: matrix.name != 'noname'
Expand Down
5 changes: 4 additions & 1 deletion doc/check_interrupt.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@ This mechanism is used to implement:
* Inefficient. Especially when you have many threads.

* Restrictions on host functions. Restarting a complex host function
might be very difficult to implement properly.
might be difficult to implement properly.
For example, a host function calling back to the wasm module, which
might even call another host function.
You can find such host functions in the [hostfunc example app].

* Non-blocking I/O on unix is a bit awkward to handle.

[hostfunc example app]: ../examples/hostfunc/hostfunc.c
14 changes: 14 additions & 0 deletions examples/build-toywasm-and-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#! /bin/sh

set -e

ROOT=${ROOT:-build}
echo "ROOT: ${ROOT}"

BUILD_TOYWASM=${ROOT}/build-toywasm
BUILD_APP=${ROOT}/build-app

cmake -G Ninja -B ${BUILD_TOYWASM} -D CMAKE_INSTALL_PREFIX=${ROOT} ${EXTRA_CMAKE_OPTIONS} ../..
cmake --build ${BUILD_TOYWASM} --target install
cmake -G Ninja -B ${BUILD_APP} -D CMAKE_PREFIX_PATH=${ROOT} ${EXTRA_CMAKE_OPTIONS} .
cmake --build ${BUILD_APP}
21 changes: 21 additions & 0 deletions examples/hostfunc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.16)

project(runwasi 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"
"hostfunc.c"
)

add_executable(hostfunc ${sources})
target_link_libraries(hostfunc toywasm-lib-core toywasm-lib-wasi m)
target_include_directories(hostfunc PRIVATE "../runwasi")
11 changes: 11 additions & 0 deletions examples/hostfunc/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /bin/sh

set -e

cd wasm
./build.sh
cd ..

# use a debug build to enable assertions for now
EXTRA_CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Debug -DUSE_ASAN=OFF -DUSE_UBSAN=OFF" \
../build-toywasm-and-app.sh
242 changes: 242 additions & 0 deletions examples/hostfunc/hostfunc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>

#include <toywasm/cconv.h>
#include <toywasm/cell.h>
#include <toywasm/endian.h>
#include <toywasm/exec_context.h>
#include <toywasm/host_instance.h>
#include <toywasm/restart.h>

static int
load(struct exec_context *ctx, uint32_t pp, uint32_t *resultp)
{
int host_ret;
uint32_t le32;

/*
* *resultp = *(*pp)++
*/

host_ret = host_func_copyin(ctx, &le32, pp, 4, 4);
if (host_ret != 0) {
goto fail;
}
uint32_t p = le32_to_host(le32);
host_ret = host_func_copyin(ctx, &le32, p, 4, 4);
if (host_ret != 0) {
goto fail;
}
uint32_t result = le32_to_host(le32);
p += 4;
le32 = host_to_le32(p);
host_ret = host_func_copyout(ctx, &le32, pp, 4, 4);
if (host_ret != 0) {
goto fail;
}
*resultp = result;
fail:
return host_ret;
}

static int
load_func(struct exec_context *ctx, const struct functype *ft, uint32_t pp,
const struct funcinst **fip)
{
int host_ret;
uint32_t funcptr;
host_ret = load(ctx, pp, &funcptr);
if (host_ret != 0) {
goto fail;
}
const struct funcinst *func;
host_ret =
cconv_deref_func_ptr(ctx, ctx->instance, funcptr, ft, &func);
if (host_ret != 0) {
goto fail;
}
*fip = func;
fail:
return host_ret;
}

static int
my_host_inst_load(struct exec_context *ctx, struct host_instance *hi,
const struct functype *ft, const struct cell *params,
struct cell *results)
{
HOST_FUNC_CONVERT_PARAMS(ft, params);
uint32_t pp = HOST_FUNC_PARAM(ft, params, 0, i32);
int host_ret;

uint32_t result;
host_ret = load(ctx, pp, &result);
if (host_ret == 0) {
HOST_FUNC_RESULT_SET(ft, results, 0, i32, result);
}
HOST_FUNC_FREE_CONVERTED_PARAMS();
return host_ret;
}

static int
my_host_inst_load_call(struct exec_context *ctx, struct host_instance *hi,
const struct functype *ft, const struct cell *params,
struct cell *results)
{
HOST_FUNC_CONVERT_PARAMS(ft, params);
uint32_t pp = HOST_FUNC_PARAM(ft, params, 0, i32);
int host_ret;

const struct funcinst *func;
host_ret = load_func(ctx, ft, pp, &func);
if (host_ret != 0) {
goto fail;
}
/* tail call with the same argument */
ctx->event_u.call.func = func;
ctx->event = EXEC_EVENT_CALL;
host_ret = ETOYWASMRESTART;
fail:
HOST_FUNC_FREE_CONVERTED_PARAMS();
return host_ret;
}

static int
my_host_inst_load_call_add(struct exec_context *ctx, struct host_instance *hi,
const struct functype *ft,
const struct cell *params, struct cell *results)
{
/*
* this function is a bit complicated as it calls other functions.
* the callee functions can be wasm functions or host functions.
*
* sum = 0;
* f1 = load_func(pp);
* v1 = f1(pp); // this might need a restart
* step1:
* sum += v1;
* f2 = load_func(pp);
* v2 = f2(pp); // this might need a restart
* step2:
* sum += v2;
* return sum;
*/

HOST_FUNC_CONVERT_PARAMS(ft, params);
uint32_t pp = HOST_FUNC_PARAM(ft, params, 0, i32);
int host_ret;
struct restart_hostfunc *hf;
uint32_t result;
uint32_t sum = 0;
uint32_t i;

host_ret = restart_info_prealloc(ctx);
if (host_ret != 0) {
return host_ret;
}
struct restart_info *restart = &VEC_NEXTELEM(ctx->restarts);
if (restart->restart_type != RESTART_NONE) {
assert(restart->restart_type == RESTART_HOSTFUNC);
hf = &restart->restart_u.hostfunc;
uint32_t step = hf->user1;
sum = hf->user2;
restart_info_clear(ctx);
switch (step) {
case 1:
case 2:
assert(ctx->stack.psize - ctx->stack.lsize >=
hf->stack_adj);
i = step - 1;
/*
* adjust the stack offset to make exec_pop_vals
* below pop the correct values.
*/
ctx->stack.lsize += hf->stack_adj;
goto after_return;
default:
assert(false);
}
assert(false);
}
sum = 0;
for (i = 0; i < 2; i++) {
/*
* Note: we know the function has the same type as
* ours. (ft)
*/
const struct funcinst *func;
host_ret = load_func(ctx, ft, pp, &func);
if (host_ret != 0) {
goto fail;
}

/*
* call the function
*/
struct val a[1] = {
{
.u.i32 = pp,
},
};
host_ret = exec_push_vals(ctx, &ft->parameter, a);
if (host_ret != 0) {
goto fail;
}
/*
* set up the restart info so that the function can
* return to us.
*/
restart->restart_type = RESTART_HOSTFUNC;
hf = &restart->restart_u.hostfunc;
hf->func = ctx->event_u.call.func; /* this func */
hf->saved_bottom = ctx->bottom;
hf->stack_adj = resulttype_cellsize(&ft->result);
hf->user1 = i + 1; /* step */
hf->user2 = sum;
ctx->event_u.call.func = func;
ctx->event = EXEC_EVENT_CALL;
ctx->bottom = ctx->frames.lsize;
ctx->restarts.lsize++; /* make restart possibly nest */
host_ret = ETOYWASMRESTART;
goto fail; /* not a failure */
after_return:;
struct val r[1];
exec_pop_vals(ctx, &ft->result, r);
uint32_t v1 = r[0].u.i32;
sum += v1;
}
result = sum;
host_ret = 0;
fail:
assert(IS_RESTARTABLE(host_ret) ||
restart->restart_type == RESTART_NONE);
if (host_ret == 0) {
HOST_FUNC_RESULT_SET(ft, results, 0, i32, result);
}
HOST_FUNC_FREE_CONVERTED_PARAMS();
return host_ret;
}

static const struct host_func my_host_inst_funcs[] = {
HOST_FUNC(my_host_inst_, load, "(i)i"),
HOST_FUNC(my_host_inst_, load_call, "(i)i"),
HOST_FUNC(my_host_inst_, load_call_add, "(i)i"),
};

static const struct name name_my_host_inst =
NAME_FROM_CSTR_LITERAL("my-host-func");

static const struct host_module module_my_host_inst[] = {{
.module_name = &name_my_host_inst,
.funcs = my_host_inst_funcs,
.nfuncs = ARRAYCOUNT(my_host_inst_funcs),
}};

int
import_object_create_for_my_host_inst(void *inst, struct import_object **impp)
{
return import_object_create_for_host_funcs(
module_my_host_inst, ARRAYCOUNT(module_my_host_inst), inst,
impp);
}
4 changes: 4 additions & 0 deletions examples/hostfunc/hostfunc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct import_object;

int import_object_create_for_my_host_inst(void *inst,
struct import_object **impp);
51 changes: 51 additions & 0 deletions examples/hostfunc/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/xlog.h>

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

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 import_object *import_obj;
ret = import_object_create_for_my_host_inst(NULL, &import_obj);
if (ret != 0) {
exit(1);
}
ret = runwasi(a->filename, a->ndirs, a->dirs, a->nenvs,
(const char *const *)a->envs, a->argc,
(const char *const *)a->argv, stdio_fds, import_obj,
&wasi_exit_code);
free(a->dirs);
free(a->envs);
if (ret != 0) {
exit(1);
}
exit(wasi_exit_code);
}
9 changes: 9 additions & 0 deletions examples/hostfunc/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/sh

set -e

for x in wasm/*.wasm; do
echo
echo "# $x"
${BIN:-./build/build-app/hostfunc} $x
done
Loading