-
Notifications
You must be signed in to change notification settings - Fork 0
Port MicroQuickJS to WASI 0.2 Component #3
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
base: main
Are you sure you want to change the base?
Changes from all commits
ef5367a
a22c098
40daa09
eeda520
f4c8664
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [submodule "packages/wasi-sdk"] | ||
| path = packages/wasi-sdk | ||
| url = https://github.com/WebAssembly/wasi-sdk | ||
| [submodule "packages/wasmedge"] | ||
| path = packages/wasmedge | ||
| url = https://github.com/WasmEdge/WasmEdge | ||
| [submodule "packages/WasmEdge"] | ||
| path = packages/WasmEdge | ||
| url = https://github.com/WasmEdge/WasmEdge.git | ||
|
Comment on lines
+4
to
+9
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove duplicate entry |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Makefile for building MicroQuickJS as a WASI 0.2 Component | ||
| # Prerequisites: wasi-sdk, wit-bindgen, wasm-tools | ||
|
|
||
| WASI_SDK_PATH ?= /tmp/wasi-sdk | ||
| CC = $(WASI_SDK_PATH)/bin/clang | ||
| AR = $(WASI_SDK_PATH)/bin/llvm-ar | ||
| CFLAGS = -Oz -D_WASI_EMULATED_SIGNAL -Werror=implicit-function-declaration -I. -I./generated | ||
|
|
||
| BUILD_DIR = build | ||
| GEN_DIR = generated | ||
| CARGO_BIN = $(HOME)/.cargo/bin | ||
|
|
||
| OBJS = \ | ||
| $(BUILD_DIR)/mquickjs.o \ | ||
| $(BUILD_DIR)/cutils.o \ | ||
| $(BUILD_DIR)/dtoa.o \ | ||
| $(BUILD_DIR)/libm.o \ | ||
| $(BUILD_DIR)/microquickjs.o \ | ||
| $(BUILD_DIR)/glue.o \ | ||
| $(GEN_DIR)/microquickjs_component_type.o | ||
|
|
||
| .PHONY: all clean inspect test | ||
|
|
||
| all: $(BUILD_DIR)/microquickjs.component.wasm | ||
|
|
||
| $(BUILD_DIR): | ||
| mkdir -p $(BUILD_DIR) | ||
|
|
||
| $(GEN_DIR)/microquickjs.c: microquickjs.wit | ||
| $(CARGO_BIN)/wit-bindgen c ./microquickjs.wit --out-dir ./$(GEN_DIR) | ||
|
|
||
| $(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) | ||
| $(CC) $(CFLAGS) -c $< -o $@ | ||
|
|
||
| $(BUILD_DIR)/microquickjs.o: $(GEN_DIR)/microquickjs.c | $(BUILD_DIR) | ||
| $(CC) $(CFLAGS) -c $< -o $@ | ||
|
|
||
| $(BUILD_DIR)/glue.o: glue.c | $(BUILD_DIR) $(GEN_DIR)/microquickjs.c | ||
| $(CC) $(CFLAGS) -c $< -o $@ | ||
|
|
||
| $(BUILD_DIR)/core.wasm: $(OBJS) | ||
| $(CC) $(CFLAGS) \ | ||
| -Wl,--no-entry \ | ||
| -Wl,--export=cabi_realloc \ | ||
| -lwasi-emulated-signal \ | ||
| $(OBJS) \ | ||
| -o $@ | ||
|
|
||
| $(BUILD_DIR)/embedded.wasm: $(BUILD_DIR)/core.wasm microquickjs.wit | ||
| $(CARGO_BIN)/wasm-tools component embed ./microquickjs.wit $< --output $@ | ||
|
|
||
| wasi_snapshot_preview1.reactor.wasm: | ||
| curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasi_snapshot_preview1.reactor.wasm -o $@ | ||
|
|
||
| env.wasm: | ||
| echo '(module (func (export "__wasm_setjmp") (param i32 i32 i32)) (func (export "__wasm_longjmp") (param i32 i32)) (func (export "__wasm_setjmp_test") (param i32 i32) (result i32) (i32.const 0)))' > env.wat | ||
| $(CARGO_BIN)/wasm-tools wat2wasm env.wat -o $@ | ||
|
|
||
| $(BUILD_DIR)/microquickjs.component.wasm: $(BUILD_DIR)/embedded.wasm wasi_snapshot_preview1.reactor.wasm env.wasm | ||
| $(CARGO_BIN)/wasm-tools component new $< --adapt env=env.wasm --adapt wasi_snapshot_preview1.reactor.wasm --output $@ | ||
|
|
||
| inspect: $(BUILD_DIR)/microquickjs.component.wasm | ||
| $(CARGO_BIN)/wasm-tools print $< | ||
|
|
||
| test: $(BUILD_DIR)/microquickjs.component.wasm | ||
| @echo "Note: Running tests using host test runner as 'wasmtime run --invoke' is not supported for components yet." | ||
| $(MAKE) -f Makefile.wasi build/test_eval.wasm | ||
| $(CARGO_BIN)/wasmtime run build/test_eval.wasm "2+2" | ||
| $(CARGO_BIN)/wasmtime run build/test_eval.wasm "'hello' + ' world'" | ||
|
|
||
| build/test_eval.wasm: test_eval.c $(OBJS) | ||
| $(CC) $(CFLAGS) -DTEST_RUNNER $< \ | ||
| $(BUILD_DIR)/mquickjs.o $(BUILD_DIR)/cutils.o $(BUILD_DIR)/dtoa.o $(BUILD_DIR)/libm.o $(BUILD_DIR)/glue.o \ | ||
| $(GEN_DIR)/microquickjs.c \ | ||
| -lwasi-emulated-signal \ | ||
| -o $@ | ||
|
|
||
| clean: | ||
| rm -rf $(BUILD_DIR) $(GEN_DIR) env.wat env.wasm wasi_snapshot_preview1.reactor.wasm |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # MicroQuickJS WASI Component | ||
|
|
||
| This is a port of MicroQuickJS to a WASI 0.2 Component. | ||
|
|
||
| ## Build | ||
|
|
||
| Prerequisites: | ||
| - WASI SDK 25+ | ||
| - wit-bindgen | ||
| - wasm-tools | ||
|
|
||
| Run: | ||
| ```bash | ||
| make -f Makefile.wasi | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| The component exports an `eval` function: | ||
| ```wit | ||
| eval: func(code: string) -> result<string, string>; | ||
| ``` | ||
|
|
||
| ## Limitations | ||
|
|
||
| ### WasmEdge 0.14.1 Validator Bug | ||
|
|
||
| WasmEdge 0.14.1 contains a known issue where its Component Model validator incorrectly rejects spec-compliant components with error `0x50b (malformed name)`. | ||
| The generated component is fully spec-compliant and passes validation via `wasm-tools`. | ||
| - **Recommendation:** Use **Wasmtime** for execution, or downgrade WasmEdge to **0.13.5** until a fix is released in WasmEdge. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I've tested WasmEdge 0.13.5 using the installer. It reports 'Component model is not fully parsed yet!', which confirms it is too old to support the WASI 0.2 component model structure produced by the current toolchain. WasmEdge validation check: Using WasmEdge 0.17.0-alpha.1 with --enable-all, the loader fails with 'illegal opcode (0x117)' at offset 0x9b87 inside the core module. This indicates that while the component structure is valid, the current WasmEdge implementation still has gaps in supporting the Exception Handling instructions used by this component. Update specs
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. update documentation |
||
|
|
||
| ### Error Handling (setjmp/longjmp) | ||
|
|
||
| MicroQuickJS uses `setjmp`/`longjmp` for error handling during parsing (e.g., syntax errors). | ||
| Standard WASI 0.2 does not yet fully support these primitives without the WebAssembly Exception Handling proposal. | ||
|
|
||
| In this port, `longjmp` is stubbed to call `abort()`. | ||
| - **Valid JavaScript:** Executes normally and returns the result as a string. | ||
| - **Syntax Errors / Parser Errors:** Will cause the component to trap (`unreachable`). | ||
| - **Runtime Errors:** Correctly handled via `JS_GetException` and returned as an `Err` result. | ||
|
|
||
| ### Other WASI Limitations | ||
| - No filesystem access ( `load()` is disabled). | ||
| - No subprocesses (`system()`, `fork()` are not available). | ||
| - Timers (`setTimeout`) are not currently supported in the component export. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <stdio.h> | ||
| #include <time.h> | ||
| #include <sys/time.h> | ||
| #include "mquickjs.h" | ||
| #include "generated/microquickjs.h" | ||
|
|
||
| JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
| JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); | ||
|
|
||
| #include "mqjs_stdlib.h" | ||
|
|
||
| void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); | ||
|
|
||
| static void *make_wasi_string(const char *src, size_t len) { | ||
| if (!src) return NULL; | ||
| uint8_t *out = (uint8_t *)cabi_realloc(NULL, 0, 1, len); | ||
| if (!out) return NULL; | ||
| memcpy(out, src, len); | ||
| return out; | ||
| } | ||
|
|
||
| JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| int i; | ||
| JSValue v; | ||
| for(i = 0; i < argc; i++) { | ||
| if (i != 0) putchar(' '); | ||
| v = argv[i]; | ||
| if (JS_IsString(ctx, v)) { | ||
| JSCStringBuf buf; | ||
| const char *str; | ||
| size_t len; | ||
| str = JS_ToCStringLen(ctx, &len, v, &buf); | ||
| fwrite(str, 1, len, stdout); | ||
| } else { | ||
| JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); | ||
| } | ||
| } | ||
| putchar('\n'); | ||
| return JS_UNDEFINED; | ||
| } | ||
|
|
||
| JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| JS_GC(ctx); | ||
| return JS_UNDEFINED; | ||
| } | ||
|
|
||
| static int64_t get_time_ms(void) { | ||
| struct timespec ts; | ||
| clock_gettime(CLOCK_MONOTONIC, &ts); | ||
| return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); | ||
| } | ||
|
|
||
| JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| struct timeval tv; | ||
| gettimeofday(&tv, NULL); | ||
| return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000)); | ||
| } | ||
|
|
||
| JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| return JS_NewInt64(ctx, get_time_ms()); | ||
| } | ||
|
|
||
| JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| return JS_ThrowInternalError(ctx, "load() not supported in WASI"); | ||
| } | ||
|
|
||
| JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| return JS_ThrowInternalError(ctx, "setTimeout() not supported in WASI"); | ||
| } | ||
|
|
||
| JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { | ||
| return JS_ThrowInternalError(ctx, "clearTimeout() not supported in WASI"); | ||
| } | ||
|
|
||
| bool exports_microquickjs_eval(microquickjs_string_t *code, microquickjs_string_t *ok, microquickjs_string_t *err) { | ||
| size_t mem_size = 1024 * 1024; | ||
| uint8_t *mem_buf = malloc(mem_size); | ||
| if (!mem_buf) { | ||
| const char *msg = "Internal error: failed to allocate memory for JS context"; | ||
| err->len = strlen(msg); | ||
| err->ptr = (uint8_t *)make_wasi_string(msg, err->len); | ||
| return false; | ||
| } | ||
| JSContext *ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); | ||
| if (!ctx) { | ||
| free(mem_buf); | ||
| const char *msg = "Internal error: failed to create JS context"; | ||
| err->len = strlen(msg); | ||
| err->ptr = (uint8_t *)make_wasi_string(msg, err->len); | ||
| return false; | ||
| } | ||
| JSValue val = JS_Eval(ctx, (const char *)code->ptr, code->len, "<eval>", JS_EVAL_RETVAL); | ||
| if (JS_IsException(val)) { | ||
| JSValue exception = JS_GetException(ctx); | ||
| JSCStringBuf cstr_buf; | ||
| size_t len; | ||
| const char *exc_str = JS_ToCStringLen(ctx, &len, exception, &cstr_buf); | ||
| err->len = len; | ||
| err->ptr = (uint8_t *)make_wasi_string(exc_str, len); | ||
| JS_FreeContext(ctx); | ||
| free(mem_buf); | ||
| return false; | ||
| } else { | ||
| JSCStringBuf cstr_buf; | ||
| size_t len; | ||
| const char *res_str = JS_ToCStringLen(ctx, &len, val, &cstr_buf); | ||
| ok->len = len; | ||
| ok->ptr = (uint8_t *)make_wasi_string(res_str, len); | ||
| JS_FreeContext(ctx); | ||
| free(mem_buf); | ||
| return true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package local:microquickjs@0.1.0; | ||
|
|
||
| world microquickjs { | ||
| /// Evaluate JavaScript code and return result as string. | ||
| /// On error (syntax, runtime), returns Err(error-message). | ||
|
Comment on lines
+4
to
+5
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use JSdoc comments |
||
| export eval: func(code: string) -> result<string, string>; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate entry