Conversation
Merging this PR will improve performance by 76.87%
|
hyfdev
left a comment
There was a problem hiding this comment.
Summary
This umbrella PR is a carefully layered set of allocation-aware path APIs (relative_cow, relative_to_slash_lossy, sealed SugarPathBuf) plus lexical relative, Windows root/UNC fixes, normalization determinism, and an ARM64 NEON common-prefix scan. Core algorithms look correct under the stated contracts; residual risks are release-surface completeness and the still-open absolutize_with owned-base allocation hole.
Issue counts by severity
- bugs: 0
- suggestions: 3
- nits: 1
All findings are posted as inline comments (including ones anchored to the nearest line still present in the diff).
There was a problem hiding this comment.
Pull request overview
This PR delivers a breaking redesign of SugarPath’s public API to center on borrowed Path/str receivers, explicit current-directory handling, and explicit Unicode conversion policy (strict vs fallible vs lossy), while adding a separate sealed consuming surface (SugarPathBuf) for PathBuf operations that can reuse owned storage. It also substantially expands cross-platform correctness, allocation-contract, and composition tests, and updates the benchmark + allocation snapshot evidence to match the new borrowing/ownership model.
Changes:
- Redesign
SugarPathas a sealed extension trait forPathandstr, withrelativereturningCow<Path>and newtry_*ambient-cwd variants plus explicit-cwd_withmethods. - Introduce
SugarPathBuf(sealed,PathBuf-only) for consuming operations that can reuse allocations (into_normalized,into_slash*). - Add/expand integration tests and benchmark/snapshot documentation to validate borrowing, encoding policy, cwd behavior, Windows root semantics, and allocation counts.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/to_slash.rs | Updates slash-conversion tests to assert borrowing/owning behavior and strict vs lossy outputs. |
| tests/relative.rs | Updates and significantly extends relative-path tests, especially Windows root/namespace matrix. |
| tests/relative_without_cwd.rs | Adds a unix-only child-process test ensuring cwd-independent relative paths don’t read cwd. |
| tests/relative_to_slash.rs | Adds tests ensuring composed relative(...).into_owned().into_slash() matches strict composition expectations. |
| tests/relative_simd.rs | Adds NEON boundary tests for aarch64 common-prefix SIMD scanning. |
| tests/relative_lexical.rs | Adds broad lexical relative matrix tests for cwd-(in)dependence and deep-path behavior. |
| tests/relative_borrowing.rs | Adds tests validating when relative can borrow from the target’s storage. |
| tests/path_identity.rs | Adds idempotence and exact-spelling identity tests for normalization across platforms. |
| tests/owned_api.rs | Adds tests for SugarPathBuf allocation reuse and strict/fallible/lossy owned conversions. |
| tests/invalid_encoding.rs | Adds cross-platform invalid-encoding coverage for normalize/relative/absolutize and slash conversions. |
| tests/deep_paths.rs | Refines deep-path tests to better cover borrowed fast paths and SmallVec spill execution paths. |
| tests/cached_current_dir.rs | Adds tests validating cached_current_dir behavior and when caching is (not) used. |
| tests/as_path.rs | Expands as_path tests to confirm deref-based str impl works for string-like wrappers. |
| tests/absolutize.rs | Updates absolutize tests to the redesigned API and adds Cow-contract checks for absolute inputs. |
| tests/absolutize_without_cwd.rs | Adds unix-only child-process tests ensuring absolute inputs don’t require cwd availability. |
| tests/absolutize_with.rs | Updates explicit-cwd tests to new _with signature (borrowed/owned direct args) and Windows drive-relative rules. |
| tasks/track_allocations/README.md | Updates allocation-task documentation and methodology notes. |
| src/utils.rs | Replaces ambient cwd helper with fallible try_get_current_dir() and caches only when enabled. |
| src/sugar_path.rs | Redefines the sealed borrowed API surface, docs, and semantics for normalize/absolutize/relative/slash conversion. |
| src/sugar_path_buf.rs | Introduces sealed consuming SugarPathBuf trait for allocation-reusing PathBuf operations. |
| src/lib.rs | Updates crate-level docs, re-exports, and examples to reflect the redesigned API and consuming surface. |
| README.md | Updates public README to explain the new API, borrowing/ownership rules, encoding policy, and examples. |
| CHANGELOG.md | Documents breaking API changes, new methods, behavior changes, and migration notes. |
| benchmarks/windows-gnu.md | Updates Windows-GNU reproduction doc to clarify evidence status and final-API context. |
| benchmarks/README.md | Updates benchmarking methodology, workload description, and snapshot regeneration instructions. |
| benchmarks/allocations/x86_64-unknown-linux-gnu-rolldown.snap | Updates committed allocation snapshot for Linux (rolldown configuration) for the new API. |
| benchmarks/allocations/x86_64-unknown-linux-gnu-default.snap | Updates committed allocation snapshot for Linux (default configuration) for the new API. |
| benchmarks/allocations/aarch64-apple-darwin-rolldown.snap | Updates committed allocation snapshot for macOS ARM64 (rolldown configuration) for the new API. |
| benchmarks/allocations/aarch64-apple-darwin-default.snap | Updates committed allocation snapshot for macOS ARM64 (default configuration) for the new API. |
| benches/to_slash.rs | Updates slash benchmarks to new strict/fallible APIs and consuming PathBuf reuse rows. |
| benches/relative.rs | Updates relative benchmarks to preserve stable IDs while adapting to Cow<Path> return and requested outputs. |
| benches/normalize.rs | Updates normalize benchmarks to exercise consuming into_normalized paths for owned receiver rows. |
| benches/absolutize.rs | Updates benchmarks for explicit-cwd APIs without requiring caller-constructed Cow. |
| AGENTS.md | Updates contributor guidance, commands, architecture notes, and PCR guidance. |
| .agents/docs/technology-stack.md | Adds PCR doc describing toolchain pins and performance dependencies/evidence. |
| .agents/docs/README.md | Adds PCR map routing contributors to relevant durable context docs. |
| .agents/docs/gotchas.md | Adds PCR gotchas covering relative directionality, borrowing rules, cwd semantics, and encoding policy. |
| .agents/docs/goal.md | Adds PCR doc describing project goals, success criteria, and non-goals. |
| .agents/docs/conventions.md | Adds PCR doc capturing testing conventions, allocation/encoding coverage expectations, and platform gating. |
| .agents/docs/architecture.md | Adds PCR doc describing the two-trait architecture, execution paths, and platform behavior boundaries. |
| .agents/docs/api-redesign.md | Adds PCR doc capturing the rationale/decision record for the breaking API redesign and its evidence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - **`src/sugar_path.rs`** — Trait definition with doc examples | ||
| - **`src/impl_sugar_path.rs`** — All implementations. Two impl blocks: one for `Path`, one for `T: Deref<Target = str>`. Contains `normalize_inner()`, `needs_normalization()`, `relative_str()` and helper functions | ||
| - **`src/sugar_path_buf.rs`** — Consuming `PathBuf` trait definition with doc examples | ||
| - **`src/impl_sugar_path.rs`** — All implementations. Two impl blocks: one for `Path`, one for `str`; `String` and other string-like values use normal deref method lookup. Contains `normalize_inner()`, `needs_normalization()`, `relative_str()` and helper functions | ||
| - **`src/utils.rs`** — `get_current_dir()` helper for `absolutize()` | ||
|
|
| cargo allocs --write benchmarks/allocations/aarch64-apple-darwin-default.snap | ||
| cargo allocs-rolldown --write benchmarks/allocations/aarch64-apple-darwin-rolldown.snap | ||
| cargo allocs --check benchmarks/allocations/aarch64-apple-darwin-default.snap | ||
| cargo allocs-rolldown --check benchmarks/allocations/aarch64-apple-darwin-rolldown.snap |
Align architecture notes with try_get_current_dir after the ambient cwd API returned Result.
* docs: redesign public API guidance * docs: complete public API guidance pass Fill remaining method examples and try/lossy contracts, polish the README for crates.io/docs.rs readers, and declare package docs metadata. * ci: stop double-running Test on PR branch pushes Restrict push triggers to main (match CodSpeed), cancel in-progress runs for the same PR, and run host-independent fmt/doc once on Ubuntu. * ci: keep only Linux and Windows Rolldown allocation gates Drop macOS and default-feature committed snapshots. Continuous CI checks the two primary-consumer Rolldown snaps only, and docs/PCR stay in sync. * refactor: make cargo allocs the only allocation entry point Always measure cached_current_dir in the tracker, drop allocs-rolldown, and rename the committed snaps to plain target triples. * docs: fix AGENTS.md cwd helper name after PR #40 review Align architecture notes with try_get_current_dir after the ambient cwd API returned Result. * fix: silence clippy write_literal in allocation snapshot header Inline the fixed cached_current_dir configuration label into the format string so CI clippy -D warnings passes again.
## Summary Upgrade workspace `sugar_path` from **2.0.1 → 3.0.0**, migrate every call site that broke, and centralize Rolldown's known-UTF-8 path compositions so the “right” usage is hard to miss. ### Why sugar_path 3 helps Rolldown sugar_path 3 is the breaking redesign aimed at Rolldown-shaped work: - **`relative` → `Cow<Path>`**: clean descendant paths can return a borrowed suffix with **zero result allocation** (the package-sideEffects-style shape was about **19–21% faster** in same-machine microbenches vs the old owned-`PathBuf` API; not a whole-build claim). - **Strict / consuming slash conversion**: `into_slash` can reuse an owned `PathBuf` buffer for the final UTF-8 `String` (one allocation for the final container). - **Owned cwd reuse**: `absolutize_with(cwd.join(out_dir))` can grow the joined buffer instead of cloning after normalize (already how Rolldown calls it). - **Windows**: relative calculation no longer allocates slash-normalized copies just to compare clean native paths. Rolldown already enables `cached_current_dir`; that stays. Those wins only show up if call sites use the intended composition. Open-coding `relative(...).to_slash_lossy().into_owned()` or `relative(...).as_path().expect_to_slash()` leaves performance on the table and fights the 3.0 API. ### API migration (compile / contract) - `relative` returns `Cow<Path>` → `.into_owned()` where a `PathBuf` is required - `to_slash()` is strict (no `Option`) → drop `.unwrap()` / `.map_or_else` - Equal paths → empty relative path; call sites that need `.` / `./` keep that policy via helpers - `absolutize_with(cwd.join(out_dir))` already passes owned cwd (no change needed) ### Prevent wrong usage next time 1. **Helpers** in `rolldown_std_utils` (`relative_path_to_slash`, `relative_path_as_js_specifier`, `path_buf_to_slash`, …) with unit tests — hot call sites use these. 2. **Style guide**: [`internal-docs/path-manipulation/style-guide.md`](https://github.com/rolldown/rolldown/blob/chore/upgrade-sugar-path-3/internal-docs/path-manipulation/style-guide.md) — domain assumptions, preferred helpers, anti-patterns, PR checklist. Linked from `internal-docs/module-id/implementation.md`. **Guidance for future code:** if you need “path relative to X as a `/` string for a module id / import / diagnostic”, use `rolldown_std_utils` first. Do not reintroduce `to_slash_lossy` on known-UTF-8 module paths. ### Intentionally left as direct sugar_path / lossy Simple display conversions that are not a relative→String composition (import-glob bases, package.json realpath, some cwd display). Those are not the measured hot composition. ## Test plan - [x] `cargo check --workspace --all-targets` - [x] `cargo test -p rolldown_std_utils` - [x] `cargo test -p rolldown_utils stabilize_id` - [x] `cargo test -p rolldown_common stabilize` - [x] `cargo clippy` on touched crates with `-D warnings` - [ ] CI full matrix ## Related - sugar_path 3.0.0: https://crates.io/crates/sugar_path/3.0.0 - sugar_path redesign: hyfdev/sugar_path#40
Summary
SugarPatharound borrowedPath/str, explicit cwd, and strict, fallible, or explicitly lossy Unicode conversionSugarPathBufsurface only for consuming operations that measurably reuse owned storagerelativereturnCow<Path>and borrow canonical host-native descendant suffixes without allocatingPerformance
Same-machine Criterion measurements used an Apple M3 Pro with
cached_current_dir. They were recorded during branch development against a saved pre-redesign public-API checkpoint, before PR #41 established the accepted main baseline. They are valid same-host microbenchmark evidence for the representative cases below, but they are not a timing comparison between9e6b627and59877dband are not CodSpeed evidence. The package-sideEffects branch counts come from a pinned Rolldown trace; the timing rows use representative 96-byte descendant and 135-byte upward fixtures.alloc/reallocrelativeThe weighted allocation row is a derived Unix result-buffer model: the previous public API allocated a result
PathBufon every call, while the final API borrows each descendant and allocates only the two upward results. It is a 99.96% reduction in result-buffer allocation calls for that observed branch mix, not a measurement of whole-build allocations. Against the saved already-borrowing Cow prototype rather than the previous public API, the final weighted implementation is still about 10–12% faster.These are representative SugarPath microbenchmarks, not a claim that an entire Rolldown build is 19–21% faster. Rolldown is not changed in this PR, still needs to migrate to the new API after the breaking release, and has no end-to-end build timing for this change yet. The ARM64 short-prefix and dotted controls are unchanged;
different_subtreeswas noisy and is not counted as a win.Accepted baseline comparison
PR #41 established the accepted pre-change baseline at
9e6b627. This PR is rebuilt directly on that commit, with the same stable benchmark identities and ordered allocation scenarios plus one final-only owned-cwd row.All GitHub Actions tests, native allocation checks, and the CodSpeed simulation and memory execution jobs pass. The external CodSpeed analysis reports 52 improvements, 18 regressions, 146 unchanged rows, and 30 skipped historical rows, but it also flags different runtime environments: the baseline CPU Simulation ran on AMD EPYC 7763, while the PR CPU Simulation ran on Intel Xeon Platinum 8573C. Those counts are included for transparency, but the 18 CPU deltas are not treated as confirmed regressions and are not used to support the speedup claims above. A same-CPU hot-path follow-up is tracked separately.
The allocation conclusions do not depend on that cross-CPU timing comparison. Committed native snapshots on macOS ARM64, Linux GNU x86_64, and Windows MSVC x86_64 verify the final allocation and reallocation counts in both default and Rolldown configurations.
Why
Rolldown is SugarPath's main consumer. A trace of the pinned ThreeJS and Rome builds recorded 19,518
relativecalls. The aggregate borrowable-descendant rate was 4,888 of 19,518 (25.04%), but caller attribution changes the decision: the package-sideEffects caller produced all 4,888 hits and only two upward misses.StableModuleId,stabilize_id, and sourcemap calls were all upward; the first ends inArcStr, while the latter two end inString.That evidence supports one borrowing
relativeAPI plus ordinary consuming composition. It does not support separate publicrelative_cowor fused relative-to-string methods.Public API
SugarPathis now sealed and implemented directly forPathandstr.PathBuf,String, and string wrappers retain method syntax through deref lookup, while genericT: SugarPathbounds and UFCS on wrappers must use the underlyingPathorstr:normalizeabsolutize,try_absolutize,absolutize_withrelative,try_relative,relative_withto_slash,try_to_slash,to_slash_lossyas_pathSugarPathBufis sealed and implemented only forPathBuf:into_normalizedinto_slashtry_into_slashinto_slash_lossyrelativenow returnsCow<Path>.to_slashandinto_slashare strict; thetry_*forms preserve failure without replacement, and only explicitly named*_lossymethods replace invalid encoding.try_into_slashreturns the originalPathBufon failure. Explicit-cwd methods accept borrowed or owned values directly, and an ownedPathBufcan supply the result buffer.Semantics
normalizepreserves one trailing separator;relativestrips the target trailing separator and returns an empty path for equal inputs.try_*exposes lookup errors; ordinary methods document their panic contract./remains a literal component character. Components that cannot be represented as a standalone native relative path fall back to the normalized target without changing their meaning.Node is the reference for host-native lexical behavior where its output is a valid native path relation. SugarPath deliberately differs for ordinary Windows UNC paths with the same server and different shares:
server + shareis the root, so it returns the normalized absolute target rather than a spelling that remains inside the original share.Final allocation contracts
Native snapshots on macOS ARM64, Linux GNU x64, and Windows MSVC x64 verify the same clean-path allocation counts in both default and Rolldown configurations:
allocreallocrelative -> Cow<Path>relative -> Cow::into_owned PathBufPathBufrelative(...).into_owned().into_slash() -> StringStringbufferrelative(...).into_owned().into_slash() -> StringStringPathBuf::into_normalizedPathBuf::into_slashabsolutize_withAll rows also have zero
alloc_zeroedcalls. Requested bytes are target- and path-shape-specific, so allocation calls and reallocations are the cross-platform contract.The ordinary Windows descendant fast path compares native prefixes and components without allocating normalized copies. Its bounded representability guard reads only the first two result bytes and does not add another target/base scan.
A caller-side
strip_prefixpre-check did not produce a stable descendant advantage and made the recorded upward misses about 70–80% slower, so the generic API keeps one shared scan.Validation
No Docker, Wine, or container-backed Windows command was run for this revision.
Review guide
PR #41 landed the accepted pre-change baseline at
9e6b627. This PR is rebuilt directly on that baseline and now contains two commits:e639516—feat!: redesign path APIs for borrowing and owned reuse59877db—bench: record final allocation resultsReview the first commit for the public API, implementation, semantics, tests, README, changelog, and PCR records. Review the second for the final native allocation snapshots and their generation records. Superseded compatibility and prototype detours are absent from the rebuilt branch history.
Follow-up
Rolldown source migration remains separate until this breaking SugarPath release lands. Its known-UTF-8 package-sideEffects, stable ID, and sourcemap call sites should then move to the final API while preserving empty-relative handling.
Native Windows timing remains the only platform-specific performance evidence gap; native Windows correctness and allocation behavior are continuously checked.