Skip to content
Merged
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
98 changes: 94 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,46 @@ jobs:
strategy:
fail-fast: false
matrix:
build: [release, debug, weval]
build: [release, debug]
os: [ubuntu-latest]
outputs:
SM_TAG_EXISTS: ${{ steps.check-sm-release.outputs.SM_TAG_EXISTS }}
SM_TAG: ${{ steps.check-sm-release.outputs.SM_TAG }}
SM_CACHE_KEY_debug: ${{ steps.check-sm-release.outputs.SM_CACHE_KEY_debug }}
SM_CACHE_KEY_release: ${{ steps.check-sm-release.outputs.SM_CACHE_KEY_release }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

- name: Install Rust 1.80.0
- name: Check if SpiderMonkey Release Exists
id: check-sm-release
run: |
rustup toolchain install 1.80.0
rustup target add wasm32-wasip1 --toolchain 1.80.0
SM_TAG="libspidermonkey_$(awk '/^set\(SM_TAG/ {gsub(/set\(SM_TAG |\)/, ""); print}' cmake/spidermonkey.cmake)"
echo "SM_TAG=${SM_TAG}" >> "$GITHUB_OUTPUT"
if gh release view "${SM_TAG}" >/dev/null 2>&1; then
echo "Found existing SpiderMonkey release tag: ${SM_TAG}"
echo "SM_TAG_EXISTS=true" >> "$GITHUB_OUTPUT"
else
echo "SM_TAG_EXISTS=false" >> "$GITHUB_OUTPUT"
echo "SM_CACHE_KEY_${{ matrix.build }}=spidermonkey-cache-${{ matrix.build }}-${{ hashFiles('cmake/spidermonkey.cmake') }}" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ github.token }}

- name: Cache SpiderMonkey tarball
if: steps.check-sm-release.outputs.SM_TAG_EXISTS == 'false'
uses: actions/cache@v4
id: sm-cache
with:
path: |
spidermonkey-dist-${{ matrix.build }}
key: spidermonkey-cache-${{ matrix.build }}-${{ hashFiles('cmake/spidermonkey.cmake') }}

- name: Set env var to use cached SpiderMonkey tarball
if: steps.check-sm-release.outputs.SM_TAG_EXISTS == 'false' && steps.sm-cache.outputs.cache-hit == 'true'
run: |
tree spidermonkey-dist-${{ matrix.build }}
echo "SPIDERMONKEY_BINARIES=$(pwd)/spidermonkey-dist-${{ matrix.build }}" >> $GITHUB_ENV

- uses: actions/setup-node@v2
with:
Expand Down Expand Up @@ -64,3 +94,63 @@ jobs:
- name: StarlingMonkey E2E, Integration, and WPT Tests
run: |
CTEST_OUTPUT_ON_FAILURE=1 ctest --test-dir cmake-build-${{ matrix.build }} -j$(nproc) --verbose

- name: Set up cacheable SpiderMonkey artifacts
if: steps.check-sm-release.outputs.SM_TAG_EXISTS == 'false' && steps.sm-cache.outputs.cache-hit != 'true'
run: |
mkdir -p spidermonkey-dist-${{ matrix.build }}
cp -a cmake-build-${{ matrix.build }}/spidermonkey-obj/dist/libspidermonkey.a spidermonkey-dist-${{ matrix.build }}/
cp -aL cmake-build-${{ matrix.build }}/spidermonkey-obj/dist/include spidermonkey-dist-${{ matrix.build }}/
tree spidermonkey-dist-${{ matrix.build }}

# Upload tarball as an artifact of the github action run, so the output
# can be inspected for pull requests.
- name: Upload SpiderMonkey tarball
uses: actions/upload-artifact@v4
if: (github.event_name != 'push' || (github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v')))
&& steps.check-sm-release.outputs.SM_TAG_EXISTS == 'false' && steps.sm-cache.outputs.cache-hit != 'true'
with:
name: spidermonkey-${{ matrix.build }}
path: spidermonkey-dist-${{ matrix.build }}/*

release-spidermonkey:
needs: test
if: needs.test.outputs.SM_TAG_EXISTS == 'false' && (github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Restore SpiderMonkey Debug Cache
uses: actions/cache/restore@v4
id: sm-cache-debug
with:
path: |
spidermonkey-dist-debug
key: ${{ needs.test.outputs.SM_CACHE_KEY_debug }}
fail-on-cache-miss: true
- name: Restore SpiderMonkey Release Cache
uses: actions/cache/restore@v4
id: sm-cache-release
with:
path: |
spidermonkey-dist-release
key: ${{ needs.test.outputs.SM_CACHE_KEY_release }}
fail-on-cache-miss: true

- name: Create SpiderMonkey Tar Balls
run: |
mkdir -p release-artifacts
tar -a -cf release-artifacts/spidermonkey-static-debug.tar.gz spidermonkey-dist-debug/*
tar -a -cf release-artifacts/spidermonkey-static-release.tar.gz spidermonkey-dist-release/*
tree release-artifacts

- name: Do the Release
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 #2.3.2
with:
body: |
This release contains pre-built SpiderMonkey artifacts to be used by the StarlingMonkey
build system. It's not meant for general public consumption and doesn't come with any
stability or availability guarantees.
tag_name: ${{ needs.test.outputs.SM_TAG }}
files: release-artifacts/*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@
/tests/e2e/*/*.log
/tests/integration/*/*.wasm
/tests/integration/*/*.log
/deps/*.lock
/deps/*-source
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ else()
endif()
message(STATUS "Using host API: ${HOST_API}")

# Ensure that the CPM cache is created outside the build dir, even if no location is specified by the developer.
if(NOT DEFINED ENV{CPM_SOURCE_CACHE})
set(ENV{CPM_SOURCE_CACHE} ${CMAKE_CURRENT_SOURCE_DIR}/deps/cpm_cache)
endif()
include("CPM")
include("toolchain")

Expand All @@ -32,8 +36,8 @@ include("binaryen")
include("wizer")
include("weval")
include("wasmtime")
include("cbindgen")

include("fmt")
include("spidermonkey")
include("openssl")
include("${HOST_API}/host_api.cmake")
Expand Down
51 changes: 34 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,43 +71,60 @@ cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug

3. Build the runtime

Building the runtime is done in two phases: first, cmake is used to build a raw version as a
WebAssembly core module. Then, that module is turned into a [WebAssembly Component][wasm-component]
using the `componentize.sh` script generated by the build.
The build system provides two targets for the runtime: `starling-raw.wasm` and `starling.wasm`. The former is a raw WebAssembly core module that can be used to build a WebAssembly Component, while the latter is the final componentized runtime that can be used directly with a WebAssembly Component-aware runtime like [wasmtime](https://wasmtime.dev/).

The following command will build the `starling-raw.wasm` runtime module in the `cmake-build-release`
A key difference is that `starling.wasm` can only be used for runtime-evaluation of JavaScript code,
while `starling-raw.wasm` can be used to build a WebAssembly Component that is specialized for a specific
JavaScript application, and as a result has much faster startup times.

## Using StarlingMonkey with dynamically loaded JS code

The following command will build the `starling.wasm` runtime module in the `cmake-build-release`
directory:

```console
# Use cmake-build-debug for the debug build
# Change the value for `--parallel` to match the number of CPU cores in your system
cmake --build cmake-build-release --parallel 8
cmake --build cmake-build-release -t starling --parallel $(nproc)
```

Then, the `starling-raw.wasm` module can be turned into a component with the following command:
The resulting runtime can be used to load and evaluate JS code dynamically:

```console
cd cmake-build-release
./componentize.sh -o starling.wasm
wasmtime -S http cmake-build-release/starling.wasm -e "console.log('hello world')"
# or, to load a file:
wasmtime -S http --dir . starling.wasm index.js
```

The resulting runtime can be used to load and evaluate JS code dynamically:

## Creating a specialized runtime for your JS code

To create a specialized version of the runtime, first build a raw, unspecialized core wasm version of StarlingMonkey:

```console
wasmtime -S http starling.wasm -e "console.log('hello world')"
# or, to load a file:
wasmtime -S http --dir . starling.wasm index.js
# Use cmake-build-debug for the debug build
cmake --build cmake-build-release -t starling-raw.wasm --parallel $(nproc)
```

Alternatively, a JS file can be provided during componentization:
Then, the `starling-raw.wasm` module can be turned into a component specialized for your code with the following command:

```console
cd cmake-build-release
./componentize.sh index.js -o starling.wasm
./componentize.sh index.js -o index.wasm
```

This way, the JS file will be loaded during componentization, and the top-level code will be
executed, and can e.g. register a handler for the `fetch` event to serve HTTP requests.
This mode currently only supports the creation of HTTP server components, which means that the `index.js` file must register a `fetch` event handler. For example, your `index.js` could contain the following code:

```javascript
addEventListener('fetch', event => {
event.respondWith(new Response('Hello, world!'));
});
```

Componentizing this code like above allows running it like this:

```console
wasmtime serve -S cli --dir . index.wasm
```

[cmake]: https://cmake.org/
[gh-pages]: https://bytecodealliance.github.io/StarlingMonkey/
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/base64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ JS::Result<std::string> forgivingBase64Decode(std::string_view data,
auto hasWhitespace = std::find_if(data.begin(), data.end(), &isAsciiWhitespace);
std::string dataWithoutAsciiWhitespace;

if (hasWhitespace) {
if (*hasWhitespace) {
dataWithoutAsciiWhitespace = data;
dataWithoutAsciiWhitespace.erase(std::remove_if(dataWithoutAsciiWhitespace.begin() +
std::distance(data.begin(), hasWhitespace),
Expand Down
4 changes: 2 additions & 2 deletions builtins/web/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ static bool console_out(JSContext *cx, unsigned argc, JS::Value *vp) {

// https://console.spec.whatwg.org/#assert
// assert(condition, ...data)
static bool assert(JSContext *cx, unsigned argc, JS::Value *vp) {
static bool assert_(JSContext *cx, unsigned argc, JS::Value *vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
auto condition = args.get(0).toBoolean();
Expand Down Expand Up @@ -811,7 +811,7 @@ static bool trace(JSContext *cx, unsigned argc, JS::Value *vp) {
}

const JSFunctionSpec Console::methods[] = {
JS_FN("assert", assert, 0, JSPROP_ENUMERATE),
JS_FN("assert", assert_, 0, JSPROP_ENUMERATE),
JS_FN("clear", no_op, 0, JSPROP_ENUMERATE),
JS_FN("count", count, 0, JSPROP_ENUMERATE),
JS_FN("countReset", countReset, 0, JSPROP_ENUMERATE),
Expand Down
2 changes: 0 additions & 2 deletions builtins/web/crypto/uuid.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#include "uuid.h"
#include "host_api.h"

#include <fmt/core.h>

namespace builtins {
namespace web {
namespace crypto {
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/event/event-target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ bool default_passive_value() {

namespace JS {

template <typename T> struct JS::GCPolicy<RefPtr<T>> {
template <typename T> struct GCPolicy<RefPtr<T>> {
static void trace(JSTracer *trc, RefPtr<T> *tp, const char *name) {
if (T *target = tp->get()) {
GCPolicy<T>::trace(trc, target, name);
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/fetch/fetch-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ std::optional<std::tuple<size_t, size_t>> extract_range(std::string_view range_q

auto to_size = [](std::string_view s) -> std::optional<size_t> {
size_t v;
auto [ptr, ec] = std::from_chars(s.begin(), s.end(), v);
auto [ptr, ec] = std::from_chars(&*s.begin(), &*s.end(), v);
return ec == std::errc() ? std::optional<size_t>(v) : std::nullopt;
};

Expand Down
2 changes: 1 addition & 1 deletion builtins/web/fetch/fetch_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ bool FetchEvent::init_incoming_request(JSContext *cx, JS::HandleObject self,
bool is_head = !is_get && method_str == "HEAD";

if (!is_get) {
JS::RootedString method(cx, JS_NewStringCopyN(cx, method_str.cbegin(), method_str.length()));
JS::RootedString method(cx, JS_NewStringCopyN(cx, &*method_str.cbegin(), method_str.length()));
if (!method) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class BodyFutureTask final : public api::AsyncTask {
auto body = RequestOrResponse::incoming_body_handle(owner);

auto read_res = body->read(HANDLE_READ_CHUNK_SIZE);
if (auto *err = read_res.to_err()) {
if (read_res.to_err()) {
auto receiver = Request::is_instance(owner) ? "request" : "response";
api::throw_error(cx, FetchErrors::IncomingBodyStreamError, receiver);
return error_stream_controller_with_pending_exception(cx, stream);
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/form-data/form-data-encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ JSObject *MultipartFormData::create(JSContext *cx, HandleObject form_data) {
}

auto res = host_api::Random::get_bytes(12);
if (auto *err = res.to_err()) {
if (res.to_err()) {
return nullptr;
}

Expand Down
25 changes: 10 additions & 15 deletions cmake/CPM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,23 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors

set(CPM_DOWNLOAD_VERSION 0.40.5)
set(CPM_HASH_SUM "c46b876ae3b9f994b4f05a4c15553e0485636862064f1fcc9d8b4f832086bc5d")
set(CPM_DOWNLOAD_VERSION 0.42.0)
set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a")

# Ensure that the CPM_SOURCE_CACHE is defined and in sync with ENV{CPM_SOURCE_CACHE}
if (NOT DEFINED CPM_SOURCE_CACHE)
if(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_SOURCE_CACHE $ENV{CPM_SOURCE_CACHE})
else()
set(CPM_SOURCE_CACHE ${CMAKE_CURRENT_SOURCE_DIR}/deps/cpm_cache)
endif()
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
set(ENV{CPM_SOURCE_CACHE} ${CPM_SOURCE_CACHE})

set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
set(CPM_USE_NAMED_CACHE_DIRECTORIES ON)

# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)

file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)

include(${CPM_DOWNLOAD_LOCATION})
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 123)

set(BINARYEN_ARCH ${HOST_ARCH})
if(HOST_OS STREQUAL "macos" AND HOST_ARCH STREQUAL "aarch64")
Expand Down
5 changes: 2 additions & 3 deletions cmake/builtins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ add_builtin(
builtins/web/form-data/form-data-parser.cpp
DEPENDENCIES
multipart
fmt)
)

add_builtin(
builtins::web::dom_exception
Expand Down Expand Up @@ -102,7 +102,7 @@ add_builtin(
builtins/web/fetch/headers.cpp
builtins/web/fetch/request-response.cpp
DEPENDENCIES
fmt)
)

add_builtin(
builtins::web::fetch::fetch_event
Expand All @@ -124,6 +124,5 @@ add_builtin(
builtins/web/crypto/uuid.cpp
DEPENDENCIES
OpenSSL::Crypto
fmt
INCLUDE_DIRS
runtime)
16 changes: 16 additions & 0 deletions cmake/cbindgen.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
set(CBINDGEN_VERSION 0.29.0)

# cbindgen doesn't have pre-built binaries for all platforms, so we install it via cargo-binstall. Which we install first, too.
find_program(CBINDGEN_EXECUTABLE cbindgen)
if(NOT CBINDGEN_EXECUTABLE)
find_program(CARGO_BINSTALL_EXECUTABLE cargo-binstall)
if(NOT CARGO_BINSTALL_EXECUTABLE)
execute_process(
COMMAND curl -L --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh
COMMAND bash
)
endif()
execute_process(
COMMAND cargo binstall -y cbindgen
)
endif()
2 changes: 1 addition & 1 deletion cmake/compile-flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ list(APPEND CMAKE_EXE_LINKER_FLAGS
list(JOIN CMAKE_EXE_LINKER_FLAGS " " CMAKE_EXE_LINKER_FLAGS)

list(APPEND CMAKE_CXX_FLAGS
-std=gnu++20 -Wall -Werror -Qunused-arguments -Wimplicit-fallthrough
-std=gnu++20 -Wall -Werror -Qunused-arguments -Wimplicit-fallthrough -Wno-unknown-warning-option
-fno-sized-deallocation -fno-aligned-new -mthread-model single
-fPIC -fno-rtti -fno-exceptions -fno-math-errno -pipe
-fno-omit-frame-pointer -funwind-tables -m32
Expand Down
Loading