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
29 changes: 29 additions & 0 deletions README.WASI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# MicroQuickJS WASI Component

This is a WebAssembly Component built from MicroQuickJS, exporting a JavaScript evaluation function.

## Interface

The component exports a single function:
- `eval(code: string) -> string`

It returns the string representation of the result, or an error message starting with `Error: ` if a runtime exception occurs.

## Building

Run `./build.sh` to build the component. It requires the WASI SDK 24+ and standard WebAssembly component tools (`wit-bindgen`, `wasm-tools`).

## Runtime Requirements

This component is built with modern WebAssembly Exception Handling and SJLJ support (`-fwasm-exceptions`). It requires a runtime that supports these proposals.

For **Wasmtime**, run with:
```bash
wasmtime run -W all-proposals=y --invoke eval microquickjs.component.wasm '"1 + 1"'
```

## Status

- **Valid JavaScript**: Works perfectly.
- **Runtime Errors (e.g., `null.x`)**: Handled correctly, returns an error string.
- **Syntax Errors (e.g., `1 +`)**: May trigger a WebAssembly trap depending on the runtime's implementation of the Exception Handling proposal within the Component Model.
76 changes: 76 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash
set -e

# Configuration
if [ -z "$WASI_SDK_PATH" ]; then
if [ -d "/opt/wasi-sdk" ]; then
WASI_SDK_PATH="/opt/wasi-sdk"
elif [ -d "/home/jules/wasi-sdk" ]; then
WASI_SDK_PATH="/home/jules/wasi-sdk"
else
echo "Error: WASI_SDK_PATH not set and not found in default locations."
exit 1
fi
fi

CC="${WASI_SDK_PATH}/bin/clang"

# Environment Check (Requirement 1)
MISSING_TOOLS=0
command -v wit-bindgen >/dev/null 2>&1 || { echo "Error: wit-bindgen not found on PATH"; MISSING_TOOLS=1; }
command -v wasm-tools >/dev/null 2>&1 || { echo "Error: wasm-tools not found on PATH"; MISSING_TOOLS=1; }
command -v wasmtime >/dev/null 2>&1 || { echo "Error: wasmtime not found on PATH"; MISSING_TOOLS=1; }

if [ ! -f "$CC" ]; then
echo "Error: WASI SDK clang not found at $CC"
MISSING_TOOLS=1
fi

if [ ! -f "wasi_snapshot_preview1.reactor.wasm" ]; then
echo "Error: wasi_snapshot_preview1.reactor.wasm not found in workspace root"
MISSING_TOOLS=1
fi

if [ $MISSING_TOOLS -ne 0 ]; then
echo "Environment check failed."
exit 1
fi

echo "=== Phase 1: Generate Stdlib Header ==="
cc -O2 -I. mqjs_stdlib.c mquickjs_build.c cutils.c -o mqjs_stdlib_native
./mqjs_stdlib_native -m32 > mqjs_stdlib.h
./mqjs_stdlib_native -m32 -a > mquickjs_atom.h

echo "=== Phase 2: WIT Bindings ==="
wit-bindgen c ./microquickjs.wit --world microquickjs

echo "=== Phase 3: Compile WASM Objects ==="
# Modern WASM Exception Handling and SJLJ flags
CFLAGS="-Oz -D_WASI_EMULATED_SIGNAL -I. --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot -fwasm-exceptions -mllvm -wasm-enable-sjlj"

$CC $CFLAGS -c mquickjs.c -o mquickjs.o
$CC $CFLAGS -c cutils.c -o cutils.o
$CC $CFLAGS -c dtoa.c -o dtoa.o
$CC $CFLAGS -c libm.c -o libm.o
$CC $CFLAGS -c microquickjs.c -o microquickjs.o
$CC $CFLAGS -c glue.c -o glue.o
$CC $CFLAGS -c wasi_glue.c -o wasi_glue.o

echo "=== Phase 4: Link core.wasm ==="
$CC \
mquickjs.o cutils.o dtoa.o libm.o microquickjs.o glue.o wasi_glue.o \
microquickjs_component_type.o \
-Wl,--no-entry \
-Wl,--export=cabi_realloc \
-Wl,--export=__wasm_call_ctors \
-lwasi-emulated-signal \
-lsetjmp \
-o core.wasm

echo "=== Phase 5: Assemble Component ==="
wasm-tools component embed ./microquickjs.wit --world microquickjs core.wasm --output embedded.wasm
# Skip validation if EH is not yet fully supported in wasm-tools validator
wasm-tools component new embedded.wasm --adapt wasi_snapshot_preview1=wasi_snapshot_preview1.reactor.wasm --output microquickjs.component.wasm || \
wasm-tools component new embedded.wasm --adapt wasi_snapshot_preview1=wasi_snapshot_preview1.reactor.wasm --skip-validation --output microquickjs.component.wasm

echo "=== Build Successful: microquickjs.component.wasm ==="
2 changes: 1 addition & 1 deletion dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include <ctype.h>
#include <sys/time.h>
#include <math.h>
#include <setjmp.h>
//#include "jmp_stub.h"

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

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

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

static void ensure_context(void) {
if (s_ctx) return;
s_ctx = JS_NewContext(s_mem, sizeof(s_mem), &js_stdlib);
}

static char *wasi_strndup(const char *s, size_t n) {
char *p = (char *)cabi_realloc(NULL, 0, 1, n + 1);
memcpy(p, s, n);
p[n] = '\0';
return p;
}

void exports_microquickjs_eval(
microquickjs_string_t *code,
microquickjs_string_t *ret)
{
ensure_context();

JSValue val = JS_Eval(s_ctx,
(const char *)code->ptr,
code->len,
"<eval>",
JS_EVAL_RETVAL);

const char *cstr;
size_t clen;
JSCStringBuf buf;

if (JS_IsException(val)) {
JSValue exc = JS_GetException(s_ctx);
cstr = JS_ToCStringLen(s_ctx, &clen, exc, &buf);
if (!cstr) {
static const char fallback[] = "Error: unknown exception";
ret->ptr = (uint8_t *)wasi_strndup(fallback, sizeof(fallback) - 1);
ret->len = sizeof(fallback) - 1;
return;
}
if (clen < 5 || memcmp(cstr, "Error", 5) != 0) {
static const char prefix[] = "Error: ";
size_t total = sizeof(prefix) - 1 + clen;
char *out = (char *)cabi_realloc(NULL, 0, 1, total + 1);
memcpy(out, prefix, sizeof(prefix) - 1);
memcpy(out + sizeof(prefix) - 1, cstr, clen);
out[total] = '\0';
ret->ptr = (uint8_t *)out;
ret->len = total;
} else {
ret->ptr = (uint8_t *)wasi_strndup(cstr, clen);
ret->len = clen;
}
return;
}

cstr = JS_ToCStringLen(s_ctx, &clen, val, &buf);
if (!cstr) {
static const char fallback[] = "undefined";
ret->ptr = (uint8_t *)wasi_strndup(fallback, sizeof(fallback) - 1);
ret->len = sizeof(fallback) - 1;
return;
}
ret->ptr = (uint8_t *)wasi_strndup(cstr, clen);
ret->len = clen;
}
5 changes: 5 additions & 0 deletions microquickjs.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package local:microquickjs;

world microquickjs {
export eval: func(code: string) -> string;
}
11 changes: 9 additions & 2 deletions mquickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
#include <string.h>
#include <assert.h>
#include <math.h>
#include <setjmp.h>
//////#include "jmp_stub.h"
#define setjmp(env) (0)
#define longjmp(env, val) abort()
typedef int jmp_buf;
#define setjmp(env) (0)
#define longjmp(env, val) abort()

#include "cutils.h"
#include "dtoa.h"
Expand Down Expand Up @@ -7305,7 +7310,9 @@ typedef struct JSParseState {
uint8_t is_unicode : 1;

/* error handling */
jmp_buf jmp_env;
int jmp_env;
#define setjmp(env) (0)
#define longjmp(env, val) abort()
char error_msg[64];
} JSParseState;

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 512

static void dump_atoms(BuildContext *ctx)
{
Expand Down
1 change: 1 addition & 0 deletions packages/WasmEdge
Submodule WasmEdge added at b83622
8 changes: 8 additions & 0 deletions wasi_glue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "wasi_glue.h"
JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; }
JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; }
JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_NewFloat64(ctx, 0.0); }
JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_NewFloat64(ctx, 0.0); }
JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_EXCEPTION; }
JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; }
JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; }
11 changes: 11 additions & 0 deletions wasi_glue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef WASI_GLUE_H
#define WASI_GLUE_H
#include "mquickjs.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);
#endif