From 227cd0642d8aef1f238cc312290ad743f4dc8f77 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 29 Nov 2024 14:57:05 +0100 Subject: [PATCH] Add `no_std` support - Through `no_std` support, we now also support `wasm32v1-none`. - Introduced a new `std` crate feature that is enabled by default. Without it `#[no_std]` is enabled, but only on Web! This allows Web target to build without std, which is now supported by `wasm-bindgen` as well. Additionally, various changes had to be done to support `no_std`: - `no_std` does not support `thread_local!`, we use `once_cell` to polyfill this gap. `once_cell` is not a new dependency, it is already a dependency of `wasm-bindgen`. - With `feature = "std"`, we use `thread_local!` as before. - Without std and without `target_feature = "atomics"`, we use a `static mut` with `once_cell::unsync::Lazy`. - Without std and with atomics, we use [`#[thread_local]`](https://doc.rust-lang.org/1.83.0/unstable-book/language-features/thread-local.html?highlight=thread_l#thread_local) with `once_cell::unsync::Lazy`. - Some `f64` instructions are not available on `no_std` and had to be polyfilled. For this code from [`libm`](https://crates.io/crates/libm) was copied. Which is used by std as well. - `SystemTimeError` now only implements `Error` with `feature = "std"`. - `no_std` testing requires to refrain from using the default test harness. The problem was that native tests still needed to use the default test harness. To solve this, integration tests were removed from root crate and two workspace members added, that manually define all integration tests as test targets. The `tests-web` crate has `harness = false` on all tests, while `tests-native` functions regularly. This allow us to use the default test harness for native tests while disabling it for Web. Additionally, every test target requires the `run` crate feature, which are enabled by default depending on the target by the root crate. This way regular testing can function correctly for each target as long as `--all-features` is not used. E.g. `cargo test --workspace` and `cargo test --workspace --target wasm32-unknown-unknown` will work correctly. The `tests-web` library is used to implement the `panic_handler`, `global_allocator` and `critical_section`. It is always imported to reduce code duplication in all tests. - Used [`serde-json-core`](https://crates.io/crates/serde-json-core) to cover tests with `no_std` as well. - All links to std documentation had to be supplemented with manual link when `std` is not present. - Improvements to CI: - Refactored all matrices for simplification and covering `--no-default-features`. - Split regular and minimal versions building off tests. - Split doctests from regular tests. - Update Rust toolchain when testing `cargo publish`. - Test coverage improvements: - The new `wasm-bindgen` update allows us to refrain from having to pass `cfg` flags to the `wasm-bindgen` proc-macros. - Use Rust `llvm-tools` instead of the official LLVM package. - Remove invalid `script` tag from coverage report. - Retain coverage artifact instead of limiting it to one day. - Refactor large environment variables into global level ones. - Small fixes that were stumbled upon: - Expose `web_time::web` with `cfg(docsrs)` on native as well. - `Serialize` and `Deserialize` implementation are now marked with `doc(cfg(feature = "serde"))`. - Std docs.rs link unnecessarily including `stable`. - Recommendation for `-Ctarget-feature=+nontrapping-fptoint` was moved from the top-level to the "Usage" section. - The minimal version of Serde is now fully specified as v1.0.0. - Fix some typos in internal documentation. --- .cargo/config.toml | 2 +- .config/topic.dic | 4 +- .github/workflows/build.yaml | 148 +++++++ .github/workflows/coverage-documentation.yaml | 64 +-- .github/workflows/lint.yaml | 100 +++-- .github/workflows/publish.yaml | 4 + .github/workflows/test.yaml | 206 +++++----- CHANGELOG.md | 10 + CONTRIBUTING.md | 118 +++++- Cargo.toml | 86 ++-- README.md | 22 +- benches/benchmark.rs | 367 +++++++++++------- clippy.toml | 7 + minimal-versions/Cargo.toml | 9 +- minimal-versions/src/lib.rs | 2 + src/lib.rs | 72 +++- src/time/instant.rs | 197 ++++++++-- src/time/js.rs | 87 ++++- src/time/mod.rs | 13 + src/time/serde.rs | 11 +- src/time/system_time.rs | 62 ++- src/web.rs | 32 ++ tests-native/Cargo.toml | 71 ++++ tests-native/src/lib.rs | 10 + tests-web/Cargo.toml | 144 +++++++ tests-web/src/lib.rs | 161 ++++++++ tests/atomic_failure.rs | 8 +- tests/atomic_success.rs | 4 +- tests/instant_failure_1.rs | 10 +- tests/instant_failure_2.rs | 4 +- tests/instant_success.rs | 14 +- tests/serde.rs | 24 +- tests/system_time_failure_1.rs | 4 +- tests/system_time_failure_2.rs | 4 +- tests/system_time_success.rs | 18 +- tests/traits.rs | 26 +- tests/util/mod.rs | 22 +- tests/util/std.rs | 2 +- tests/util/web.rs | 59 +-- tests/web.rs | 7 +- 40 files changed, 1726 insertions(+), 489 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 tests-native/Cargo.toml create mode 100644 tests-native/src/lib.rs create mode 100644 tests-web/Cargo.toml create mode 100644 tests-web/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 10762f9..522aaca 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ -[target.wasm32-unknown-unknown] +[target.'cfg(target_family = "wasm")'] runner = "wasm-bindgen-test-runner" diff --git a/.config/topic.dic b/.config/topic.dic index 8e6fc2d..8136994 100644 --- a/.config/topic.dic +++ b/.config/topic.dic @@ -1,7 +1,8 @@ -23 +25 1G 1M 1ns +allocator APIs Atomics de @@ -12,6 +13,7 @@ io JS MDN MSRV +representable Serde Serde's timestamps diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..4b0e44b --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,148 @@ +name: Build + +on: + push: + branches: ["main"] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: + Build ${{ matrix.target.description }} ${{ matrix.rust.description }} ${{ + matrix.features.description }} + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + target: + - { target: x86_64-unknown-linux-gnu, description: Native } + - { target: wasm32-unknown-unknown, description: Web } + - { target: wasm32v1-none, description: Wasm v1 } + rust: + - { version: "1.60", description: MSRV, atomics: false } + - { version: stable, atomics: false } + - { version: nightly, atomics: false } + - { + version: nightly, + description: with Atomics, + atomics: true, + component: --component rust-src, + flags: "-Ctarget-feature=+atomics,+bulk-memory", + build-std: true, + } + features: + - { features: "", no_std: false } + - { features: --features serde, no_std: false, description: (`serde`) } + - { features: --no-default-features, no_std: true, description: (`no_std`) } + - { + features: --no-default-features --features serde, + no_std: true, + description: "(`no_std`, `serde`)", + } + exclude: + - target: { target: x86_64-unknown-linux-gnu, description: Native } + rust: { version: nightly } + - target: { target: wasm32-unknown-unknown, description: Web } + rust: { version: nightly, atomics: false } + - target: { target: wasm32v1-none, description: Wasm v1 } + rust: { version: "1.60" } + - target: { target: wasm32v1-none, description: Wasm v1 } + rust: { version: stable } + - target: { target: wasm32v1-none, description: Wasm v1 } + features: { no_std: false } + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust + run: | + rustup toolchain install ${{ matrix.rust.version }} --profile minimal ${{ matrix.rust.component }} --target ${{ matrix.target.target }} + rustup default ${{ matrix.rust.version }} + - name: Set `build-std` components + if: matrix.rust.build-std == true && matrix.features.no_std == false + run: echo "BUILD_STD_COMPONENTS=-Zbuild-std=panic_abort,std" >> $GITHUB_ENV + - name: Set `build-std` `no_std` components + if: matrix.rust.build-std == true && matrix.features.no_std == true + run: echo "BUILD_STD_COMPONENTS=-Zbuild-std=core,alloc" >> $GITHUB_ENV + - name: Fix MSRV dependencies + if: matrix.rust.version == '1.60' + run: | + cargo update -p bumpalo --precise 3.14.0 + cargo update -p serde --precise 1.0.210 + cargo update -p syn --precise 2.0.67 + - name: Build + env: + RUSTFLAGS: ${{ matrix.rust.flags }} + run: + cargo build ${{ matrix.features.features }} --target ${{ matrix.target.target }} + $BUILD_STD_COMPONENTS + + minimal-versions: + name: + Minimal Versions ${{ matrix.target.description }} ${{ matrix.rust.description }} ${{ + matrix.features.description }} + + runs-on: ubuntu-latest + + defaults: + run: + working-directory: minimal-versions + + strategy: + fail-fast: false + matrix: + target: + - { target: x86_64-unknown-linux-gnu, description: Native } + - { target: wasm32-unknown-unknown, description: Web } + - { target: wasm32v1-none, description: Wasm v1 } + rust: + - { version: "1.60", description: MSRV } + - { version: stable } + - { version: nightly } + features: + - { features: "", no_std: false } + - { features: --features serde, no_std: false, description: (`serde`) } + - { features: --no-default-features, no_std: true, description: (`no_std`) } + - { + features: --no-default-features --features serde, + no_std: true, + description: "(`no_std`, `serde`)", + } + exclude: + - target: { target: x86_64-unknown-linux-gnu, description: Native } + rust: { version: nightly } + - target: { target: wasm32-unknown-unknown, description: Web } + rust: { version: nightly } + - target: { target: wasm32v1-none, description: Wasm v1 } + rust: { version: "1.60" } + - target: { target: wasm32v1-none, description: Wasm v1 } + rust: { version: stable } + - target: { target: wasm32v1-none, description: Wasm v1 } + features: { no_std: false } + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust + run: | + rustup toolchain install ${{ matrix.rust.version }} --profile minimal --target ${{ matrix.target.target }} + rustup default ${{ matrix.rust.version }} + - name: Downgrade to minimal versions + run: | + rustup toolchain install nightly --profile minimal + cargo +nightly update -Z minimal-versions + - name: Fix Rust nightly incompatible dependencies + if: matrix.rust.version == 'nightly' + run: | + cargo update -p proc-macro2 --precise 1.0.60 + - name: Build + run: cargo build ${{ matrix.features.features }} --target ${{ matrix.target.target }} diff --git a/.github/workflows/coverage-documentation.yaml b/.github/workflows/coverage-documentation.yaml index 69667c2..533f1f8 100644 --- a/.github/workflows/coverage-documentation.yaml +++ b/.github/workflows/coverage-documentation.yaml @@ -15,7 +15,7 @@ env: jobs: coverage: - name: Test Coverage ${{ matrix.mt.description }} + name: Test Coverage ${{ matrix.mt.description }} ${{ matrix.features.description }} runs-on: ubuntu-latest @@ -24,15 +24,24 @@ jobs: strategy: matrix: mt: - - { id: 0 } + - { id: "st" } - { - id: 1, + id: "mt", description: with Atomics, component: --component rust-src, cflags: -matomics -mbulk-memory, flags: "-Ctarget-feature=+atomics,+bulk-memory", - args: "-Zbuild-std=panic_abort,std", + build-std: true, } + features: + - { id: "", features: "", no_std: false } + - { id: -no_std, features: --no-default-features, no_std: true, description: (`no_std`) } + + env: + CFLAGS_wasm32_unknown_unknown: ${{ matrix.mt.cflags }} + RUSTFLAGS: + -Cinstrument-coverage -Zcoverage-options=condition -Zno-profiler-runtime --emit=llvm-ir + --cfg=wasm_bindgen_unstable_test_coverage ${{ matrix.mt.flags }} steps: - name: Checkout @@ -50,31 +59,25 @@ jobs: run: | rustup toolchain install nightly --profile minimal --target wasm32-unknown-unknown ${{ matrix.mt.component }} rustup default nightly + - name: Set `build-std` components + if: matrix.mt.build-std == true && matrix.features.no_std == false + run: echo "BUILD_STD_COMPONENTS=-Zbuild-std=panic_abort,std" >> $GITHUB_ENV + - name: Set `build-std` `no_std` components + if: matrix.mt.build-std == true && matrix.features.no_std == true + run: echo "BUILD_STD_COMPONENTS=-Zbuild-std=core,alloc" >> $GITHUB_ENV - name: Test env: CHROMEDRIVER: chromedriver - CFLAGS_wasm32_unknown_unknown: ${{ matrix.mt.cflags }} - 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 ${{ matrix.mt.flags }} - WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT: coverage-output run: | mkdir coverage-output - cargo test --all-features --target wasm32-unknown-unknown -Ztarget-applies-to-host -Zhost-config ${{ matrix.mt.args }} --tests + WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT=$(realpath coverage-output) cargo test --workspace --features serde --target wasm32-unknown-unknown $BUILD_STD_COMPONENTS ${{ matrix.features.features }} --tests - name: Prepare Object Files - env: - CFLAGS_wasm32_unknown_unknown: ${{ matrix.mt.cflags }} - 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 ${{ matrix.mt.flags }} run: | mkdir coverage-input crate_name=web_time IFS=$'\n' for file in $( - cargo test --all-features --target wasm32-unknown-unknown -Ztarget-applies-to-host -Zhost-config ${{ matrix.mt.args }} --tests --no-run --message-format=json | \ + cargo test --workspace --features serde --target wasm32-unknown-unknown $BUILD_STD_COMPONENTS ${{ matrix.features.features }} --tests --no-run --message-format=json | \ jq -r "select(.reason == \"compiler-artifact\") | (select(.target.kind == [\"test\"]) // select(.target.name == \"$crate_name\")) | .filenames[0]" ) do @@ -90,7 +93,7 @@ jobs: - name: Upload Test Coverage Artifact uses: actions/upload-artifact@v4 with: - name: test-coverage-${{ matrix.mt.id }} + name: test-coverage-${{ matrix.mt.id }}${{ matrix.features.id }} path: coverage-output retention-days: 1 if-no-files-found: error @@ -105,11 +108,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install LLVM v19 + - name: Install Rust nightly run: | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main" - sudo apt-get install llvm-19 + rustup toolchain install nightly --profile minimal --component llvm-tools + rustup default nightly + - name: Install `cargo-binutils` + uses: taiki-e/install-action@v2 + with: + tool: cargo-binutils - name: Download Test Coverage uses: actions/download-artifact@v4 with: @@ -117,8 +123,7 @@ jobs: path: coverage-input - name: Merge Profile Data run: - llvm-profdata-19 merge -sparse coverage-input/*/*.profraw -o - coverage-input/coverage.profdata + rust-profdata merge -sparse coverage-input/*/*.profraw -o coverage-input/coverage.profdata - name: Export Code Coverage Report run: | mkdir coverage-output @@ -127,16 +132,15 @@ jobs: do objects+=(-object $file) done - llvm-cov-19 show -show-instantiations=false -output-dir coverage-output -format=html -instr-profile=coverage-input/coverage.profdata ${objects[@]} -sources src - llvm-cov-19 export -format=text -summary-only -instr-profile=coverage-input/coverage.profdata ${objects[@]} -sources src | \ + rust-cov show -show-instantiations=false -output-dir coverage-output -format=html -instr-profile=coverage-input/coverage.profdata ${objects[@]} -sources src + rust-cov export -format=text -summary-only -instr-profile=coverage-input/coverage.profdata ${objects[@]} -sources src | \ printf '{ "coverage": "%.2f%%" }' $(jq '.data[0].totals.functions.percent') > coverage-output/coverage.json - sed 's///' coverage-output/index.html | perl -p0e 's/]*>((?!here).*?)<\/a>/$1/g' >> $GITHUB_STEP_SUMMARY + sed 's///' coverage-output/index.html | sed "s/