Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "packages/WasmEdge"]
path = packages/WasmEdge
url = https://github.com/WasmEdge/WasmEdge.git
42 changes: 42 additions & 0 deletions Makefile.wasi
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
WASI_SDK_PATH ?= /home/jules/tools/wasi-sdk
CC = $(WASI_SDK_PATH)/bin/clang
CFLAGS = -Oz -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_PROCESS_CLOCKS -I. --sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot
LDFLAGS = -Wl,--no-entry \
-Wl,--export=__wasm_export_exports_microquickjs_eval \
-Wl,--export=cabi_realloc \
-Wl,--export=__wasm_call_ctors \
-lwasi-emulated-signal -lwasi-emulated-process-clocks

ADAPTER ?= /home/jules/tools/bin/wasi_snapshot_preview1.reactor.wasm

OBJS = mquickjs.o cutils.o dtoa.o libm.o microquickjs.o glue.o mqjs_stdlib_wasm.o

all: microquickjs.component.wasm

microquickjs.component.wasm: embedded.wasm $(ADAPTER)
wasm-tools component new embedded.wasm \
--adapt wasi_snapshot_preview1=$(ADAPTER) \
--output microquickjs.component.wasm

embedded.wasm: core.wasm wit/microquickjs.wit
wasm-tools component embed ./wit --world microquickjs core.wasm --output embedded.wasm

core.wasm: $(OBJS) microquickjs_component_type.o
$(CC) $(OBJS) microquickjs_component_type.o $(LDFLAGS) -o core.wasm

microquickjs.c microquickjs.h microquickjs_component_type.o: wit/microquickjs.wit
wit-bindgen c ./wit --world microquickjs --out-dir .

mquickjs_atom.h mqjs_stdlib.h: mquickjs_build_native mqjs_stdlib.c
./mquickjs_build_native -m32 > mqjs_stdlib.h
./mquickjs_build_native -m32 -a > mquickjs_atom.h

mquickjs_build_native: mquickjs_build.c mqjs_stdlib.c cutils.c
gcc -O2 -I. mquickjs_build.c mqjs_stdlib.c cutils.c -lm -o mquickjs_build_native

%.o: %.c mquickjs_atom.h mqjs_stdlib.h microquickjs.h wasi_sjlj.h
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -f $(OBJS) microquickjs_component_type.o core.wasm embedded.wasm microquickjs.component.wasm
rm -f microquickjs.c microquickjs.h mquickjs_build_native mquickjs_atom.h mqjs_stdlib.h wasi_sjlj.h
31 changes: 31 additions & 0 deletions build/BUILD_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build Summary: MicroQuickJS WASI 0.2 Component

## Environment
- **WASI SDK**: 25.0 (llvm 19.1.5)
- **Wasmtime**: 43.0.0 (Verified - Reference Implementation)
- **WasmEdge**: 0.14.1, 0.17.0-alpha.1 (Tested)
- **wit-bindgen**: 0.55.0

## Verification Status (Wasmtime v43)
- **Arithmetic (1+1)**: ✅ PASS
- **Large Strings (100KB)**: ✅ PASS
- **WASI 0.2 stdout**: ✅ PASS
- **WASI 0.2 wall-clock**: ✅ PASS
- **Error Handling**: ✅ PASS (JavaScript exceptions are caught and returned as `err(string)`)

## Runtime Compatibility
- **Wasmtime v43**: ✅ Full support for WASI 0.2 and Component Model.
- **WasmEdge v0.14.1**: ❌ Fails with `0x50b` (malformed name) during validation.
- **WasmEdge v0.17.0-alpha.1**: ❌ Fails with `instantiation failed: unknown import (wasi:cli/environment@0.2.3)`. Component model support is still experimental in WasmEdge.

## Test Commands
```bash
# Arithmetic
wasmtime run --invoke 'eval("1+1")' microquickjs.component.wasm

# 100KB String
wasmtime run --invoke 'eval("\"A\".repeat(102400)")' microquickjs.component.wasm

# stdout
wasmtime run --invoke 'eval("print(\"hello\"); \"done\"")' microquickjs.component.wasm
```
2 changes: 2 additions & 0 deletions dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
#include <ctype.h>
#include <sys/time.h>
#include <math.h>
#ifndef __wasi__
#include <setjmp.h>
#endif

#include "cutils.h"
#include "dtoa.h"
Expand Down
109 changes: 109 additions & 0 deletions glue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "microquickjs.h"
#include "mquickjs.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size);

static wasi_cli_stdout_own_output_stream_t s_stdout_stream = {0};
static bool s_stdout_initialized = false;

JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
if (!s_stdout_initialized) {
s_stdout_stream = wasi_cli_stdout_get_stdout();
s_stdout_initialized = true;
}
wasi_io_streams_borrow_output_stream_t stream = wasi_io_streams_borrow_output_stream(s_stdout_stream);
for(int i = 0; i < argc; i++) {
if (i != 0) {
uint8_t space = ' ';
microquickjs_list_u8_t list = { &space, 1 };
wasi_io_streams_stream_error_t err;
wasi_io_streams_method_output_stream_blocking_write_and_flush(stream, &list, &err);
}
JSCStringBuf sbuf;
size_t len;
const char *str = JS_ToCStringLen(ctx, &len, argv[i], &sbuf);
if (str) {
microquickjs_list_u8_t list = { (uint8_t *)str, len };
wasi_io_streams_stream_error_t err;
wasi_io_streams_method_output_stream_blocking_write_and_flush(stream, &list, &err);
}
}
uint8_t newline = '\n';
microquickjs_list_u8_t list = { &newline, 1 };
wasi_io_streams_stream_error_t err;
wasi_io_streams_method_output_stream_blocking_write_and_flush(stream, &list, &err);
return JS_UNDEFINED;
}

JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
wasi_clocks_wall_clock_datetime_t dt;
wasi_clocks_wall_clock_now(&dt);
return JS_NewInt64(ctx, (int64_t)dt.seconds * 1000 + (dt.nanoseconds / 1000000));
}

JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
return js_date_now(ctx, this_val, argc, argv);
}

JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
JS_GC(ctx);
return JS_UNDEFINED;
}

JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
return JS_ThrowInternalError(ctx, "load() not supported in component");
}

JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
return JS_ThrowInternalError(ctx, "setTimeout() not supported in component");
}

JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {
return JS_UNDEFINED;
}

static JSContext *s_ctx = NULL;
static uint8_t s_mem_buf[4 * 1024 * 1024];

static void ensure_runtime(void) {
if (s_ctx) return;
extern const JSSTDLibraryDef js_stdlib;
s_ctx = JS_NewContext(s_mem_buf, sizeof(s_mem_buf), &js_stdlib);
}

static char *make_wasi_string(const char *src, size_t len) {
if (!src) return NULL;
char *out = (char *)cabi_realloc(NULL, 0, 1, len);
if (!out) return NULL;
memcpy(out, src, len);
return out;
}

bool exports_microquickjs_eval(microquickjs_string_t *code, microquickjs_string_t *ret, microquickjs_string_t *err) {
ensure_runtime();
char *src = malloc(code->len + 1);
memcpy(src, code->ptr, code->len);
src[code->len] = '\0';
JSValue val = JS_Eval(s_ctx, src, code->len, "<eval>", JS_EVAL_RETVAL);
free(src);
if (JS_IsException(val)) {
JSValue exc = JS_GetException(s_ctx);
const char *result_cstr;
JSCStringBuf sbuf;
size_t len;
result_cstr = JS_ToCStringLen(s_ctx, &len, exc, &sbuf);
err->ptr = (uint8_t *)make_wasi_string(result_cstr ? result_cstr : "Unknown error", result_cstr ? len : 13);
err->len = result_cstr ? len : 13;
return false;
}
const char *result_cstr;
JSCStringBuf sbuf;
size_t len;
result_cstr = JS_ToCStringLen(s_ctx, &len, val, &sbuf);
ret->ptr = (uint8_t *)make_wasi_string(result_cstr ? result_cstr : "undefined", result_cstr ? len : 9);
ret->len = result_cstr ? len : 9;
return true;
}
119 changes: 119 additions & 0 deletions mqjs_stdlib_wasm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "mquickjs.h"

extern const JSSTDLibraryDef js_stdlib;

JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_create(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_keys(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_object_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_call(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_apply(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_bind(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_function_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_number_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_get_length(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_set_length(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_charAt(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_slice(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_substring(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_concat(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_match(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_replace(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_search(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_split(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_trim(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_repeat(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_string_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_concat(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_get_length(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_set_length(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_push(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_array_pop(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_join(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_reverse(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_shift(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_slice(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_splice(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_every(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_array_reduce(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_array_sort(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_isArray(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_error_toString(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_error_get_message(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_error_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_math_min_max(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
double js_math_sign(double f);
double js_fabs(double f);
double js_floor(double f);
double js_ceil(double f);
double js_round_inf(double f);
double js_sqrt(double f);
double js_sin(double f);
double js_cos(double f);
double js_tan(double f);
double js_asin(double f);
double js_acos(double f);
double js_atan(double f);
JSValue js_math_atan2(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
double js_exp(double f);
double js_log(double f);
JSValue js_math_pow(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_math_random(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_math_imul(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_math_clz32(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
double js_math_fround(double f);
double js_trunc(double f);
double js_log2(double f);
double js_log10(double f);
JSValue js_json_parse(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_json_stringify(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic);
JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_date_constructor(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_global_eval(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);
JSValue js_function_bound(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params);

/* defined in glue.c */
JSValue js_print(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_gc(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"
8 changes: 8 additions & 0 deletions mquickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@
#include <string.h>
#include <assert.h>
#include <math.h>
#ifndef __wasi__
#include <setjmp.h>
#else
#include <stdlib.h>
typedef struct { int dummy; } jmp_buf_st;
typedef jmp_buf_st jmp_buf[1];
static inline int setjmp(jmp_buf env) { return 0; }
static inline void longjmp(jmp_buf env, int val) { abort(); }
#endif

#include "cutils.h"
#include "dtoa.h"
Expand Down
2 changes: 1 addition & 1 deletion mquickjs_build.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ static int atom_cmp(const void *p1, const void *p2)

/* js_atom_table must be properly aligned because the property hash
table uses the low bits of the atom pointer value */
#define ATOM_ALIGN 64
#define ATOM_ALIGN 256

static void dump_atoms(BuildContext *ctx)
{
Expand Down
1 change: 1 addition & 0 deletions packages/WasmEdge
Submodule WasmEdge added at a96f35
8 changes: 8 additions & 0 deletions wasi_sjlj.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef WASI_SJLJ_H
#define WASI_SJLJ_H
#include <stdlib.h>
typedef struct { int dummy; } wasi_jmp_buf_st;
typedef wasi_jmp_buf_st jmp_buf[1];
#define setjmp(env) (0)
#define longjmp(env, val) abort()
#endif
5 changes: 5 additions & 0 deletions wit/deps/cli/stdout.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package wasi:cli@0.2.0;
interface stdout {
use wasi:io/streams@0.2.0.{output-stream};
get-stdout: func() -> output-stream;
}
8 changes: 8 additions & 0 deletions wit/deps/clocks/wall-clock.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package wasi:clocks@0.2.0;
interface wall-clock {
record datetime {
seconds: u64,
nanoseconds: u32,
}
now: func() -> datetime;
}
4 changes: 4 additions & 0 deletions wit/deps/io/error.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package wasi:io@0.2.0;
interface error {
resource error;
}
14 changes: 14 additions & 0 deletions wit/deps/io/streams.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package wasi:io@0.2.0;
interface streams {
resource output-stream {
check-write: func() -> result<u64, stream-error>;
write: func(contents: list<u8>) -> result<_, stream-error>;
blocking-write-and-flush: func(contents: list<u8>) -> result<_, stream-error>;
blocking-flush: func() -> result<_, stream-error>;
}
variant stream-error {
last-operation-failed(error),
closed,
}
resource error;
}
9 changes: 9 additions & 0 deletions wit/microquickjs.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package local:microquickjs;

world microquickjs {
import wasi:io/streams@0.2.0;
import wasi:cli/stdout@0.2.0;
import wasi:clocks/wall-clock@0.2.0;

export eval: func(code: string) -> result<string, string>;
}