ruby-changes:70937
From: Yuta <ko1@a...>
Date: Wed, 19 Jan 2022 11:19:54 +0900 (JST)
Subject: [ruby-changes:70937] f72f01abd8 (master): [wasm] add unit test suite for fiber, register scan, sjlj in platform dir
https://git.ruby-lang.org/ruby.git/commit/?id=f72f01abd8 From f72f01abd89640b083b4067e4be399448f0fb6ce Mon Sep 17 00:00:00 2001 From: Yuta Saito <kateinoigakukun@g...> Date: Wed, 8 Dec 2021 22:19:52 +0900 Subject: [wasm] add unit test suite for fiber, register scan, sjlj in platform dir --- .gitignore | 3 ++ wasm/GNUmakefile.in | 12 +++++ wasm/tests/fiber_test.c | 66 ++++++++++++++++++++++++++ wasm/tests/machine_test.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++ wasm/tests/setjmp_test.c | 108 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 304 insertions(+) create mode 100644 wasm/tests/fiber_test.c create mode 100644 wasm/tests/machine_test.c create mode 100644 wasm/tests/setjmp_test.c diff --git a/.gitignore b/.gitignore index 31bfd787a70..675020fa91e 100644 --- a/.gitignore +++ b/.gitignore @@ -232,3 +232,6 @@ lcov*.info https://github.com/ruby/ruby/blob/trunk/.gitignore#L232 /rb_mjit_header.h /mjit_config.h /include/ruby-*/*/rb_mjit_min_header-*.h + +# /wasm/ +/wasm/tests/*.wasm diff --git a/wasm/GNUmakefile.in b/wasm/GNUmakefile.in index 4328dec9c66..18ddd06739a 100644 --- a/wasm/GNUmakefile.in +++ b/wasm/GNUmakefile.in @@ -6,6 +6,8 @@ GNUmakefile: $(wasmdir)/GNUmakefile.in https://github.com/ruby/ruby/blob/trunk/wasm/GNUmakefile.in#L6 WASMOPT = @WASMOPT@ wasmoptflags = @wasmoptflags@ +WASM_TESTRUNNER = wasmtime +WASM_TESTS = $(wasmdir)/tests/machine_test.wasm $(wasmdir)/tests/setjmp_test.wasm $(wasmdir)/tests/fiber_test.wasm WASM_OBJS = $(wasmdir)/machine_core.o $(wasmdir)/machine.o $(wasmdir)/setjmp.o $(wasmdir)/setjmp_core.o $(wasmdir)/fiber.o $(wasmdir)/runtime.o wasm/missing.$(OBJEXT): $(wasmdir)/missing.c $(PLATFORM_D) @@ -18,3 +20,13 @@ wasm/%.$(OBJEXT): $(wasmdir)/%.S $(PLATFORM_D) https://github.com/ruby/ruby/blob/trunk/wasm/GNUmakefile.in#L20 @$(ECHO) compiling $< $(Q) $(CC) $(CFLAGS) $(COUTFLAG)$@ -c $< +test-wasm: $(WASM_TESTS) + $(foreach x,$(WASM_TESTS), $(WASM_TESTRUNNER) $(x);) +clean-test-wasm: + @$(RM) $(WASM_TESTS) + +$(wasmdir)/tests/%.wasm: $(wasmdir)/tests/%.c $(WASM_OBJS) + $(Q) $(CC) -g $(XCFLAGS) $(CFLAGS) $^ -o $@ + $(Q) $(WASMOPT) -g --asyncify --pass-arg=asyncify-ignore-imports -o $@ $@ + +.PHONY: test-wasm clean-test-wasm diff --git a/wasm/tests/fiber_test.c b/wasm/tests/fiber_test.c new file mode 100644 index 00000000000..e6b36631ceb --- /dev/null +++ b/wasm/tests/fiber_test.c @@ -0,0 +1,66 @@ https://github.com/ruby/ruby/blob/trunk/wasm/tests/fiber_test.c#L1 +#include "wasm/fiber.h" +#include "wasm/asyncify.h" +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +static rb_wasm_fiber_context fctx_main, fctx_func1, fctx_func2; + +static int counter = 0; + +static void func1(void *arg0, void *arg1) { + assert(counter == 2); + fprintf(stderr, "func1: started\n"); + fprintf(stderr, "func1: swapcontext(&fctx_func1, &fctx_func2)\n"); + counter++; + rb_wasm_swapcontext(&fctx_func1, &fctx_func2); + + fprintf(stderr, "func1: returning\n"); +} + +static void func2(void *arg0, void *arg1) { + assert(counter == 1); + fprintf(stderr, "func2: started\n"); + fprintf(stderr, "func2: swapcontext(&fctx_func2, &fctx_func1)\n"); + counter++; + rb_wasm_swapcontext(&fctx_func2, &fctx_func1); + + assert(counter == 3); + fprintf(stderr, "func2: swapcontext(&fctx_func2, &fctx_func2)\n"); + counter++; + rb_wasm_swapcontext(&fctx_func2, &fctx_func2); + + assert(counter == 4); + fprintf(stderr, "func2: swapcontext(&fctx_func2, &fctx_main)\n"); + counter++; + rb_wasm_swapcontext(&fctx_func2, &fctx_main); + + fprintf(stderr, "func2: returning\n"); + assert(false && "unreachable"); +} + +// top level function should not be inlined to stop unwinding immediately after this function returns +__attribute__((noinline)) +int start(int argc, char **argv) { + rb_wasm_init_context(&fctx_main, NULL, NULL, NULL); + fctx_main.is_started = true; + + rb_wasm_init_context(&fctx_func1, func1, NULL, NULL); + + rb_wasm_init_context(&fctx_func2, func2, NULL, NULL); + + counter++; + fprintf(stderr, "start: swapcontext(&uctx_main, &fctx_func2)\n"); + rb_wasm_swapcontext(&fctx_main, &fctx_func2); + assert(counter == 5); + + fprintf(stderr, "start: exiting\n"); + return 42; +} + +int main(int argc, char **argv) { + extern int rb_wasm_rt_start(int (main)(int argc, char **argv), int argc, char **argv); + int result = rb_wasm_rt_start(start, argc, argv); + assert(result == 42); + return 0; +} diff --git a/wasm/tests/machine_test.c b/wasm/tests/machine_test.c new file mode 100644 index 00000000000..f4b62ff580b --- /dev/null +++ b/wasm/tests/machine_test.c @@ -0,0 +1,115 @@ https://github.com/ruby/ruby/blob/trunk/wasm/tests/machine_test.c#L1 +#include <stdio.h> +#include <assert.h> +#include <stdint.h> +#include <stdbool.h> +#include "wasm/machine.h" +#include "wasm/asyncify.h" + +void *rb_wasm_get_stack_pointer(void); + +static void *base_stack_pointer = NULL; + +int __attribute__((constructor)) record_base_sp(void) { + base_stack_pointer = rb_wasm_get_stack_pointer(); + return 0; +} + +void dump_memory(uint8_t *base, uint8_t *end) { + size_t chunk_size = 16; + + for (uint8_t *ptr = base; ptr <= end; ptr += chunk_size) { + printf("%p", ptr); + for (size_t offset = 0; offset < chunk_size; offset++) { + printf(" %02x", *(ptr + offset)); + } + printf("\n"); + } +} + +bool find_in_stack(uint32_t target, void *base, void *end) { + for (uint32_t *ptr = base; ptr <= (uint32_t *)end; ptr++) { + if (*ptr == target) { + return true; + } + } + return false; +} + +void *_rb_wasm_stack_mem[2]; +void rb_wasm_mark_mem_range(void *start, void *end) { + _rb_wasm_stack_mem[0] = start; + _rb_wasm_stack_mem[1] = end; +} + +#define check_live(target, ctx) do { \ + rb_wasm_scan_stack(rb_wasm_mark_mem_range); \ + _check_live(target, ctx); \ + } while (0); + +void _check_live(uint32_t target, const char *ctx) { + printf("checking %#x ... ", target); + bool found_in_locals = false, found_in_stack = false; + if (find_in_stack(target, _rb_wasm_stack_mem[0], _rb_wasm_stack_mem[1])) { + found_in_stack = true; + } + rb_wasm_scan_locals(rb_wasm_mark_mem_range); + if (find_in_stack(target, _rb_wasm_stack_mem[0], _rb_wasm_stack_mem[1])) { + found_in_locals = true; + } + if (found_in_locals && found_in_stack) { + printf("ok (found in C stack and Wasm locals)\n"); + } else if (found_in_stack) { + printf("ok (found in C stack)\n"); + } else if (found_in_locals) { + printf("ok (found in Wasm locals)\n"); + } else { + printf("not found: %s\n", ctx); + assert(false); + } +} + +void new_frame(uint32_t val, uint32_t depth) { + if (depth == 0) { + dump_memory(rb_wasm_get_stack_pointer(), base_stack_pointer); + for (uint32_t i = 0; i < 5; i++) { + check_live(0x00bab10c + i, "argument value"); + } + } else { + new_frame(val, depth - 1); + } +} + +uint32_t return_value(void) { + return 0xabadbabe; +} + +uint32_t check_return_value(void) { + check_live(0xabadbabe, "returned value"); + return 0; +} + +void take_two_args(uint32_t a, uint32_t b) { +} + +__attribute__((noinline)) +int start(int argc, char **argv) { + + uint32_t deadbeef; + register uint32_t facefeed; + deadbeef = 0xdeadbeef; + facefeed = 0xfacefeed; + + check_live(0xdeadbeef, "local variable"); + check_live(0xfacefeed, "local reg variable"); + + new_frame(0x00bab10c, 5); + + take_two_args(return_value(), check_return_value()); + + return 0; +} + +int main(int argc, char **argv) { + extern int rb_wasm_rt_start(int (main)(int argc, char **argv), int argc, char **argv); + return rb_wasm_rt_start(start, argc, argv); +} diff --git a/wasm/tests/setjmp_test.c b/wasm/tests/setjmp_test.c new file mode 100644 index 00000000000..f263dcfa3e7 --- /dev/null +++ b/wasm/tests/setjmp_test.c @@ -0,0 +1,108 @@ https://github.com/ruby/ruby/blob/trunk/wasm/tests/setjmp_test.c#L1 +#include "wasm/setjmp.h" +#include "wasm/asyncify.h" +#include "wasm/machine.h" +#include <stdio.h> +#include <assert.h> + +void check_direct(void) { + rb_wasm_jmp_buf buf; + int val; + printf("[%s] start\n", __func__); + printf("[%s] call rb_wasm_setjmp\n", __func__); + if ((val = rb_wasm_setjmp(buf)) == 0) { + printf("[%s] rb_wasm_setjmp(buf) == 0\n", __func__); + printf("[%s] call rb_wasm_longjmp(buf, 2)\n", __func__); + rb_wasm_longjmp(buf, 2); + assert(0 && "unreachable after longjmp"); + } else { + printf("[%s] rb_wasm_setjmp(buf) == %d\n", __func__, val); + printf("[%s] sp = %p\n", __func__, rb_wasm_get_stack_pointer()); + assert(val == 2 && "unexpected returned value"); + } + printf("[%s] end\n", __func__); +} + +void jump_to_dst(rb_wasm_jmp_buf *dst) { + rb_wasm_jmp_buf buf; + printf("[%s] start sp = %p\n", __func__, rb_wasm_get_stack_pointer()); + printf("[%s] call rb_wasm_setjmp\n", __func__); + if (rb_wasm_setjmp(buf) == 0) { + printf("[%s] rb_wasm_setjmp(buf) == 0\n", __func__); + printf("[%s] call rb_wasm_longjmp(dst, 4)\n", __func__); + rb_wasm_longjmp(*dst, 4); + assert(0 && "unreachable after longjmp"); + } else { + assert(0 && "unreachable"); + } + printf("[%s] end\n", __func__); +} + +void check_jump_two_level(void) { + rb_wasm_jmp_buf buf; + int val; + printf("[%s] start\n", __func__); + printf("[%s] call rb_wasm_setjmp\n", __func__); + if ((val = rb_wasm_setjmp(buf)) == 0) { + printf("[%s] rb_wasm_setjmp(buf) == 0\n", __func__); + printf("[%s] call jump_to_dst(&buf)\n", __func__); + jump_to_dst(&buf); + assert(0 && "unreachable after longjmp"); + } else { + printf("[%s] rb_wasm_setjmp(buf) == %d\n", __func__, val); + assert(val == 4 && "unexpected returned value"); + } + printf("[%s] end\n", __func__); +} + +void check_reuse(void) { + rb_wasm_jmp_buf buf; + int val; + printf("[%s] start\n", __func__); + printf("[%s] call rb_wasm_setjmp\n", __func__); + if ((val = rb_wasm_setjmp(buf)) == 0) { + printf("[%s] rb_wasm_setjmp(buf) == 0\n", __func__ (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/