[wasm] add unit test suite for fiber, register scan, sjlj in platform dir

This commit is contained in:
Yuta Saito 2021-12-08 22:19:52 +09:00
parent 3794ef6f01
commit f72f01abd8
Notes: git 2022-01-19 11:19:38 +09:00
5 changed files with 304 additions and 0 deletions

3
.gitignore vendored
View File

@ -232,3 +232,6 @@ lcov*.info
/rb_mjit_header.h
/mjit_config.h
/include/ruby-*/*/rb_mjit_min_header-*.h
# /wasm/
/wasm/tests/*.wasm

View File

@ -6,6 +6,8 @@ GNUmakefile: $(wasmdir)/GNUmakefile.in
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)
@$(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

66
wasm/tests/fiber_test.c Normal file
View File

@ -0,0 +1,66 @@
#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;
}

115
wasm/tests/machine_test.c Normal file
View File

@ -0,0 +1,115 @@
#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);
}

108
wasm/tests/setjmp_test.c Normal file
View File

@ -0,0 +1,108 @@
#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__);
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);
if (val < 5) {
printf("[%s] re-call rb_wasm_longjmp(buf, %d)\n", __func__, val + 1);
rb_wasm_longjmp(buf, val + 1);
}
}
printf("[%s] end\n", __func__);
}
void check_stack_ptr(void) {
static void *normal_sp;
rb_wasm_jmp_buf buf;
normal_sp = rb_wasm_get_stack_pointer();
printf("[%s] start sp = %p\n", __func__, normal_sp);
printf("[%s] call rb_wasm_setjmp\n", __func__);
if (rb_wasm_setjmp(buf) == 0) {
printf("[%s] call jump_to_dst(&buf)\n", __func__);
jump_to_dst(&buf);
assert(0 && "unreachable after longjmp");
} else {
printf("[%s] sp = %p\n", __func__, rb_wasm_get_stack_pointer());
assert(rb_wasm_get_stack_pointer() == normal_sp);
}
printf("[%s] end\n", __func__);
}
// top level function should not be inlined to stop unwinding immediately after this function returns
__attribute__((noinline))
int start(int argc, char **argv) {
check_direct();
check_jump_two_level();
check_reuse();
check_stack_ptr();
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);
}