feat: add a histrical wit-bindgen
This commit is contained in:
7
__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/.gitignore
vendored
Normal file
7
__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
spidermonkey-*
|
||||
lib/*
|
||||
*.o
|
||||
spidermonkey.initial.wasm
|
||||
wasi-sdk-12.0
|
||||
mozbuild
|
||||
.exports
|
||||
@@ -0,0 +1,142 @@
|
||||
SM_REPO := https://github.com/fitzgen/gecko-dev
|
||||
SM_COMMIT := dafd3165f45c55023ece4787a86444029e4f475e
|
||||
|
||||
# TODO: support building `spidermonkey.wasm` on other OSes. But for some reason
|
||||
# the resulting `.wasm` binary is slower when the host compiler is on macOS.
|
||||
WASI_SDK_URL := https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz
|
||||
|
||||
CC := $(CURDIR)/wasi-sdk-12.0/bin/clang
|
||||
CXX := $(CURDIR)/wasi-sdk-12.0/bin/clang++
|
||||
|
||||
# Set this to `1` to enable logging via all the `SMW_LOG(...)` calls.
|
||||
LOGGING := 0
|
||||
|
||||
# Set this to `-DDEBUG` and uncomment the `--enable-debug` line in `mozconfig`
|
||||
# to enable debug builds of SpiderMonkey.
|
||||
DEBUG := ""
|
||||
|
||||
# Set this to `""` in debug mode for better debugging.
|
||||
OPT := "-O2"
|
||||
|
||||
CFLAGS := \
|
||||
--sysroot=$(CURDIR)/wasi-sdk-12.0/share/wasi-sysroot \
|
||||
-Wall \
|
||||
--target=wasm32-unknown-wasi \
|
||||
-Ispidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/dist/include \
|
||||
-I$(CURDIR)/include \
|
||||
$(DEBUG) \
|
||||
$(OPT) \
|
||||
-DLOGGING=$(LOGGING)
|
||||
|
||||
CXXFLAGS := \
|
||||
$(CFLAGS) \
|
||||
-fno-exceptions \
|
||||
-std=c++17
|
||||
|
||||
# Local object files.
|
||||
LOCAL_OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp))
|
||||
|
||||
# Object files needed within SpiderMonkey's obj dir.
|
||||
SM_OBJECTS := \
|
||||
js/src/build/libjs_static.a \
|
||||
memory/build/Unified_cpp_memory_build0.o \
|
||||
memory/mozalloc/mozalloc_abort.o \
|
||||
memory/mozalloc/Unified_cpp_memory_mozalloc0.o \
|
||||
mfbt/Unified_cpp_mfbt0.o \
|
||||
mfbt/Unified_cpp_mfbt1.o \
|
||||
mfbt/lz4.o \
|
||||
mfbt/lz4frame.o \
|
||||
mfbt/lz4hc.o \
|
||||
mfbt/xxhash.o \
|
||||
mozglue/misc/AutoProfilerLabel.o \
|
||||
mozglue/misc/ConditionVariable_noop.o \
|
||||
mozglue/misc/Decimal.o \
|
||||
mozglue/misc/MmapFaultHandler.o \
|
||||
mozglue/misc/Mutex_noop.o \
|
||||
mozglue/misc/Printf.o \
|
||||
mozglue/misc/StackWalk.o \
|
||||
mozglue/misc/TimeStamp.o \
|
||||
mozglue/misc/TimeStamp_posix.o \
|
||||
mozglue/misc/Uptime.o \
|
||||
modules/zlib/src/compress.o \
|
||||
modules/zlib/src/gzclose.o \
|
||||
modules/zlib/src/infback.o \
|
||||
modules/zlib/src/uncompr.o \
|
||||
wasm32-wasi/release/libjsrust.a
|
||||
|
||||
# The `./lib/*` copies of SpiderMonkey's object files that we check into the
|
||||
# repo.
|
||||
SM_LIB_OBJECTS := $(shell echo $(SM_OBJECTS) | xargs -d' ' -I{} basename {} | xargs -I{} echo lib/{})
|
||||
|
||||
.PHONY: all clean clean-all clean-spidermonkey clean-wasi-sdk
|
||||
|
||||
all: spidermonkey.wasm
|
||||
@echo "Done!"
|
||||
|
||||
spidermonkey.initial.wasm: $(SM_LIB_OBJECTS) $(LOCAL_OBJECTS)
|
||||
$(CXX) $(CXXFLAGS) \
|
||||
-mexec-model=reactor \
|
||||
$(LOCAL_OBJECTS) \
|
||||
$(SM_LIB_OBJECTS) \
|
||||
-o spidermonkey.initial.wasm \
|
||||
-Wl,--export-dynamic \
|
||||
-Wl,--growable-table \
|
||||
-Wl,--export-table \
|
||||
-Wl,--gc-sections
|
||||
|
||||
spidermonkey.wasm: spidermonkey.initial.wasm
|
||||
# Uncomment this `wasm-opt` invocation and comment the following one out to
|
||||
# enable better debugging.
|
||||
#
|
||||
# wasm-opt -g --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm
|
||||
wasm-opt -O2 --strip-dwarf --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm
|
||||
|
||||
|
||||
# Build all `*.cpp` files into `*.o` files.
|
||||
%.o: %.cpp $(SM_LIB_OBJECTS)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
# Actually build SpiderMonkey.
|
||||
$(SM_LIB_OBJECTS): spidermonkey-$(SM_COMMIT) mozbuild wasi-sdk-12.0 mozconfig
|
||||
cd spidermonkey-$(SM_COMMIT) \
|
||||
&& MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild MOZCONFIG=$(CURDIR)/mozconfig ./mach build
|
||||
mkdir -p lib
|
||||
for x in $(SM_OBJECTS); do \
|
||||
cp spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/$$x lib/; \
|
||||
done
|
||||
|
||||
# Clone `mozilla-central` at the `SM_COMMIT` commit.
|
||||
spidermonkey-$(SM_COMMIT):
|
||||
-rm -rf spidermonkey-temp
|
||||
mkdir spidermonkey-temp
|
||||
cd spidermonkey-temp \
|
||||
&& git init \
|
||||
&& git remote add origin $(SM_REPO) \
|
||||
&& git fetch origin $(SM_COMMIT) \
|
||||
&& git checkout $(SM_COMMIT)
|
||||
mv spidermonkey-temp spidermonkey-$(SM_COMMIT)
|
||||
|
||||
mozbuild: spidermonkey-$(SM_COMMIT)
|
||||
-mkdir mozbuild
|
||||
cd spidermonkey-$(SM_COMMIT) \
|
||||
&& MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild ./mach bootstrap --application-choice js --no-system-changes \
|
||||
|| rm -rf $(CURDIR)/mozbuild
|
||||
|
||||
wasi-sdk-12.0:
|
||||
curl -L $(WASI_SDK_URL) | tar -x -z
|
||||
|
||||
clean-all: clean clean-spidermonkey clean-wasi-sdk
|
||||
|
||||
clean-wasi-sdk:
|
||||
-rm -rf wasi-sdk-12.0
|
||||
|
||||
clean-spidermonkey:
|
||||
-rm -rf spidermonkey-$(SM_COMMIT)
|
||||
-rm -rf spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/
|
||||
-rm -rf mozbuild
|
||||
|
||||
clean:
|
||||
@echo 'Only cleaning our own artifacts, not upstream deps. Run `make clean-{all,spidermonkey,wasi-sdk}` to clean others.'
|
||||
-rm -rf spidermonkey-temp
|
||||
-rm -rf ./*.o
|
||||
-rm -rf spidermonkey.wasm
|
||||
@@ -0,0 +1,13 @@
|
||||
# `spidermonkey.wasm`
|
||||
|
||||
This directory contains the source code for `spidermonkey.wasm`, which is an
|
||||
embedding of the SpiderMonkey JavaScript engine for targeting `wasm32-wasi` and
|
||||
use with `wit-bindgen-gen-spidermonkey`. It exports a variety of helper
|
||||
functions that are used by `wit-bindgen-gen-spidermonkey`'s generated glue
|
||||
code. These helpers are typically named something like `SMW_whatever_function`.
|
||||
|
||||
## Building `spidermonkey.wasm`
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Winvalid-offsetof"
|
||||
#include "js/Exception.h"
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#include "smw/abort.h"
|
||||
#include "smw/cx.h"
|
||||
#include "smw/dump.h"
|
||||
|
||||
namespace smw {
|
||||
|
||||
void abort(const char* msg) {
|
||||
abort(get_js_context(), msg);
|
||||
}
|
||||
|
||||
void abort(JSContext *cx, const char* msg) {
|
||||
fprintf(stderr, "Error: %s", msg);
|
||||
|
||||
if (JS_IsExceptionPending(cx)) {
|
||||
fprintf(stderr, ":");
|
||||
JS::ExceptionStack exception(cx);
|
||||
if (!JS::GetPendingExceptionStack(cx, &exception)) {
|
||||
fprintf(stderr, " failed to get pending exception value and stack\n");
|
||||
} else {
|
||||
fprintf(stderr, "\n exception value: ");
|
||||
if (!dump_value(cx, exception.exception(), stderr)) {
|
||||
fprintf(stderr, "<failed to dump value>");
|
||||
}
|
||||
fprintf(stderr, "\n exception stack:\n");
|
||||
if (!dump_stack(cx, exception.stack(), stderr)) {
|
||||
fprintf(stderr, "<failed to dump stack>\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
// TODO: check for unhandled promise rejections.
|
||||
|
||||
fflush(stderr);
|
||||
::abort();
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,452 @@
|
||||
/*!
|
||||
* This module implements the intrinsics used by code emitted in the
|
||||
* `wit_bindgen_gen_spidermonkey::Bindgen` trait implementation.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "smw/abort.h"
|
||||
#include "smw/cx.h"
|
||||
#include "smw/logging.h"
|
||||
#include "smw/wasm.h"
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "jsapi.h"
|
||||
#include "js/Array.h"
|
||||
#include "js/Conversions.h"
|
||||
#include "js/ForOfIterator.h"
|
||||
#include "js/Modules.h"
|
||||
|
||||
#ifdef LOGGING
|
||||
#include "js/friend/DumpFunctions.h"
|
||||
#endif
|
||||
|
||||
namespace smw {
|
||||
|
||||
using UniqueChars = mozilla::UniquePtr<char[]>;
|
||||
|
||||
using PersistentRootedValueVector = JS::PersistentRooted<JS::GCVector<JS::Value>>;
|
||||
|
||||
// Used for general Wasm<-->JS conversions.
|
||||
static PersistentRootedValueVector* OPERANDS;
|
||||
|
||||
// Used for holding arguments to JS calls.
|
||||
static PersistentRootedValueVector* ARGS;
|
||||
|
||||
// Used for holding returns from Wasm calls.
|
||||
static PersistentRootedValueVector* RETS;
|
||||
|
||||
void init_operands(JSContext* cx) {
|
||||
assert(!OPERANDS && "OPERANDS must only be initialized once");
|
||||
OPERANDS = new PersistentRootedValueVector(cx, cx);
|
||||
if (!OPERANDS) {
|
||||
abort(cx, "failed to allocate OPERANDS");
|
||||
}
|
||||
|
||||
assert(!ARGS && "ARGS must only be initialized once");
|
||||
ARGS = new PersistentRootedValueVector(cx, cx);
|
||||
if (!ARGS) {
|
||||
abort(cx, "failed to allocate ARGS");
|
||||
}
|
||||
|
||||
assert(!RETS && "RETS must only be initialized once");
|
||||
RETS = new PersistentRootedValueVector(cx, cx);
|
||||
if (!RETS) {
|
||||
abort(cx, "failed to allocate RETS");
|
||||
}
|
||||
}
|
||||
|
||||
PersistentRootedValueVector& operands() {
|
||||
assert(OPERANDS && OPERANDS->initialized() && "OPERANDS must be initialized");
|
||||
return *OPERANDS;
|
||||
}
|
||||
|
||||
void save_operand(size_t dest, JS::HandleValue val) {
|
||||
#if LOGGING==1
|
||||
SMW_LOG("operands[%zu] = ", dest);
|
||||
js::DumpValue(val, stderr);
|
||||
#endif // LOGGING==1
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
|
||||
if (operands().length() <= dest) {
|
||||
size_t needed_capacity = 1 + dest - operands().length();
|
||||
if (!operands().reserve(needed_capacity)) {
|
||||
abort("failed to reserve capacity for the OPERANDS vector");
|
||||
}
|
||||
if (dest == operands().length()) {
|
||||
bool ok = operands().append(val);
|
||||
assert(ok && "already reserved space");
|
||||
return;
|
||||
}
|
||||
JS::RootedValue placeholder(cx, JS::UndefinedValue());
|
||||
for (size_t i = 0; i < needed_capacity; i++) {
|
||||
bool ok = operands().append(placeholder);
|
||||
assert(ok && "already reserved space");
|
||||
}
|
||||
}
|
||||
|
||||
operands()[dest].set(val);
|
||||
}
|
||||
|
||||
PersistentRootedValueVector& args() {
|
||||
assert(ARGS && ARGS->initialized() && "ARGS must be initialized");
|
||||
return *ARGS;
|
||||
}
|
||||
|
||||
PersistentRootedValueVector& rets() {
|
||||
assert(RETS && RETS->initialized() && "RETS must be initialized");
|
||||
return *RETS;
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void canonical_abi_free(void* ptr, size_t size, size_t align) {
|
||||
(void) size;
|
||||
(void) align;
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void* canonical_abi_realloc(void* ptr, size_t old_size, size_t align, size_t new_size) {
|
||||
(void) old_size;
|
||||
(void) align;
|
||||
return realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_fill_operands(unsigned argc, JS::Value* vp) {
|
||||
SMW_LOG("SMW_fill_operands(argc = %d, vp = %p)\n", argc, vp);
|
||||
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
|
||||
if (!operands().reserve(size_t(args.length()))) {
|
||||
abort(get_js_context(), "failed to reserve space in the operands vector");
|
||||
}
|
||||
for (unsigned i = 0; i < args.length(); i++) {
|
||||
#if LOGGING==1
|
||||
SMW_LOG("operands[%d] = ", i);
|
||||
js::DumpValue(args.get(i), stderr);
|
||||
#endif // LOGGING==1
|
||||
|
||||
bool ok = operands().append(args.get(i));
|
||||
assert(ok && "already reserved space");
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_clear_operands() {
|
||||
SMW_LOG("SMW_clear_operands\n");
|
||||
operands().clear();
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_push_arg(size_t i) {
|
||||
SMW_LOG("SMW_push_arg(i = %zu)\n", i);
|
||||
if (!args().append(operands()[i])) {
|
||||
abort("failed to push arg");
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_call(char *funcName, size_t funcNameLen, size_t numResults, size_t dest) {
|
||||
#ifdef LOGGING
|
||||
SMW_LOG("SMW_call(funcName = %p \"", funcName);
|
||||
for (size_t i = 0; i < funcNameLen; i++) {
|
||||
SMW_LOG("%c", funcName[i]);
|
||||
}
|
||||
SMW_LOG("\", funcNameLen = %zu, numResults = %zu, dest = %zu)\n",
|
||||
funcNameLen,
|
||||
numResults,
|
||||
dest);
|
||||
#endif
|
||||
|
||||
UniqueChars uniqFuncName(funcName);
|
||||
|
||||
JSContext *cx = get_js_context();
|
||||
|
||||
JS::RootedString funcNameAtom(cx, JS_AtomizeStringN(cx, uniqFuncName.get(), funcNameLen));
|
||||
if (!funcNameAtom) {
|
||||
abort(cx, "failed to atomize function name");
|
||||
}
|
||||
|
||||
JS::RootedObject module(cx, get_user_module());
|
||||
JS::RootedValue exportVal(cx);
|
||||
bool hasExport = false;
|
||||
if (!JS::GetModuleExport(cx, module, funcNameAtom, &exportVal, &hasExport)) {
|
||||
abort(cx, "failed to get module export");
|
||||
}
|
||||
if (!hasExport) {
|
||||
// TODO: include the export name in this message to help users debug
|
||||
// which export they're missing.
|
||||
abort(cx, "user module does not have the requested export");
|
||||
}
|
||||
|
||||
JS::RootedFunction exportFunc(cx, JS_ValueToFunction(cx, exportVal));
|
||||
if (!exportFunc) {
|
||||
// TODO: include the export name in this message.
|
||||
abort(cx, "exported value is not a function");
|
||||
}
|
||||
|
||||
// XXX: we have to copy ARGS into a `JS::RootedVector<JS::Value>` because
|
||||
// `JS::Call` takes a `JS::HandleValueArray` and you can't construct that
|
||||
// from a `JS::PersistentRooted<JS::GCVector<JS::Value>>`, only a
|
||||
// `JS::RootedVector<JS::Value>`. And we can't make `ARGS` a
|
||||
// `JS::RootedVector<JS::Value>` because it is a global, not an on-stack
|
||||
// RAII value as required by `JS::RootedVector<JS::Value>`. Gross!
|
||||
JS::RootedVector<JS::Value> argsVector(cx);
|
||||
if (!argsVector.reserve(args().length())) {
|
||||
abort(cx, "failed to reserve space for arguments vector");
|
||||
}
|
||||
for (size_t i = 0; i < args().length(); i++) {
|
||||
bool ok = argsVector.append(args()[i]);
|
||||
assert(ok && "already reserved space");
|
||||
}
|
||||
|
||||
JS::RootedObject thisObj(cx);
|
||||
JS::RootedValue result(cx);
|
||||
if (!JS::Call(cx, thisObj, exportFunc, argsVector, &result)) {
|
||||
// TODO: include the export name in this message.
|
||||
abort(cx, "calling export function failed");
|
||||
}
|
||||
|
||||
args().clear();
|
||||
|
||||
if (numResults == 0) {
|
||||
// Nothing to push onto the operands vector.
|
||||
} else if (numResults == 1) {
|
||||
save_operand(dest, result);
|
||||
} else {
|
||||
// Treat the "physical" return value as an iterator and unpack the
|
||||
// "logical" return values from within it. This allows JS to return
|
||||
// multiple WIT values as an array or any other iterable.
|
||||
JS::ForOfIterator iter(cx);
|
||||
if (!iter.init(result)) {
|
||||
// TODO: include the export name in this message.
|
||||
abort(cx, "failed to convert return value to iterable");
|
||||
}
|
||||
JS::RootedValue val(cx);
|
||||
bool done = false;
|
||||
for (size_t i = 0; i < numResults; i++) {
|
||||
if (done) {
|
||||
// TODO: include the export name in this message.
|
||||
abort(cx, "function's returned iterator did not yield enough return values");
|
||||
}
|
||||
if (!iter.next(&val, &done)) {
|
||||
// TODO: include the export name in this message.
|
||||
abort(cx, "failed to get the next value out of the return values iterator");
|
||||
}
|
||||
save_operand(dest + i, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_push_return_value(size_t i) {
|
||||
SMW_LOG("SMW_push_return_value(i = %zu)\n", i);
|
||||
if (!rets().append(operands()[i])) {
|
||||
abort(get_js_context(), "failed to push arg");
|
||||
}
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_finish_returns(unsigned argc, JS::Value* vp) {
|
||||
SMW_LOG("SMW_finish_returns(argc = %d, vp = %p)\n", argc, vp);
|
||||
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
switch (rets().length()) {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
args.rval().set(rets().back());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedVector<JS::Value> elems(cx);
|
||||
if (!elems.reserve(rets().length())) {
|
||||
abort(cx, "failed to reserve space for results vector");
|
||||
}
|
||||
for (size_t i = 0; i < rets().length(); i++) {
|
||||
bool ok = elems.append(rets()[i]);
|
||||
assert(ok && "already reserved space");
|
||||
}
|
||||
JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems));
|
||||
if (!arr) {
|
||||
abort(cx, "failed to allocate array for function's return values");
|
||||
}
|
||||
args.rval().setObject(*arr.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rets().clear();
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
uint32_t SMW_i32_from_u32(size_t i) {
|
||||
SMW_LOG("SMW_i32_from_u32(i = %zu)\n", i);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedValue val(cx, operands()[i]);
|
||||
double number = 0.0;
|
||||
if (!JS::ToNumber(cx, val, &number)) {
|
||||
abort(cx, "failed to convert value to number");
|
||||
}
|
||||
number = std::round(number);
|
||||
return uint32_t(number);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_u32_from_i32(uint32_t x, size_t dest) {
|
||||
SMW_LOG("SMW_u32_from_i32(x = %ull, dest = %zu)\n", x, dest);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedValue val(cx, JS::NumberValue(x));
|
||||
save_operand(dest, val);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_string_canon_lower(uint32_t* ret_ptr, size_t i) {
|
||||
SMW_LOG("SMW_string_canon_lower(ret_ptr = %p, i = %zu)\n", ret_ptr, i);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedValue strVal(cx, operands()[i]);
|
||||
if (!strVal.isString()) {
|
||||
abort(cx, "value is not a string");
|
||||
}
|
||||
JS::RootedString str(cx, strVal.toString());
|
||||
JS::Rooted<JSLinearString*> linearStr(cx, JS_EnsureLinearString(cx, str));
|
||||
if (!linearStr) {
|
||||
abort(cx, "failed to linearize JS string");
|
||||
}
|
||||
|
||||
size_t len = JS::GetDeflatedUTF8StringLength(linearStr);
|
||||
char* ptr = static_cast<char*>(malloc(len));
|
||||
if (!ptr) {
|
||||
abort(cx, "out of memory");
|
||||
}
|
||||
|
||||
size_t num_written = JS::DeflateStringToUTF8Buffer(linearStr, mozilla::Span(ptr, len));
|
||||
assert(num_written == len);
|
||||
|
||||
ret_ptr[0] = reinterpret_cast<uint32_t>(ptr);
|
||||
ret_ptr[1] = static_cast<uint32_t>(len);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_string_canon_lift(char* ptr, size_t len, size_t dest) {
|
||||
SMW_LOG("SMW_string_canon_lift(ptr = %p, len = %zu, dest = %zu)\n", ptr, len, dest);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(ptr, len)));
|
||||
if (!str) {
|
||||
abort(cx, "failed to create JS string from UTF-8 buffer");
|
||||
}
|
||||
JS::RootedValue strVal(cx, JS::StringValue(str));
|
||||
save_operand(dest, strVal);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
uint32_t SMW_spread_into_array(size_t i) {
|
||||
SMW_LOG("SMW_spread_into_array; i = %zu\n", i);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
|
||||
JS::RootedValue iterable(cx, operands()[i]);
|
||||
bool is_array = false;
|
||||
if (!JS::IsArrayObject(cx, iterable, &is_array)) {
|
||||
abort(cx, "failed to check if object is an array");
|
||||
}
|
||||
|
||||
if (is_array) {
|
||||
JS::RootedObject arr(cx, &iterable.toObject());
|
||||
uint32_t length = 0;
|
||||
if (!JS::GetArrayLength(cx, arr, &length)) {
|
||||
abort(cx, "failed to get array length");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
JS::RootedVector<JS::Value> elems(cx);
|
||||
JS::ForOfIterator iter(cx);
|
||||
if (!iter.init(iterable)) {
|
||||
abort(cx, "failed to convert operand value to iterable");
|
||||
}
|
||||
JS::RootedValue val(cx);
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
if (!iter.next(&val, &done)) {
|
||||
abort(cx, "failed to get the next value out of iterator");
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
if (!elems.append(val)) {
|
||||
abort(cx, "failed to append value to vector");
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems));
|
||||
if (!arr) {
|
||||
abort(cx, "failed to allocate JS array object");
|
||||
}
|
||||
operands()[i].setObject(*arr);
|
||||
|
||||
return elems.length();
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_get_array_element(size_t array, size_t index, size_t dest) {
|
||||
SMW_LOG("SMW_get_array_element(array = %zu, index = %zu, dest = %zu)\n", array, index, dest);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
|
||||
JS::RootedValue array_val(cx, operands()[array]);
|
||||
assert(array_val.isObject());
|
||||
JS::RootedObject array_obj(cx, &array_val.toObject());
|
||||
JS::RootedValue elem(cx);
|
||||
if (!JS_GetElement(cx, array_obj, index, &elem)) {
|
||||
abort(cx, "failed to get array element");
|
||||
}
|
||||
|
||||
save_operand(dest, elem);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_new_array(size_t dest) {
|
||||
SMW_LOG("SMW_new_array(dest = %zu)\n", dest);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
JS::RootedObject arr(cx, JS::NewArrayObject(cx, 0));
|
||||
if (!arr) {
|
||||
abort(cx, "failed to allocate a new JS array object");
|
||||
}
|
||||
JS::RootedValue arr_val(cx, JS::ObjectValue(*arr));
|
||||
save_operand(dest, arr_val);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_array_push(size_t array, size_t elem) {
|
||||
SMW_LOG("SMW_array_push(array = %zu, elem = %zu)\n", array, elem);
|
||||
|
||||
JSContext* cx = get_js_context();
|
||||
|
||||
JS::RootedValue array_val(cx, operands()[array]);
|
||||
assert(array_val.isObject());
|
||||
JS::RootedObject array_obj(cx, &array_val.toObject());
|
||||
|
||||
uint32_t length = 0;
|
||||
if (!JS::GetArrayLength(cx, array_obj, &length)) {
|
||||
abort(cx, "failed to get JS array object length");
|
||||
}
|
||||
|
||||
JS::RootedValue elem_val(cx, operands()[elem]);
|
||||
if (!JS_SetElement(cx, array_obj, length, elem_val)) {
|
||||
abort(cx, "failed to set JS array element");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "smw/cx.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
namespace smw {
|
||||
|
||||
static JSContext* CONTEXT = nullptr;
|
||||
|
||||
void init_js_context(JSContext *cx) {
|
||||
assert(!CONTEXT && "CONTEXT should only be initialized once");
|
||||
CONTEXT = cx;
|
||||
}
|
||||
|
||||
JSContext *get_js_context() {
|
||||
assert(CONTEXT && "CONTEXT should be initialized");
|
||||
return CONTEXT;
|
||||
}
|
||||
|
||||
static JS::PersistentRooted<JSObject*> USER_MODULE;
|
||||
|
||||
void init_user_module(JSContext* cx, JSObject* user_module) {
|
||||
assert(!USER_MODULE && "USER_MODULE should only be initialized once");
|
||||
USER_MODULE.init(cx, user_module);
|
||||
}
|
||||
|
||||
JSObject* get_user_module() {
|
||||
assert(USER_MODULE && "USER_MODULE should be initialized");
|
||||
return USER_MODULE;
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "smw/dump.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include "jsapi.h"
|
||||
#include "smw/wasm.h"
|
||||
|
||||
namespace smw {
|
||||
|
||||
static JS::UniqueChars stringify_value(JSContext *cx, JS::HandleValue val) {
|
||||
JS::RootedString str(cx, JS_ValueToSource(cx, val));
|
||||
if (!str) {
|
||||
return nullptr;
|
||||
}
|
||||
return JS_EncodeStringToUTF8(cx, str);
|
||||
}
|
||||
|
||||
bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp) {
|
||||
JS::UniqueChars str = stringify_value(cx, val);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
fprintf(fp, "%s\n", str.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp) {
|
||||
JS::RootedString str(cx);
|
||||
size_t indent = 4;
|
||||
if (!JS::BuildStackString(cx, nullptr, stack, &str, indent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str);
|
||||
if (!utf8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(fp, "%s\n", utf8.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
int32_t dump_i32(int32_t x) {
|
||||
fprintf(stderr, "dump_i32: %d\n", x);
|
||||
return x;
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef _smw_abort_h
|
||||
#define _smw_abort_h
|
||||
|
||||
struct JSContext;
|
||||
|
||||
namespace smw {
|
||||
|
||||
/**
|
||||
* Print the given error message and abort.
|
||||
*/
|
||||
void abort(const char* msg);
|
||||
void abort(JSContext *cx, const char* msg);
|
||||
|
||||
}
|
||||
|
||||
#endif // _smw_abort_h
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef _smw_bindgen_h
|
||||
#define _smw_bindgen_h
|
||||
|
||||
struct JSContext;
|
||||
|
||||
namespace smw {
|
||||
|
||||
void init_operands(JSContext* cx);
|
||||
|
||||
}
|
||||
|
||||
#endif // _smw_bindgen_h
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef _smw_cx_h
|
||||
#define _smw_cx_h
|
||||
|
||||
struct JSContext;
|
||||
class JSObject;
|
||||
|
||||
namespace smw {
|
||||
|
||||
void init_js_context(JSContext* cx);
|
||||
JSContext* get_js_context();
|
||||
|
||||
void init_user_module(JSContext* cx, JSObject* user_module);
|
||||
JSObject* get_user_module();
|
||||
|
||||
}
|
||||
|
||||
#endif // _smw_cx_h
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef _smw_dump_h
|
||||
#define _smw_dump_h
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Winvalid-offsetof"
|
||||
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/Value.h"
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
namespace smw {
|
||||
|
||||
/**
|
||||
* Dump a human-readable representation of the given JS value to the given file.
|
||||
*/
|
||||
bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp);
|
||||
|
||||
/**
|
||||
* Dump a human-readable representation of the given JS exception stack to the
|
||||
* given file.
|
||||
*/
|
||||
bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp);
|
||||
|
||||
}
|
||||
|
||||
#endif // _smw_dump_h
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef _smw_logging_h
|
||||
#define _smw_logging_h
|
||||
|
||||
#if LOGGING==1
|
||||
|
||||
#include <stdio.h>
|
||||
#define SMW_LOG(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__)
|
||||
|
||||
#else // LOGGING==1
|
||||
|
||||
#define SMW_LOG(msg, ...) do { } while(false)
|
||||
|
||||
#endif // LOGGING==1
|
||||
|
||||
#endif // _smw_logging_h
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef _smw_wasm_h
|
||||
#define _smw_wasm_h
|
||||
|
||||
/**
|
||||
* An attribute for making a function exported from the final Wasm binary.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* WASM_EXPORT
|
||||
* int add(int a, int b) {
|
||||
* return a + b;
|
||||
* }
|
||||
*/
|
||||
#define WASM_EXPORT \
|
||||
__attribute__((visibility("default"))) \
|
||||
extern "C"
|
||||
|
||||
#endif // _smw_wasm_h
|
||||
@@ -0,0 +1,380 @@
|
||||
/*!
|
||||
* JS engine initialization and JS top-level evaluation.
|
||||
*
|
||||
* This file contains the code to start up the JS engine, define import-able
|
||||
* modules from VM functions, and evaluate the user JS.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "smw/abort.h"
|
||||
#include "smw/bindgen.h"
|
||||
#include "smw/cx.h"
|
||||
#include "smw/wasm.h"
|
||||
|
||||
#include "js/AllocPolicy.h"
|
||||
#include "js/CompilationAndEvaluation.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/GCVector.h"
|
||||
#include "js/Initialization.h"
|
||||
#include "js/Modules.h"
|
||||
#include "js/Promise.h"
|
||||
#include "js/Realm.h"
|
||||
#include "js/SourceText.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/Warnings.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
namespace smw {
|
||||
|
||||
using UniqueChars = mozilla::UniquePtr<char[]>;
|
||||
|
||||
bool INITIALIZED = false;
|
||||
JS::PersistentRootedObject GLOBAL;
|
||||
|
||||
static JSClass global_class = {
|
||||
"global",
|
||||
JSCLASS_GLOBAL_FLAGS,
|
||||
&JS::DefaultGlobalClassOps
|
||||
};
|
||||
|
||||
/**
|
||||
* Compile the given JS source as a module in the context of the given global.
|
||||
*
|
||||
* Takes ownership of `jsSource`.
|
||||
*
|
||||
* Does not take ownership of `jsFileName`.
|
||||
*
|
||||
* Sets `outModule` to the resulting source text module record object.
|
||||
*/
|
||||
bool compile_js_module(JSContext *cx,
|
||||
const char *jsFileName,
|
||||
char *jsSource,
|
||||
size_t jsSourceLen,
|
||||
JS::MutableHandleObject outModule) {
|
||||
JS::CompileOptions copts(cx);
|
||||
copts
|
||||
.setFileAndLine(jsFileName, 1)
|
||||
.setNoScriptRval(true)
|
||||
.setForceFullParse();
|
||||
|
||||
JS::SourceText<mozilla::Utf8Unit> srcBuf;
|
||||
if (!srcBuf.init(cx, jsSource, jsSourceLen, JS::SourceOwnership::TakeOwnership)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RootedObject module(cx);
|
||||
|
||||
// Disabling generational GC during compilation seems to slightly reduce
|
||||
// the number of pages touched post-wizening. (Whereas disabling it
|
||||
// during execution meaningfully increases it, which is why this is
|
||||
// scoped to just compilation.)
|
||||
JS::AutoDisableGenerationalGC noGgc(cx);
|
||||
module = JS::CompileModule(cx, copts, srcBuf);
|
||||
if (!module) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outModule.set(module);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthesized module that exports `JSNative` functions.
|
||||
*/
|
||||
struct SynthesizedModule {
|
||||
JS::Heap<JSString*> moduleName;
|
||||
JS::Heap<JSObject*> moduleObject;
|
||||
|
||||
SynthesizedModule(JS::HandleString moduleName, JS::HandleObject moduleObject)
|
||||
: moduleName(moduleName)
|
||||
, moduleObject(moduleObject)
|
||||
{ }
|
||||
|
||||
void trace(JSTracer* tracer) {
|
||||
JS::TraceEdge(tracer, &moduleObject, "SynthesizedModule.moduleObject");
|
||||
}
|
||||
};
|
||||
|
||||
JS::PersistentRooted<JS::GCVector<SynthesizedModule, 0, js::SystemAllocPolicy>> MODULES;
|
||||
|
||||
JSObject* module_resolve_hook(JSContext *cx,
|
||||
JS::HandleValue referencing_private,
|
||||
JS::HandleObject module_request) {
|
||||
JS::RootedString specifier(cx, JS::GetModuleRequestSpecifier(cx, module_request));
|
||||
if (!specifier) {
|
||||
abort(cx, "failed to get module request specifier");
|
||||
}
|
||||
|
||||
size_t len = MODULES.length();
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
JS::RootedObject it_module(cx, MODULES[i].get().moduleObject);
|
||||
JS::RootedString it_name(cx, MODULES[i].get().moduleName);
|
||||
int32_t result = 0;
|
||||
if (!JS_CompareStrings(cx, it_name, specifier, &result)) {
|
||||
abort(cx, "failed to compare module specifier to registered module name");
|
||||
}
|
||||
if (result == 0) {
|
||||
return it_module.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, specifier);
|
||||
if (!utf8) {
|
||||
JS_ReportErrorASCII(cx, "failed to find module import");
|
||||
return nullptr;
|
||||
}
|
||||
JS_ReportErrorASCII(cx, "failed to find module import: `%s`", utf8.get());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS::RealmOptions make_realm_options() {
|
||||
JS::RealmOptions options;
|
||||
options
|
||||
.creationOptions()
|
||||
.setStreamsEnabled(true)
|
||||
.setReadableByteStreamsEnabled(true)
|
||||
.setBYOBStreamReadersEnabled(true)
|
||||
.setReadableStreamPipeToEnabled(true)
|
||||
.setWritableStreamsEnabled(true)
|
||||
.setIteratorHelpersEnabled(true)
|
||||
.setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithoutCleanupSome);
|
||||
return options;
|
||||
}
|
||||
|
||||
bool init_js(JSContext *cx) {
|
||||
if (!js::UseInternalJobQueues(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS::InitSelfHostedCode(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RealmOptions options = make_realm_options();
|
||||
|
||||
JS::DisableIncrementalGC(cx);
|
||||
|
||||
JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
|
||||
JS::FireOnNewGlobalHook, options));
|
||||
if (!global) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::EnterRealm(cx, global);
|
||||
|
||||
if (!JS::InitRealmStandardClasses(cx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// JS::SetPromiseRejectionTrackerCallback(cx, rejection_tracker);
|
||||
|
||||
JS::SetModuleResolveHook(JS_GetRuntime(cx), module_resolve_hook);
|
||||
|
||||
GLOBAL.init(cx, global);
|
||||
return true;
|
||||
}
|
||||
|
||||
// static void report_warning(JSContext *cx, JSErrorReport *report) {
|
||||
// JS::PrintError(stderr, report, true);
|
||||
// if (!report->isWarning()) {
|
||||
// ::abort();
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Initialize the JS engine and evaluate the top-level of the given JavaScript
|
||||
* source.
|
||||
*
|
||||
* Takes ownership of its parameters.
|
||||
*/
|
||||
WASM_EXPORT
|
||||
void SMW_initialize_engine() {
|
||||
assert(!INITIALIZED);
|
||||
|
||||
bool ok = true;
|
||||
|
||||
ok = JS_Init();
|
||||
assert(ok && "JS_Init failed");
|
||||
|
||||
JSContext *cx = JS_NewContext(JS::DefaultHeapMaxBytes);
|
||||
assert(cx != nullptr && "JS_NewContext failed");
|
||||
init_js_context(cx);
|
||||
|
||||
// JS::SetWarningReporter(cx, report_warning);
|
||||
|
||||
if (!init_js(cx)) {
|
||||
abort(cx, "initializing the JavaScript engine failed");
|
||||
}
|
||||
|
||||
init_operands(cx);
|
||||
|
||||
MODULES.init(cx);
|
||||
INITIALIZED = true;
|
||||
}
|
||||
|
||||
class ModuleBuilder {
|
||||
JS::PersistentRootedString moduleName;
|
||||
JS::PersistentRooted<JS::IdValueVector> exports;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct a new `ModuleBuilder` and take ownership of `moduleName`.
|
||||
*/
|
||||
ModuleBuilder(JSContext *cx, JS::HandleString moduleName)
|
||||
: moduleName(cx, moduleName)
|
||||
, exports(cx, cx)
|
||||
{
|
||||
assert(moduleName && "moduleName must not be nullptr");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an exported function to this module and take ownership of `funcName`.
|
||||
*/
|
||||
void add_export(const char *funcName, size_t funcNameLen, JSNative func, unsigned numArgs) {
|
||||
assert(funcName && "function name must not be nullptr");
|
||||
assert(funcNameLen > 0 && "function name length must be greater than zero");
|
||||
assert(func && "the function must not be nullptr");
|
||||
|
||||
JSContext *cx = get_js_context();
|
||||
|
||||
JS::RootedString jsFuncName(cx, JS_NewStringCopyN(cx, funcName, funcNameLen));
|
||||
if (!jsFuncName) {
|
||||
abort(cx, "failed to create new JS string");
|
||||
}
|
||||
|
||||
JS::RootedId funcNameId(cx);
|
||||
if (!JS_StringToId(cx, jsFuncName, &funcNameId)) {
|
||||
abort(cx, "failed to convert string to id");
|
||||
}
|
||||
|
||||
JS::RootedFunction jsFunc(cx, JS_NewFunction(cx, func, numArgs, 0, funcName));
|
||||
if (!jsFunc) {
|
||||
abort(cx, "failed to create new JS function");
|
||||
}
|
||||
|
||||
JS::RootedObject jsFuncObj(cx, JS_GetFunctionObject(jsFunc));
|
||||
assert(jsFuncObj && "getting function object is infallible");
|
||||
JS::RootedValue jsFuncVal(cx, JS::ObjectValue(*jsFuncObj));
|
||||
|
||||
if (!exports.append(JS::IdValuePair(funcNameId, jsFuncVal))) {
|
||||
abort(cx, "failed to append export to exports list");
|
||||
}
|
||||
}
|
||||
|
||||
void finish() {
|
||||
JSContext *cx = get_js_context();
|
||||
|
||||
JS::RootedObject module(cx, JS::CreateModule(cx, exports));
|
||||
if (!module) {
|
||||
abort(cx, "failed to create synthetic module");
|
||||
}
|
||||
|
||||
if (!MODULES.append(SynthesizedModule(moduleName, module))) {
|
||||
abort(cx, "failed to append to MODULES");
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
};
|
||||
|
||||
WASM_EXPORT
|
||||
ModuleBuilder *SMW_new_module_builder(char *module_name, size_t module_name_len) {
|
||||
auto unique_module_name = UniqueChars(module_name);
|
||||
|
||||
JSContext *cx = get_js_context();
|
||||
|
||||
JS::RootedString js_module_name(cx, JS_NewStringCopyN(cx, unique_module_name.get(), module_name_len));
|
||||
if (!js_module_name) {
|
||||
abort(cx, "failed to allocate JS string");
|
||||
}
|
||||
|
||||
auto b = new ModuleBuilder(cx, js_module_name);
|
||||
if (!b) {
|
||||
abort(cx, "failed to create new ModuleBuilder");
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_module_builder_add_export(ModuleBuilder *builder,
|
||||
char *funcName,
|
||||
size_t funcNameLen,
|
||||
JSNative func,
|
||||
unsigned numArgs) {
|
||||
assert(builder && "builder must not be nullptr");
|
||||
assert(funcName && "funcName must not be nullptr");
|
||||
assert(funcNameLen > 0 && "funcNameLen must be greater than 0");
|
||||
assert(func && "func must not be nullptr");
|
||||
|
||||
auto uniqFuncName = UniqueChars(funcName);
|
||||
builder->add_export(uniqFuncName.get(), funcNameLen, func, numArgs);
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_finish_module_builder(ModuleBuilder *builder) {
|
||||
builder->finish();
|
||||
}
|
||||
|
||||
WASM_EXPORT
|
||||
void SMW_eval_module(char *jsFileName, char *jsSource, size_t jsSourceLen) {
|
||||
JSContext *cx = get_js_context();
|
||||
|
||||
assert(GLOBAL && "GLOBAL should be initialized");
|
||||
JS::RootedObject global(cx, GLOBAL);
|
||||
JSAutoRealm autoRealm(cx, global);
|
||||
|
||||
JS::RootedObject module(cx);
|
||||
if (!compile_js_module(cx, jsFileName, jsSource, jsSourceLen, &module)) {
|
||||
abort(cx, "module compilation failed");
|
||||
}
|
||||
|
||||
if (!JS::ModuleInstantiate(cx, module)) {
|
||||
abort(cx, "failed to instantiate module");
|
||||
}
|
||||
|
||||
JS::RootedValue result(cx);
|
||||
if (!JS::ModuleEvaluate(cx, module, &result)) {
|
||||
abort(cx, "failed to evaluate module");
|
||||
}
|
||||
|
||||
// TODO: if `result` is a promise because of top-level await, then don't
|
||||
// return until the micro task queue is empty.
|
||||
if (result.isObject()) {
|
||||
JS::RootedObject resultObj(cx, &result.toObject());
|
||||
if (!JS::IsPromiseObject(resultObj)) {
|
||||
goto done_handling_promise;
|
||||
}
|
||||
switch (JS::GetPromiseState(resultObj)) {
|
||||
case JS::PromiseState::Fulfilled: {
|
||||
JS::RootedValue promiseResolution(cx, JS::GetPromiseResult(resultObj));
|
||||
break;
|
||||
}
|
||||
case JS::PromiseState::Rejected: {
|
||||
JS::RootedValue promiseRejection(cx, JS::GetPromiseResult(resultObj));
|
||||
JS_SetPendingException(cx, promiseRejection);
|
||||
abort(cx, "module evaluation failed");
|
||||
}
|
||||
case JS::PromiseState::Pending: {
|
||||
abort(cx, "module evaluation returned a pending promise, but top-level await isn't enabled yet");
|
||||
}
|
||||
default:
|
||||
abort(cx, "module evaluation returned a promise in an unknown promise state");
|
||||
}
|
||||
}
|
||||
|
||||
done_handling_promise:
|
||||
|
||||
init_user_module(cx, module);
|
||||
|
||||
JS::PrepareForFullGC(cx);
|
||||
JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API);
|
||||
|
||||
free(jsFileName);
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "smw/abort.h"
|
||||
#include "smw/wasm.h"
|
||||
|
||||
namespace smw {
|
||||
|
||||
WASM_EXPORT
|
||||
void* SMW_malloc(size_t size) {
|
||||
auto p = malloc(size);
|
||||
if (p == nullptr) {
|
||||
abort("out of memory");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace smw
|
||||
@@ -0,0 +1,27 @@
|
||||
WASI_SDK="$(pwd)/../wasi-sdk-12.0"
|
||||
|
||||
CC="$WASI_SDK/bin/clang --sysroot=$WASI_SDK/share/wasi-sysroot"
|
||||
CXX="$WASI_SDK/bin/clang++ --sysroot=$WASI_SDK/share/wasi-sysroot"
|
||||
AR="$WASI_SDK/bin/ar"
|
||||
|
||||
HOST_CC=gcc
|
||||
HOST_CXX=g++
|
||||
|
||||
ac_add_options --enable-application=js
|
||||
ac_add_options --target=wasm32-unknown-wasi
|
||||
|
||||
ac_add_options --without-system-zlib
|
||||
ac_add_options --without-intl-api
|
||||
|
||||
ac_add_options --disable-jit
|
||||
ac_add_options --disable-shared-js
|
||||
ac_add_options --disable-shared-memory
|
||||
|
||||
# Comment out `--enable-optimize` and `--disable-debug` and then uncomment
|
||||
# `--enable-debug` to switch to debug builds of SpiderMonkey. Also update
|
||||
# `DEFINE=` in the `Makefile`.
|
||||
ac_add_options --enable-optimize
|
||||
ac_add_options --disable-debug
|
||||
# ac_add_options --enable-debug
|
||||
|
||||
mk_add_options AUTOCLOBBER=1
|
||||
Reference in New Issue
Block a user