Skip to content

Commit 255d563

Browse files
Change Rust integration to emit a single static library (#212)
Instead of compiling multiple crates to static libs and trying to link all of them into the final binary, we now synthesize a crate that bundles all individual crates into a single staticlib. We maintain a single `Cargo.lock` file for this synthesized crate, which means that we can have a situation in which not all dependencies are tracked. That's "okay" in that we can live with the lockfile being best-effort. This also replaces linking SpiderMonkey's Rust static library with including the required crates into our own library, achieving an end result of having a single static Rust library to link, and enables thin-LTO for all libraries: this is in general useful, but even more relevant with more Rust code. Rolled into this is an update to newer version of Corrosion: the release of Rustup 1.28.0 broke the version of Corrosion we were previously using, so an update is required regardless of whatever else we do with our Rust integration. Since the newer version of Corrosion didn't work out-of-the-box with the previous Rust setup, I decided to fold it in here. And finally, the PR includes a bugfix in TextDecoder. The fixed issue is preexisting, but is only getting caught with the Rust changes. Either something changed in Rust's stdlib between 1.77.1 and 1.80.0, or the encoding_rs crate got bumped and changed in a way that exposes this issue whereas before it papered over it.
1 parent cd72251 commit 255d563

File tree

22 files changed

+744
-61
lines changed

22 files changed

+744
-61
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ jobs:
2727
steps:
2828
- uses: actions/checkout@v2
2929

30-
- name: Install Rust 1.77.1
30+
- name: Install Rust 1.80.0
3131
run: |
32-
rustup toolchain install 1.77.1
33-
rustup target add wasm32-wasi --toolchain 1.77.1
32+
rustup toolchain install 1.80.0
33+
rustup target add wasm32-wasip1 --toolchain 1.80.0
3434
3535
- uses: actions/setup-node@v2
3636
with:
@@ -58,10 +58,10 @@ jobs:
5858
with:
5959
submodules: recursive
6060

61-
- name: Install Rust 1.77.1
61+
- name: Install Rust 1.80.0
6262
run: |
63-
rustup toolchain install 1.77.1
64-
rustup target add wasm32-wasi --toolchain 1.77.1
63+
rustup toolchain install 1.80.0
64+
rustup target add wasm32-wasip1 --toolchain 1.80.0
6565
6666
- uses: actions/setup-node@v2
6767
with:

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
cmake_minimum_required(VERSION 3.27)
22
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
33
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
4+
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
45

56
if (DEFINED ENV{HOST_API})
67
set(HOST_API $ENV{HOST_API})
@@ -66,7 +67,7 @@ add_executable(starling-raw.wasm
6667
runtime/debugger.cpp
6768
)
6869

69-
target_link_libraries(starling-raw.wasm PRIVATE host_api extension_api builtins spidermonkey rust-url multipart)
70+
target_link_libraries(starling-raw.wasm PRIVATE host_api extension_api builtins spidermonkey rust-crates)
7071

7172
option(USE_WASM_OPT "use wasm-opt to optimize the StarlingMonkey binary" ON)
7273

@@ -119,11 +120,10 @@ endif()
119120

120121
set(RUNTIME_FILE "starling-raw.wasm")
121122
set(ADAPTER_FILE "preview1-adapter.wasm")
122-
configure_file("componentize.sh" "${CMAKE_CURRENT_BINARY_DIR}/componentize.sh" @ONLY)
123+
configure_file("componentize.sh.in" "${CMAKE_CURRENT_BINARY_DIR}/componentize.sh" @ONLY)
123124
if(EXISTS ${ADAPTER})
124125
configure_file(${ADAPTER} "${CMAKE_CURRENT_BINARY_DIR}/${ADAPTER_FILE}" COPYONLY)
125126
endif()
126-
configure_file(spin.toml spin.toml COPYONLY)
127127

128128
function(componentize OUTPUT)
129129
set(options)

builtins/web/text-codec/text-decoder.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,24 @@ bool TextDecoder::decode(JSContext *cx, unsigned argc, JS::Value *vp) {
1818

1919
auto source_value = args.get(0);
2020
std::optional<std::span<uint8_t>> src;
21+
uint8_t *src_ptr = nullptr;
2122

23+
// If the input is undefined, we use an empty buffer. We can't return early though,
24+
// because the decoder might have state that needs to be flushed in streaming mode.
2225
if (source_value.isUndefined()) {
2326
src = std::span<uint8_t, 0>();
27+
// Quoting from the encoding_rs docs:
28+
// `src` must be non-`NULL` even if `src_len` is zero. When`src_len` is zero,
29+
// it is OK for `src` to be something non-dereferencable, such as `0x1`.
30+
// Likewise for `dst` when `dst_len` is zero. This is required due to Rust's
31+
// optimization for slices within `Option`.
32+
src_ptr = reinterpret_cast<uint8_t *>(0x1);
2433
} else {
2534
src = value_to_buffer(cx, source_value, "TextDecoder#decode: input");
26-
}
27-
if (!src.has_value()) {
28-
return false;
35+
if (!src.has_value()) {
36+
return false;
37+
}
38+
src_ptr = src->data();
2939
}
3040

3141
bool stream = false;
@@ -60,14 +70,14 @@ bool TextDecoder::decode(JSContext *cx, unsigned argc, JS::Value *vp) {
6070
return false;
6171
}
6272
if (fatal) {
63-
result = jsencoding::decoder_decode_to_utf16_without_replacement(decoder, src->data(), &srcLen,
73+
result = jsencoding::decoder_decode_to_utf16_without_replacement(decoder, src_ptr, &srcLen,
6474
dest.get(), &destLen, !stream);
6575
if (result != 0) {
6676
return api::throw_error(cx, TextCodecErrors::DecodingFailed);
6777
}
6878
} else {
6979
bool hadReplacements;
70-
result = jsencoding::decoder_decode_to_utf16(decoder, src->data(), &srcLen, dest.get(),
80+
result = jsencoding::decoder_decode_to_utf16(decoder, src_ptr, &srcLen, dest.get(),
7181
&destLen, !stream, &hadReplacements);
7282
}
7383
MOZ_ASSERT(result == 0);

cmake/build-crates.cmake

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
1-
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/Cargo.toml NO_LINKER_OVERRIDE)
2-
set_property(TARGET rust-url PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/)
1+
# To avoid conflicts caused by multiple definitions of the same symbols,
2+
# we need to build all Rust crates as part of a single staticlib.
3+
# This is done by synthesizing a Rust crate that depends on all the others,
4+
# and contains `pub use` statements for each of them.
5+
#
6+
# You might ask why we don't just do all of this in Rust itself, without CMake
7+
# involved at all.
8+
# A key reason is that this setup enables us to add Rust crates wherever we'd also
9+
# be able to add C++ libraries, and have them all be built together.
10+
# Notably, this way we can have host-API implementations that include Rust code,
11+
# while keeping the host-API implementation fully self-contained, without changes
12+
# outside its own folder.
313

4-
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-multipart/Cargo.toml NO_LINKER_OVERRIDE CRATES multipart FEATURES capi)
5-
set_property(TARGET multipart PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-multipart/)
14+
set(RUST_STATICLIB_RS "${CMAKE_CURRENT_BINARY_DIR}/rust-staticlib.rs" CACHE INTERNAL "Path to the Rust staticlibs bundler source file" FORCE)
15+
set(RUST_STATICLIB_TOML "${CMAKE_CURRENT_BINARY_DIR}/Cargo.toml" CACHE INTERNAL "Path to the Rust staticlibs bundler Cargo.toml file" FORCE)
16+
set(RUST_STATICLIB_LOCK "${CMAKE_CURRENT_BINARY_DIR}/Cargo.lock" CACHE INTERNAL "Path to the Rust staticlibs bundler Cargo.toml file" FORCE)
17+
18+
configure_file("runtime/crates/staticlib-template/rust-staticlib.rs.in" "${RUST_STATICLIB_RS}" COPYONLY)
19+
configure_file("runtime/crates/staticlib-template/Cargo.toml.in" "${RUST_STATICLIB_TOML}")
20+
configure_file("runtime/crates/staticlib-template/Cargo.lock" "${RUST_STATICLIB_LOCK}" COPYONLY)
21+
22+
corrosion_import_crate(
23+
MANIFEST_PATH ${RUST_STATICLIB_TOML}
24+
CRATES "rust-staticlib"
25+
NO_LINKER_OVERRIDE
26+
)
27+
28+
# Add a Rust library to the staticlib bundle.
29+
function(add_rust_lib name path)
30+
add_library(${name} INTERFACE)
31+
target_include_directories(${name} INTERFACE "${path}")
32+
file(APPEND $CACHE{RUST_STATICLIB_TOML} "${name} = { path = \"${path}\", features = [${ARGN}] }\n")
33+
string(REPLACE "-" "_" name ${name})
34+
file(APPEND $CACHE{RUST_STATICLIB_RS} "pub use ${name};\n")
35+
endfunction()
36+
37+
# These two crates are needed by SpiderMonkey:
38+
add_rust_lib(rust-encoding "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-encoding")
39+
add_rust_lib(rust-hooks "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-hooks")
40+
41+
# The rust-hooks crate needs a supporting CPP file
42+
add_library(rust-hooks-wrappers STATIC "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-hooks/src/wrappers.cpp")
43+
target_link_libraries(rust-hooks-wrappers PRIVATE spidermonkey)
44+
add_library(rust-crates STATIC ${CMAKE_CURRENT_BINARY_DIR}/null.cpp)
45+
target_link_libraries(rust-crates PRIVATE rust_staticlib rust-hooks-wrappers)
46+
47+
# Add crates as needed here:
48+
add_rust_lib(rust-url "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url")
49+
add_rust_lib(multipart "${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-multipart" "\"capi\", \"simd\"")

cmake/init-corrosion.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# NOTE: This file must not be called `corrosion.cmake`, because otherwise it'll be called recursively instead of the
33
# same-named one coming with Corrosion.
44

5-
set(Rust_CARGO_TARGET wasm32-wasi)
5+
set(Rust_CARGO_TARGET wasm32-wasip1)
66
# Necessary to make cross-compiling to wasm32-wasi work for crates with -sys dependencies.
77
set(Rust_CARGO_TARGET_LINK_NATIVE_LIBS "")
88

@@ -11,5 +11,5 @@ string(REGEX MATCH "[0-9.]+" Rust_TOOLCHAIN "${Rust_TOOLCHAIN}")
1111
execute_process(COMMAND rustup toolchain install ${Rust_TOOLCHAIN})
1212
execute_process(COMMAND rustup target add --toolchain ${Rust_TOOLCHAIN} wasm32-wasi)
1313

14-
CPMAddPackage("gh:corrosion-rs/corrosion#be76480232216a64f65e3b1d9794d68cbac6c690")
14+
CPMAddPackage("gh:corrosion-rs/corrosion@0.5.1")
1515
string(TOLOWER ${Rust_CARGO_HOST_ARCH} HOST_ARCH)

cmake/spidermonkey.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ file(GLOB SM_OBJS ${SM_SOURCE_DIR}/lib/*.o)
3838

3939
add_library(spidermonkey STATIC)
4040
target_sources(spidermonkey PRIVATE ${SM_OBJS} ${CMAKE_CURRENT_BINARY_DIR}/null.cpp)
41-
set_property(TARGET spidermonkey PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${SM_INCLUDE_DIR})
42-
target_link_libraries(spidermonkey PUBLIC ${SM_SOURCE_DIR}/lib/libjs_static.a ${SM_SOURCE_DIR}/lib/libjsrust.a)
41+
target_include_directories(spidermonkey PUBLIC ${SM_INCLUDE_DIR})
42+
target_link_libraries(spidermonkey PUBLIC ${SM_SOURCE_DIR}/lib/libjs_static.a)
4343

4444
add_compile_definitions("MOZ_JS_STREAMS")

cmake/toolchain.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_OS)
2+
cmake_host_system_information(RESULT HOST_CPU QUERY OS_PLATFORM)
3+
24

35
if (${HOST_OS} STREQUAL "darwin")
46
set(HOST_OS "macos")
File renamed without changes.

crates/rust-encoding/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "rust-encoding"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Character encoding library used by SpiderMonkey and web builtins"
6+
7+
[lib]
8+
9+
[dependencies]
10+
encoding_c = "0.9.8"
11+
encoding_c_mem = "0.2.6"

crates/rust-encoding/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Ensure that the encoding modules are built, as SpiderMonkey relies on them.
2+
pub use encoding_c;
3+
pub use encoding_c_mem;

0 commit comments

Comments
 (0)