Thank you for contributing!
This crate has some code paths that depend on Wasm Atomics, which has some prerequisites to compile:
- Rust nightly.
- The
rust-src
component. - Cargo's
build-std
. - The
atomics
andbulk-memory
target features.
Example usage:
# Installing Rust nightly and necessary components:
rustup toolchain install nightly --target wasm32-unknown-unknown --component rust-src
# Example `cargo build` usage:
RUSTFLAGS=-Ctarget-feature=+atomics,+bulk-memory cargo +nightly build -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown
To get proper diagnostics for Rust Atomics it can be helpful to configure Rust Analyzer to support that.
Here is an example configuration for Visual Studio Code:
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.cargo.extraArgs": [
"-Zbuild-std=panic_abort,std"
],
"rust-analyzer.cargo.extraEnv": {
"RUSTUP_TOOLCHAIN": "nightly",
"RUSTFLAGS": "-Ctarget-feature=+atomics,+bulk-memory"
},
Tests are run as usual, but tests that require Wasm Atomics can be run like this:
RUSTFLAGS=-Ctarget-feature=+atomics,+bulk-memory cargo +nightly test -Zbuild-std=panic_abort,std --target wasm32-unknown-unknown
Additionally, keep in mind that usage of #[should_panic]
is known to cause
browsers to get stuck because of the lack of unwinding support.
The current workaround is to split tests using await
into separate test targets.
The only benchmark is marked as an example target because of the lack of Wasm support. To run it you can use the following command:
cargo build --example benchmark --target wasm32-unknown-unknown --profile bench
wasm-bindgen --out-dir benches --target web --no-typescript target/wasm32-unknown-unknown/release/examples/benchmark.wasm
The benches
folder then needs to be hosted by a HTTP server to run it in a browser.
Optionally wasm-opt
could be added as well:
wasm-opt benches/benchmark_bg.wasm -o benches/benchmark_bg.wasm -O4
The process to generate WebAssembly test coverage is quite involved, see the wasm-bindgen
Guide
on the matter. If you open a PR the "Coverage & Documentation" CI workflow will generate test
coverage data for you and will post a summary via a Job Summary, but you can also download the
full test coverage data via an artifact called test-coverage
.
If you want to generate test coverage locally, here is an example shell script that you can use:
# Single-threaded test run.
st () {
CARGO_HOST_RUSTFLAGS=--cfg=wasm_bindgen_unstable_test_coverage RUSTFLAGS="-Cinstrument-coverage -Zcoverage-options=condition -Zno-profiler-runtime --emit=llvm-ir --cfg=wasm_bindgen_unstable_test_coverage" cargo +nightly test --all-features --target wasm32-unknown-unknown -Ztarget-applies-to-host -Zhost-config --tests $@
}
# Multi-threaded test run.
mt () {
CFLAGS_wasm32_unknown_unknown="-matomics -mbulk-memory" CARGO_HOST_RUSTFLAGS=--cfg=wasm_bindgen_unstable_test_coverage RUSTFLAGS="-Cinstrument-coverage -Zcoverage-options=condition -Zno-profiler-runtime --emit=llvm-ir --cfg=wasm_bindgen_unstable_test_coverage -Ctarget-feature=+atomics,+bulk-memory" cargo +nightly test --all-features --target wasm32-unknown-unknown -Ztarget-applies-to-host -Zhost-config -Zbuild-std=panic_abort,std --tests $@
}
# To collect object files.
objects=()
# Run tests and adjust LLVM IR.
test () {
local command=$1
local path=$2
# Run tests.
mkdir -p coverage-input/$path
CHROMEDRIVER=chromedriver WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT=coverage-input/$path $command
local crate_name=web_time
local IFS=$'\n'
for file in $(
# Extract path to artifacts.
$command --no-run --message-format=json | \
jq -r "select(.reason == \"compiler-artifact\") | (select(.target.kind == [\"test\"]) // select(.target.name == \"$crate_name\")) | .filenames[0]"
)
do
# Get the path to the LLVM IR files instead of the rlib and Wasm artifacts.
local base
if [[ ${file##*.} == "rlib" ]]; then
base=$(basename $file .rlib)
file=$(dirname $file)/${base#"lib"}.ll
else
file=$(dirname $file)/$(basename $file .wasm).ll
fi
# Copy LLVM IR files.
local input=coverage-input/$path/$(basename $file)
cp $file $input
# Adjust LLVM IR files.
perl -i -p0e 's/(^define.*?$).*?^}/$1\nstart:\n unreachable\n}/gms' $input
local counter=1
while (( counter != 0 )); do
counter=$(perl -i -p0e '$c+= s/(^(define|declare)(,? [^\n ]+)*),? range\(.*?\)/$1/gm; END{print "$c"}' $input)
done
# Compile LLVM IR files to object files.
local output=coverage-input/$path/$(basename $file .ll).o
clang-18 $input -Wno-override-module -c -o $output
objects+=(-object $output)
done
}
test st 'st'
test mt 'mt'
# Merge all generated `*.profraw` files.
llvm-profdata-18 merge -sparse coverage-input/*/*.profraw -o coverage-input/coverage.profdata
# Finally generate coverage information.
llvm-cov-18 show -show-instantiations=false -output-dir coverage-output -format=html -instr-profile=coverage-input/coverage.profdata ${objects[@]} -sources src