CanonicalizePath: Remove kMaxComponents limit

This patch refactors the CanonicalizePath() to fix
two issues and improve performance. This is achieved
through the following:

- Remove the kMaxPathComponents limit entirely,
  which fixes ninja-build#1732, by dropping the
  `components` array entirely, in favor of
  back-tracking the destination pointer.

- Properly handle '/' and '\' which were incorrectly
  converted into an empty string. This fixes
  ninja-build#2008.

- Skip initial '../' components in relative paths,
  as these are common when referencing source files
  in build plans, and doing so make the loop after
  this step run faster in practice since most
  source files do not need adjustments.

- Simplify the inner loop logic by handling the
  last component (which is not followed by a
  trailing directory separator) separately.

  This noticeably improves performance because
  the inner loop becomes smaller with less branch
  mis-predictions in the general case.

- Never access or copy the caharacter after the
  end of the input string.

- Use memchr() to find the next '/' on Posix, which
  allows the use of SIMD implementations provided by
  the C runtime (e.g. through IFUNC functions on Linux),
  resulting in very noticeable speedup. This is also
  why a statically Ninja executable will be slower
  than one that links to the C library dynamically :-/

- Avoid performing any writes when the input path
  doesn't need any adjustment, which is also quite
  common.

Note that this patch does _not_ remove the 64-bit
limit for the `slash_bits`  value, which is only
used on Win32.

Benchmarking was done in several ways:

- On Linux, running `hyperfine canon-perftest` to run the
  canonicalization benchmark program and measure its total
  running time. Three compilers were used to generate
  dynamically-linked executables.

  ```
               BEFORE (ms)  AFTER (ms)

  GCC 13.2.0      651         369
  Clang 14.0      591         402
  Clang 18,0      653         400
  ```

- On Windows, running `canon-perftest` 5 times and keeping
  the best reported average result. The number are slower
  since they only measure the benched function.

  ```
                   BEFORE (ms)  AFTER (ms)

  Mingw64 GCC 12      246         195
  ```

- On Linux, run `hyperfine ninja -C out/default -n --quiet`
  on a large Fuchsia build plan, once with 70000+ pending
  commands, and once after the build (i.e. `ninja: no work to do`).

  ````
               BEFORE (s)  AFTER (s)

  pre_build       8.789    8.647
  post_build      6.703    6.590
  ```
2 files changed
tree: e92a09a54a4817b24416880a3457388d59449db9
  1. .github/
  2. doc/
  3. misc/
  4. src/
  5. windows/
  6. .clang-format
  7. .clang-tidy
  8. .editorconfig
  9. .gitignore
  10. appveyor.yml
  11. CMakeLists.txt
  12. configure.py
  13. CONTRIBUTING.md
  14. COPYING
  15. README.md
  16. RELEASING.md
README.md

Ninja

Ninja is a small build system with a focus on speed. https://ninja-build.org/

See the manual or doc/manual.asciidoc included in the distribution for background and more details.

Binaries for Linux, Mac and Windows are available on GitHub. Run ./ninja -h for Ninja help.

Installation is not necessary because the only required file is the resulting ninja binary. However, to enable features like Bash completion and Emacs and Vim editing modes, some files in misc/ must be copied to appropriate locations.

If you're interested in making changes to Ninja, read CONTRIBUTING.md first.

Building Ninja itself

You can either build Ninja via the custom generator script written in Python or via CMake. For more details see the wiki.

Python

./configure.py --bootstrap

This will generate the ninja binary and a build.ninja file you can now use to build Ninja with itself.

CMake

cmake -Bbuild-cmake
cmake --build build-cmake

The ninja binary will now be inside the build-cmake directory (you can choose any other name you like).

To run the unit tests:

./build-cmake/ninja_test