Skip to content
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

Add Rust bindings for the runtime and the SpiderMonkey engines #103

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
/.spin

# Rust compilation output
/target
/runtime/crates/target/

/tests/e2e/*/*.wasm
/tests/e2e/*/*.log
Expand Down
6 changes: 5 additions & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
The portions of this code found in the initial commit are Copyright © 2021-2023 Fastly Inc. and contributors to Fastly's [js-compute-runtime project](https://github.com/fastly/js-compute-runtime/).

Changes following that initial commit are Copyright © the respective contributors.
Changes following that initial commit are Copyright © the respective contributors where not documented otherwise.

## Rust Bindings

The Rust bindings found under [runtime/crates](runtime/crates) are based on the Servo project's [mozjs and mozjs-sys](https://github.com/servo/mozjs) crates. The code under that folder is Copyright © The Servo Project Developers up to the fork, and Copyright © the respective contributors where not documented otherwise from then on.
25 changes: 12 additions & 13 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ include("openssl")
include("host_api")
include("build-crates")

add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h runtime/decode.h)
add_library(extension_api INTERFACE include/extension-api.h runtime/cpp/encode.h runtime/cpp/decode.h)
target_link_libraries(extension_api INTERFACE rust-url spidermonkey)
target_include_directories(extension_api INTERFACE include deps/include runtime)
target_include_directories(extension_api INTERFACE include deps/include runtime/cpp)

include("builtins")

Expand All @@ -49,14 +49,14 @@ if (ENABLE_WPT)
endif()

add_executable(starling.wasm
runtime/js.cpp
runtime/allocator.cpp
runtime/encode.cpp
runtime/decode.cpp
runtime/engine.cpp
runtime/event_loop.cpp
runtime/builtin.cpp
runtime/script_loader.cpp
runtime/cpp/js.cpp
runtime/cpp/allocator.cpp
runtime/cpp/encode.cpp
runtime/cpp/decode.cpp
runtime/cpp/engine.cpp
runtime/cpp/event_loop.cpp
runtime/cpp/builtin.cpp
runtime/cpp/script_loader.cpp
)

option(USE_WASM_OPT "use wasm-opt to optimize the StarlingMonkey binary" ON)
Expand Down Expand Up @@ -89,7 +89,7 @@ else()
endif()
endif()

target_link_libraries(starling.wasm PRIVATE host_api extension_api builtins spidermonkey rust-url)
target_link_libraries(starling.wasm PRIVATE host_api extension_api builtins spidermonkey rust_staticlib rust-glue)

# build a compilation cache of ICs
if(WEVAL)
Expand All @@ -112,9 +112,8 @@ endif()

set(RUNTIME_FILE "starling.wasm")
set(ADAPTER_FILE "preview1-adapter.wasm")
configure_file("componentize.sh" "${CMAKE_CURRENT_BINARY_DIR}/componentize.sh" @ONLY)
configure_file("componentize.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/componentize.sh" @ONLY)
configure_file(${ADAPTER} "${CMAKE_CURRENT_BINARY_DIR}/${ADAPTER_FILE}" COPYONLY)
configure_file(spin.toml spin.toml COPYONLY)

function(componentize OUTPUT)
set(options)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2020 Fastly, Inc.
Copyright StarlingMonkey project contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
18 changes: 13 additions & 5 deletions builtins/install_builtins.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
#include "extension-api.h"

#define NS_DEF(ns) \
namespace ns { \
extern bool install(api::Engine *engine); \
#define NS_DEF(ns) \
namespace ns { \
extern bool install(api::Engine *engine); \
}
#define RS_DEF(install_fn) \
extern "C" bool install_fn(api::Engine *engine);
#include "builtins.incl"
#undef RS_DEF
#undef NS_DEF


bool install_builtins(api::Engine *engine) {
#define NS_DEF(ns) \
if (!ns::install(engine)) \
#define NS_DEF(ns) \
if (!ns::install(engine)) \
return false;
#define RS_DEF(install_fn) \
if (!install_fn(engine)) \
return false;
#include "builtins.incl"
#undef RS_DEF
#undef NS_DEF

return true;
Expand Down
7 changes: 2 additions & 5 deletions builtins/web/fetch/fetch-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

namespace builtins::web::fetch {

static api::Engine *ENGINE;

// TODO: throw in all Request methods/getters that rely on host calls once a
// request has been sent. The host won't let us act on them anymore anyway.
/**
Expand Down Expand Up @@ -86,7 +84,8 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) {
// If the request body is streamed, we need to wait for streaming to complete
// before marking the request as pending.
if (!streaming) {
ENGINE->queue_async_task(new ResponseFutureTask(request_obj, pending_handle));
api::Engine::from_context(cx)
.queue_async_task(new ResponseFutureTask(request_obj, pending_handle));
}

SetReservedSlot(request_obj, static_cast<uint32_t>(Request::Slots::ResponsePromise),
Expand All @@ -101,8 +100,6 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) {
const JSFunctionSpec methods[] = {JS_FN("fetch", fetch, 2, JSPROP_ENUMERATE), JS_FS_END};

bool install(api::Engine *engine) {
ENGINE = engine;

if (!JS_DefineFunctions(engine->cx(), engine->global(), methods))
return false;
if (!request_response::install(engine)) {
Expand Down
21 changes: 10 additions & 11 deletions builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ bool NativeStreamSource::stream_is_body(JSContext *cx, JS::HandleObject stream)

namespace builtins::web::fetch {

static api::Engine *ENGINE;

bool error_stream_controller_with_pending_exception(JSContext *cx, HandleObject controller) {
RootedValue exn(cx);
if (!JS_GetPendingException(cx, &exn))
Expand Down Expand Up @@ -466,7 +464,8 @@ bool finish_outgoing_body_streaming(JSContext* cx, HandleObject body_owner) {
.toPrivate());
SetReservedSlot(body_owner, static_cast<uint32_t>(Request::Slots::PendingResponseHandle),
PrivateValue(nullptr));
ENGINE->queue_async_task(new ResponseFutureTask(body_owner, pending_handle));
api::Engine::from_context(cx)
.queue_async_task(new ResponseFutureTask(body_owner, pending_handle));
}

return true;
Expand All @@ -476,9 +475,10 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha
MOZ_ASSERT(!body_used(source));
MOZ_ASSERT(!body_used(self));
MOZ_ASSERT(self != source);
auto engine = api::Engine::from_context(cx);
host_api::HttpIncomingBody *source_body = incoming_body_handle(source);
host_api::HttpOutgoingBody *dest_body = outgoing_body_handle(self);
auto res = dest_body->append(ENGINE, source_body, finish_outgoing_body_streaming, self);
auto res = dest_body->append(&engine, source_body, finish_outgoing_body_streaming, self);
if (auto *err = res.to_err()) {
HANDLE_ERROR(cx, *err);
return false;
Expand Down Expand Up @@ -892,7 +892,7 @@ bool RequestOrResponse::body_source_pull_algorithm(JSContext *cx, CallArgs args,
}
}

ENGINE->queue_async_task(new BodyFutureTask(source));
api::Engine::from_context(cx).queue_async_task(new BodyFutureTask(source));

args.rval().setUndefined();
return true;
Expand Down Expand Up @@ -945,7 +945,7 @@ bool reader_for_outgoing_body_then_handler(JSContext *cx, JS::HandleObject body_
// Uint8Array?
fprintf(stderr, "Error: read operation on body ReadableStream didn't respond with a "
"Uint8Array. Received value: ");
ENGINE->dump_value(val, stderr);
api::Engine::from_context(cx).dump_value(val, stderr);
return false;
}

Expand Down Expand Up @@ -985,7 +985,7 @@ bool reader_for_outgoing_body_catch_handler(JSContext *cx, JS::HandleObject body
// stream errored during the streaming send. Not much we can do, but at least
// close the stream, and warn.
fprintf(stderr, "Warning: body ReadableStream closed during body streaming. Exception: ");
ENGINE->dump_value(args.get(0), stderr);
api::Engine::from_context(cx).dump_value(args.get(0), stderr);

return finish_outgoing_body_streaming(cx, body_owner);
}
Expand All @@ -1004,7 +1004,8 @@ bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_o
if (is_incoming(body_owner)) {
auto *source_body = incoming_body_handle(body_owner);
auto *dest_body = destination->body().unwrap();
auto res = dest_body->append(ENGINE, source_body, finish_outgoing_body_streaming, nullptr);
auto engine = api::Engine::from_context(cx);
auto res = dest_body->append(&engine, source_body, finish_outgoing_body_streaming, nullptr);
if (auto *err = res.to_err()) {
HANDLE_ERROR(cx, *err);
return false;
Expand Down Expand Up @@ -1233,7 +1234,7 @@ bool Request::clone(JSContext *cx, unsigned argc, JS::Value *vp) {
Value url_val = GetReservedSlot(self, static_cast<uint32_t>(Slots::URL));
SetReservedSlot(new_request, static_cast<uint32_t>(Slots::URL), url_val);
Value method_val = JS::StringValue(method(self));
ENGINE->dump_value(method_val, stderr);
api::Engine::from_context(cx).dump_value(method_val, stderr);
SetReservedSlot(new_request, static_cast<uint32_t>(Slots::Method), method_val);

// clone operation step 2.
Expand Down Expand Up @@ -2459,8 +2460,6 @@ JSObject * Response::create_incoming(JSContext *cx, host_api::HttpIncomingRespon
namespace request_response {

bool install(api::Engine *engine) {
ENGINE = engine;

if (!Request::init_class(engine->cx(), engine->global()))
return false;
if (!Response::init_class(engine->cx(), engine->global()))
Expand Down
12 changes: 5 additions & 7 deletions builtins/web/timers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class TimersMap {
}

static PersistentRooted<js::UniquePtr<TimersMap>> TIMERS_MAP;
static api::Engine *ENGINE;

class TimerTask final : public api::AsyncTask {
using TimerArgumentsVector = std::vector<JS::Heap<JS::Value>>;
Expand Down Expand Up @@ -77,7 +76,7 @@ class TimerTask final : public api::AsyncTask {
}

// The task might've been canceled during the callback.
if (handle_ != INVALID_POLLABLE_HANDLE) {
if (handle_ != api::INVALID_POLLABLE_HANDLE) {
host_api::MonotonicClock::unsubscribe(handle_);
}

Expand Down Expand Up @@ -119,12 +118,12 @@ class TimerTask final : public api::AsyncTask {
}
}

static bool clear(int32_t timer_id) {
static bool clear(api::Engine &engine, int32_t timer_id) {
if (!TIMERS_MAP->timers_.contains(timer_id)) {
return false;
}

ENGINE->cancel_async_task(TIMERS_MAP->timers_[timer_id]);
engine.cancel_async_task(TIMERS_MAP->timers_[timer_id]);
TIMERS_MAP->timers_.erase(timer_id);
return true;
}
Expand Down Expand Up @@ -171,7 +170,7 @@ template <bool repeat> bool setTimeout_or_interval(JSContext *cx, const unsigned
}

const auto timer = new TimerTask(delay, repeat, handler, handler_args);
ENGINE->queue_async_task(timer);
api::Engine::from_context(cx).queue_async_task(timer);
args.rval().setInt32(timer->timer_id());

return true;
Expand All @@ -193,7 +192,7 @@ template <bool interval> bool clearTimeout_or_interval(JSContext *cx, unsigned a
return false;
}

TimerTask::clear(id);
TimerTask::clear(api::Engine::from_context(cx), id);

args.rval().setUndefined();
return true;
Expand All @@ -206,7 +205,6 @@ constexpr JSFunctionSpec methods[] = {
JS_FN("clearTimeout", clearTimeout_or_interval<false>, 1, JSPROP_ENUMERATE), JS_FS_END};

bool install(api::Engine *engine) {
ENGINE = engine;
TIMERS_MAP.init(engine->cx(), js::MakeUnique<TimersMap>());
return JS_DefineFunctions(engine->cx(), engine->global(), methods);
}
Expand Down
35 changes: 35 additions & 0 deletions cmake/add_builtin.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,38 @@ function(add_builtin)
file(APPEND $CACHE{INSTALL_BUILTINS} "NS_DEF(${NS})\n")
return(PROPAGATE LIB_NAME)
endfunction()


function(add_rust_builtin name path)
# TODO: restore additional config args
set(LIB_NAME ${name})
set(LIB_PATH ${path})
set(DEFAULT_ENABLE ON)
set(LIB_TARGET_NAME ${LIB_NAME})
string(REPLACE "-" "_" LIB_NAME ${LIB_NAME})
string(PREPEND LIB_NAME "builtin_")
string(TOUPPER ${LIB_NAME} LIB_NAME_UPPER)
set(OPT_NAME ENABLE_${LIB_NAME_UPPER})
set(DESCRIPTION "${LIB_TARGET_NAME} (option: ${OPT_NAME}, default: ${DEFAULT_ENABLE})")

# In script-mode, just show the available builtins.
if(CMAKE_SCRIPT_MODE_FILE)
message(STATUS " ${DESCRIPTION}")
return()
endif()

option(${OPT_NAME} "Enable ${LIB_NAME}" ${DEFAULT_ENABLE})
if (${${OPT_NAME}})
else()
message(STATUS "Skipping builtin ${DESCRIPTION}")
return()
endif()

message(STATUS "Adding builtin ${DESCRIPTION}")

add_rust_lib(${name} ${path})
add_dependencies(${LIB_TARGET_NAME} rust-bindings)

file(APPEND $CACHE{INSTALL_BUILTINS} "RS_DEF(${LIB_NAME}_install)\n")
return(PROPAGATE LIB_NAME)
endfunction()
2 changes: 1 addition & 1 deletion cmake/binaryen.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set(BINARYEN_VERSION 117)
set(BINARYEN_VERSION 118)

set(BINARYEN_ARCH ${HOST_ARCH})
if(HOST_OS STREQUAL "macos" AND HOST_ARCH STREQUAL "aarch64")
Expand Down
45 changes: 43 additions & 2 deletions cmake/build-crates.cmake
Original file line number Diff line number Diff line change
@@ -1,2 +1,43 @@
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/Cargo.toml NO_LINKER_OVERRIDE)
set_property(TARGET rust-url PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/)
set(RUST_STATICLIB_RS "${CMAKE_CURRENT_BINARY_DIR}/rust-staticlib.rs" CACHE INTERNAL "Path to the Rust staticlibs bundler source file" FORCE)
set(RUST_STATICLIB_TOML "${CMAKE_CURRENT_BINARY_DIR}/Cargo.toml" CACHE INTERNAL "Path to the Rust staticlibs bundler Cargo.toml file" FORCE)
configure_file("runtime/crates/staticlib-template/rust-staticlib.rs.in" "${RUST_STATICLIB_RS}" COPYONLY)

# Add the debugmozjs feature for debug builds.
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DEBUGMOZJS_FEATURE "\"debugmozjs\"")
endif()
configure_file("runtime/crates/staticlib-template/Cargo.toml.in" "${RUST_STATICLIB_TOML}")

corrosion_import_crate(
MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/runtime/crates/Cargo.toml
CRATES "generate-bindings"
)
corrosion_set_env_vars(generate_bindings
SYSROOT=${WASI_SDK_PREFIX}/share/wasi-sysroot
CXXFLAGS="${CMAKE_CXX_FLAGS}"
BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}
SM_HEADERS=${SM_INCLUDE_DIR}
RUST_LOG=bindgen
)

corrosion_import_crate(
MANIFEST_PATH ${RUST_STATICLIB_TOML}
CRATES "rust-staticlib"
NO_LINKER_OVERRIDE
)

add_dependencies("cargo-prebuild_rust_staticlib" cargo-build_generate_bindings)

add_library(rust-glue ${CMAKE_CURRENT_SOURCE_DIR}/runtime/crates/jsapi-rs/cpp/jsglue.cpp)
target_include_directories(rust-glue PRIVATE ${SM_INCLUDE_DIR})
add_dependencies(rust_staticlib rust-glue)

function(add_rust_lib name path)
add_library(${name} INTERFACE)
file(APPEND $CACHE{RUST_STATICLIB_TOML} "${name} = { path = \"${path}\" }\n")
string(REPLACE "-" "_" name ${name})
file(APPEND $CACHE{RUST_STATICLIB_RS} "pub use ${name};\n")
endfunction()

add_rust_lib("rust-url" "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url")
set_property(TARGET rust-url PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url")
2 changes: 2 additions & 0 deletions cmake/builtins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ add_builtin(
fmt
INCLUDE_DIRS
runtime)

add_rust_builtin(test-builtin "${CMAKE_CURRENT_SOURCE_DIR}/runtime/crates/test-builtin")
Loading