Repository navigation
chore: rustc: 1.98.1 -> 1.99.0 - #11735
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The canbench baseline updates promised by the PR description are absent from the current diff.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Upgrades the repository’s Rust toolchain from 1.98.1 to 1.99.0.
Changes:
- Updates the rustup toolchain pin.
- Synchronizes the Bazel Rust toolchain version.
| File | Description |
|---|---|
rust-toolchain.toml |
Pins Rust 1.99.0. |
bazel/rust.MODULE.bazel |
Pins Bazel’s Rust toolchain to 1.99.0. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):
-
Update
unreleased_changelog.md(if there are behavior changes, even if they are
non-breaking). -
Are there BREAKING changes?
-
Is a data migration needed?
-
Security review?
How to Satisfy This Automatic Review
-
Go to the bottom of the pull request page.
-
Look for where it says this bot is requesting changes.
-
Click the three dots to the right.
-
Select "Dismiss review".
-
In the text entry box, respond to each of the numbered items in the previous
section, declare one of the following:
-
Done.
-
$REASON_WHY_NO_NEED. E.g. for
unreleased_changelog.md, "No
canister behavior changes.", or for item 2, "Existing APIs
behave as before.".
Brief Guide to "Externally Visible" Changes
"Externally visible behavior change" is very often due to some NEW canister API.
Changes to EXISTING APIs are more likely to be "breaking".
If these changes are breaking, make sure that clients know how to migrate, how to
maintain their continuity of operations.
If your changes are behind a feature flag, then, do NOT add entrie(s) to
unreleased_changelog.md in this PR! But rather, add entrie(s) later, in the PR
that enables these changes in production.
Reference(s)
For a more comprehensive checklist, see here.
GOVERNANCE_CHECKLIST_REMINDER_DEDUP
No behavioural canister changes.
|
✅ No security or compliance issues detected. Reviewed everything up to 2c50207. Security OverviewDetected Code Changes
|
|
✅ No security or compliance issues detected. Reviewed everything up to 2c50207. Security OverviewDetected Code Changes
|
daniel-wong-dfinity-org-twin
left a comment
There was a problem hiding this comment.
Thank you for including the tables in the PR description. Much easier to understand than the noisy canbench_results.yml diffs.
d3db099 to
5634179
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `bazel run //rs/bitcoin/ckbtc/minter:ckbtc_minter_canbench_update`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `bazel run //rs/nns/governance:governance-canbench_update`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The totals are unchanged; the split-finder picks a different but equally balanced partition. All ten values come from one 1.99.0 reference run, in its source/destination orientation. Five uncached runs give byte-identical estimates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `bazel run //rs/registry/canister:registry-canbench_update`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `bazel run //rs/ledger_suite/icrc1/ledger:canbench_u256_update`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated with `bazel run //rs/ledger_suite/icrc1/ledger:canbench_u64_update`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5634179 to
2c50207
Compare
Upgrade the rust toolchain from 1.99.0 to 1.99.1. See: https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/. 1.99.1 is a point release with three backported fixes ([1.99.0...1.99.1](rust-lang/rust@1.99.0...1.99.1)). This PR is only the version bump in `rust-toolchain.toml` and `bazel/rust.MODULE.bazel`. No clippy fixes, test data or canbench baselines need to change. Clippy (`Cargo Lint Linux` and `PocketIC Lint on Windows`) is clean on 1.99.1. ### The x86_64 `bool` miscompilation did not affect the IC [rust#163911](rust-lang/rust#163911): rust#159317, new in 1.99.0, dropped `zeroext` from `bool` return values of `extern "C"` and most other non-Rust ABIs (`"C-unwind"`, `"system"`, `"sysv64"`, ...) on x86_64 targets other than macOS and Windows, such as `x86_64-unknown-linux-gnu`. A Rust *callee* built with 1.99.0 can then return `false` with bits 1-7 of `al` set, which a caller that tests the whole byte reads as `true`. Rust *callers* are not affected, because 1.99.0 only tests bit 0. 1.99.1 restores `zeroext` (rust#163917, backported in rust#164232). The one place in the IC where this could bite is Wasmtime. Every host function (the `ic0.*` system API, `__.out_of_instructions`, `__.internal_trap`, ...) is called through Wasmtime's `extern "C" fn array_call_trampoline(..) -> bool`, which returns `false` when the host call traps. Cranelift's wasm-to-host trampolines test that byte with `test %al,%al`. A dirty `false` would therefore have let canister code keep running after a host call that trapped. That did not happen. `//publish/binaries:canister_sandbox` and `//publish/binaries:replica` were built from master 592d14c with both toolchains, with the release settings (`-c opt`, `-Cdebug-assertions=off`), and compared: * All 166 `array_call_trampoline` instances (the 83 `func_wrap` closures in `linker.rs`, each compiled for 32- and 64-bit main memory) and the 10 Wasmtime libcalls that return `bool` (5 of them are compiled out in the IC and only panic) set `al` only with `xor %eax,%eax`, `mov $0x1,%al` or a call to such a function, on every return path, in both binaries. Apart from `breakpoint`, which returns a constant 1, they return the result of `catch_unwind_and_record_trap`, a Rust-ABI function, and Rust-ABI `bool` returns kept `zeroext` in 1.99.0. * Comparing every function of the two builds finds no function where 1.99.1 adds masking before a `ret`. The only `bool`-ABI changes are on the caller side, in three Wasmtime functions (`catch_traps` twice and `CodeMemory::publish`), where for example `test $0x1,%r15b` becomes `test %r15b,%r15b`. Their callees (Cranelift's array-to-wasm trampolines, the host trampolines above when an exported function is an imported one, and a C helper) always return 0 or 1, so behaviour does not change. * Apart from Wasmtime, neither IC code nor any other third-party crate in the lock files defines a non-Rust-ABI function returning `bool` that C, asm or JIT code calls. Wasmtime's other such functions (component model, stack switching) are not compiled into the IC. The remaining machine-code differences (under 1% of the functions in each binary, plus renamed symbols) are optimisation churn. rustc hashes its version string into every crate's `StableCrateId`. That changes symbol names, `TypeId`s and how items are partitioned and ordered, and with them some inlining and function-merging decisions. With the compiler held fixed, changing only that hash input reproduces the same kinds of differences in test builds of Wasmtime and of a canister. ### canbench All 8 `_update` targets were run on the same commit with both toolchains. No benchmark or scope moves beyond its noise threshold, so no baseline is updated (as with the suites that stayed within their thresholds in dfinity#11735). All 8 `_test` targets pass on 1.99.1 against the committed baselines. | suite | `_test` target | threshold | rustc 1.99.0 → 1.99.1, same commit | committed baseline → 1.99.1 (what `_test` checks) | |---|---|---|---|---| | ckBTC minter | `//rs/bitcoin/ckbtc/minter:ckbtc_minter_canbench_test` | 2% | identical | 0.00% … +0.25% | | ckETH minter | `//rs/ethereum/cketh/minter:cketh_minter_canbench_test` | 2% | +50 instructions | +1.72% | | ICP ledger | `//rs/ledger_suite/icp/ledger:ledger_canbench_test` | 2% | identical | -1.19% … +0.19% | | ICRC-1 ledger (u64) | `//rs/ledger_suite/icrc1/ledger:canbench_u64_test` | 2% | identical | -<0.01% … +0.01% | | ICRC-1 ledger (u256) | `//rs/ledger_suite/icrc1/ledger:canbench_u256_test` | 2% | identical | -0.04% … +1.68% | | NNS governance | `//rs/nns/governance:governance-canbench_test` | 5% | 30 of 30 entries, -0.90% … +1.14% | -2.59% … +1.14% | | Registry | `//rs/registry/canister:registry-canbench_test` | 2% | identical | -0.35% … +0.01% | | SNS governance | `//rs/sns/governance:governance-canbench_test` | 2% | identical | +0.01% | None of the three fixes changes wasm32 codegen, so 1.99.1's own effect on canister Wasm is the same version-hash churn as above. Across the 8 modules, about 180 of ~20,400 functions get a different instruction sequence, and nearly all other differences in function bodies are renumbered function indices, data addresses and table slots. With the crate hashes pinned (`RUSTC_FORCE_RUSTC_VERSION`), 1.99.0 and 1.99.1 produce identical code and data for the SNS governance canister. The churn moves NNS governance by -0.90% to +1.14% and ckETH by 50 instructions, and leaves the other six suites identical. The rest of the last column is drift that master already has. Regenerating the baselines with 1.99.0 on master gives the same numbers for the other six suites, and all of ckETH's +1.72% except those 50 instructions. <details><summary>NNS governance, rustc 1.99.0 → 1.99.1 on the same commit (30 entries)</summary> | name | ins (1.99.1) | ins Δ | ins Δ% | |---|---|---|---| | `add_neuron_active_maximum` | 65.33M | +737.49K | +1.14% | | `add_neuron_inactive_maximum` | 65.33M | +737.48K | +1.14% | | `add_neuron_active_typical` | 4.61M | +41.97K | +0.92% | | `add_neuron_inactive_typical` | 4.61M | +41.96K | +0.92% | | `list_ready_to_spawn_neuron_ids` | 37.14M | -336.15K | -0.90% | | `unstake_maturity_of_dissolved_neurons::list_neuron_ids` | 38.52M | -347.15K | -0.89% | | `list_active_neurons_fund_neurons` | 2.18M | -18.87K | -0.86% | | `unstake_maturity_of_dissolved_neurons` | 63.97M | -368.75K | -0.57% | | `compute_ballots_for_new_proposal_with_stable_neurons` | 2.35M | -11.26K | -0.48% | | `list_neurons_by_subaccount` | 33.07M | +144.90K | +0.44% | | `list_neurons` | 34.20M | +134.28K | +0.39% | | `with_neuron_mut_all_sections_typical` | 852.14K | +2.98K | +0.35% | | `neuron_metrics_calculation` | 3.32M | -11.24K | -0.34% | | `range_neurons_performance` | 50.71M | +156.18K | +0.31% | | `record_neuron_vote_known_neuron_voting_history` | 143.65K | +419 | +0.29% | | `single_vote_all` | 279.43K | +755 | +0.27% | | `voting_power_snapshots_latest_snapshot_timestamp_seconds` | 7.58K | -19 | -0.25% | | `record_known_neuron_vote` | 183.62K | +442 | +0.24% | | `distribute_rewards_with_stable_neurons` | 2.80M | -5.71K | -0.20% | | `voting_power_snapshots_is_latest_snapshot_a_spike_false` | 30.05K | -38 | -0.13% | | `voting_power_snapshots_is_latest_snapshot_a_spike_true` | 30.07K | -38 | -0.13% | | `centralized_following_all` | 42.26M | +52.89K | +0.13% | | `neuron_data_validation` | 205.01M | -211.68K | -0.10% | | `with_neuron_mut_all_sections_maximum` | 4.12M | +4.17K | +0.10% | | `draw_maturity_from_neurons_fund` | 7.58M | +7.60K | +0.10% | | `list_proposals` | 77.12K | -72 | -0.09% | | `unstake_maturity_of_dissolved_neurons::unstake_maturity` | 25.45M | -21.60K | -0.08% | | `cascading_vote_stable_everything` | 105.40M | -38.92K | -0.04% | | `with_neuron_mut_main_section_typical` | 393.94K | +75 | +0.02% | | `with_neuron_mut_main_section_maximum` | 2.25M | -384 | -0.02% | </details> ### Other checks * `//rs/recovery/subnet_splitting:integration_test` passes on 1.99.1 without a re-pin. * [rust#163614](rust-lang/rust#163614) (`wcslen`): compiler-builtins now provides a weak `wcslen` on `wasm32-unknown-unknown`. It is only linked when referenced, and no canister references it: code that did would have failed to link on 1.99.0. * [rust#163947](rust-lang/rust#163947) (macOS `rust-lld`): `rust-lld` could not load `libLLVM.dylib` when rustc ran without the rustup proxy. The `arm64-darwin` CI job, which builds canister Wasm with Bazel, never hit this: rules_rust unpacks `llvm-tools` into the same directory as `rustc`, and `llvm-tools` ships `libLLVM.dylib` in `lib/rustlib/<host>/lib/`, which is where `rust-lld` looks for it. * No repin is needed: Bazel accepts the existing `Cargo.Bazel.*.lock` files and no third-party crate changed. rules_rust 0.74.0 ships no checksums for 1.99.1, so the toolchain is downloaded unverified, as 1.99.0 is today. * `bazel build //... --nobuild` and buildifier pass. CI passes on all targets (`CI_ALL_BAZEL_TARGETS`), including the arm64-linux and arm64-darwin builds and the Windows PocketIC jobs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

Upgrade the rust toolchain from 1.98.1 to 1.99.0. See:
https://blog.rust-lang.org/2026/10/01/Rust-1.99.0/.
The
cargo clippyfixes this needs are in the PR below this one in the stack (#11734). This PR istherefore the version bump in
rust-toolchain.tomlandbazel/rust.MODULE.bazel, plus the testdata that the new codegen (LLVM 22 → 23) shifts.
//rs/bitcoin/ckbtc/minter:ckbtc_minter_canbench_testwas regenerated withbazel run //rs/bitcoin/ckbtc/minter:ckbtc_minter_canbench_update. All 7 benchmark totals staywithin the noise threshold (+0.10% to +0.49%), but 7 small
::greedyscopes moved beyond it:build_unsigned_transaction_6_10_btc::greedybuild_unsigned_transaction_4_10m_sats::greedybuild_unsigned_transaction_5_1_btc::greedybuild_estimate_retrieve_btc_fee_1_50k_sats::greedybuild_unsigned_transaction_1_50k_sats::greedybuild_unsigned_transaction_2_100k_sats::greedybuild_unsigned_transaction_3_1m_sats::greedy//rs/nns/governance:governance-canbench_testwas regenerated withbazel run //rs/nns/governance:governance-canbench_update. One of its 28 benchmarks moved beyondthe 5% noise threshold:
neuron_metrics_calculation//rs/registry/canister:registry-canbench_testwas regenerated withbazel run //rs/registry/canister:registry-canbench_update. One of its 14 benchmarks moved beyondthe noise threshold:
measure_routing_table_invariant_checks_shards_and_unsharded//rs/ledger_suite/icrc1/ledger:canbench_u64_testand:canbench_u256_testwereregenerated with
bazel run //rs/ledger_suite/icrc1/ledger:canbench_u64_updateand:canbench_u256_update. Both moved the same way. u64:bench_icrc1_transfers::icrc103_get_allowancesbench_icrc1_transfers::icrc2_approvebench_icrc1_transfers::icrc2_transfer_frombench_icrc1_transfersbench_icrc1_transfers::upgradebench_icrc1_transfers::post_upgradeu256:
bench_icrc1_transfers::icrc103_get_allowancesbench_icrc1_transfers::icrc2_approvebench_icrc1_transfers::icrc2_transfer_frombench_icrc1_transfersbench_icrc1_transfers::upgradebench_icrc1_transfers::post_upgrade//rs/recovery/subnet_splitting:integration_testhas no_updatetarget. Its expected valuesare hardcoded in the test, so all ten were re-pinned by hand from one 1.99.0 reference run, in that
run's source/destination orientation.
As with 1.98.1 (#11534), what changed is the partition, not the workload:
heartbeats 694. Total instructions moved by 0.013% and total state size by 0.26%.
that is enough to flip a near-tied optimum to a different partition.
states_sizes_bytescanisters_installedingress_messages_executedremote_subnet_messages_executed_lower_boundlocal_subnet_messages_executed_upper_boundhttp_outcalls_executedheartbeats_and_global_timers_executedThe test is deterministic: 5/5 uncached runs on 1.99.0 give byte-identical estimates. As noted in
#11534, it pins the exact solution of a degenerate optimisation, so it will keep flipping on
unrelated changes too. Asserting on the totals and on the ~50/50 instruction balance would make it
robust.
The other three canbench suites (ckETH minter, ICP ledger, SNS governance) stay within their noise
thresholds. CI passes on all targets (
CI_ALL_BAZEL_TARGETS), including the Windows PocketIC jobsand Build Determinism.
No repin is needed. The
Cargo.Bazel.*.lockdigests hash thecargo/rustcof rules_rust's ownhost tools (pinned at 1.98.0 by rules_rust 0.74.0), not the version selected by
rust.toolchain,and no third-party crate changed. rules_rust 0.74.0 ships no checksums for 1.99.0 (nor for the
current 1.98.1), so the toolchain is downloaded unverified, exactly as 1.98.1 is today.
Also checked, since tests won't necessarily catch these:
RangeInclusive(rust#155114): an exhaustedrange now reports different
start()/end(). No code in the repo observes a range afteriterating it.
wasm32-unknown-unknowntarget features are identical on both toolchains.Canister-like modules built with the canister flags use no new Wasm proposal, and they pass
validation configured like the IC's.
lldcannot read.This is harmless today: canisters (
-C linker-plugin-lto) link with rustc's bundledrust-lld, andnative links carry no bitcode. Until the hermetic toolchain reaches LLVM 23, though, don't route
wasm links through it or add
-C linker-plugin-ltoto native targets.🤖 Generated with Claude Code