diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5147099f..3d9f975d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - build: [release, debug, weval] + build: [release, debug, weval, nightmonkey] os: [ubuntu-latest] outputs: SM_TAG_EXISTS: ${{ steps.check-sm-release.outputs.SM_TAG_EXISTS }} @@ -32,6 +32,7 @@ jobs: 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 }} SM_CACHE_KEY_weval: ${{ steps.check-sm-release.outputs.SM_CACHE_KEY_weval }} + SM_CACHE_KEY_nightmonkey: ${{ steps.check-sm-release.outputs.SM_CACHE_KEY_nightmonkey }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -73,8 +74,9 @@ jobs: - name: Configure StarlingMonkey run: | cmake -S . -B cmake-build-${{ matrix.build }}\ - -DCMAKE_BUILD_TYPE=${{ (matrix.build == 'release' || matrix.build == 'weval') && 'Release' || 'Debug' }}\ - ${{matrix.build == 'weval' && '-DUSE_WASM_OPT=OFF -DWEVAL=ON' || ''}} + -DCMAKE_BUILD_TYPE=${{ (matrix.build == 'release' || matrix.build == 'weval' || matrix.build == 'nightmonkey') && 'Release' || 'Debug' }}\ + ${{matrix.build == 'weval' && '-DUSE_WASM_OPT=OFF -DWEVAL=ON' || ''}}\ + ${{matrix.build == 'nightmonkey' && '-DNIGHTMONKEY=ON' || ''}} - name: Build StarlingMonkey run: | @@ -102,7 +104,15 @@ jobs: 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 }} + if [ "${{ matrix.build }}" = "nightmonkey" ]; then + # The NightMonkey runtime is compiled against the engine's private + # headers and with the flags libjs was built with, which a + # --enable-external-compiler-hooks build exports alongside the + # public headers (see cmake/nightmonkey.cmake). + cp -aL cmake-build-${{ matrix.build }}/spidermonkey-obj/dist/include-private spidermonkey-dist-${{ matrix.build }}/ + cp -aL cmake-build-${{ matrix.build }}/spidermonkey-obj/dist/system_wrappers spidermonkey-dist-${{ matrix.build }}/ + fi + tree -L 2 spidermonkey-dist-${{ matrix.build }} # Upload tarball as an artifact of the github action run, so the output # can be inspected for pull requests. @@ -153,6 +163,14 @@ jobs: spidermonkey-dist-weval key: ${{ needs.test.outputs.SM_CACHE_KEY_weval }} fail-on-cache-miss: true + - name: Restore SpiderMonkey NightMonkey Cache + uses: actions/cache/restore@v4 + id: sm-cache-nightmonkey + with: + path: | + spidermonkey-dist-nightmonkey + key: ${{ needs.test.outputs.SM_CACHE_KEY_nightmonkey }} + fail-on-cache-miss: true - name: Create SpiderMonkey Tar Balls run: | @@ -160,6 +178,7 @@ jobs: 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/* tar -a -cf release-artifacts/spidermonkey-static-release_weval.tar.gz spidermonkey-dist-weval/* + tar -a -cf release-artifacts/spidermonkey-static-release_nightmonkey.tar.gz spidermonkey-dist-nightmonkey/* tree release-artifacts - name: Do the Release @@ -171,3 +190,52 @@ jobs: stability or availability guarantees. tag_name: ${{ needs.test.outputs.SM_TAG }} files: release-artifacts/* + + # The NightMonkey compiler is a host binary that transforms a wizened + # snapshot of the (NightMonkey-enabled) runtime. + nightmonkey-compilers: + name: NightMonkey compiler (${{ matrix.artifact }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact: nightmonkey-x86_64-linux + binary: nightmonkey + - os: ubuntu-24.04-arm + artifact: nightmonkey-aarch64-linux + binary: nightmonkey + - os: macos-14 + artifact: nightmonkey-aarch64-macos + binary: nightmonkey + - os: windows-2022 + artifact: nightmonkey-x86_64-windows.exe + binary: nightmonkey.exe + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Read NightMonkey pins + id: nightmonkey + shell: bash + run: | + revision="$(awk '/^set\(NIGHTMONKEY_TAG / {gsub(/set\(NIGHTMONKEY_TAG |\)/, ""); print}' cmake/nightmonkey.cmake)" + engine="$(awk '/^set\(NIGHTMONKEY_ENGINE_VERSION / {gsub(/"/, "", $2); print $2}' cmake/nightmonkey.cmake)" + echo "revision=$revision" >> "$GITHUB_OUTPUT" + echo "engine=$engine" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v4 + with: + repository: bytecodealliance/nightmonkey + ref: ${{ steps.nightmonkey.outputs.revision }} + path: nightmonkey + - name: Build compiler + shell: bash + run: | + cargo build --release -p nightmonkey --manifest-path nightmonkey/Cargo.toml \ + --no-default-features --features "${{ steps.nightmonkey.outputs.engine }}" + compiler="nightmonkey/target/release/${{ matrix.binary }}" + "$compiler" --help + cp "$compiler" "${{ matrix.artifact }}" + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 05b30f19..5fd68b5b 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -55,6 +55,7 @@ jobs: just mode=release builddir=build-release build starling just mode=relwithdebinfo builddir=build-debug build starling just mode=weval builddir=build-weval build starling-ics.wevalcache + just mode=nightmonkey builddir=build-nightmonkey build starling - name: Prepare Artifacts run: | @@ -64,6 +65,7 @@ jobs: cp build-release/starling-raw.wasm release-artifacts/starling-raw.wasm cp build-debug/starling-raw.wasm release-artifacts/starling-raw-debug.wasm cp build-weval/starling-raw.wasm release-artifacts/starling-raw-weval.wasm + cp build-nightmonkey/starling-raw.wasm release-artifacts/starling-raw-nightmonkey.wasm # Component-versions of the runtime cp build-release/starling.wasm release-artifacts/starling.wasm @@ -82,6 +84,60 @@ jobs: tag_name: ${{ needs.release-please.outputs.tag_name }} files: release-artifacts/* + # The NightMonkey compiler for each supported host, built as in the CI + # workflow (see the nightmonkey-compilers job in main.yml) and uploaded next + # to the starling-raw-nightmonkey.wasm runtime it belongs to. + build-nightmonkey-compilers: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact: nightmonkey-x86_64-linux + binary: nightmonkey + - os: ubuntu-24.04-arm + artifact: nightmonkey-aarch64-linux + binary: nightmonkey + - os: macos-14 + artifact: nightmonkey-aarch64-macos + binary: nightmonkey + - os: windows-2022 + artifact: nightmonkey-x86_64-windows.exe + binary: nightmonkey.exe + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Read NightMonkey pins + id: nightmonkey + shell: bash + run: | + revision="$(awk '/^set\(NIGHTMONKEY_TAG / {gsub(/set\(NIGHTMONKEY_TAG |\)/, ""); print}' cmake/nightmonkey.cmake)" + engine="$(awk '/^set\(NIGHTMONKEY_ENGINE_VERSION / {gsub(/"/, "", $2); print $2}' cmake/nightmonkey.cmake)" + echo "revision=$revision" >> "$GITHUB_OUTPUT" + echo "engine=$engine" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v4 + with: + repository: bytecodealliance/nightmonkey + ref: ${{ steps.nightmonkey.outputs.revision }} + path: nightmonkey + - name: Build compiler + shell: bash + run: | + cargo build --release -p nightmonkey --manifest-path nightmonkey/Cargo.toml \ + --no-default-features --features "${{ steps.nightmonkey.outputs.engine }}" + compiler="nightmonkey/target/release/${{ matrix.binary }}" + "$compiler" --help + cp "$compiler" "${{ matrix.artifact }}" + - name: Upload Release Asset + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 #2.3.2 + with: + tag_name: ${{ needs.release-please.outputs.tag_name }} + files: ${{ matrix.artifact }} + publish-extension: needs: release-please if: ${{ needs.release-please.outputs.debugger_release_created }} diff --git a/CMakeLists.txt b/CMakeLists.txt index a44ab177..5638ac41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ include("wasmtime") include("cbindgen") include("spidermonkey") +include("nightmonkey") include("openssl") include("${HOST_API}/host_api.cmake") include("build-crates") @@ -49,7 +50,7 @@ add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h run if (ENABLE_JS_DEBUGGER) target_compile_definitions(extension_api INTERFACE ENABLE_JS_DEBUGGER) endif() -target_link_libraries(extension_api INTERFACE rust-url spidermonkey) +target_link_libraries(extension_api INTERFACE rust-url spidermonkey nightmonkey) target_include_directories(extension_api INTERFACE include deps/include runtime) include("builtins") @@ -121,6 +122,12 @@ else() set(AOT 0) endif() +if(NIGHTMONKEY) + set(NIGHTMONKEY_AOT 1) +else() + set(NIGHTMONKEY_AOT 0) +endif() + set(RUNTIME_FILE "starling-raw.wasm") set(ADAPTER_FILE "preview1-adapter.wasm") configure_file("componentize.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/componentize.sh" @ONLY) diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 3258ba96..ffd94b39 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -12,6 +12,9 @@ #include #include #include +#ifdef ENABLE_JS_NIGHTMONKEY +# include "runtime/NightRegistration.h" +#endif #include #include @@ -493,6 +496,11 @@ static void dispatch_fetch_event(HandleObject event, double *total_compute) { } bool handle_incoming_request(host_api::HttpIncomingRequest *request) { +#ifdef ENABLE_JS_NIGHTMONKEY + if (!JS::NightActivate(ENGINE->cx())) { + return false; + } +#endif #ifdef DEBUG std::println(stderr, "Warning: Using a DEBUG build. Expect things to be SLOW."); #endif diff --git a/cmake/manage-git-source.cmake b/cmake/manage-git-source.cmake index 50cef2c9..047d1ea8 100644 --- a/cmake/manage-git-source.cmake +++ b/cmake/manage-git-source.cmake @@ -1,4 +1,5 @@ -# Function to manage git-based source dependencies with shallow cloning and tag management +# Function to manage git-based source dependencies with shallow cloning and tag management. +# TAG may be either a git tag name or a full 40-character commit hash. function(manage_git_source) cmake_parse_arguments( GIT_SRC @@ -17,19 +18,37 @@ function(manage_git_source) # Use file locking to prevent concurrent clone operations file(LOCK ${LOCK_FILE} GUARD FUNCTION) + # CMake's regex syntax has no bounded repetition, hence the length check. + string(LENGTH "${GIT_SRC_TAG}" GIT_SRC_TAG_LENGTH) + if(GIT_SRC_TAG_LENGTH EQUAL 40 AND GIT_SRC_TAG MATCHES "^[0-9a-f]+$") + set(GIT_SRC_IS_COMMIT TRUE) + else() + set(GIT_SRC_IS_COMMIT FALSE) + endif() + # Check if source directory already exists and has the correct tag set(NEED_CLONE TRUE) set(NEED_CHECKOUT FALSE) if(EXISTS ${GIT_SRC_SOURCE_DIR}/.git) - # Check current tag - execute_process( - COMMAND git -C ${GIT_SRC_SOURCE_DIR} describe --tags --exact-match HEAD - OUTPUT_VARIABLE CURRENT_TAG - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE TAG_CHECK_RESULT - ) + # Check current revision + if(GIT_SRC_IS_COMMIT) + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} rev-parse HEAD + OUTPUT_VARIABLE CURRENT_TAG + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE TAG_CHECK_RESULT + ) + else() + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} describe --tags --exact-match HEAD + OUTPUT_VARIABLE CURRENT_TAG + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE TAG_CHECK_RESULT + ) + endif() if(TAG_CHECK_RESULT EQUAL 0 AND CURRENT_TAG STREQUAL ${GIT_SRC_TAG}) set(NEED_CLONE FALSE) @@ -49,50 +68,86 @@ function(manage_git_source) file(REMOVE_RECURSE ${GIT_SRC_SOURCE_DIR}) endif() - # Perform shallow clone of specific tag - execute_process( - COMMAND git clone --depth 1 --branch ${GIT_SRC_TAG} - ${GIT_SRC_REPO_URL} - ${GIT_SRC_SOURCE_DIR} - RESULT_VARIABLE CLONE_RESULT - ERROR_VARIABLE CLONE_ERROR - ) + if(GIT_SRC_IS_COMMIT) + # `git clone --branch` only accepts branch and tag names, so a + # commit is fetched into a fresh repository instead. + file(MAKE_DIRECTORY ${GIT_SRC_SOURCE_DIR}) + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} init --quiet + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} remote add origin ${GIT_SRC_REPO_URL} + COMMAND_ERROR_IS_FATAL ANY + ) + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} fetch --depth 1 origin ${GIT_SRC_TAG} + RESULT_VARIABLE CLONE_RESULT + ERROR_VARIABLE CLONE_ERROR + ) + if(CLONE_RESULT EQUAL 0) + execute_process( + COMMAND git -C ${GIT_SRC_SOURCE_DIR} checkout --detach FETCH_HEAD + RESULT_VARIABLE CLONE_RESULT + ERROR_VARIABLE CLONE_ERROR + ) + endif() + else() + # Perform shallow clone of specific tag + execute_process( + COMMAND git clone --depth 1 --branch ${GIT_SRC_TAG} + ${GIT_SRC_REPO_URL} + ${GIT_SRC_SOURCE_DIR} + RESULT_VARIABLE CLONE_RESULT + ERROR_VARIABLE CLONE_ERROR + ) + endif() if(NOT CLONE_RESULT EQUAL 0) message(FATAL_ERROR "Failed to clone ${GIT_SRC_NAME} source: ${CLONE_ERROR}") endif() elseif(NEED_CHECKOUT) - # Check if the tag already exists locally + # Check if the revision already exists locally + if(GIT_SRC_IS_COMMIT) + set(GIT_SRC_REF "${GIT_SRC_TAG}^{commit}") + else() + set(GIT_SRC_REF "refs/tags/${GIT_SRC_TAG}") + endif() execute_process( - COMMAND git -C ${GIT_SRC_SOURCE_DIR} rev-parse --verify "refs/tags/${GIT_SRC_TAG}" + COMMAND git -C ${GIT_SRC_SOURCE_DIR} rev-parse --verify --quiet "${GIT_SRC_REF}" OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE TAG_EXISTS_RESULT ) if(NOT TAG_EXISTS_RESULT EQUAL 0) - # Tag doesn't exist locally, fetch it - message(STATUS "Fetching tag ${GIT_SRC_TAG}") + # Revision doesn't exist locally, fetch it + message(STATUS "Fetching ${GIT_SRC_TAG}") + if(GIT_SRC_IS_COMMIT) + set(FETCH_REF ${GIT_SRC_TAG}) + else() + set(FETCH_REF tag ${GIT_SRC_TAG}) + endif() execute_process( - COMMAND git -C ${GIT_SRC_SOURCE_DIR} fetch --depth 1 origin tag ${GIT_SRC_TAG} + COMMAND git -C ${GIT_SRC_SOURCE_DIR} fetch --depth 1 ${GIT_SRC_REPO_URL} ${FETCH_REF} RESULT_VARIABLE FETCH_RESULT ERROR_VARIABLE FETCH_ERROR ) if(NOT FETCH_RESULT EQUAL 0) - message(FATAL_ERROR "Failed to fetch tag ${GIT_SRC_TAG}: ${FETCH_ERROR}") + message(FATAL_ERROR "Failed to fetch ${GIT_SRC_TAG}: ${FETCH_ERROR}") endif() endif() - # Checkout the tag (whether it was already local or just fetched) + # Checkout the revision (whether it was already local or just fetched) execute_process( - COMMAND git -C ${GIT_SRC_SOURCE_DIR} checkout ${GIT_SRC_TAG} + COMMAND git -C ${GIT_SRC_SOURCE_DIR} checkout --detach ${GIT_SRC_TAG} RESULT_VARIABLE CHECKOUT_RESULT ERROR_VARIABLE CHECKOUT_ERROR ) if(NOT CHECKOUT_RESULT EQUAL 0) - message(FATAL_ERROR "Failed to checkout tag ${GIT_SRC_TAG}: ${CHECKOUT_ERROR}") + message(FATAL_ERROR "Failed to checkout ${GIT_SRC_TAG}: ${CHECKOUT_ERROR}") endif() endif() endfunction() diff --git a/cmake/night-runtime-compile.cmake b/cmake/night-runtime-compile.cmake new file mode 100644 index 00000000..4e200ccd --- /dev/null +++ b/cmake/night-runtime-compile.cmake @@ -0,0 +1,106 @@ +# Compiles one NightMonkey runtime source file against SpiderMonkey's private +# headers, with the flags libjs itself was compiled with, so that the object, +# shape, string and script layouts the runtime bakes into its code match the +# engine it is linked with. Both come from dist/include-private, which a +# `--enable-external-compiler-hooks` build of SpiderMonkey exports: +# js-build-config.json records the flags, and the headers mirror js/src. +# +# Run in script mode from cmake/nightmonkey.cmake: +# cmake -DCXX= -DSPIDERMONKEY_DIST= -DNIGHTMONKEY_SOURCE_DIR= +# -DSOURCE= -DOBJECT= [-DDEPFILE=] +# -P night-runtime-compile.cmake +# +# This mirrors the flag handling of NightMonkey's own CMakeLists.txt, except +# that the compiler is the one StarlingMonkey builds with (the same one that +# built libjs) rather than the path recorded in the JSON, which does not +# survive being moved to another machine as pre-built artifacts. +cmake_minimum_required(VERSION 3.27) + +foreach(var CXX SPIDERMONKEY_DIST NIGHTMONKEY_SOURCE_DIR SOURCE OBJECT) + if (NOT DEFINED ${var}) + message(FATAL_ERROR "night-runtime-compile.cmake: ${var} is required") + endif() +endforeach() + +set(SM_CONFIG_JSON "${SPIDERMONKEY_DIST}/include-private/js-build-config.json") +if (NOT EXISTS "${SM_CONFIG_JSON}") + message(FATAL_ERROR "${SM_CONFIG_JSON} not found: SpiderMonkey must be built with --enable-external-compiler-hooks") +endif() +file(READ "${SM_CONFIG_JSON}" SM_JSON) + +function(sm_json_list out key) + string(JSON n LENGTH "${SM_JSON}" ${key}) + set(result) + if (n GREATER 0) + math(EXPR last "${n} - 1") + foreach(i RANGE ${last}) + string(JSON v GET "${SM_JSON}" ${key} ${i}) + list(APPEND result "${v}") + endforeach() + endif() + set(${out} "${result}" PARENT_SCOPE) +endfunction() + +sm_json_list(SM_CXX_BASE_FLAGS cxx_base_flags) +sm_json_list(SM_OS_CXXFLAGS os_cxxflags) +sm_json_list(SM_OPTIMIZE_FLAGS optimize_flags) +sm_json_list(SM_DEBUG_FLAGS debug_flags) +sm_json_list(SM_EXTRA_CXXFLAGS extra_cxxflags) +sm_json_list(SM_WARNINGS_CXXFLAGS warnings_cxxflags) +sm_json_list(SM_DEBUG_DEFINES debug_defines) +sm_json_list(SM_LIBRARY_DEFINES library_defines) +sm_json_list(SM_FORCE_INCLUDES force_includes) +sm_json_list(SM_INCLUDE_DIRS include_dirs) + +# The base flags are the target, standard and (if one was configured) the +# sysroot. A sysroot recorded on another machine is dropped in favor of the +# compiler's own. +set(FLAGS) +set(skip_next FALSE) +foreach(flag ${SM_CXX_BASE_FLAGS}) + if (skip_next) + set(skip_next FALSE) + if (EXISTS "${flag}") + list(APPEND FLAGS "--sysroot" "${flag}") + endif() + elseif (flag STREQUAL "--sysroot") + set(skip_next TRUE) + elseif (flag MATCHES "^--sysroot=(.*)$") + if (EXISTS "${CMAKE_MATCH_1}") + list(APPEND FLAGS "${flag}") + endif() + else() + list(APPEND FLAGS "${flag}") + endif() +endforeach() + +list(APPEND FLAGS ${SM_OS_CXXFLAGS} ${SM_OPTIMIZE_FLAGS} ${SM_DEBUG_FLAGS} ${SM_EXTRA_CXXFLAGS}) +foreach(define ${SM_DEBUG_DEFINES}) + list(APPEND FLAGS "-D${define}=1") +endforeach() +foreach(define ${SM_LIBRARY_DEFINES}) + list(APPEND FLAGS "-D${define}") +endforeach() +list(APPEND FLAGS "-DENABLE_JS_NIGHTMONKEY=1") +# The configuration headers every engine TU sees. +foreach(header ${SM_FORCE_INCLUDES}) + list(APPEND FLAGS "-include" "${SPIDERMONKEY_DIST}/include-private/${header}") +endforeach() +list(APPEND FLAGS "-I${NIGHTMONKEY_SOURCE_DIR}") +foreach(dir ${SM_INCLUDE_DIRS}) + list(APPEND FLAGS "-I${SPIDERMONKEY_DIST}/${dir}") +endforeach() +# The engine's own warning set (its headers are not clean under a plain -Wall), +# plus what the runtime's sources need. +list(APPEND FLAGS ${SM_WARNINGS_CXXFLAGS} -Wno-invalid-offsetof -Wno-unused-private-field) +if (DEFINED DEPFILE) + list(APPEND FLAGS -MD -MF "${DEPFILE}") +endif() + +execute_process( + COMMAND ${CXX} ${FLAGS} -c "${SOURCE}" -o "${OBJECT}" + RESULT_VARIABLE result +) +if (NOT result EQUAL 0) + message(FATAL_ERROR "Compiling ${SOURCE} failed") +endif() diff --git a/cmake/nightmonkey.cmake b/cmake/nightmonkey.cmake new file mode 100644 index 00000000..c1db3b31 --- /dev/null +++ b/cmake/nightmonkey.cmake @@ -0,0 +1,160 @@ +# NightMonkey: an ahead-of-time JS-to-Wasm compiler layered on top of +# SpiderMonkey (https://github.com/bytecodealliance/nightmonkey). +# +# NightMonkey has two halves, both built here from a pinned checkout of its +# repository: +# +# - The runtime (`runtime/*.cpp` in the NightMonkey tree): the helpers that +# compiled code calls into, the snapshot registration and activation, and the +# engine's external compiler hook table. It is compiled against the private +# headers and with the exact flags of the SpiderMonkey build it is linked +# with, both of which a `--enable-external-compiler-hooks` build exports under +# dist/include-private, and linked into starling-raw.wasm. +# - The `nightmonkey` host binary: a cargo build of the compiler that +# transforms a wizened snapshot of the runtime into one with compiled bodies. +# componentize.sh runs it when `--enable-nightmonkey` is passed. +# +# The compiler and the runtime share layout facts with the engine, so all +# three must come from the same trees: the NightMonkey pin below and the +# SpiderMonkey pin in cmake/spidermonkey.cmake move together. The compiler +# carries a checked-in opcode table per engine version, and the build checks +# the selected one against the engine's Opcodes.h (below). +set(NIGHTMONKEY_TAG 751695b96cb77becbd8be5d2a5497f7ba1d54fe2) +set(NIGHTMONKEY_REPO_URL https://github.com/bytecodealliance/nightmonkey.git) +set(NIGHTMONKEY_ENGINE_VERSION "ff147" CACHE STRING + "Engine version NightMonkey is built for (compiler/src/opcodes/.rs in its tree)") + +include("manage-git-source") + +# Everything that links against SpiderMonkey also links against this target; +# it is empty unless NIGHTMONKEY is enabled. +add_library(nightmonkey INTERFACE) + +if (NOT NIGHTMONKEY) + return() +endif() + +# Like SM_SOURCE_DIR, this can point at a local checkout for in-tree +# development of NightMonkey itself. +set(NIGHTMONKEY_SOURCE_DIR "${CMAKE_SOURCE_DIR}/deps/nightmonkey-source" CACHE PATH + "Path to a local NightMonkey source checkout") +if (NIGHTMONKEY_SOURCE_DIR STREQUAL "${CMAKE_SOURCE_DIR}/deps/nightmonkey-source") + manage_git_source( + NAME nightmonkey + REPO_URL ${NIGHTMONKEY_REPO_URL} + TAG ${NIGHTMONKEY_TAG} + SOURCE_DIR ${NIGHTMONKEY_SOURCE_DIR} + ) +elseif (NOT EXISTS "${NIGHTMONKEY_SOURCE_DIR}/runtime/Night.h") + message(FATAL_ERROR "NIGHTMONKEY_SOURCE_DIR does not contain a NightMonkey checkout: ${NIGHTMONKEY_SOURCE_DIR}") +else() + message(STATUS "Using NightMonkey source from ${NIGHTMONKEY_SOURCE_DIR}") +endif() + +# The SpiderMonkey dist/ directory: `include/`, `include-private/` (with +# js-build-config.json) and `system_wrappers/`, whether built from source or +# unpacked from pre-built artifacts. +set(NIGHTMONKEY_SM_DIST ${SM_LIB_DIR}) +set(NIGHTMONKEY_OBJ_DIR ${CMAKE_CURRENT_BINARY_DIR}/nightmonkey-obj) +file(MAKE_DIRECTORY ${NIGHTMONKEY_OBJ_DIR}) + +# --- the opcode table check -------------------------------------------------- +# +# NightMonkey's compiler does not read the engine at build time: its opcode +# table is generated ahead of time and checked in per engine version, +# selected by the cargo feature of the same name. This verifies that the +# selected table is what the SpiderMonkey being built against generates, so +# an engine whose bytecode differs is refused rather than miscompiled. The +# runtime and compiler builds depend on it (and it on the engine build). +find_program(NIGHTMONKEY_PYTHON python3 REQUIRED DOC "python3, for NightMonkey's scripts/gen_opcodes.py") +add_custom_target(nightmonkey_opcodes_check + COMMAND ${NIGHTMONKEY_PYTHON} ${NIGHTMONKEY_SOURCE_DIR}/scripts/gen_opcodes.py check + ${NIGHTMONKEY_ENGINE_VERSION} ${NIGHTMONKEY_SM_DIST}/include-private/vm/Opcodes.h + COMMENT "Checking NightMonkey's ${NIGHTMONKEY_ENGINE_VERSION} opcode table against SpiderMonkey's Opcodes.h" + VERBATIM +) +if (TARGET spidermonkey_build) + add_dependencies(nightmonkey_opcodes_check spidermonkey_build) +endif() + +# --- the runtime ------------------------------------------------------------- +# +# The compile flags come from js-build-config.json, which only exists once +# SpiderMonkey has been built, so each source is compiled by a script that +# reads them at build time (cmake/night-runtime-compile.cmake). The engine +# build is ordered first through a target-level dependency (a file-level one +# on the engine library would make the Makefile generator run the engine build +# once per consuming target); the headers each object actually depends on, +# including the engine's, are tracked through the compiler's depfile. +file(GLOB NIGHT_RUNTIME_SOURCES ${NIGHTMONKEY_SOURCE_DIR}/runtime/*.cpp) +# The in-process compilation lane (the jit-test harness) is not part of the +# snapshot flow. +list(FILTER NIGHT_RUNTIME_SOURCES EXCLUDE REGEX "/NightInproc[A-Za-z]*\\.cpp$") +set(NIGHT_RUNTIME_COMPILE_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/night-runtime-compile.cmake) + +set(NIGHT_RUNTIME_OBJS) +foreach(source ${NIGHT_RUNTIME_SOURCES}) + get_filename_component(name ${source} NAME_WE) + set(object ${NIGHTMONKEY_OBJ_DIR}/${name}.o) + add_custom_command( + OUTPUT ${object} + COMMAND ${CMAKE_COMMAND} + -DCXX=${CMAKE_CXX_COMPILER} + -DSPIDERMONKEY_DIST=${NIGHTMONKEY_SM_DIST} + -DNIGHTMONKEY_SOURCE_DIR=${NIGHTMONKEY_SOURCE_DIR} + -DSOURCE=${source} + -DOBJECT=${object} + -DDEPFILE=${object}.d + -P ${NIGHT_RUNTIME_COMPILE_SCRIPT} + DEPENDS ${source} ${NIGHT_RUNTIME_COMPILE_SCRIPT} + DEPFILE ${object}.d + COMMENT "Compiling NightMonkey runtime: ${name}.cpp" + VERBATIM + ) + list(APPEND NIGHT_RUNTIME_OBJS ${object}) +endforeach() + +set(NIGHT_RUNTIME_LIB ${NIGHTMONKEY_OBJ_DIR}/libnight_runtime.a) +add_custom_command( + OUTPUT ${NIGHT_RUNTIME_LIB} + COMMAND ${CMAKE_COMMAND} -E rm -f ${NIGHT_RUNTIME_LIB} + COMMAND ${CMAKE_AR} qcs ${NIGHT_RUNTIME_LIB} ${NIGHT_RUNTIME_OBJS} + DEPENDS ${NIGHT_RUNTIME_OBJS} + COMMENT "Creating NightMonkey runtime library" + VERBATIM +) +add_custom_target(night_runtime_build DEPENDS ${NIGHT_RUNTIME_LIB}) +add_dependencies(night_runtime_build nightmonkey_opcodes_check) + +# --- the compiler ------------------------------------------------------------ +find_program(CARGO_BIN cargo REQUIRED DOC "cargo, for building the NightMonkey compiler") +set(NIGHTMONKEY_CARGO_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}/nightmonkey-cargo) +set(NIGHTMONKEY_BIN "${NIGHTMONKEY_CARGO_TARGET_DIR}/release/nightmonkey" CACHE FILEPATH + "Path to the NightMonkey compiler" FORCE) +add_custom_target(nightmonkey_compiler + # Self-contained apart from the engine version, which selects the + # checked-in opcode table. + COMMAND ${CARGO_BIN} build --release -p nightmonkey + --no-default-features --features ${NIGHTMONKEY_ENGINE_VERSION} + --manifest-path ${NIGHTMONKEY_SOURCE_DIR}/Cargo.toml + --target-dir ${NIGHTMONKEY_CARGO_TARGET_DIR} + BYPRODUCTS ${NIGHTMONKEY_BIN} + # Run from the StarlingMonkey tree so rustup picks up its rust-toolchain.toml + # (which is not CMAKE_SOURCE_DIR when StarlingMonkey is a subproject). + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/.. + COMMENT "Building the NightMonkey compiler" + VERBATIM +) +add_dependencies(nightmonkey_compiler nightmonkey_opcodes_check) + +# --- the interface target ---------------------------------------------------- +add_dependencies(nightmonkey night_runtime_build nightmonkey_compiler) +# `runtime/Night.h` etc. are included from the NightMonkey tree, and gate their +# declarations on ENABLE_JS_NIGHTMONKEY. +target_include_directories(nightmonkey INTERFACE ${NIGHTMONKEY_SOURCE_DIR}) +target_compile_definitions(nightmonkey INTERFACE ENABLE_JS_NIGHTMONKEY=1) +# The whole runtime is linked in: the compiled bodies the snapshot transform +# appends call its exported helpers by name, so nothing in it may be dropped as +# unreferenced. +target_link_libraries(nightmonkey INTERFACE + -Wl,--whole-archive ${NIGHT_RUNTIME_LIB} -Wl,--no-whole-archive) diff --git a/cmake/spidermonkey.cmake b/cmake/spidermonkey.cmake index 7ce5cf82..1605dcdc 100644 --- a/cmake/spidermonkey.cmake +++ b/cmake/spidermonkey.cmake @@ -1,4 +1,8 @@ -set(SM_TAG FIREFOX_147_0_4_RELEASE_STARLING) +# A tag or full commit hash in SM_REPO_URL. The NightMonkey build needs a +# revision that carries `--enable-external-compiler-hooks` (the `wasi-ff147` +# branch). +set(SM_TAG f0c060c52260d41fc631a14cda05372ad1c2cbe1) +set(SM_REPO_URL https://github.com/bytecodealliance/firefox.git) include("manage-git-source") @@ -9,9 +13,16 @@ else() endif() option(WEVAL "Build with a SpiderMonkey variant that supports weval-based AOT compilation" OFF) +option(NIGHTMONKEY "Build with NightMonkey AOT compilation support (see cmake/nightmonkey.cmake)" OFF) + +if (WEVAL AND NIGHTMONKEY) + message(FATAL_ERROR "WEVAL and NIGHTMONKEY cannot be enabled together") +endif() if (WEVAL) set(SM_BUILD_TYPE "${SM_BUILD_TYPE}_weval") +elseif (NIGHTMONKEY) + set(SM_BUILD_TYPE "${SM_BUILD_TYPE}_nightmonkey") endif() # If the developer has specified an alternate local set of SpiderMonkey @@ -49,20 +60,33 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/null.cpp "") if (DEFINED SM_LIB_DIR) set(SM_INCLUDE_DIR ${SM_LIB_DIR}/include) + # NightMonkey's runtime is compiled against the engine's private headers, + # which a `--enable-external-compiler-hooks` build exports next to the + # public ones (see cmake/nightmonkey.cmake). + if (NIGHTMONKEY AND NOT EXISTS ${SM_LIB_DIR}/include-private/js-build-config.json) + message(FATAL_ERROR "The pre-built SpiderMonkey artifacts in ${SM_LIB_DIR} do not include the \ + private headers NightMonkey needs (include-private/). Use artifacts from a NightMonkey \ + build, or unset SPIDERMONKEY_BINARIES to build SpiderMonkey from source.") + endif() + add_library(spidermonkey INTERFACE) target_include_directories(spidermonkey INTERFACE ${SM_INCLUDE_DIR}) target_link_libraries(spidermonkey INTERFACE ${SM_LIB_DIR}/libspidermonkey.a) else() - # Clone SpiderMonkey source using git directly for shallow clone - # Use deps folder in project root for shared access across build directories - set(SM_SOURCE_DIR ${CMAKE_SOURCE_DIR}/deps/spidermonkey-source) - - manage_git_source( - NAME spidermonkey - REPO_URL https://github.com/bytecodealliance/firefox.git - TAG ${SM_TAG} - SOURCE_DIR ${SM_SOURCE_DIR} - ) + set(SM_SOURCE_DIR "${CMAKE_SOURCE_DIR}/deps/spidermonkey-source" CACHE PATH + "Path to a local Firefox source checkout") + if (SM_SOURCE_DIR STREQUAL "${CMAKE_SOURCE_DIR}/deps/spidermonkey-source") + manage_git_source( + NAME spidermonkey + REPO_URL ${SM_REPO_URL} + TAG ${SM_TAG} + SOURCE_DIR ${SM_SOURCE_DIR} + ) + elseif (NOT EXISTS "${SM_SOURCE_DIR}/mach") + message(FATAL_ERROR "SM_SOURCE_DIR does not contain a Firefox checkout: ${SM_SOURCE_DIR}") + else() + message(STATUS "Using SpiderMonkey source from ${SM_SOURCE_DIR}") + endif() # Each build configuration gets its own object directory set(SM_OBJ_DIR ${CMAKE_CURRENT_BINARY_DIR}/spidermonkey-obj) @@ -151,6 +175,11 @@ mk_add_options AUTOCLOBBER=1 string(APPEND MOZCONFIG_CONTENT "ac_add_options --enable-aot-ics\n") string(APPEND MOZCONFIG_CONTENT "ac_add_options --enable-aot-ics-force\n") string(APPEND MOZCONFIG_CONTENT "ac_add_options --enable-pbl-weval\n") + elseif(NIGHTMONKEY) + # The external compiler hook surface NightMonkey plugs into, plus the + # export of the engine's private headers and compile flags that its + # runtime is built against. + string(APPEND MOZCONFIG_CONTENT "ac_add_options --enable-external-compiler-hooks\n") endif() file(GENERATE OUTPUT ${MOZCONFIG} CONTENT "${MOZCONFIG_CONTENT}") diff --git a/componentize.sh.in b/componentize.sh.in index c51b0d71..585e7862 100755 --- a/componentize.sh.in +++ b/componentize.sh.in @@ -1,26 +1,53 @@ #!/usr/bin/env bash -#set -euo pipefail +set -euo pipefail wizer="${WIZER:-@WASMTIME_DIR@/wasmtime wizer}" wasm_tools="${WASM_TOOLS:-@WASM_TOOLS_BIN@}" weval="${WEVAL:-@WEVAL_BIN@}" +configured_nightmonkey="@NIGHTMONKEY_BIN@" aot=@AOT@ +nightmonkey_aot=@NIGHTMONKEY_AOT@ preopen_dir="${PREOPEN_DIR:-}" usage() { - echo "Usage: $(basename "$0") [--verbose] [-i,--initializer-script-path path] [--strip-path-prefix prefix] [--legacy-script] [input.js] [-o output.wasm]" + echo "Usage: $(basename "$0") [--verbose] [--enable-nightmonkey] [-i,--initializer-script-path path] [--strip-path-prefix prefix] [--legacy-script] [input.js] [-o output.wasm]" echo " Providing an input file but no output uses the input base name with a .wasm extension" echo " Providing an output file but no input creates a component without running any top-level script" echo " Specifying '--verbose' causes the detailed output during initialization and execution" echo " Specifying '-i' or '--initializer-script-path' allows specifying an initializer script" echo " Specifying '--strip-path-prefix' will cause the provided prefix to be stripped from paths in stack traces and the debugger" echo " Specifying '--legacy-script' causes evaluation as a legacy JS script instead of a module" + echo " Specifying '--enable-nightmonkey' AOT-compiles the wizened core module with NightMonkey" + echo " Set NIGHTMONKEY_FLAGS (e.g. '--stats') to pass extra diagnostic options to the NightMonkey compiler" echo " Specifying '--wpt-mode' enables WPT compatibility mode" echo " Specifying '--init-location url' allows setting the URL to use for 'globalThis.location' during initialization" exit 1 } +find_nightmonkey() { + if [[ -n ${NIGHTMONKEY:-} ]]; then + printf '%s\n' "$NIGHTMONKEY" + return + fi + if [[ -n $configured_nightmonkey && -x $configured_nightmonkey ]]; then + printf '%s\n' "$configured_nightmonkey" + return + fi + + local platform machine artifact + platform="$(uname -s)" + machine="$(uname -m)" + case "$platform:$machine" in + Linux:x86_64) artifact=nightmonkey-x86_64-linux ;; + Linux:aarch64|Linux:arm64) artifact=nightmonkey-aarch64-linux ;; + Darwin:arm64) artifact=nightmonkey-aarch64-macos ;; + MINGW*:x86_64|MSYS*:x86_64|CYGWIN*:x86_64) artifact=nightmonkey-x86_64-windows.exe ;; + *) echo "No released NightMonkey compiler is available for $platform/$machine; set NIGHTMONKEY" >&2; return 1 ;; + esac + printf '%s/%s\n' "$(dirname "$0")" "$artifact" +} + if [ $# -lt 1 ] then usage @@ -31,10 +58,15 @@ OUT_FILE="" LEGACY_SCRIPT_PARAM="" STARLING_ARGS="" VERBOSE=0 +ENABLE_NIGHTMONKEY=0 while [ $# -gt 0 ] do case "$1" in + --enable-nightmonkey) + ENABLE_NIGHTMONKEY=1 + shift + ;; --legacy-script) LEGACY_SCRIPT_PARAM="$1 " IN_FILE="$2" @@ -83,6 +115,27 @@ then usage fi +nightmonkey="" +if [[ $ENABLE_NIGHTMONKEY -ne 0 ]]; then + if [[ $nightmonkey_aot -eq 0 ]]; then + echo "This runtime was not built with NightMonkey support" >&2 + exit 1 + fi + if [[ $aot -ne 0 ]]; then + echo "NightMonkey and weval cannot be enabled together" >&2 + exit 1 + fi + if [[ -z $IN_FILE ]]; then + echo "NightMonkey requires an input script to snapshot" >&2 + exit 1 + fi + nightmonkey="$(find_nightmonkey)" + if [[ ! -x $nightmonkey ]]; then + echo "NightMonkey compiler not found or not executable: $nightmonkey; set NIGHTMONKEY to the matching compiler from this StarlingMonkey release" >&2 + exit 1 + fi +fi + # Use the -o param as output file if provided, otherwise use the input base name with a .wasm # extension. if [ -z "$OUT_FILE" ] @@ -122,6 +175,33 @@ if [[ -n "$IN_FILE" ]]; then $preopen_dir -o "$OUT_FILE" \ "$(dirname "$0")/starling-raw.wasm" fi + + if [[ $ENABLE_NIGHTMONKEY -ne 0 ]]; then + SNAPSHOT_FILE="$(mktemp "${TMPDIR:-/tmp}/nightmonkey-snapshot.XXXXXX")" + COMPILED_FILE="$(mktemp "${TMPDIR:-/tmp}/nightmonkey-output.XXXXXX")" + cleanup_nightmonkey() { + rm -f "$SNAPSHOT_FILE" "$COMPILED_FILE" + } + trap cleanup_nightmonkey EXIT + NIGHTMONKEY_ARGS=() + if [[ $VERBOSE -ne 0 ]]; then + NIGHTMONKEY_ARGS+=(--keep-names) + fi + # Compiler diagnostics (e.g. `--stats`) are opt-in via NIGHTMONKEY_FLAGS + # rather than tied to --verbose, since they are very noisy. + if [[ -n ${NIGHTMONKEY_FLAGS:-} ]]; then + read -r -a NIGHTMONKEY_EXTRA_ARGS <<< "$NIGHTMONKEY_FLAGS" + NIGHTMONKEY_ARGS+=("${NIGHTMONKEY_EXTRA_ARGS[@]}") + fi + cp "$OUT_FILE" "$SNAPSHOT_FILE" + if [[ -n ${NIGHTMONKEY_KEEP_SNAPSHOT:-} ]]; then + cp "$SNAPSHOT_FILE" "$NIGHTMONKEY_KEEP_SNAPSHOT" + fi + "$nightmonkey" "${NIGHTMONKEY_ARGS[@]}" "$SNAPSHOT_FILE" -o "$COMPILED_FILE" + mv "$COMPILED_FILE" "$OUT_FILE" + cleanup_nightmonkey + trap - EXIT + fi else echo "Creating runtime-eval component $OUT_FILE" cp "$(dirname "$0")/starling-raw.wasm" "$OUT_FILE" diff --git a/justfile b/justfile index f0be229b..d71194db 100644 --- a/justfile +++ b/justfile @@ -20,13 +20,13 @@ default: build target="all" *flags: #!/usr/bin/env bash set -euo pipefail - echo 'Setting build directory to {{ builddir }}, build type {{ if mode == "weval" { "Release (weval)" } else { capitalize(mode) } }}' + echo 'Setting build directory to {{ builddir }}, build type {{ if mode == "weval" { "Release (weval)" } else if mode == "nightmonkey" { "Release (NightMonkey)" } else { capitalize(mode) } }}' # Only run configure step if build directory doesn't exist yet if ! {{ path_exists(builddir) }} || {{ reconfigure }} = 'true'; then cmake -S . -B {{ builddir }} {{ flags }} \ - -DCMAKE_BUILD_TYPE={{ if mode == "weval" { "Release" } else { capitalize(mode) } }} \ - {{ if mode == "weval" { "-DUSE_WASM_OPT=OFF -DWEVAL=ON" } else { "" } }} + -DCMAKE_BUILD_TYPE={{ if mode == "weval" { "Release" } else if mode == "nightmonkey" { "Release" } else { capitalize(mode) } }} \ + {{ if mode == "weval" { "-DUSE_WASM_OPT=OFF -DWEVAL=ON" } else if mode == "nightmonkey" { "-DNIGHTMONKEY=ON" } else { "" } }} else echo 'build directory already exists, skipping cmake configure' fi diff --git a/runtime/engine.cpp b/runtime/engine.cpp index e9da55dd..1feabb93 100644 --- a/runtime/engine.cpp +++ b/runtime/engine.cpp @@ -7,6 +7,10 @@ #include "js/CompilationAndEvaluation.h" #include "js/Modules.h" +#ifdef ENABLE_JS_NIGHTMONKEY +# include "runtime/Night.h" +# include "runtime/NightHooks.h" +#endif #include "js/ForOfIterator.h" #include "js/Initialization.h" #include "js/Promise.h" @@ -308,6 +312,11 @@ bool init_js(const EngineConfig& config) { if (!cx) { return false; } +#ifdef ENABLE_JS_NIGHTMONKEY + // NightMonkey plugs into the engine through its external compiler hook + // table, which has to be registered before any script runs. + js::night::NightInstallHooks(JS_GetRuntime(cx)); +#endif CONTEXT = cx; SCRIPT_VALUE.init(cx); @@ -524,6 +533,11 @@ const mozilla::Maybe &Engine::init_location() const { void Engine::finish_pre_initialization() { MOZ_ASSERT(state_ == EngineState::ScriptPreInitializing); +#ifdef ENABLE_JS_NIGHTMONKEY + if (!js::NightSnapshotCaptureHeap(cx())) { + abort("capturing NightMonkey snapshot state"); + } +#endif js::ResetMathRandomSeed(ENGINE->cx()); state_ = EngineState::Initialized; } diff --git a/runtime/js.cpp b/runtime/js.cpp index dd3a9774..be886b43 100644 --- a/runtime/js.cpp +++ b/runtime/js.cpp @@ -11,6 +11,9 @@ #include "wasi/api.h" #include "wasi/libc-environ.h" #include "wizer.h" +#ifdef ENABLE_JS_NIGHTMONKEY +# include "runtime/NightRegistration.h" +#endif #ifdef MEM_STATS #include #endif @@ -86,6 +89,11 @@ WIZER_INIT(wizen); * load the file `./index.js` and run it as the top-level module script. */ extern "C" bool exports_wasi_cli_run_run() { +#ifdef ENABLE_JS_NIGHTMONKEY + if (ENGINE && !JS::NightActivate(ENGINE->cx())) { + return false; + } +#endif auto arg_strings = host_api::environment_get_arguments(); std::vector args; args.reserve(arg_strings.size()); diff --git a/runtime/script_loader.cpp b/runtime/script_loader.cpp index e7d50eb9..4e30e19d 100644 --- a/runtime/script_loader.cpp +++ b/runtime/script_loader.cpp @@ -1,4 +1,9 @@ #include "script_loader.h" + +#ifdef ENABLE_JS_NIGHTMONKEY +# include "runtime/Night.h" +# include "runtime/NightRegistration.h" +#endif #include "encode.h" #include @@ -542,6 +547,16 @@ bool ScriptLoader::eval_top_level_script(std::string_view path, } // Execute the top-level module script. +#ifdef ENABLE_JS_NIGHTMONKEY + if (ENGINE->state() == api::EngineState::ScriptPreInitializing) { + JS::RootedScript root(cx, MODULE_MODE ? JS::GetModuleScript(module) : script.get()); + if (!root || !JS::NightRegisterRoot(cx, root, /* executedAtInit = */ true) || + !js::NightSnapshotCaptureExtras(cx, root)) { + return false; + } + } +#endif + if (!MODULE_MODE) { return JS_ExecuteScript(cx, script, result); } diff --git a/tests/e2e/nightmonkey-fallback/expect_serve_body.txt b/tests/e2e/nightmonkey-fallback/expect_serve_body.txt new file mode 100644 index 00000000..c60cf781 --- /dev/null +++ b/tests/e2e/nightmonkey-fallback/expect_serve_body.txt @@ -0,0 +1 @@ +fallback 42 diff --git a/tests/e2e/nightmonkey-fallback/nightmonkey-fallback.js b/tests/e2e/nightmonkey-fallback/nightmonkey-fallback.js new file mode 100644 index 00000000..23345f45 --- /dev/null +++ b/tests/e2e/nightmonkey-fallback/nightmonkey-fallback.js @@ -0,0 +1,7 @@ +function interpreted(value) { + return eval("value + 1"); +} + +addEventListener("fetch", event => { + event.respondWith(new Response(`fallback ${interpreted(41)}\n`)); +}); diff --git a/tests/e2e/runtime-err/expect_serve_stderr_nightmonkey.txt b/tests/e2e/runtime-err/expect_serve_stderr_nightmonkey.txt new file mode 100644 index 00000000..82d4e5a1 --- /dev/null +++ b/tests/e2e/runtime-err/expect_serve_stderr_nightmonkey.txt @@ -0,0 +1,4 @@ +stderr [0] :: Error while running request handler: runtime error +stderr [0] :: Caused by: error cause +stderr [0] :: Stack: +stderr [0] :: diff --git a/tests/test.sh b/tests/test.sh index 84f22d95..7849f9e4 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -15,6 +15,9 @@ test_serve_body_expectation="$test_dir/expect_serve_body.txt" test_serve_headers_expectation="$test_dir/expect_serve_headers.txt" test_serve_stdout_expectation="$test_dir/expect_serve_stdout.txt" test_serve_stderr_expectation="$test_dir/expect_serve_stderr.txt" +if [[ "$componentize_flags" == *--enable-nightmonkey* ]] && [ -f "$test_dir/expect_serve_stderr_nightmonkey.txt" ]; then + test_serve_stderr_expectation="$test_dir/expect_serve_stderr_nightmonkey.txt" +fi test_serve_status_expectation=$(cat "$test_dir/expect_serve_status.txt" 2> /dev/null || echo "200") body_log="$test_dir/body.log" diff --git a/tests/tests.cmake b/tests/tests.cmake index d7d6b086..e6356a3e 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -4,10 +4,16 @@ find_program(BASH_PROGRAM bash) include("wasmtime") include("weval") +if(NIGHTMONKEY) + set(TEST_COMPONENTIZE_FLAGS "--enable-nightmonkey") +else() + set(TEST_COMPONENTIZE_FLAGS "") +endif() + function(test_e2e TEST_NAME) get_target_property(RUNTIME_DIR starling-raw.wasm BINARY_DIR) add_test(e2e-${TEST_NAME} ${BASH_PROGRAM} ${CMAKE_SOURCE_DIR}/tests/test.sh ${RUNTIME_DIR} ${CMAKE_SOURCE_DIR}/tests/e2e/${TEST_NAME}) - set_property(TEST e2e-${TEST_NAME} PROPERTY ENVIRONMENT "WASMTIME=${WASMTIME};WASM_TOOLS=${WASM_TOOLS_DIR}/wasm-tools") + set_property(TEST e2e-${TEST_NAME} PROPERTY ENVIRONMENT "WASMTIME=${WASMTIME};WASM_TOOLS=${WASM_TOOLS_DIR}/wasm-tools;COMPONENTIZE_FLAGS=${TEST_COMPONENTIZE_FLAGS}") set_tests_properties(e2e-${TEST_NAME} PROPERTIES TIMEOUT 120) endfunction() @@ -31,7 +37,7 @@ function(integration_tests) add_custom_command( OUTPUT test-server.wasm WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND ${CMAKE_COMMAND} -E env "WASM_TOOLS=${WASM_TOOLS_DIR}/wasm-tools" env "PREOPEN_DIR=${CMAKE_SOURCE_DIR}/tests" ${RUNTIME_DIR}/componentize.sh ${TESTS_DIR}/test-server.js test-server.wasm + COMMAND ${CMAKE_COMMAND} -E env "WASM_TOOLS=${WASM_TOOLS_DIR}/wasm-tools" env "PREOPEN_DIR=${CMAKE_SOURCE_DIR}/tests" ${RUNTIME_DIR}/componentize.sh ${TEST_COMPONENTIZE_FLAGS} ${TESTS_DIR}/test-server.js test-server.wasm DEPENDS ${DEPS} VERBATIM ) @@ -61,6 +67,7 @@ test_e2e(teed-stream-as-outgoing-body) test_e2e(init-script) test_e2e(no-init-location) test_e2e(init-location) +test_e2e(nightmonkey-fallback) integration_tests( blob diff --git a/tests/wpt-harness/wpt.cmake b/tests/wpt-harness/wpt.cmake index a00b08cc..afe8eece 100644 --- a/tests/wpt-harness/wpt.cmake +++ b/tests/wpt-harness/wpt.cmake @@ -8,6 +8,8 @@ include("manage-git-source") if(WEVAL) set(COMPONENTIZE_FLAGS "--aot") +elseif(NIGHTMONKEY) + set(COMPONENTIZE_FLAGS "--enable-nightmonkey") else() set(COMPONENTIZE_FLAGS "") endif()