mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
[wasm] add unit test suite for fiber, register scan, sjlj in platform dir
This commit is contained in:
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
3
.gitignore
vendored
|
@ -232,3 +232,6 @@ lcov*.info
|
|||
/rb_mjit_header.h
|
||||
/mjit_config.h
|
||||
/include/ruby-*/*/rb_mjit_min_header-*.h
|
||||
|
||||
# /wasm/
|
||||
/wasm/tests/*.wasm
|
||||
|
|
|
@ -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
66
wasm/tests/fiber_test.c
Normal 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
115
wasm/tests/machine_test.c
Normal 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
108
wasm/tests/setjmp_test.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue