From 369a2cdf6a93266c01a267dbd9e94aa95c8659c9 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 9 Sep 2024 16:07:42 -0500 Subject: [PATCH 01/11] Run linters on our markdown. --- CODE_OF_CONDUCT.md | 50 ++++++++++++++++++------------------- README.md | 35 +++++++++++++------------- docs/BinarySize.md | 4 +-- docs/Development.md | 10 ++++---- docs/DigitSeparators.md | 13 +++++----- fuzz/README.md | 3 +-- lexical-asm/README.md | 3 +-- lexical-benchmark/README.md | 5 ++-- lexical-size/README.md | 3 +-- 9 files changed, 60 insertions(+), 66 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 74fd657b..d209e5bb 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -21,29 +21,29 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: - * Using welcoming and inclusive language. - * Being respectful of differing viewpoints and experiences. - * Gracefully accepting constructive feedback. - * Focusing on what is best for the community. - * Showing empathy and kindness towards other community members. - * Encouraging and raising up your peers in the project so you can all bask in hacks and glory. +- Using welcoming and inclusive language. +- Being respectful of differing viewpoints and experiences. +- Gracefully accepting constructive feedback. +- Focusing on what is best for the community. +- Showing empathy and kindness towards other community members. +- Encouraging and raising up your peers in the project so you can all bask in hacks and glory. Examples of unacceptable behavior by participants include: - * The use of sexualized language or imagery and unwelcome sexual attention or advances, including when simulated online. The only exception to sexual topics is channels/spaces specifically for topics of sexual identity. - * Casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology. - * Making light of/making mocking comments about trigger warnings and content warnings. - * Trolling, insulting/derogatory comments, and personal or political attacks. - * Public or private harassment, deliberate intimidation, or threats. - * Publishing others' private information, such as a physical or electronic address, without explicit permission. This includes any sort of "outing" of any aspect of someone's identity without their consent. - * Publishing private screenshots or quotes of interactions in the context of this project without all quoted users' *explicit* consent. - * Publishing of private communication that doesn't have to do with reporting harrassment. - * Any of the above even when [presented as "ironic" or "joking"](https://en.wikipedia.org/wiki/Hipster_racism). - * Any attempt to present "reverse-ism" versions of the above as violations. Examples of reverse-isms are "reverse racism", "reverse sexism", "heterophobia", and "cisphobia". - * Unsolicited explanations under the assumption that someone doesn't already know it. Ask before you teach! Don't assume what people's knowledge gaps are. - * [Feigning or exaggerating surprise](https://www.recurse.com/manual#no-feigned-surprise) when someone admits to not knowing something. - * "[Well-actuallies](https://www.recurse.com/manual#no-well-actuallys)" - * Other conduct which could reasonably be considered inappropriate in a professional or community setting. +- The use of sexualized language or imagery and unwelcome sexual attention or advances, including when simulated online. The only exception to sexual topics is channels/spaces specifically for topics of sexual identity. +- Casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology. +- Making light of/making mocking comments about trigger warnings and content warnings. +- Trolling, insulting/derogatory comments, and personal or political attacks. +- Public or private harassment, deliberate intimidation, or threats. +- Publishing others' private information, such as a physical or electronic address, without explicit permission. This includes any sort of "outing" of any aspect of someone's identity without their consent. +- Publishing private screenshots or quotes of interactions in the context of this project without all quoted users' *explicit* consent. +- Publishing of private communication that doesn't have to do with reporting harrassment. +- Any of the above even when [presented as "ironic" or "joking"](https://en.wikipedia.org/wiki/Hipster_racism). +- Any attempt to present "reverse-ism" versions of the above as violations. Examples of reverse-isms are "reverse racism", "reverse sexism", "heterophobia", and "cisphobia". +- Unsolicited explanations under the assumption that someone doesn't already know it. Ask before you teach! Don't assume what people's knowledge gaps are. +- [Feigning or exaggerating surprise](https://www.recurse.com/manual#no-feigned-surprise) when someone admits to not knowing something. +- "[Well-actuallies](https://www.recurse.com/manual#no-well-actuallys)" +- Other conduct which could reasonably be considered inappropriate in a professional or community setting. ## Scope @@ -70,12 +70,12 @@ You may get in touch with the maintainer team through any of the following metho ### Further Enforcement -If you've already followed the [initial enforcement steps](#enforcement), these are the steps maintainers will take for further enforcement, as needed: +If you've already followed the [initial enforcement steps](#maintainer-enforcement-process), these are the steps maintainers will take for further enforcement, as needed: - 1. Repeat the request to stop. - 2. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked. - 3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours. - 4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used. +1. Repeat the request to stop. +2. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked. +3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours. +4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used. On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary. diff --git a/README.md b/README.md index e4431f79..e99e23bf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -lexical -======= +# lexical High-performance numeric conversion routines for use in a `no_std` environment. This does not depend on any standard library features, nor a system allocator. @@ -26,7 +25,7 @@ If you want a minimal, stable, and compile-time friendly version of lexical's fl - [License](#license) - [Contributing](#contributing) -# Getting Started +## Getting Started Add lexical to your `Cargo.toml`: @@ -67,7 +66,7 @@ where } ``` -# Partial/Complete Parsers +## Partial/Complete Parsers Lexical has both partial and complete parsers: the complete parsers ensure the entire buffer is used while parsing, without ignoring trailing characters, while the partial parsers parse as many characters as possible, returning both the parsed value and the number of parsed digits. Upon encountering an error, lexical will return an error indicating both the error type and the index at which the error occurred inside the buffer. @@ -88,7 +87,7 @@ let x: i32 = lexical_core::parse(b"123 456")?; let (x, count): (i32, usize) = lexical_core::parse_partial(b"123 456")?; ``` -# no_std +## no_std `lexical-core` does not depend on a standard library, nor a system allocator. To use `lexical-core` in a `no_std` environment, add the following to `Cargo.toml`: @@ -120,7 +119,7 @@ let d: f64 = lexical_core::parse(b"3.5")?; // Ok(3.5), error checking parse. let d: f64 = lexical_core::parse(b"3a")?; // Err(Error(_)), failed to parse. ``` -# Features +## Features Lexical feature-gates each numeric conversion routine, resulting in faster compile times if certain numeric conversions. These features can be enabled/disabled for both `lexical-core` (which does not require a system allocator) and `lexical`. By default, all conversions are enabled. @@ -149,7 +148,7 @@ To ensure the safety when bounds checking is disabled, we extensively fuzz the a Lexical also places a heavy focus on code bloat: with algorithms both optimized for performance and size. By default, this focuses on performance, however, using the `compact` feature, you can also opt-in to reduced code size at the cost of performance. The compact algorithms minimize the use of pre-computed tables and other optimizations at the cost of performance. -# Customization +## Customization > ⚠ **WARNING:** If changing the number of significant digits written, disabling the use of exponent notation, or changing exponent notation thresholds, `BUFFER_SIZE` may be insufficient to hold the resulting output. `WriteOptions::buffer_size` will provide a correct upper bound on the number of bytes written. If a buffer of insufficient length is provided, lexical-core will panic. @@ -176,7 +175,7 @@ Due the high variability in the syntax of numbers in different programming and d A limited subset of functionality is documented in examples below, however, the complete specification can be found in the API reference documentation. -## Number Format API +### Number Format API The number format class provides numerous flags to specify number syntax when parsing or writing. When the `power-of-two` feature is enabled, additional flags are added: @@ -213,7 +212,7 @@ const FORMAT: u128 = lexical_core::NumberFormatBuilder::new() debug_assert!(lexical_core::format_is_valid::()); ``` -## Options API +### Options API The options API allows customizing number parsing and writing at run-time, such as specifying the maximum number of significant digits, exponent characters, and more. @@ -239,7 +238,7 @@ let options = lexical_core::WriteFloatOptions::builder() .unwrap(); ``` -# Documentation +## Documentation Lexical's API reference can be found on [docs.rs](https://docs.rs/lexical), as can [lexical-core's](lexical-core). Detailed descriptions of the algorithms used can be found here: @@ -250,7 +249,7 @@ Lexical's API reference can be found on [docs.rs](https://docs.rs/lexical), as c In addition, descriptions of how lexical handles [digit separators](https://github.com/Alexhuszagh/rust-lexical/blob/main/docs/DigitSeparators.md) and implements [big-integer arithmetic](https://github.com/Alexhuszagh/rust-lexical/blob/main/lexical-parse-float/docs/BigInteger.md) are also documented. -# Validation +## Validation **Float-Parsing** @@ -264,7 +263,7 @@ Float parsing is difficult to do correctly, and major bugs have been found in im Although lexical may contain bugs leading to rounding error, it is tested against a comprehensive suite of random-data and near-halfway representations, and should be fast and correct for the vast majority of use-cases. -# Metrics +## Metrics Various benchmarks, binary sizes, and compile times are shown here: @@ -305,13 +304,13 @@ A benchmark on writing floats generated via a random-number generator and parsed ![Random Data](https://raw.githubusercontent.com/Alexhuszagh/rust-lexical/main/lexical-write-float/assets/json.svg) -# Safety +## Safety Due to the use of memory unsafe code in the integer and float writers, we extensively fuzz our float writers and parsers. The fuzz harnesses may be found under [fuzz](https://github.com/Alexhuszagh/rust-lexical/tree/main/fuzz), and are run continuously. So far, we've parsed and written over 72 billion floats. Due to the simple logic of the integer writers, and the lack of memory safety in the integer parsers, we minimally fuzz both, and test it with edge-cases, which has shown no memory safety issues to date. -# Platform Support +## Platform Support lexical-core is tested on a wide variety of platforms, including big and small-endian systems, to ensure portable code. Supported architectures include: - x86_64 Linux, Windows, macOS, Android, iOS, FreeBSD, and NetBSD. @@ -326,7 +325,7 @@ lexical-core is tested on a wide variety of platforms, including big and small-e lexical-core should also work on a wide variety of other architectures and ISAs. If you have any issue compiling lexical-core on any architecture, please file a bug report. -# Versioning and Version Support +## Versioning and Version Support **Version Support** @@ -349,15 +348,15 @@ Please report any errors compiling a supported lexical-core version on a compati lexical uses [semantic versioning](https://semver.org/). Removing support for Rustc versions newer than the latest stable Debian or Ubuntu version is considered an incompatible API change, requiring a major version change. -# Changelog +## Changelog All changes are documented in [CHANGELOG](https://github.com/Alexhuszagh/rust-lexical/blob/main/CHANGELOG). -# License +## License Lexical is dual licensed under the Apache 2.0 license as well as the MIT license. See the [LICENSE.md](LICENSE.md) file for full license details. -# Contributing +## Contributing Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in lexical by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. Contributing to the repository means abiding by the [code of conduct](https://github.com/Alexhuszagh/rust-lexical/blob/main/CODE_OF_CONDUCT.md). diff --git a/docs/BinarySize.md b/docs/BinarySize.md index bd228e8d..a93ec6e1 100644 --- a/docs/BinarySize.md +++ b/docs/BinarySize.md @@ -6,7 +6,7 @@ Each binary is generated using all optimization levels, and includes the result All these binaries sizes are *relative* to the size of an empty Rust binary: that is, the size of the empty executable is subtracted from the total binary's size. For some cases, this leads to results of 0 bytes, which isn't real, but in practice leads to no additional size in the resulting executable. -# Default +## Default **Optimization Level "0"** @@ -50,7 +50,7 @@ All these binaries sizes are *relative* to the size of an empty Rust binary: tha ![Parse Stripped - Optimization Level "z"](https://raw.githubusercontent.com/Alexhuszagh/rust-lexical/main/assets/size_parse_stripped_optz_posix.svg) ![Write Stripped - Optimization Level "z"](https://raw.githubusercontent.com/Alexhuszagh/rust-lexical/main/assets/size_write_stripped_optz_posix.svg) -# Compact +## Compact **Optimization Level "0"** diff --git a/docs/Development.md b/docs/Development.md index f48fd165..e6eaa914 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -7,7 +7,7 @@ cargo +nightly build cargo +nightly test ``` -# Code Structure +## Code Structure Lexical is broken up into compact, relatively isolated workspaces to separate functionality based on the numeric conversion, minimizing compile times and simplifying testing feature-dependent code. The workspaces are: @@ -26,7 +26,7 @@ Furthermore, any unsafe code uses the following conventions: 1. Each unsafe function must contain a `# Safety` section. 2. Unsafe operations/calls in unsafe functions must be marked as unsafe, with their safety guarantees clearly documented via a `// SAFETY:` section. -# Dependencies +## Dependencies In order to fully test and develop lexical, a recent, nightly compiler along with following Rust dependencies is required: @@ -57,7 +57,7 @@ In addition, the following non-Rust dependencies must be installed: - python-magic (python-magic-win64 on Windows) - Valgrind -# Development Process +## Development Process The [scripts](https://github.com/Alexhuszagh/rust-lexical/tree/main/scripts) directory contains numerous scripts for testing, fuzzing, analyzing, and formatting code. Since many development features are nightly-only, this ensures the proper compiler features are used. This requires a recent version of a nightly compiler (1.65.0+) installed via Rustup, which can be invoked as `cargo +nightly`. @@ -87,7 +87,7 @@ scripts/check.sh SKIP_MIRI=1 scripts/test.sh ``` -# Safety +## Safety In order to ensure memory safety even when using unsafe features, we have the following requirements. @@ -106,6 +106,6 @@ RUSTFLAGS="--deny warnings" cargo +nightly build --features=lint cargo +nightly clippy --all-features -- --deny warnings ``` -# Algorithm Changes +## Algorithm Changes Each workspace has a "docs" directory containing detailed descriptions of algorithms and benchmarks. If you make any substantial changes to an algorithm, you should both update the algorithm description and the provided benchmarks. diff --git a/docs/DigitSeparators.md b/docs/DigitSeparators.md index 399119a4..9293c8e1 100644 --- a/docs/DigitSeparators.md +++ b/docs/DigitSeparators.md @@ -1,5 +1,4 @@ -Digit Separators -================ +# Digit Separators Supporting performant parsers using digit separators in a no-allocator context is difficult to support correctly with adequate performance. One of the major issues is that the syntax of numbers that accept digit separators varies between implementations. @@ -25,11 +24,11 @@ double x = 1._0; // invalid This means any parser must be context-aware, and also understand control characters: a digit separator followed by a decimal point is a trailing digit separator, while one followed by a digit is an internal one. -# Defining Grammar +## Defining Grammar Due to the context-aware nature, it's important to define the grammar on how digit separators work: -1. Leading digit separators come before any other input, or after control characters. Any digit separators after a leading digit separator are considered leading, even if consecutive digit separators are not allowed. +- Leading digit separators come before any other input, or after control characters. Any digit separators after a leading digit separator are considered leading, even if consecutive digit separators are not allowed. Examples therefore include: @@ -44,7 +43,7 @@ __1.0 1.0e__5 ``` -2. Trailing digit separators come after any other input, or before control characters. Any digit separators before another trailing digit separator are considered trailing, even if consecutive digit separators are not allowed. +- Trailing digit separators come after any other input, or before control characters. Any digit separators before another trailing digit separator are considered trailing, even if consecutive digit separators are not allowed. Examples therefore include: @@ -59,7 +58,7 @@ Examples therefore include: 1.0e5__ ``` -3. Internal digit separators therefore are any digit separators that cannot be classified as leading or trailing. Likewise, any digit separators that are adjacent to another internal digit separator are considered internal, even if consecutive digit separators are not allowed. +- Internal digit separators therefore are any digit separators that cannot be classified as leading or trailing. Likewise, any digit separators that are adjacent to another internal digit separator are considered internal, even if consecutive digit separators are not allowed. Examples therefore include: @@ -78,7 +77,7 @@ Examples therefore include: This opens up a lot of possibilities: what is a valid control character? In practice, it's much easier to define control characters as every character that's not a valid digit, and therefore to handle parsing we just need to check against valid digits and the digit separator. -# Iterator Design +## Iterator Design The iterator is therefore a generic based on the format specification: this allows the iterator to resolve all unnecessary branching at compile time. diff --git a/fuzz/README.md b/fuzz/README.md index 2fcae3fa..870be4d6 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -1,4 +1,3 @@ -lexical-fuzz -============ +# lexical-fuzz Fuzzing routines to minimize the risk of any memory unsafety. See [scripts/fuzz.sh](/scripts/fuzz.sh) for use. diff --git a/lexical-asm/README.md b/lexical-asm/README.md index 91244d98..ec13d493 100644 --- a/lexical-asm/README.md +++ b/lexical-asm/README.md @@ -1,5 +1,4 @@ -lexical-asm -=========== +# lexical-asm Utilities to carefully monitor the assembly generation of lexical's numeric conversion routines. See [scripts/asm.sh](/scripts/asm.sh) for use. diff --git a/lexical-benchmark/README.md b/lexical-benchmark/README.md index 0aeb8fd1..3fa08179 100644 --- a/lexical-benchmark/README.md +++ b/lexical-benchmark/README.md @@ -1,9 +1,8 @@ -lexical-benchmark -================= +# lexical-benchmark Benchmarks comparing lexical to other numeric conversion routines. -# Running the Benchmark +## Running the Benchmark The benchmark requires the following: diff --git a/lexical-size/README.md b/lexical-size/README.md index 44e3885a..d2ce049b 100644 --- a/lexical-size/README.md +++ b/lexical-size/README.md @@ -1,5 +1,4 @@ -lexical-size -============ +# lexical-size Utilities to detect the binary size of lexical's numeric conversion routines. See [scripts/size.py](/scripts/size.py) for use. From 6dbe8e3b3ba0cb897e618ee38279fec59a53b4b4 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 9 Sep 2024 18:20:35 -0500 Subject: [PATCH 02/11] Remove table unsafe. --- lexical-parse-float/src/table_decimal.rs | 61 +- lexical-parse-float/src/table_radix.rs | 937 +++++++---------------- lexical-write-integer/src/radix.rs | 40 +- 3 files changed, 329 insertions(+), 709 deletions(-) diff --git a/lexical-parse-float/src/table_decimal.rs b/lexical-parse-float/src/table_decimal.rs index 0028ed1b..60bc15fd 100644 --- a/lexical-parse-float/src/table_decimal.rs +++ b/lexical-parse-float/src/table_decimal.rs @@ -14,46 +14,31 @@ use static_assertions::const_assert; // ------- /// Get lookup table for small int powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] #[cfg(not(feature = "power-of-two"))] -pub unsafe fn get_small_int_power(exponent: usize, radix: u32) -> u64 { +pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { // NOTE: don't check the radix since we also use it for half radix, or 5. match radix { - 5 => unsafe { get_small_int_power5(exponent) }, - 10 => unsafe { get_small_int_power10(exponent) }, + 5 => get_small_int_power5(exponent), + 10 => get_small_int_power10(exponent), _ => unreachable!(), } } /// Get lookup table for small f32 powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] #[cfg(not(feature = "power-of-two"))] -pub unsafe fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { +pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { debug_assert_radix(radix); - unsafe { get_small_f32_power10(exponent) } + get_small_f32_power10(exponent) } /// Get lookup table for small f64 powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] #[cfg(not(feature = "power-of-two"))] -pub unsafe fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { +pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { debug_assert_radix(radix); - unsafe { get_small_f64_power10(exponent) } + get_small_f64_power10(exponent) } /// Get pre-computed power for a large power of radix. @@ -63,43 +48,27 @@ pub const fn get_large_int_power(_: u32) -> (&'static [Limb], u32) { } /// Get pre-computed int power of 5. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW5.len()`. #[inline(always)] -pub unsafe fn get_small_int_power5(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW5[exponent]) } +pub fn get_small_int_power5(exponent: usize) -> u64 { + SMALL_INT_POW5[exponent] } /// Get pre-computed int power of 10. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW10.len()`. #[inline(always)] -pub unsafe fn get_small_int_power10(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW10[exponent]) } +pub fn get_small_int_power10(exponent: usize) -> u64 { + SMALL_INT_POW10[exponent] } /// Get pre-computed f32 power of 10. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW10.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power10(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW10[exponent]) } +pub fn get_small_f32_power10(exponent: usize) -> f32 { + SMALL_F32_POW10[exponent] } /// Get pre-computed f64 power of 10. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW10.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power10(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW10[exponent]) } +pub fn get_small_f64_power10(exponent: usize) -> f64 { + SMALL_F64_POW10[exponent] } // TABLES diff --git a/lexical-parse-float/src/table_radix.rs b/lexical-parse-float/src/table_radix.rs index 2adb5942..9dab33be 100644 --- a/lexical-parse-float/src/table_radix.rs +++ b/lexical-parse-float/src/table_radix.rs @@ -17,155 +17,134 @@ use static_assertions::const_assert; // ------- /// Get lookup table for 2 digit radix conversions. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] -pub unsafe fn get_small_int_power(exponent: usize, radix: u32) -> u64 { +pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { debug_assert_radix(radix); - unsafe { - match radix { - 2 => get_small_int_power2(exponent), - 3 => get_small_int_power3(exponent), - 4 => get_small_int_power4(exponent), - 5 => get_small_int_power5(exponent), - 6 => get_small_int_power6(exponent), - 7 => get_small_int_power7(exponent), - 8 => get_small_int_power8(exponent), - 9 => get_small_int_power9(exponent), - 10 => get_small_int_power10(exponent), - 11 => get_small_int_power11(exponent), - 12 => get_small_int_power12(exponent), - 13 => get_small_int_power13(exponent), - 14 => get_small_int_power14(exponent), - 15 => get_small_int_power15(exponent), - 16 => get_small_int_power16(exponent), - 17 => get_small_int_power17(exponent), - 18 => get_small_int_power18(exponent), - 19 => get_small_int_power19(exponent), - 20 => get_small_int_power20(exponent), - 21 => get_small_int_power21(exponent), - 22 => get_small_int_power22(exponent), - 23 => get_small_int_power23(exponent), - 24 => get_small_int_power24(exponent), - 25 => get_small_int_power25(exponent), - 26 => get_small_int_power26(exponent), - 27 => get_small_int_power27(exponent), - 28 => get_small_int_power28(exponent), - 29 => get_small_int_power29(exponent), - 30 => get_small_int_power30(exponent), - 31 => get_small_int_power31(exponent), - 32 => get_small_int_power32(exponent), - 33 => get_small_int_power33(exponent), - 34 => get_small_int_power34(exponent), - 35 => get_small_int_power35(exponent), - 36 => get_small_int_power36(exponent), - _ => hint::unreachable_unchecked(), - } + match radix { + 2 => get_small_int_power2(exponent), + 3 => get_small_int_power3(exponent), + 4 => get_small_int_power4(exponent), + 5 => get_small_int_power5(exponent), + 6 => get_small_int_power6(exponent), + 7 => get_small_int_power7(exponent), + 8 => get_small_int_power8(exponent), + 9 => get_small_int_power9(exponent), + 10 => get_small_int_power10(exponent), + 11 => get_small_int_power11(exponent), + 12 => get_small_int_power12(exponent), + 13 => get_small_int_power13(exponent), + 14 => get_small_int_power14(exponent), + 15 => get_small_int_power15(exponent), + 16 => get_small_int_power16(exponent), + 17 => get_small_int_power17(exponent), + 18 => get_small_int_power18(exponent), + 19 => get_small_int_power19(exponent), + 20 => get_small_int_power20(exponent), + 21 => get_small_int_power21(exponent), + 22 => get_small_int_power22(exponent), + 23 => get_small_int_power23(exponent), + 24 => get_small_int_power24(exponent), + 25 => get_small_int_power25(exponent), + 26 => get_small_int_power26(exponent), + 27 => get_small_int_power27(exponent), + 28 => get_small_int_power28(exponent), + 29 => get_small_int_power29(exponent), + 30 => get_small_int_power30(exponent), + 31 => get_small_int_power31(exponent), + 32 => get_small_int_power32(exponent), + 33 => get_small_int_power33(exponent), + 34 => get_small_int_power34(exponent), + 35 => get_small_int_power35(exponent), + 36 => get_small_int_power36(exponent), + _ => hint::unreachable_unchecked(), } } /// Get lookup table for small f32 powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] -pub unsafe fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { +pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { debug_assert_radix(radix); - unsafe { - match radix { - 2 => get_small_f32_power2(exponent), - 3 => get_small_f32_power3(exponent), - 4 => get_small_f32_power4(exponent), - 5 => get_small_f32_power5(exponent), - 6 => get_small_f32_power6(exponent), - 7 => get_small_f32_power7(exponent), - 8 => get_small_f32_power8(exponent), - 9 => get_small_f32_power9(exponent), - 10 => get_small_f32_power10(exponent), - 11 => get_small_f32_power11(exponent), - 12 => get_small_f32_power12(exponent), - 13 => get_small_f32_power13(exponent), - 14 => get_small_f32_power14(exponent), - 15 => get_small_f32_power15(exponent), - 16 => get_small_f32_power16(exponent), - 17 => get_small_f32_power17(exponent), - 18 => get_small_f32_power18(exponent), - 19 => get_small_f32_power19(exponent), - 20 => get_small_f32_power20(exponent), - 21 => get_small_f32_power21(exponent), - 22 => get_small_f32_power22(exponent), - 23 => get_small_f32_power23(exponent), - 24 => get_small_f32_power24(exponent), - 25 => get_small_f32_power25(exponent), - 26 => get_small_f32_power26(exponent), - 27 => get_small_f32_power27(exponent), - 28 => get_small_f32_power28(exponent), - 29 => get_small_f32_power29(exponent), - 30 => get_small_f32_power30(exponent), - 31 => get_small_f32_power31(exponent), - 32 => get_small_f32_power32(exponent), - 33 => get_small_f32_power33(exponent), - 34 => get_small_f32_power34(exponent), - 35 => get_small_f32_power35(exponent), - 36 => get_small_f32_power36(exponent), - _ => hint::unreachable_unchecked(), - } + match radix { + 2 => get_small_f32_power2(exponent), + 3 => get_small_f32_power3(exponent), + 4 => get_small_f32_power4(exponent), + 5 => get_small_f32_power5(exponent), + 6 => get_small_f32_power6(exponent), + 7 => get_small_f32_power7(exponent), + 8 => get_small_f32_power8(exponent), + 9 => get_small_f32_power9(exponent), + 10 => get_small_f32_power10(exponent), + 11 => get_small_f32_power11(exponent), + 12 => get_small_f32_power12(exponent), + 13 => get_small_f32_power13(exponent), + 14 => get_small_f32_power14(exponent), + 15 => get_small_f32_power15(exponent), + 16 => get_small_f32_power16(exponent), + 17 => get_small_f32_power17(exponent), + 18 => get_small_f32_power18(exponent), + 19 => get_small_f32_power19(exponent), + 20 => get_small_f32_power20(exponent), + 21 => get_small_f32_power21(exponent), + 22 => get_small_f32_power22(exponent), + 23 => get_small_f32_power23(exponent), + 24 => get_small_f32_power24(exponent), + 25 => get_small_f32_power25(exponent), + 26 => get_small_f32_power26(exponent), + 27 => get_small_f32_power27(exponent), + 28 => get_small_f32_power28(exponent), + 29 => get_small_f32_power29(exponent), + 30 => get_small_f32_power30(exponent), + 31 => get_small_f32_power31(exponent), + 32 => get_small_f32_power32(exponent), + 33 => get_small_f32_power33(exponent), + 34 => get_small_f32_power34(exponent), + 35 => get_small_f32_power35(exponent), + 36 => get_small_f32_power36(exponent), + _ => hint::unreachable_unchecked(), } } /// Get lookup table for small f64 powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] -pub unsafe fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { +pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { debug_assert_radix(radix); - unsafe { - match radix { - 2 => get_small_f64_power2(exponent), - 3 => get_small_f64_power3(exponent), - 4 => get_small_f64_power4(exponent), - 5 => get_small_f64_power5(exponent), - 6 => get_small_f64_power6(exponent), - 7 => get_small_f64_power7(exponent), - 8 => get_small_f64_power8(exponent), - 9 => get_small_f64_power9(exponent), - 10 => get_small_f64_power10(exponent), - 11 => get_small_f64_power11(exponent), - 12 => get_small_f64_power12(exponent), - 13 => get_small_f64_power13(exponent), - 14 => get_small_f64_power14(exponent), - 15 => get_small_f64_power15(exponent), - 16 => get_small_f64_power16(exponent), - 17 => get_small_f64_power17(exponent), - 18 => get_small_f64_power18(exponent), - 19 => get_small_f64_power19(exponent), - 20 => get_small_f64_power20(exponent), - 21 => get_small_f64_power21(exponent), - 22 => get_small_f64_power22(exponent), - 23 => get_small_f64_power23(exponent), - 24 => get_small_f64_power24(exponent), - 25 => get_small_f64_power25(exponent), - 26 => get_small_f64_power26(exponent), - 27 => get_small_f64_power27(exponent), - 28 => get_small_f64_power28(exponent), - 29 => get_small_f64_power29(exponent), - 30 => get_small_f64_power30(exponent), - 31 => get_small_f64_power31(exponent), - 32 => get_small_f64_power32(exponent), - 33 => get_small_f64_power33(exponent), - 34 => get_small_f64_power34(exponent), - 35 => get_small_f64_power35(exponent), - 36 => get_small_f64_power36(exponent), - _ => hint::unreachable_unchecked(), - } + match radix { + 2 => get_small_f64_power2(exponent), + 3 => get_small_f64_power3(exponent), + 4 => get_small_f64_power4(exponent), + 5 => get_small_f64_power5(exponent), + 6 => get_small_f64_power6(exponent), + 7 => get_small_f64_power7(exponent), + 8 => get_small_f64_power8(exponent), + 9 => get_small_f64_power9(exponent), + 10 => get_small_f64_power10(exponent), + 11 => get_small_f64_power11(exponent), + 12 => get_small_f64_power12(exponent), + 13 => get_small_f64_power13(exponent), + 14 => get_small_f64_power14(exponent), + 15 => get_small_f64_power15(exponent), + 16 => get_small_f64_power16(exponent), + 17 => get_small_f64_power17(exponent), + 18 => get_small_f64_power18(exponent), + 19 => get_small_f64_power19(exponent), + 20 => get_small_f64_power20(exponent), + 21 => get_small_f64_power21(exponent), + 22 => get_small_f64_power22(exponent), + 23 => get_small_f64_power23(exponent), + 24 => get_small_f64_power24(exponent), + 25 => get_small_f64_power25(exponent), + 26 => get_small_f64_power26(exponent), + 27 => get_small_f64_power27(exponent), + 28 => get_small_f64_power28(exponent), + 29 => get_small_f64_power29(exponent), + 30 => get_small_f64_power30(exponent), + 31 => get_small_f64_power31(exponent), + 32 => get_small_f64_power32(exponent), + 33 => get_small_f64_power33(exponent), + 34 => get_small_f64_power34(exponent), + 35 => get_small_f64_power35(exponent), + 36 => get_small_f64_power36(exponent), + _ => hint::unreachable_unchecked(), } } @@ -194,863 +173,519 @@ pub const fn get_large_int_power(radix: u32) -> (&'static [Limb], u32) { } /// Get pre-computed int power of 3. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW3.len()`. #[inline(always)] -pub unsafe fn get_small_int_power3(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW3[exponent]) } +pub fn get_small_int_power3(exponent: usize) -> u64 { + SMALL_INT_POW3[exponent] } /// Get pre-computed f32 power of 3. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW3.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power3(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW3[exponent]) } +pub fn get_small_f32_power3(exponent: usize) -> f32 { + SMALL_F32_POW3[exponent] } /// Get pre-computed f64 power of 3. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW3.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power3(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW3[exponent]) } +pub fn get_small_f64_power3(exponent: usize) -> f64 { + SMALL_F64_POW3[exponent] } /// Get pre-computed f32 power of 5. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW5.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power5(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW5[exponent]) } +pub fn get_small_f32_power5(exponent: usize) -> f32 { + SMALL_F32_POW5[exponent] } /// Get pre-computed f64 power of 5. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW5.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power5(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW5[exponent]) } +pub fn get_small_f64_power5(exponent: usize) -> f64 { + SMALL_F64_POW5[exponent] } /// Get pre-computed int power of 6. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW6.len()`. #[inline(always)] -pub unsafe fn get_small_int_power6(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW6[exponent]) } +pub fn get_small_int_power6(exponent: usize) -> u64 { + SMALL_INT_POW6[exponent] } /// Get pre-computed f32 power of 6. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW6.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power6(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW6[exponent]) } +pub fn get_small_f32_power6(exponent: usize) -> f32 { + SMALL_F32_POW6[exponent] } /// Get pre-computed f64 power of 6. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW6.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power6(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW6[exponent]) } +pub fn get_small_f64_power6(exponent: usize) -> f64 { + SMALL_F64_POW6[exponent] } /// Get pre-computed int power of 7. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW7.len()`. #[inline(always)] -pub unsafe fn get_small_int_power7(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW7[exponent]) } +pub fn get_small_int_power7(exponent: usize) -> u64 { + SMALL_INT_POW7[exponent] } /// Get pre-computed f32 power of 7. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW7.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power7(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW7[exponent]) } +pub fn get_small_f32_power7(exponent: usize) -> f32 { + SMALL_F32_POW7[exponent] } /// Get pre-computed f64 power of 7. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW7.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power7(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW7[exponent]) } +pub fn get_small_f64_power7(exponent: usize) -> f64 { + SMALL_F64_POW7[exponent] } /// Get pre-computed int power of 9. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW9.len()`. #[inline(always)] -pub unsafe fn get_small_int_power9(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW9[exponent]) } +pub fn get_small_int_power9(exponent: usize) -> u64 { + SMALL_INT_POW9[exponent] } /// Get pre-computed f32 power of 9. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW9.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power9(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW9[exponent]) } +pub fn get_small_f32_power9(exponent: usize) -> f32 { + SMALL_F32_POW9[exponent] } /// Get pre-computed f64 power of 9. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW9.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power9(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW9[exponent]) } +pub fn get_small_f64_power9(exponent: usize) -> f64 { + SMALL_F64_POW9[exponent] } /// Get pre-computed int power of 11. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW11.len()`. #[inline(always)] -pub unsafe fn get_small_int_power11(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW11[exponent]) } +pub fn get_small_int_power11(exponent: usize) -> u64 { + SMALL_INT_POW11[exponent] } /// Get pre-computed f32 power of 11. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW11.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power11(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW11[exponent]) } +pub fn get_small_f32_power11(exponent: usize) -> f32 { + SMALL_F32_POW11[exponent] } /// Get pre-computed f64 power of 11. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW11.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power11(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW11[exponent]) } +pub fn get_small_f64_power11(exponent: usize) -> f64 { + SMALL_F64_POW11[exponent] } /// Get pre-computed int power of 12. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW12.len()`. #[inline(always)] -pub unsafe fn get_small_int_power12(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW12[exponent]) } +pub fn get_small_int_power12(exponent: usize) -> u64 { + SMALL_INT_POW12[exponent] } /// Get pre-computed f32 power of 12. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW12.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power12(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW12[exponent]) } +pub fn get_small_f32_power12(exponent: usize) -> f32 { + SMALL_F32_POW12[exponent] } /// Get pre-computed f64 power of 12. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW12.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power12(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW12[exponent]) } +pub fn get_small_f64_power12(exponent: usize) -> f64 { + SMALL_F64_POW12[exponent] } /// Get pre-computed int power of 13. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW13.len()`. #[inline(always)] -pub unsafe fn get_small_int_power13(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW13[exponent]) } +pub fn get_small_int_power13(exponent: usize) -> u64 { + SMALL_INT_POW13[exponent] } /// Get pre-computed f32 power of 13. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW13.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power13(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW13[exponent]) } +pub fn get_small_f32_power13(exponent: usize) -> f32 { + SMALL_F32_POW13[exponent] } /// Get pre-computed f64 power of 13. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW13.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power13(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW13[exponent]) } +pub fn get_small_f64_power13(exponent: usize) -> f64 { + SMALL_F64_POW13[exponent] } /// Get pre-computed int power of 14. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW14.len()`. #[inline(always)] -pub unsafe fn get_small_int_power14(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW14[exponent]) } +pub fn get_small_int_power14(exponent: usize) -> u64 { + SMALL_INT_POW14[exponent] } /// Get pre-computed f32 power of 14. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW14.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power14(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW14[exponent]) } +pub fn get_small_f32_power14(exponent: usize) -> f32 { + SMALL_F32_POW14[exponent] } /// Get pre-computed f64 power of 14. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW14.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power14(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW14[exponent]) } +pub fn get_small_f64_power14(exponent: usize) -> f64 { + SMALL_F64_POW14[exponent] } /// Get pre-computed int power of 15. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW15.len()`. #[inline(always)] -pub unsafe fn get_small_int_power15(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW15[exponent]) } +pub fn get_small_int_power15(exponent: usize) -> u64 { + SMALL_INT_POW15[exponent] } /// Get pre-computed f32 power of 15. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW15.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power15(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW15[exponent]) } +pub fn get_small_f32_power15(exponent: usize) -> f32 { + SMALL_F32_POW15[exponent] } /// Get pre-computed f64 power of 15. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW15.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power15(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW15[exponent]) } +pub fn get_small_f64_power15(exponent: usize) -> f64 { + SMALL_F64_POW15[exponent] } /// Get pre-computed int power of 17. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW17.len()`. #[inline(always)] -pub unsafe fn get_small_int_power17(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW17[exponent]) } +pub fn get_small_int_power17(exponent: usize) -> u64 { + SMALL_INT_POW17[exponent] } /// Get pre-computed f32 power of 17. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW17.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power17(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW17[exponent]) } +pub fn get_small_f32_power17(exponent: usize) -> f32 { + SMALL_F32_POW17[exponent] } /// Get pre-computed f64 power of 17. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW17.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power17(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW17[exponent]) } +pub fn get_small_f64_power17(exponent: usize) -> f64 { + SMALL_F64_POW17[exponent] } /// Get pre-computed int power of 18. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW18.len()`. #[inline(always)] -pub unsafe fn get_small_int_power18(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW18[exponent]) } +pub fn get_small_int_power18(exponent: usize) -> u64 { + SMALL_INT_POW18[exponent] } /// Get pre-computed f32 power of 18. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW18.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power18(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW18[exponent]) } +pub fn get_small_f32_power18(exponent: usize) -> f32 { + SMALL_F32_POW18[exponent] } /// Get pre-computed f64 power of 18. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW18.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power18(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW18[exponent]) } +pub fn get_small_f64_power18(exponent: usize) -> f64 { + SMALL_F64_POW18[exponent] } /// Get pre-computed int power of 19. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW19.len()`. #[inline(always)] -pub unsafe fn get_small_int_power19(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW19[exponent]) } +pub fn get_small_int_power19(exponent: usize) -> u64 { + SMALL_INT_POW19[exponent] } /// Get pre-computed f32 power of 19. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW19.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power19(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW19[exponent]) } +pub fn get_small_f32_power19(exponent: usize) -> f32 { + SMALL_F32_POW19[exponent] } /// Get pre-computed f64 power of 19. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW19.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power19(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW19[exponent]) } +pub fn get_small_f64_power19(exponent: usize) -> f64 { + SMALL_F64_POW19[exponent] } /// Get pre-computed int power of 20. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW20.len()`. #[inline(always)] -pub unsafe fn get_small_int_power20(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW20[exponent]) } +pub fn get_small_int_power20(exponent: usize) -> u64 { + SMALL_INT_POW20[exponent] } /// Get pre-computed f32 power of 20. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW20.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power20(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW20[exponent]) } +pub fn get_small_f32_power20(exponent: usize) -> f32 { + SMALL_F32_POW20[exponent] } /// Get pre-computed f64 power of 20. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW20.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power20(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW20[exponent]) } +pub fn get_small_f64_power20(exponent: usize) -> f64 { + SMALL_F64_POW20[exponent] } /// Get pre-computed int power of 21. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW21.len()`. #[inline(always)] -pub unsafe fn get_small_int_power21(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW21[exponent]) } +pub fn get_small_int_power21(exponent: usize) -> u64 { + SMALL_INT_POW21[exponent] } /// Get pre-computed f32 power of 21. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW21.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power21(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW21[exponent]) } +pub fn get_small_f32_power21(exponent: usize) -> f32 { + SMALL_F32_POW21[exponent] } /// Get pre-computed f64 power of 21. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW21.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power21(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW21[exponent]) } +pub fn get_small_f64_power21(exponent: usize) -> f64 { + SMALL_F64_POW21[exponent] } /// Get pre-computed int power of 22. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW22.len()`. #[inline(always)] -pub unsafe fn get_small_int_power22(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW22[exponent]) } +pub fn get_small_int_power22(exponent: usize) -> u64 { + SMALL_INT_POW22[exponent] } /// Get pre-computed f32 power of 22. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW22.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power22(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW22[exponent]) } +pub fn get_small_f32_power22(exponent: usize) -> f32 { + SMALL_F32_POW22[exponent] } /// Get pre-computed f64 power of 22. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW22.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power22(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW22[exponent]) } +pub fn get_small_f64_power22(exponent: usize) -> f64 { + SMALL_F64_POW22[exponent] } /// Get pre-computed int power of 23. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW23.len()`. #[inline(always)] -pub unsafe fn get_small_int_power23(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW23[exponent]) } +pub fn get_small_int_power23(exponent: usize) -> u64 { + SMALL_INT_POW23[exponent] } /// Get pre-computed f32 power of 23. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW23.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power23(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW23[exponent]) } +pub fn get_small_f32_power23(exponent: usize) -> f32 { + SMALL_F32_POW23[exponent] } /// Get pre-computed f64 power of 23. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW23.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power23(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW23[exponent]) } +pub fn get_small_f64_power23(exponent: usize) -> f64 { + SMALL_F64_POW23[exponent] } /// Get pre-computed int power of 24. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW24.len()`. #[inline(always)] -pub unsafe fn get_small_int_power24(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW24[exponent]) } +pub fn get_small_int_power24(exponent: usize) -> u64 { + SMALL_INT_POW24[exponent] } /// Get pre-computed f32 power of 24. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW24.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power24(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW24[exponent]) } +pub fn get_small_f32_power24(exponent: usize) -> f32 { + SMALL_F32_POW24[exponent] } /// Get pre-computed f64 power of 24. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW24.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power24(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW24[exponent]) } +pub fn get_small_f64_power24(exponent: usize) -> f64 { + SMALL_F64_POW24[exponent] } /// Get pre-computed int power of 25. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW25.len()`. #[inline(always)] -pub unsafe fn get_small_int_power25(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW25[exponent]) } +pub fn get_small_int_power25(exponent: usize) -> u64 { + SMALL_INT_POW25[exponent] } /// Get pre-computed f32 power of 25. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW25.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power25(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW25[exponent]) } +pub fn get_small_f32_power25(exponent: usize) -> f32 { + SMALL_F32_POW25[exponent] } /// Get pre-computed f64 power of 25. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW25.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power25(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW25[exponent]) } +pub fn get_small_f64_power25(exponent: usize) -> f64 { + SMALL_F64_POW25[exponent] } /// Get pre-computed int power of 26. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW26.len()`. #[inline(always)] -pub unsafe fn get_small_int_power26(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW26[exponent]) } +pub fn get_small_int_power26(exponent: usize) -> u64 { + SMALL_INT_POW26[exponent] } /// Get pre-computed f32 power of 26. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW26.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power26(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW26[exponent]) } +pub fn get_small_f32_power26(exponent: usize) -> f32 { + SMALL_F32_POW26[exponent] } /// Get pre-computed f64 power of 26. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW26.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power26(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW26[exponent]) } +pub fn get_small_f64_power26(exponent: usize) -> f64 { + SMALL_F64_POW26[exponent] } /// Get pre-computed int power of 27. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW27.len()`. #[inline(always)] -pub unsafe fn get_small_int_power27(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW27[exponent]) } +pub fn get_small_int_power27(exponent: usize) -> u64 { + SMALL_INT_POW27[exponent] } /// Get pre-computed f32 power of 27. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW27.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power27(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW27[exponent]) } +pub fn get_small_f32_power27(exponent: usize) -> f32 { + SMALL_F32_POW27[exponent] } /// Get pre-computed f64 power of 27. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW27.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power27(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW27[exponent]) } +pub fn get_small_f64_power27(exponent: usize) -> f64 { + SMALL_F64_POW27[exponent] } /// Get pre-computed int power of 28. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW28.len()`. #[inline(always)] -pub unsafe fn get_small_int_power28(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW28[exponent]) } +pub fn get_small_int_power28(exponent: usize) -> u64 { + SMALL_INT_POW28[exponent] } /// Get pre-computed f32 power of 28. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW28.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power28(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW28[exponent]) } +pub fn get_small_f32_power28(exponent: usize) -> f32 { + SMALL_F32_POW28[exponent] } /// Get pre-computed f64 power of 28. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW28.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power28(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW28[exponent]) } +pub fn get_small_f64_power28(exponent: usize) -> f64 { + SMALL_F64_POW28[exponent] } /// Get pre-computed int power of 29. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW29.len()`. #[inline(always)] -pub unsafe fn get_small_int_power29(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW29[exponent]) } +pub fn get_small_int_power29(exponent: usize) -> u64 { + SMALL_INT_POW29[exponent] } /// Get pre-computed f32 power of 29. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW29.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power29(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW29[exponent]) } +pub fn get_small_f32_power29(exponent: usize) -> f32 { + SMALL_F32_POW29[exponent] } /// Get pre-computed f64 power of 29. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW29.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power29(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW29[exponent]) } +pub fn get_small_f64_power29(exponent: usize) -> f64 { + SMALL_F64_POW29[exponent] } /// Get pre-computed int power of 30. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW30.len()`. #[inline(always)] -pub unsafe fn get_small_int_power30(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW30[exponent]) } +pub fn get_small_int_power30(exponent: usize) -> u64 { + SMALL_INT_POW30[exponent] } /// Get pre-computed f32 power of 30. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW30.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power30(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW30[exponent]) } +pub fn get_small_f32_power30(exponent: usize) -> f32 { + SMALL_F32_POW30[exponent] } /// Get pre-computed f64 power of 30. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW30.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power30(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW30[exponent]) } +pub fn get_small_f64_power30(exponent: usize) -> f64 { + SMALL_F64_POW30[exponent] } /// Get pre-computed int power of 31. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW31.len()`. #[inline(always)] -pub unsafe fn get_small_int_power31(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW31[exponent]) } +pub fn get_small_int_power31(exponent: usize) -> u64 { + SMALL_INT_POW31[exponent] } /// Get pre-computed f32 power of 31. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW31.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power31(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW31[exponent]) } +pub fn get_small_f32_power31(exponent: usize) -> f32 { + SMALL_F32_POW31[exponent] } /// Get pre-computed f64 power of 31. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW31.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power31(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW31[exponent]) } +pub fn get_small_f64_power31(exponent: usize) -> f64 { + SMALL_F64_POW31[exponent] } /// Get pre-computed int power of 33. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW33.len()`. #[inline(always)] -pub unsafe fn get_small_int_power33(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW33[exponent]) } +pub fn get_small_int_power33(exponent: usize) -> u64 { + SMALL_INT_POW33[exponent] } /// Get pre-computed f32 power of 33. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW33.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power33(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW33[exponent]) } +pub fn get_small_f32_power33(exponent: usize) -> f32 { + SMALL_F32_POW33[exponent] } /// Get pre-computed f64 power of 33. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW33.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power33(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW33[exponent]) } +pub fn get_small_f64_power33(exponent: usize) -> f64 { + SMALL_F64_POW33[exponent] } /// Get pre-computed int power of 34. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW34.len()`. #[inline(always)] -pub unsafe fn get_small_int_power34(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW34[exponent]) } +pub fn get_small_int_power34(exponent: usize) -> u64 { + SMALL_INT_POW34[exponent] } /// Get pre-computed f32 power of 34. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW34.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power34(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW34[exponent]) } +pub fn get_small_f32_power34(exponent: usize) -> f32 { + SMALL_F32_POW34[exponent] } /// Get pre-computed f64 power of 34. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW34.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power34(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW34[exponent]) } +pub fn get_small_f64_power34(exponent: usize) -> f64 { + SMALL_F64_POW34[exponent] } /// Get pre-computed int power of 35. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW35.len()`. #[inline(always)] -pub unsafe fn get_small_int_power35(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW35[exponent]) } +pub fn get_small_int_power35(exponent: usize) -> u64 { + SMALL_INT_POW35[exponent] } /// Get pre-computed f32 power of 35. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW35.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power35(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW35[exponent]) } +pub fn get_small_f32_power35(exponent: usize) -> f32 { + SMALL_F32_POW35[exponent] } /// Get pre-computed f64 power of 35. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW35.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power35(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW35[exponent]) } +pub fn get_small_f64_power35(exponent: usize) -> f64 { + SMALL_F64_POW35[exponent] } /// Get pre-computed int power of 36. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_INT_POW36.len()`. #[inline(always)] -pub unsafe fn get_small_int_power36(exponent: usize) -> u64 { - unsafe { index_unchecked!(SMALL_INT_POW36[exponent]) } +pub fn get_small_int_power36(exponent: usize) -> u64 { + SMALL_INT_POW36[exponent] } /// Get pre-computed f32 power of 36. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F32_POW36.len()`. #[inline(always)] -pub unsafe fn get_small_f32_power36(exponent: usize) -> f32 { - unsafe { index_unchecked!(SMALL_F32_POW36[exponent]) } +pub fn get_small_f32_power36(exponent: usize) -> f32 { + SMALL_F32_POW36[exponent] } /// Get pre-computed f64 power of 36. -/// -/// # Safety -/// -/// Safe as long as the `exponent < SMALL_F64_POW36.len()`. #[inline(always)] -pub unsafe fn get_small_f64_power36(exponent: usize) -> f64 { - unsafe { index_unchecked!(SMALL_F64_POW36[exponent]) } +pub fn get_small_f64_power36(exponent: usize) -> f64 { + SMALL_F64_POW36[exponent] } // TABLES diff --git a/lexical-write-integer/src/radix.rs b/lexical-write-integer/src/radix.rs index aeb9eb92..a2bc877b 100644 --- a/lexical-write-integer/src/radix.rs +++ b/lexical-write-integer/src/radix.rs @@ -18,6 +18,8 @@ use core::mem; use lexical_util::algorithm::copy_to_dst; use lexical_util::format; use lexical_util::num::{Integer, UnsignedInteger}; +use lexical_util::format::NumberFormat; +use lexical_util::assert::assert_buffer; /// Write integer to radix string. pub trait Radix: UnsignedInteger { @@ -25,7 +27,7 @@ pub trait Radix: UnsignedInteger { /// /// Safe as long as buffer is at least `FORMATTED_SIZE` elements long, /// (or `FORMATTED_SIZE_DECIMAL` for decimal), and the radix is valid. - unsafe fn radix( + fn radix( self, buffer: &mut [u8], ) -> usize; @@ -36,7 +38,7 @@ macro_rules! radix_unimpl { ($($t:ty)*) => ($( impl Radix for $t { #[inline(always)] - unsafe fn radix(self, _: &mut [u8]) -> usize { + fn radix(self, _: &mut [u8]) -> usize { // Forces a hard error if we have a logic error in our code. unimplemented!() } @@ -51,18 +53,25 @@ macro_rules! radix_impl { ($($t:ty)*) => ($( impl Radix for $t { #[inline(always)] - unsafe fn radix( + fn radix( self, buffer: &mut [u8] ) -> usize { - // SAFETY: safe as long as buffer is large enough to hold the max value. - // We never read unwritten values, and we never assume the data is initialized. debug_assert!(::BITS <= 64); + assert_buffer::<$t>(NumberFormat::<{ FORMAT }>::RADIX, buffer.len()); + let radix = format::radix_from_flags(FORMAT, MASK, SHIFT); + // TODO: Remove unsafe + let table = unsafe { get_table::() }; + let mut digits: mem::MaybeUninit<[u8; 64]> = mem::MaybeUninit::uninit(); + // # Safety + // + // Safe as long as buffer is large enough to hold the max value, which we validate. + // above. We never read unwritten values, and we never assume the data is initialized. + // Need at least $T::BITS-bits, at least as many as the bits in the current type. Ensuring + // no uninitialized memory is read is verified by miri. unsafe { let digits = &mut *digits.as_mut_ptr(); - let radix = format::radix_from_flags(FORMAT, MASK, SHIFT); - let table = get_table::(); let index = algorithm(self, radix, table, digits); copy_to_dst(buffer, &mut index_unchecked_mut!(digits[index..])) } @@ -75,17 +84,24 @@ radix_impl! { u32 u64 } impl Radix for u128 { #[inline(always)] - unsafe fn radix( + fn radix( self, buffer: &mut [u8], ) -> usize { - // SAFETY: safe as long as buffer is large enough to hold the max value. - // We never read unwritten values, and we never assume the data is initialized. - // Need at least 128-bits, at least as many as the bits in the current type. + debug_assert!(::BITS <= 128); + assert_buffer::(NumberFormat::<{ FORMAT }>::RADIX, buffer.len()); + // TODO: Remove unsafe + let table = unsafe { get_table::() }; + let mut digits: mem::MaybeUninit<[u8; 128]> = mem::MaybeUninit::uninit(); + // # Safety + // + // Safe as long as buffer is large enough to hold the max value, which we validate. + // above. We never read unwritten values, and we never assume the data is initialized. + // Need at least 128-bits, at least as many as the bits in the current type. Ensuring + // no uninitialized memory is read is verified by miri. unsafe { let digits = &mut *digits.as_mut_ptr(); - let table = get_table::(); let index = algorithm_u128::(self, table, digits); copy_to_dst(buffer, &mut index_unchecked_mut!(digits[index..])) } From 65460607f2a1732c56ede3fd7b2e0f3934dde184 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 9 Sep 2024 20:17:27 -0500 Subject: [PATCH 03/11] Remove _unchecked API methods. --- CHANGELOG | 1 + lexical-benchmark/input.rs | 2 +- lexical-core/src/lib.rs | 127 ----------- lexical-util/src/algorithm.rs | 8 +- lexical-util/src/api.rs | 70 ------ lexical-util/tests/algorithm_tests.rs | 2 +- lexical-write-float/file.diff | 304 -------------------------- lexical-write-float/src/api.rs | 38 +--- lexical-write-integer/src/api.rs | 78 ++----- lexical/src/lib.rs | 5 +- 10 files changed, 31 insertions(+), 604 deletions(-) delete mode 100644 lexical-write-float/file.diff diff --git a/CHANGELOG b/CHANGELOG index 139a5a03..ecbf3f23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Support for mips (MIPS), mipsel (MIPS LE), mips64 (MIPS64 BE), and mips64el (MIPS64 LE) on Linux. +- All `_unchecked` API methods, since the performance benefits are dubious and it makes safety invariant checking much harder. ## [0.8.5] 2022-06-06 diff --git a/lexical-benchmark/input.rs b/lexical-benchmark/input.rs index 2bc1048e..3f20f2b7 100644 --- a/lexical-benchmark/input.rs +++ b/lexical-benchmark/input.rs @@ -386,7 +386,7 @@ macro_rules! to_lexical_generator { $group.bench_function($name, |bench| { bench.iter(|| { $iter.for_each(|&x| { - black_box(unsafe { x.to_lexical_unchecked(&mut buffer) }); + black_box(unsafe { x.to_lexical(&mut buffer) }); }) }) }); diff --git a/lexical-core/src/lib.rs b/lexical-core/src/lib.rs index c1917b1b..54f7f238 100644 --- a/lexical-core/src/lib.rs +++ b/lexical-core/src/lib.rs @@ -81,9 +81,7 @@ #![cfg_attr(feature = "write", doc = " **Write**")] #![cfg_attr(feature = "write", doc = "")] #![cfg_attr(feature = "write", doc = " - [`write`]")] -#![cfg_attr(feature = "write", doc = " - [`write_unchecked`]")] #![cfg_attr(feature = "write", doc = " - [`write_with_options`]")] -#![cfg_attr(feature = "write", doc = " - [`write_with_options_unchecked`]")] //! #![cfg_attr(feature = "write", doc = " **From String**")] #![cfg_attr(feature = "write", doc = "")] @@ -312,9 +310,7 @@ //! support. Older versions of lexical support older Rust versions. //! //! [`write`]: crate::write -//! [`write_unchecked`]: crate::write_unchecked //! [`write_with_options`]: crate::write_with_options -//! [`write_with_options_unchecked`]: crate::write_with_options_unchecked //! [`parse`]: crate::parse //! [`parse_partial`]: crate::parse_partial //! [`parse_with_options`]: crate::parse_with_options @@ -469,12 +465,6 @@ float_from_lexical! { f32 f64 } macro_rules! to_lexical_impl { ($t:ident, $to:ident, $to_options:ident, $options:ident) => { impl ToLexical for $t { - #[cfg_attr(not(feature = "compact"), inline)] - unsafe fn to_lexical_unchecked(self, bytes: &mut [u8]) -> &mut [u8] { - // SAFETY: safe as long as `bytes` is large enough to hold the significant digits. - unsafe { ::to_lexical_unchecked(self, bytes) } - } - #[cfg_attr(not(feature = "compact"), inline)] fn to_lexical(self, bytes: &mut [u8]) -> &mut [u8] { ::to_lexical(self, bytes) @@ -483,21 +473,6 @@ macro_rules! to_lexical_impl { impl ToLexicalWithOptions for $t { type Options = $options; - - #[cfg_attr(not(feature = "compact"), inline)] - unsafe fn to_lexical_with_options_unchecked<'a, const FORMAT: u128>( - self, - bytes: &'a mut [u8], - options: &Self::Options, - ) -> &'a mut [u8] { - // SAFETY: safe as long as `bytes` is large enough to hold the significant digits. - unsafe { - ::to_lexical_with_options_unchecked::( - self, bytes, options, - ) - } - } - #[cfg_attr(not(feature = "compact"), inline(always))] fn to_lexical_with_options<'a, const FORMAT: u128>( self, @@ -584,48 +559,6 @@ pub fn write(n: N, bytes: &mut [u8]) -> &mut [u8] { n.to_lexical(bytes) } -/// Write number to string, without bounds checking the buffer. -/// -/// Returns a subslice of the input buffer containing the written bytes, -/// starting from the same address in memory as the input slice. -/// -/// * `value` - Number to serialize. -/// * `bytes` - Buffer to write number to. -/// -/// # Safety -/// -/// If the buffer is not be large enough to hold the serialized number, -/// it will overflow the buffer unless the `safe` feature is enabled. -/// Buffer overflows are severe security vulnerabilities, and therefore -/// to ensure the function will not overwrite the buffer, provide a -/// buffer with at least `{integer}::FORMATTED_SIZE` elements. -/// -/// # Example -/// -/// ``` -/// # pub fn main() { -/// #[cfg(feature = "write-floats")] { -/// // import `BUFFER_SIZE` to get the maximum bytes written by the number. -/// use lexical_core::BUFFER_SIZE; -/// -/// let mut buffer = [0u8; BUFFER_SIZE]; -/// let float = 3.14159265359_f32; -/// -/// unsafe { -/// lexical_core::write_unchecked(float, &mut buffer); -/// } -/// -/// assert_eq!(&buffer[0..9], b"3.1415927"); -/// # } -/// # } -/// ``` -#[inline] -#[cfg(feature = "write")] -pub unsafe fn write_unchecked(n: N, bytes: &mut [u8]) -> &mut [u8] { - // SAFETY: safe if the provided buffer is large enough for the numerical string - unsafe { n.to_lexical_unchecked(bytes) } -} - /// Write number to string with custom options. /// /// Returns a subslice of the input buffer containing the written bytes, @@ -696,66 +629,6 @@ pub fn write_with_options<'a, N: ToLexicalWithOptions, const FORMAT: u128>( n.to_lexical_with_options::(bytes, options) } -/// Write number to string with custom options. -/// -/// Returns a subslice of the input buffer containing the written bytes, -/// starting from the same address in memory as the input slice. -/// -/// * `FORMAT` - Packed struct containing the number format. -/// * `value` - Number to serialize. -/// * `bytes` - Buffer to write number to. -/// * `options` - Options to customize number parsing. -/// -/// # Safety -/// -/// If the buffer is not be large enough to hold the serialized number, -/// it will overflow the buffer unless the `safe` feature is enabled. -/// Buffer overflows are severe security vulnerabilities, and therefore -/// to ensure the function will not overwrite the buffer, provide a -/// buffer with at least `{integer}::FORMATTED_SIZE` elements. If you -/// are using custom digit precision control or exponent break points -/// for writing floats, these constants may be insufficient to store -/// the serialized number, and up to 1200 bytes may be required with -/// radix support. -/// -/// # Panics -/// -/// If the provided `FORMAT` is not valid, the function may panic. Please -/// ensure `is_valid()` is called prior to using the format, or checking -/// its validity using a static assertion. -/// -/// # Example -/// -/// ``` -/// # pub fn main() { -/// #[cfg(feature = "write-floats")] { -/// // import `BUFFER_SIZE` to get the maximum bytes written by the number. -/// use lexical_core::BUFFER_SIZE; -/// -/// let mut buffer = [0u8; BUFFER_SIZE]; -/// let float = 3.14159265359_f32; -/// -/// const FORMAT: u128 = lexical_core::format::STANDARD; -/// let options = lexical_core::WriteFloatOptions::new(); -/// unsafe { -/// lexical_core::write_with_options_unchecked::<_, FORMAT>(float, &mut buffer, &options); -/// } -/// -/// assert_eq!(&buffer[0..9], b"3.1415927"); -/// # } -/// # } -/// ``` -#[inline] -#[cfg(feature = "write")] -pub unsafe fn write_with_options_unchecked<'a, N: ToLexicalWithOptions, const FORMAT: u128>( - n: N, - bytes: &'a mut [u8], - options: &N::Options, -) -> &'a mut [u8] { - // SAFETY: safe if the provided buffer is large enough for the numerical string - unsafe { n.to_lexical_with_options_unchecked::(bytes, options) } -} - /// Parse complete number from string. /// /// This method parses the entire string, returning an error if diff --git a/lexical-util/src/algorithm.rs b/lexical-util/src/algorithm.rs index c68345d6..50ea1466 100644 --- a/lexical-util/src/algorithm.rs +++ b/lexical-util/src/algorithm.rs @@ -4,14 +4,10 @@ use core::ptr; /// Copy bytes from source to destination. -/// -/// # Safety -/// -/// Safe as long as `dst` is larger than `src`. #[inline(always)] #[cfg(feature = "write")] -pub unsafe fn copy_to_dst>(dst: &mut [u8], src: Bytes) -> usize { - debug_assert!(dst.len() >= src.as_ref().len()); +pub fn copy_to_dst>(dst: &mut [u8], src: Bytes) -> usize { + assert!(dst.len() >= src.as_ref().len()); // SAFETY: safe, if `dst.len() <= src.len()`. let src = src.as_ref(); diff --git a/lexical-util/src/api.rs b/lexical-util/src/api.rs index 705fb482..c87c46e7 100644 --- a/lexical-util/src/api.rs +++ b/lexical-util/src/api.rs @@ -124,23 +124,6 @@ macro_rules! to_lexical { pub trait ToLexical: lexical_util::constants::FormattedSize + lexical_util::num::Number { - /// Serializer for a number-to-string conversion. - /// - /// Returns a subslice of the input buffer containing the written bytes, - /// starting from the same address in memory as the input slice. - /// - /// * `value` - Number to serialize. - /// * `bytes` - Buffer to write number to. - /// - /// # Safety - /// - /// Safe as long as the caller has provided a buffer of at least - /// [`FORMATTED_SIZE_DECIMAL`] elements. If a smaller buffer is - /// provided, a buffer overflow is very likely. - /// - /// [`FORMATTED_SIZE_DECIMAL`]: lexical_util::constants::FormattedSize::FORMATTED_SIZE_DECIMAL - unsafe fn to_lexical_unchecked<'a>(self, bytes: &'a mut [u8]) -> &'a mut [u8]; - /// Serializer for a number-to-string conversion. /// /// Returns a subslice of the input buffer containing the written bytes, @@ -184,59 +167,6 @@ macro_rules! to_lexical_with_options { /// Custom formatting options for writing a number. type Options: lexical_util::options::WriteOptions; - /// Serializer for a number-to-string conversion. - /// - /// Returns a subslice of the input buffer containing the written bytes, - /// starting from the same address in memory as the input slice. - /// - /// * `FORMAT` - Flags and characters designating the number grammar. - /// * `value` - Number to serialize. - /// * `bytes` - Buffer to write number to. - /// * `options` - Options for number formatting. - /// - /// # Safety - /// - /// Safe as long as the caller has provided a buffer of at least - /// [`FORMATTED_SIZE`] elements. If a smaller buffer is provided, a - /// buffer overflow is very likely. If you are changing the - /// number significant digits written, the exponent break points, - /// or disabling scientific notation, you will need a larger buffer - /// than the one provided. An upper limit on the buffer size can - /// then be determined using [`WriteOptions::buffer_size`]. If you - /// are not using `min_significant_digits`, 1200 bytes is always - /// enough to hold the the output for a custom radix, and `400` - /// is always enough for decimal strings. - /// - /// # Panics - /// - /// **Floats Only** - /// - /// These panics are only when using uncommon features for float - /// writing, represent configuration errors, so runtime error - /// handling is not provided. - /// - /// Panics if the provided number format is invalid, or if the - /// mantissa radix is not equal to the exponent base - /// and the mantissa radix/exponent base combinations are - /// not in the following list: - /// - /// - `4, 2` - /// - `8, 2` - /// - `16, 2` - /// - `32, 2` - /// - `16, 4` - /// - /// Panics as well if the NaN or Inf string provided to the writer - /// is disabled, but the value provided is NaN or Inf, respectively. - /// - /// [`WriteOptions::buffer_size`]: lexical_util::options::WriteOptions::buffer_size - /// [`FORMATTED_SIZE`]: lexical_util::constants::FormattedSize::FORMATTED_SIZE - unsafe fn to_lexical_with_options_unchecked<'a, const FORMAT: u128>( - self, - bytes: &'a mut [u8], - options: &Self::Options, - ) -> &'a mut [u8]; - /// Serializer for a number-to-string conversion. /// /// Returns a subslice of the input buffer containing the written bytes, diff --git a/lexical-util/tests/algorithm_tests.rs b/lexical-util/tests/algorithm_tests.rs index a8b19077..ce2afdec 100644 --- a/lexical-util/tests/algorithm_tests.rs +++ b/lexical-util/tests/algorithm_tests.rs @@ -7,7 +7,7 @@ fn copy_to_dest_test() { let src = b"12345"; let mut dst = [b'0'; 16]; - assert_eq!(5, unsafe { algorithm::copy_to_dst(&mut dst, src) }); + assert_eq!(5, algorithm::copy_to_dst(&mut dst, src)); assert_eq!(&dst[..5], src); } diff --git a/lexical-write-float/file.diff b/lexical-write-float/file.diff deleted file mode 100644 index 0ba1a653..00000000 --- a/lexical-write-float/file.diff +++ /dev/null @@ -1,304 +0,0 @@ -diff --git a/lexical-write-float/src/algorithm.rs b/lexical-write-float/src/algorithm.rs -index a16dcdb..de7ec62 100644 ---- a/lexical-write-float/src/algorithm.rs -+++ b/lexical-write-float/src/algorithm.rs -@@ -64,6 +64,7 @@ pub unsafe fn write_float( - // in most cases. - - write_float!( -+ float, - FORMAT, - sci_exp, - options, -@@ -71,7 +72,8 @@ pub unsafe fn write_float( - write_float_positive_exponent, - write_float_negative_exponent, - generic => F, -- args => bytes, fp, sci_exp, options, -+ bytes => bytes, -+ args => fp, sci_exp, options, - ) - } - -@@ -153,7 +155,7 @@ pub unsafe fn write_float_negative_exponent usize { -- debug_assert!(sci_exp < 0); -+ debug_assert!(sci_exp < 0 || fp.mant == 0); - debug_assert_eq!(count_factors(10, fp.mant), 0); - - // Config options. -diff --git a/lexical-write-float/src/binary.rs b/lexical-write-float/src/binary.rs -index de8ab8e..13dbc97 100644 ---- a/lexical-write-float/src/binary.rs -+++ b/lexical-write-float/src/binary.rs -@@ -85,6 +85,7 @@ where - } - - write_float!( -+ float, - FORMAT, - sci_exp, - options, -@@ -92,7 +93,8 @@ where - write_float_positive_exponent, - write_float_negative_exponent, - generic => _, -- args => mantissa, exp, sci_exp, bytes, options, -+ bytes => bytes, -+ args => mantissa, exp, sci_exp, options, - ) - } - -@@ -110,10 +112,10 @@ where - /// and `mantissa_radix` in `FORMAT` must be identical. - #[inline(always)] - pub unsafe fn write_float_scientific( -+ bytes: &mut [u8], - mantissa: M, - exp: i32, - sci_exp: i32, -- bytes: &mut [u8], - options: &Options, - ) -> usize - where -@@ -191,10 +193,10 @@ where - /// significant digits and the leading zeros. - #[inline(always)] - pub unsafe fn write_float_negative_exponent( -+ bytes: &mut [u8], - mantissa: M, - exp: i32, - sci_exp: i32, -- bytes: &mut [u8], - options: &Options, - ) -> usize - where -@@ -275,10 +277,10 @@ where - /// significant digits and the (optional) trailing zeros. - #[inline(always)] - pub unsafe fn write_float_positive_exponent( -+ bytes: &mut [u8], - mantissa: M, - exp: i32, - sci_exp: i32, -- bytes: &mut [u8], - options: &Options, - ) -> usize - where -diff --git a/lexical-write-float/src/compact.rs b/lexical-write-float/src/compact.rs -index 19960b7..b3e99d1 100644 ---- a/lexical-write-float/src/compact.rs -+++ b/lexical-write-float/src/compact.rs -@@ -83,13 +83,15 @@ pub unsafe fn write_float( - - let sci_exp = kappa + digit_count as i32 - 1 + carried as i32; - write_float!( -+ float, - FORMAT, - sci_exp, - options, - write_float_scientific, - write_float_positive_exponent, - write_float_negative_exponent, -- args => bytes, &mut digits, digit_count, sci_exp, options, -+ bytes => bytes, -+ args => &mut digits, digit_count, sci_exp, options, - ) - } - -diff --git a/lexical-write-float/src/hex.rs b/lexical-write-float/src/hex.rs -index a3d7c71..5124525 100644 ---- a/lexical-write-float/src/hex.rs -+++ b/lexical-write-float/src/hex.rs -@@ -107,6 +107,7 @@ where - } - - write_float!( -+ float, - FORMAT, - sci_exp, - options, -@@ -114,7 +115,8 @@ where - write_float_positive_exponent, - write_float_negative_exponent, - generic => _, -- args => mantissa, exp, sci_exp, bytes, options, -+ bytes => bytes, -+ args => mantissa, exp, sci_exp, options, - ) - } - -@@ -131,10 +133,10 @@ where - /// based on the number of maximum digits. - #[inline(always)] - pub unsafe fn write_float_scientific( -+ bytes: &mut [u8], - mantissa: M, - exp: i32, - sci_exp: i32, -- bytes: &mut [u8], - options: &Options, - ) -> usize - where -diff --git a/lexical-write-float/src/radix.rs b/lexical-write-float/src/radix.rs -index dda56ab..dc9cc7d 100644 ---- a/lexical-write-float/src/radix.rs -+++ b/lexical-write-float/src/radix.rs -@@ -178,13 +178,15 @@ where - let zero_count = ltrim_char_count(digits, b'0'); - let sci_exp: i32 = initial_cursor as i32 - integer_cursor as i32 - zero_count as i32 - 1; - write_float!( -+ float, - FORMAT, - sci_exp, - options, - write_float_scientific, - write_float_nonscientific, - write_float_nonscientific, -- args => sci_exp, &mut buffer, bytes, initial_cursor, -+ bytes => bytes, -+ args => sci_exp, &mut buffer, initial_cursor, - integer_cursor, fraction_cursor, options, - ) - } -@@ -203,9 +205,9 @@ where - /// and `mantissa_radix` in `FORMAT` must be identical. - #[inline(always)] - pub unsafe fn write_float_scientific( -+ bytes: &mut [u8], - sci_exp: i32, - buffer: &mut [u8], -- bytes: &mut [u8], - initial_cursor: usize, - integer_cursor: usize, - fraction_cursor: usize, -@@ -294,9 +296,9 @@ pub unsafe fn write_float_scientific( - /// significant digits and the leading zeros. - #[inline(always)] - pub unsafe fn write_float_nonscientific( -+ bytes: &mut [u8], - _: i32, - buffer: &mut [u8], -- bytes: &mut [u8], - initial_cursor: usize, - integer_cursor: usize, - fraction_cursor: usize, -diff --git a/lexical-write-float/src/shared.rs b/lexical-write-float/src/shared.rs -index f6b838a..c4d4369 100644 ---- a/lexical-write-float/src/shared.rs -+++ b/lexical-write-float/src/shared.rs -@@ -171,6 +171,7 @@ pub unsafe fn write_exponent( - /// Detect the notation to use for the float formatter and call the appropriate function.. - macro_rules! write_float { - ( -+ $float:ident, - $format:ident, - $sci_exp:ident, - $options:ident, -@@ -178,6 +179,7 @@ macro_rules! write_float { - $write_positive:ident, - $write_negative:ident, - $(generic => $generic:tt,)? -+ bytes => $bytes:ident, - args => $($args:expr,)* - ) => {{ - use lexical_util::format::NumberFormat; -@@ -191,15 +193,22 @@ macro_rules! write_float { - if !format.no_exponent_notation() && require_exponent { - // Write digits in scientific notation. - // SAFETY: safe as long as bytes is large enough to hold all the digits. -- unsafe { $write_scientific::<$($generic,)? FORMAT>($($args,)*) } -- } else if $sci_exp >= 0 { -- // Write positive exponent without scientific notation. -+ unsafe { $write_scientific::<$($generic,)? FORMAT>($bytes, $($args,)*) } -+ } else if $sci_exp < 0 { -+ // Write negative exponent without scientific notation. - // SAFETY: safe as long as bytes is large enough to hold all the digits. -- unsafe { $write_positive::<$($generic,)? FORMAT>($($args,)*) } -+ unsafe { $write_negative::<$($generic,)? FORMAT>($bytes, $($args,)*) } -+ } else if $float.is_sign_negative() { -+ // handle this as a positive, just write a leading '-' and then add 1 to our count -+ // # Safety: This is always safe since our buffer is much larger than 1 byte. -+ unsafe { index_unchecked_mut!($bytes[0]) = b'-'; } -+ // # Safety: This is always safe since our buffer is much larger than 1 byte. -+ let bytes = unsafe { &mut index_unchecked_mut!($bytes[1..]) }; -+ unsafe { $write_positive::<$($generic,)? FORMAT>(bytes, $($args,)*) + 1 } - } else { -- // Write negative exponent without scientific notation. -+ // Write positive exponent without scientific notation. - // SAFETY: safe as long as bytes is large enough to hold all the digits. -- unsafe { $write_negative::<$($generic,)? FORMAT>($($args,)*) } -+ unsafe { $write_positive::<$($generic,)? FORMAT>($bytes, $($args,)*) } - } - }}; - } -diff --git a/lexical-write-float/tests/binary_tests.rs b/lexical-write-float/tests/binary_tests.rs -index ac27db9..e488182 100644 ---- a/lexical-write-float/tests/binary_tests.rs -+++ b/lexical-write-float/tests/binary_tests.rs -@@ -177,7 +177,7 @@ where - } - - let count = unsafe { -- binary::write_float_scientific::<_, FORMAT>(mantissa, exp, sci_exp, &mut buffer, options) -+ binary::write_float_scientific::<_, FORMAT>(&mut buffer, mantissa, exp, sci_exp, options) - }; - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - assert_eq!(actual, expected); -@@ -465,10 +465,10 @@ fn write_float_negative_exponent( - - let count = unsafe { - binary::write_float_negative_exponent::<_, FORMAT>( -+ &mut buffer, - mantissa, - exp, - sci_exp, -- &mut buffer, - options, - ) - }; -@@ -708,10 +708,10 @@ fn write_float_positive_exponent( - - let count = unsafe { - binary::write_float_positive_exponent::<_, FORMAT>( -+ &mut buffer, - mantissa, - exp, - sci_exp, -- &mut buffer, - options, - ) - }; -diff --git a/lexical-write-float/tests/hex_tests.rs b/lexical-write-float/tests/hex_tests.rs -index ac8ff68..a4827c2 100644 ---- a/lexical-write-float/tests/hex_tests.rs -+++ b/lexical-write-float/tests/hex_tests.rs -@@ -50,7 +50,7 @@ where - } - - let count = unsafe { -- hex::write_float_scientific::<_, FORMAT>(mantissa, exp, sci_exp, &mut buffer, options) -+ hex::write_float_scientific::<_, FORMAT>(&mut buffer, mantissa, exp, sci_exp, options) - }; - let actual = unsafe { std::str::from_utf8_unchecked(&buffer[..count]) }; - assert_eq!(actual, expected); -diff --git a/lexical-write-float/tests/issue_94_tests.rs b/lexical-write-float/tests/issue_94_tests.rs -new file mode 100644 -index 0000000..13b2dbf ---- /dev/null -+++ b/lexical-write-float/tests/issue_94_tests.rs -@@ -0,0 +1,12 @@ -+use core::str; -+ -+use lexical_util::constants::BUFFER_SIZE; -+use lexical_write_float::ToLexical; -+ -+#[test] -+fn issue_94_test() { -+ let mut buffer = [b'\x00'; BUFFER_SIZE]; -+ let neg0: f64 = -0.0; -+ let result = neg0.to_lexical(&mut buffer); -+ assert_eq!(str::from_utf8(result), Ok("-0.0")); -+} diff --git a/lexical-write-float/src/api.rs b/lexical-write-float/src/api.rs index 6b1bd108..57fb00c9 100644 --- a/lexical-write-float/src/api.rs +++ b/lexical-write-float/src/api.rs @@ -9,7 +9,7 @@ use lexical_util::bf16::bf16; use lexical_util::constants::FormattedSize; #[cfg(feature = "f16")] use lexical_util::f16::f16; -use lexical_util::format::{is_valid_options_punctuation, NumberFormat, STANDARD}; +use lexical_util::format::STANDARD; use lexical_util::options::WriteOptions; use lexical_util::{to_lexical, to_lexical_with_options}; @@ -32,58 +32,34 @@ macro_rules! float_to_lexical { ($($t:tt $(, #[$meta:meta])? ; )*) => ($( impl ToLexical for $t { $(#[$meta:meta])? - unsafe fn to_lexical_unchecked(self, bytes: &mut [u8]) + fn to_lexical(self, bytes: &mut [u8]) -> &mut [u8] { - debug_assert!(check_buffer::(bytes.len(), &DEFAULT_OPTIONS)); - // SAFETY: safe if `check_buffer::(bytes.len(), &options)` passes. + assert!(check_buffer::(bytes.len(), &DEFAULT_OPTIONS)); + // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. unsafe { let len = self.write_float::<{ STANDARD }>(bytes, &DEFAULT_OPTIONS); &mut index_unchecked_mut!(bytes[..len]) } } - - $(#[$meta:meta])? - fn to_lexical(self, bytes: &mut [u8]) - -> &mut [u8] - { - assert!(check_buffer::(bytes.len(), &DEFAULT_OPTIONS)); - // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. - unsafe { self.to_lexical_unchecked(bytes) } - } } impl ToLexicalWithOptions for $t { type Options = Options; - $(#[$meta:meta])? - unsafe fn to_lexical_with_options_unchecked<'a, const FORMAT: u128>( + fn to_lexical_with_options<'a, const FORMAT: u128>( self, bytes: &'a mut [u8], options: &Self::Options, ) -> &'a mut [u8] { - assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); - assert!(is_valid_options_punctuation(FORMAT, options.exponent(), options.decimal_point())); - debug_assert!(check_buffer::(bytes.len(), &options)); - // SAFETY: safe if `check_buffer::(bytes.len(), &options)` passes. + assert!(check_buffer::(bytes.len(), &options)); + // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. unsafe { let len = self.write_float::<{ FORMAT }>(bytes, &options); &mut index_unchecked_mut!(bytes[..len]) } } - - $(#[$meta:meta])? - fn to_lexical_with_options<'a, const FORMAT: u128>( - self, - bytes: &'a mut [u8], - options: &Self::Options, - ) -> &'a mut [u8] - { - assert!(check_buffer::(bytes.len(), &options)); - // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. - unsafe { self.to_lexical_with_options_unchecked::(bytes, options) } - } } )*) } diff --git a/lexical-write-integer/src/api.rs b/lexical-write-integer/src/api.rs index cc4dc54e..2cd2bf21 100644 --- a/lexical-write-integer/src/api.rs +++ b/lexical-write-integer/src/api.rs @@ -4,7 +4,7 @@ use crate::options::Options; use crate::write::WriteInteger; -use lexical_util::assert::{assert_buffer, debug_assert_buffer}; +use lexical_util::assert::assert_buffer; use lexical_util::format::{NumberFormat, STANDARD}; use lexical_util::num::SignedInteger; use lexical_util::{to_lexical, to_lexical_with_options}; @@ -91,46 +91,21 @@ macro_rules! unsigned_to_lexical { ($($narrow:tt $wide:tt $(, #[$meta:meta])? ; )*) => ($( impl ToLexical for $narrow { $(#[$meta:meta])? - unsafe fn to_lexical_unchecked(self, bytes: &mut [u8]) + fn to_lexical(self, bytes: &mut [u8]) -> &mut [u8] { - debug_assert_buffer::<$narrow>(10, bytes.len()); + assert_buffer::<$narrow>(10, bytes.len()); // SAFETY: safe if `bytes.len() > Self::FORMATTED_SIZE_DECIMAL`. unsafe { let len = unsigned::<$narrow, $wide, { STANDARD }>(self, bytes); &mut index_unchecked_mut!(bytes[..len]) } } - - $(#[$meta:meta])? - fn to_lexical(self, bytes: &mut [u8]) - -> &mut [u8] - { - assert_buffer::<$narrow>(10, bytes.len()); - // SAFETY: safe since `bytes.len() > Self::FORMATTED_SIZE_DECIMAL`. - unsafe { self.to_lexical_unchecked(bytes) } - } } impl ToLexicalWithOptions for $narrow { type Options = Options; - $(#[$meta:meta])? - unsafe fn to_lexical_with_options_unchecked<'a, const FORMAT: u128>( - self, - bytes: &'a mut [u8], - _: &Self::Options, - ) -> &'a mut [u8] - { - debug_assert_buffer::<$narrow>(NumberFormat::<{ FORMAT }>::RADIX, bytes.len()); - assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); - // SAFETY: safe if `bytes.len() > Self::FORMATTED_SIZE`. - unsafe { - let len = unsigned::<$narrow, $wide, FORMAT>(self, bytes); - &mut index_unchecked_mut!(bytes[..len]) - } - } - $(#[$meta:meta])? fn to_lexical_with_options<'a, const FORMAT: u128>( self, @@ -138,10 +113,14 @@ macro_rules! unsigned_to_lexical { options: &Self::Options, ) -> &'a mut [u8] { + _ = options; assert_buffer::<$narrow>(NumberFormat::<{ FORMAT }>::RADIX, bytes.len()); assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); // SAFETY: safe since `bytes.len() > Self::FORMATTED_SIZE`. - unsafe { self.to_lexical_with_options_unchecked::(bytes, options) } + unsafe { + let len = unsigned::<$narrow, $wide, FORMAT>(self, bytes); + &mut index_unchecked_mut!(bytes[..len]) + } } } )*) @@ -168,18 +147,6 @@ macro_rules! signed_to_lexical { ($($narrow:tt $wide:tt $unsigned:tt $(, #[$meta:meta])? ; )*) => ($( impl ToLexical for $narrow { $(#[$meta:meta])? - #[inline] - unsafe fn to_lexical_unchecked(self, bytes: &mut [u8]) - -> &mut [u8] - { - debug_assert_buffer::<$narrow>(10, bytes.len()); - // SAFETY: safe if `bytes.len() > Self::FORMATTED_SIZE_DECIMAL`. - unsafe { - let len = signed::<$narrow, $wide, $unsigned, { STANDARD }>(self, bytes); - &mut index_unchecked_mut!(bytes[..len]) - } - } - $(#[$meta:meta])? #[inline] fn to_lexical(self, bytes: &mut [u8]) @@ -187,30 +154,15 @@ macro_rules! signed_to_lexical { { assert_buffer::<$narrow>(10, bytes.len()); // SAFETY: safe since `bytes.len() > Self::FORMATTED_SIZE_DECIMAL`. - unsafe { self.to_lexical_unchecked(bytes) } - } - } - - impl ToLexicalWithOptions for $narrow { - type Options = Options; - - $(#[$meta:meta])? - #[inline] - unsafe fn to_lexical_with_options_unchecked<'a, const FORMAT: u128>( - self, - bytes: &'a mut [u8], - _: &Self::Options, - ) -> &'a mut [u8] - { - debug_assert_buffer::<$narrow>(NumberFormat::<{ FORMAT }>::RADIX, bytes.len()); - assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); - // SAFETY: safe if `bytes.len() > Self::FORMATTED_SIZE`. unsafe { - let len = signed::<$narrow, $wide, $unsigned, FORMAT>(self, bytes); + let len = signed::<$narrow, $wide, $unsigned, { STANDARD }>(self, bytes); &mut index_unchecked_mut!(bytes[..len]) } } + } + impl ToLexicalWithOptions for $narrow { + type Options = Options; $(#[$meta:meta])? #[inline] fn to_lexical_with_options<'a, const FORMAT: u128>( @@ -219,10 +171,14 @@ macro_rules! signed_to_lexical { options: &Self::Options, ) -> &'a mut [u8] { + _ = options; assert_buffer::<$narrow>(NumberFormat::<{ FORMAT }>::RADIX, bytes.len()); assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); // SAFETY: safe since `bytes.len() > Self::FORMATTED_SIZE`. - unsafe { self.to_lexical_with_options_unchecked::(bytes, options) } + unsafe { + let len = signed::<$narrow, $wide, $unsigned, FORMAT>(self, bytes); + &mut index_unchecked_mut!(bytes[..len]) + } } } )*) diff --git a/lexical/src/lib.rs b/lexical/src/lib.rs index 05258e15..6078407a 100644 --- a/lexical/src/lib.rs +++ b/lexical/src/lib.rs @@ -252,7 +252,6 @@ //! [`to_string`]: fn.to_string.html //! [`to_string_with_options`]: fn.to_string_with_options.html //! [`write_with_options`]: crate::write_with_options -//! [`write_with_options_unchecked`]: crate::write_with_options_unchecked //! [`parse`]: crate::parse //! [`parse_partial`]: crate::parse_partial //! [`parse_with_options`]: crate::parse_with_options @@ -339,7 +338,7 @@ pub fn to_string(n: N) -> String { // SAFETY: safe since the buffer is of sufficient size. unsafe { let mut buf = Vec::::with_capacity(N::FORMATTED_SIZE_DECIMAL); - let len = lexical_core::write_unchecked(n, vector_as_slice(&mut buf)).len(); + let len = lexical_core::write(n, vector_as_slice(&mut buf)).len(); buf.set_len(len); String::from_utf8_unchecked(buf) } @@ -375,7 +374,7 @@ pub fn to_string_with_options( // SAFETY: safe since the buffer is of sufficient size. unsafe { let mut buf = Vec::::with_capacity(size); - let len = lexical_core::write_with_options_unchecked::<_, FORMAT>( + let len = lexical_core::write_with_options::<_, FORMAT>( n, vector_as_slice(&mut buf), options, From a4e748067024f16dc4b61142c29dc4acd7e6cd92 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 9 Sep 2024 20:41:52 -0500 Subject: [PATCH 04/11] Remove all unsafety from our table API methods. --- lexical-benchmark/algorithm/bigint.rs | 2 +- lexical-benchmark/input.rs | 2 +- lexical-parse-float/src/bigint.rs | 1 + lexical-parse-float/src/float.rs | 26 ++-- lexical-parse-float/src/table_binary.rs | 167 +++++++--------------- lexical-parse-float/src/table_radix.rs | 9 +- lexical-parse-float/tests/float_tests.rs | 6 +- lexical-write-float/src/api.rs | 2 + lexical-write-integer/src/radix.rs | 11 +- lexical-write-integer/src/table_binary.rs | 11 +- lexical-write-integer/src/table_radix.rs | 11 +- lexical/src/lib.rs | 9 +- 12 files changed, 83 insertions(+), 174 deletions(-) diff --git a/lexical-benchmark/algorithm/bigint.rs b/lexical-benchmark/algorithm/bigint.rs index 975b01d7..0a97b631 100644 --- a/lexical-benchmark/algorithm/bigint.rs +++ b/lexical-benchmark/algorithm/bigint.rs @@ -26,7 +26,7 @@ fn small_pow(big: &mut bigint::Bigint, mut exp: u32) { } if exp != 0 { // SAFETY: safe, since `exp < small_step`. - let small_power = unsafe { f64::int_pow_fast_path(exp as usize, 5) }; + let small_power = f64::int_pow_fast_path(exp as usize, 5); big.data.mul_small(small_power as bigint::Limb).unwrap(); } diff --git a/lexical-benchmark/input.rs b/lexical-benchmark/input.rs index 3f20f2b7..058db375 100644 --- a/lexical-benchmark/input.rs +++ b/lexical-benchmark/input.rs @@ -386,7 +386,7 @@ macro_rules! to_lexical_generator { $group.bench_function($name, |bench| { bench.iter(|| { $iter.for_each(|&x| { - black_box(unsafe { x.to_lexical(&mut buffer) }); + black_box(x.to_lexical(&mut buffer)); }) }) }); diff --git a/lexical-parse-float/src/bigint.rs b/lexical-parse-float/src/bigint.rs index b0b8f8c6..b26478db 100644 --- a/lexical-parse-float/src/bigint.rs +++ b/lexical-parse-float/src/bigint.rs @@ -771,6 +771,7 @@ impl<'a, T> ops::Index for ReverseView<'a, T> { #[inline(always)] pub unsafe fn nonzero(x: &[Limb], rindex: usize) -> bool { debug_assert!(rindex <= x.len()); + // TODO: Change to have no unsafe? let len = x.len(); // SAFETY: safe if `rindex < x.len()`, since then `x.len() - rindex < x.len()`. diff --git a/lexical-parse-float/src/float.rs b/lexical-parse-float/src/float.rs index d422e490..ef0ea0cf 100644 --- a/lexical-parse-float/src/float.rs +++ b/lexical-parse-float/src/float.rs @@ -52,22 +52,14 @@ pub trait RawFloat: Float + ExactFloat + MaxDigits { } /// Get a small power-of-radix for fast-path multiplication. - /// - /// # Safety - /// - /// Safe as long as the exponent is smaller than the table size. - unsafe fn pow_fast_path(exponent: usize, radix: u32) -> Self; + fn pow_fast_path(exponent: usize, radix: u32) -> Self; /// Get a small, integral power-of-radix for fast-path multiplication. - /// - /// # Safety - /// - /// Safe as long as the exponent is smaller than the table size. #[inline(always)] - unsafe fn int_pow_fast_path(exponent: usize, radix: u32) -> u64 { + fn int_pow_fast_path(exponent: usize, radix: u32) -> u64 { // SAFETY: safe as long as the exponent is smaller than the radix table. #[cfg(not(feature = "compact"))] - return unsafe { get_small_int_power(exponent, radix) }; + return get_small_int_power(exponent, radix); #[cfg(feature = "compact")] return (radix as u64).wrapping_pow(exponent as u32); @@ -76,10 +68,10 @@ pub trait RawFloat: Float + ExactFloat + MaxDigits { impl RawFloat for f32 { #[inline(always)] - unsafe fn pow_fast_path(exponent: usize, radix: u32) -> Self { + fn pow_fast_path(exponent: usize, radix: u32) -> Self { // SAFETY: safe as long as the exponent is smaller than the radix table. #[cfg(not(feature = "compact"))] - return unsafe { get_small_f32_power(exponent, radix) }; + return get_small_f32_power(exponent, radix); #[cfg(feature = "compact")] return powf(radix as f32, exponent as f32); @@ -88,10 +80,10 @@ impl RawFloat for f32 { impl RawFloat for f64 { #[inline(always)] - unsafe fn pow_fast_path(exponent: usize, radix: u32) -> Self { + fn pow_fast_path(exponent: usize, radix: u32) -> Self { // SAFETY: safe as long as the exponent is smaller than the radix table. #[cfg(not(feature = "compact"))] - return unsafe { get_small_f64_power(exponent, radix) }; + return get_small_f64_power(exponent, radix); #[cfg(feature = "compact")] return powd(radix as f64, exponent as f64); @@ -101,7 +93,7 @@ impl RawFloat for f64 { #[cfg(feature = "f16")] impl RawFloat for f16 { #[inline(always)] - unsafe fn pow_fast_path(_: usize, _: u32) -> Self { + fn pow_fast_path(_: usize, _: u32) -> Self { unimplemented!() } } @@ -109,7 +101,7 @@ impl RawFloat for f16 { #[cfg(feature = "f16")] impl RawFloat for bf16 { #[inline(always)] - unsafe fn pow_fast_path(_: usize, _: u32) -> Self { + fn pow_fast_path(_: usize, _: u32) -> Self { unimplemented!() } } diff --git a/lexical-parse-float/src/table_binary.rs b/lexical-parse-float/src/table_binary.rs index 26216f84..42d98422 100644 --- a/lexical-parse-float/src/table_binary.rs +++ b/lexical-parse-float/src/table_binary.rs @@ -7,8 +7,6 @@ #[cfg(not(feature = "radix"))] use crate::table_decimal::*; #[cfg(not(feature = "radix"))] -use core::hint; -#[cfg(not(feature = "radix"))] use lexical_util::assert::debug_assert_radix; use lexical_util::num::Float; @@ -23,8 +21,9 @@ use lexical_util::num::Float; /// than the table for the radix. #[inline(always)] #[cfg(not(feature = "radix"))] -pub unsafe fn get_small_int_power(exponent: usize, radix: u32) -> u64 { +pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { // NOTE: don't check the radix since we also use it for half radix, or 5. + // TODO: Change this to take a format so we can validate it at compile time? unsafe { match radix { 2 => get_small_int_power2(exponent), @@ -34,31 +33,25 @@ pub unsafe fn get_small_int_power(exponent: usize, radix: u32) -> u64 { 10 => get_small_int_power10(exponent), 16 => get_small_int_power16(exponent), 32 => get_small_int_power32(exponent), - _ => hint::unreachable_unchecked(), + _ => unreachable!(), } } } /// Get lookup table for small f32 powers. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid, and exponent is smaller -/// than the table for the radix. #[inline(always)] #[cfg(not(feature = "radix"))] -pub unsafe fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { +pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { + // TODO: Change this to take a format so we can validate it at compile time? debug_assert_radix(radix); - unsafe { - match radix { - 2 => get_small_f32_power2(exponent), - 4 => get_small_f32_power4(exponent), - 8 => get_small_f32_power8(exponent), - 10 => get_small_f32_power10(exponent), - 16 => get_small_f32_power16(exponent), - 32 => get_small_f32_power32(exponent), - _ => hint::unreachable_unchecked(), - } + match radix { + 2 => get_small_f32_power2(exponent), + 4 => get_small_f32_power4(exponent), + 8 => get_small_f32_power8(exponent), + 10 => get_small_f32_power10(exponent), + 16 => get_small_f32_power16(exponent), + 32 => get_small_f32_power32(exponent), + _ => unreachable!(), } } @@ -70,18 +63,16 @@ pub unsafe fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { /// than the table for the radix. #[inline(always)] #[cfg(not(feature = "radix"))] -pub unsafe fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { +pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { debug_assert_radix(radix); - unsafe { - match radix { - 2 => get_small_f64_power2(exponent), - 4 => get_small_f64_power4(exponent), - 8 => get_small_f64_power8(exponent), - 10 => get_small_f64_power10(exponent), - 16 => get_small_f64_power16(exponent), - 32 => get_small_f64_power32(exponent), - _ => hint::unreachable_unchecked(), - } + match radix { + 2 => get_small_f64_power2(exponent), + 4 => get_small_f64_power4(exponent), + 8 => get_small_f64_power8(exponent), + 10 => get_small_f64_power10(exponent), + 16 => get_small_f64_power16(exponent), + 32 => get_small_f64_power32(exponent), + _ => unreachable!(), } } @@ -91,22 +82,14 @@ pub unsafe fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { // integers, or by setting the exponent directly. /// Get pre-computed int power of 2. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_int_power2(exponent: usize) -> u64 { +pub fn get_small_int_power2(exponent: usize) -> u64 { 1 << exponent } /// Get pre-computed f32 power of 2. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f32_power2(exponent: usize) -> f32 { +pub fn get_small_f32_power2(exponent: usize) -> f32 { // Can't handle values above the denormal size. debug_assert!(exponent as i32 <= f32::EXPONENT_BIAS - f32::MANTISSA_SIZE); let shift = (f32::EXPONENT_BIAS - f32::MANTISSA_SIZE) as u32; @@ -115,12 +98,8 @@ pub unsafe fn get_small_f32_power2(exponent: usize) -> f32 { } /// Get pre-computed f64 power of 2. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f64_power2(exponent: usize) -> f64 { +pub fn get_small_f64_power2(exponent: usize) -> f64 { // Can't handle values above the denormal size. debug_assert!(exponent as i32 <= f64::EXPONENT_BIAS - f64::MANTISSA_SIZE); let shift = (f64::EXPONENT_BIAS - f64::MANTISSA_SIZE) as u64; @@ -129,121 +108,73 @@ pub unsafe fn get_small_f64_power2(exponent: usize) -> f64 { } /// Get pre-computed int power of 4. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_int_power4(exponent: usize) -> u64 { - unsafe { get_small_int_power2(2 * exponent) } +pub fn get_small_int_power4(exponent: usize) -> u64 { + get_small_int_power2(2 * exponent) } /// Get pre-computed f32 power of 4. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f32_power4(exponent: usize) -> f32 { - unsafe { get_small_f32_power2(2 * exponent) } +pub fn get_small_f32_power4(exponent: usize) -> f32 { + get_small_f32_power2(2 * exponent) } /// Get pre-computed f64 power of 4. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f64_power4(exponent: usize) -> f64 { - unsafe { get_small_f64_power2(2 * exponent) } +pub fn get_small_f64_power4(exponent: usize) -> f64 { + get_small_f64_power2(2 * exponent) } /// Get pre-computed int power of 8. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_int_power8(exponent: usize) -> u64 { - unsafe { get_small_int_power2(3 * exponent) } +pub fn get_small_int_power8(exponent: usize) -> u64 { + get_small_int_power2(3 * exponent) } /// Get pre-computed f32 power of 8. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f32_power8(exponent: usize) -> f32 { - unsafe { get_small_f32_power2(3 * exponent) } +pub fn get_small_f32_power8(exponent: usize) -> f32 { + get_small_f32_power2(3 * exponent) } /// Get pre-computed f64 power of 8. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f64_power8(exponent: usize) -> f64 { - unsafe { get_small_f64_power2(3 * exponent) } +pub fn get_small_f64_power8(exponent: usize) -> f64 { + get_small_f64_power2(3 * exponent) } /// Get pre-computed int power of 16. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_int_power16(exponent: usize) -> u64 { - unsafe { get_small_int_power2(4 * exponent) } +pub fn get_small_int_power16(exponent: usize) -> u64 { + get_small_int_power2(4 * exponent) } /// Get pre-computed f32 power of 16. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f32_power16(exponent: usize) -> f32 { - unsafe { get_small_f32_power2(4 * exponent) } +pub fn get_small_f32_power16(exponent: usize) -> f32 { + get_small_f32_power2(4 * exponent) } /// Get pre-computed f64 power of 16. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f64_power16(exponent: usize) -> f64 { - unsafe { get_small_f64_power2(4 * exponent) } +pub fn get_small_f64_power16(exponent: usize) -> f64 { + get_small_f64_power2(4 * exponent) } /// Get pre-computed int power of 32. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_int_power32(exponent: usize) -> u64 { - unsafe { get_small_int_power2(5 * exponent) } +pub fn get_small_int_power32(exponent: usize) -> u64 { + get_small_int_power2(5 * exponent) } /// Get pre-computed f32 power of 32. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f32_power32(exponent: usize) -> f32 { - unsafe { get_small_f32_power2(5 * exponent) } +pub fn get_small_f32_power32(exponent: usize) -> f32 { + get_small_f32_power2(5 * exponent) } /// Get pre-computed f64 power of 32. -/// -/// # Safety -/// -/// Always safe, just marked unsafe for API compatibility. #[inline(always)] -pub unsafe fn get_small_f64_power32(exponent: usize) -> f64 { - unsafe { get_small_f64_power2(5 * exponent) } +pub fn get_small_f64_power32(exponent: usize) -> f64 { + get_small_f64_power2(5 * exponent) } diff --git a/lexical-parse-float/src/table_radix.rs b/lexical-parse-float/src/table_radix.rs index 9dab33be..f554af0d 100644 --- a/lexical-parse-float/src/table_radix.rs +++ b/lexical-parse-float/src/table_radix.rs @@ -9,7 +9,6 @@ use crate::bigint::Limb; use crate::limits::{f32_exponent_limit, f64_exponent_limit, f64_mantissa_limit, u64_power_limit}; use crate::table_binary::*; use crate::table_decimal::*; -use core::hint; use lexical_util::assert::debug_assert_radix; use static_assertions::const_assert; @@ -20,6 +19,7 @@ use static_assertions::const_assert; #[inline(always)] pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { debug_assert_radix(radix); + // TODO: Change this to take a format so we can validate it at compile time? match radix { 2 => get_small_int_power2(exponent), 3 => get_small_int_power3(exponent), @@ -56,7 +56,7 @@ pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { 34 => get_small_int_power34(exponent), 35 => get_small_int_power35(exponent), 36 => get_small_int_power36(exponent), - _ => hint::unreachable_unchecked(), + _ => unreachable!(), } } @@ -64,6 +64,7 @@ pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { #[inline(always)] pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { debug_assert_radix(radix); + // TODO: Change this to take a format so we can validate it at compile time? match radix { 2 => get_small_f32_power2(exponent), 3 => get_small_f32_power3(exponent), @@ -100,7 +101,7 @@ pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { 34 => get_small_f32_power34(exponent), 35 => get_small_f32_power35(exponent), 36 => get_small_f32_power36(exponent), - _ => hint::unreachable_unchecked(), + _ => unreachable!(), } } @@ -144,7 +145,7 @@ pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { 34 => get_small_f64_power34(exponent), 35 => get_small_f64_power35(exponent), 36 => get_small_f64_power36(exponent), - _ => hint::unreachable_unchecked(), + _ => unreachable!(), } } diff --git a/lexical-parse-float/tests/float_tests.rs b/lexical-parse-float/tests/float_tests.rs index 1e9bc745..c4bcd18e 100644 --- a/lexical-parse-float/tests/float_tests.rs +++ b/lexical-parse-float/tests/float_tests.rs @@ -32,12 +32,12 @@ fn slow_f64_power(exponent: usize, radix: u32) -> f64 { fn pow_fast_path(radix: u32) { for exponent in 0..f32::exponent_limit(radix).1 + 1 { let exponent = exponent as usize; - let actual = unsafe { f32::pow_fast_path(exponent, radix) }; + let actual = f32::pow_fast_path(exponent, radix); assert_eq!(actual, slow_f32_power(exponent, radix)); } for exponent in 0..f64::exponent_limit(radix).1 + 1 { let exponent = exponent as usize; - let actual = unsafe { f64::pow_fast_path(exponent, radix) }; + let actual = f64::pow_fast_path(exponent, radix); assert_eq!(actual, slow_f64_power(exponent, radix)); } } @@ -97,7 +97,7 @@ fn slow_int_power(exponent: usize, radix: u32) -> u64 { fn int_pow_fast_path(radix: u32) { for exponent in 0..f64::mantissa_limit(radix) { let exponent = exponent as usize; - let actual = unsafe { f64::int_pow_fast_path(exponent, radix) }; + let actual = f64::int_pow_fast_path(exponent, radix); assert_eq!(actual, slow_int_power(exponent, radix)); } } diff --git a/lexical-write-float/src/api.rs b/lexical-write-float/src/api.rs index 57fb00c9..03f9e555 100644 --- a/lexical-write-float/src/api.rs +++ b/lexical-write-float/src/api.rs @@ -35,6 +35,7 @@ macro_rules! float_to_lexical { fn to_lexical(self, bytes: &mut [u8]) -> &mut [u8] { + // TODO: Remove, move inside assert!(check_buffer::(bytes.len(), &DEFAULT_OPTIONS)); // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. unsafe { @@ -53,6 +54,7 @@ macro_rules! float_to_lexical { options: &Self::Options, ) -> &'a mut [u8] { + // TODO: Remove, move inside assert!(check_buffer::(bytes.len(), &options)); // SAFETY: safe since `check_buffer::(bytes.len(), &options)` passes. unsafe { diff --git a/lexical-write-integer/src/radix.rs b/lexical-write-integer/src/radix.rs index a2bc877b..d8b63439 100644 --- a/lexical-write-integer/src/radix.rs +++ b/lexical-write-integer/src/radix.rs @@ -16,10 +16,10 @@ use crate::algorithm::{algorithm, algorithm_u128}; use crate::table::get_table; use core::mem; use lexical_util::algorithm::copy_to_dst; +use lexical_util::assert::assert_buffer; use lexical_util::format; -use lexical_util::num::{Integer, UnsignedInteger}; use lexical_util::format::NumberFormat; -use lexical_util::assert::assert_buffer; +use lexical_util::num::{Integer, UnsignedInteger}; /// Write integer to radix string. pub trait Radix: UnsignedInteger { @@ -60,8 +60,7 @@ macro_rules! radix_impl { debug_assert!(::BITS <= 64); assert_buffer::<$t>(NumberFormat::<{ FORMAT }>::RADIX, buffer.len()); let radix = format::radix_from_flags(FORMAT, MASK, SHIFT); - // TODO: Remove unsafe - let table = unsafe { get_table::() }; + let table = get_table::(); let mut digits: mem::MaybeUninit<[u8; 64]> = mem::MaybeUninit::uninit(); // # Safety @@ -88,10 +87,8 @@ impl Radix for u128 { self, buffer: &mut [u8], ) -> usize { - debug_assert!(::BITS <= 128); assert_buffer::(NumberFormat::<{ FORMAT }>::RADIX, buffer.len()); - // TODO: Remove unsafe - let table = unsafe { get_table::() }; + let table = get_table::(); let mut digits: mem::MaybeUninit<[u8; 128]> = mem::MaybeUninit::uninit(); // # Safety diff --git a/lexical-write-integer/src/table_binary.rs b/lexical-write-integer/src/table_binary.rs index 454cc7d1..c78f111a 100644 --- a/lexical-write-integer/src/table_binary.rs +++ b/lexical-write-integer/src/table_binary.rs @@ -7,8 +7,6 @@ #[cfg(not(feature = "radix"))] use crate::table_decimal::*; #[cfg(not(feature = "radix"))] -use core::hint; -#[cfg(not(feature = "radix"))] use lexical_util::assert::debug_assert_radix; #[cfg(not(feature = "radix"))] use lexical_util::format::radix_from_flags; @@ -18,13 +16,9 @@ use lexical_util::format::radix_from_flags; /// * `FORMAT` - Number format. /// * `MASK` - Mask to extract the radix value. /// * `SHIFT` - Shift to normalize the radix value in `[0, 0x3f]`. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid. #[inline(always)] #[cfg(not(feature = "radix"))] -pub unsafe fn get_table() -> &'static [u8] { +pub fn get_table() -> &'static [u8] { debug_assert_radix(radix_from_flags(FORMAT, MASK, SHIFT)); match radix_from_flags(FORMAT, MASK, SHIFT) { 2 => &DIGIT_TO_BASE2_SQUARED, @@ -33,8 +27,7 @@ pub unsafe fn get_table( 10 => &DIGIT_TO_BASE10_SQUARED, 16 => &DIGIT_TO_BASE16_SQUARED, 32 => &DIGIT_TO_BASE32_SQUARED, - // SAFETY: This is safe as long as the radix is valid. - _ => unsafe { hint::unreachable_unchecked() }, + _ => unreachable!(), } } diff --git a/lexical-write-integer/src/table_radix.rs b/lexical-write-integer/src/table_radix.rs index 95c9b4a4..86f6dd20 100644 --- a/lexical-write-integer/src/table_radix.rs +++ b/lexical-write-integer/src/table_radix.rs @@ -6,7 +6,6 @@ use crate::table_binary::*; use crate::table_decimal::*; -use core::hint; use lexical_util::assert::debug_assert_radix; use lexical_util::format::radix_from_flags; @@ -15,14 +14,11 @@ use lexical_util::format::radix_from_flags; /// * `FORMAT` - Number format. /// * `MASK` - Mask to extract the radix value. /// * `SHIFT` - Shift to normalize the radix value in `[0, 0x3f]`. -/// -/// # Safety -/// -/// Safe as long as the radix provided is valid. #[inline(always)] #[cfg(feature = "radix")] -pub unsafe fn get_table() -> &'static [u8] { +pub fn get_table() -> &'static [u8] { debug_assert_radix(radix_from_flags(FORMAT, MASK, SHIFT)); + // TODO: Change this to take a format so we can validate it at compile time? match radix_from_flags(FORMAT, MASK, SHIFT) { 2 => &DIGIT_TO_BASE2_SQUARED, 3 => &DIGIT_TO_BASE3_SQUARED, @@ -59,8 +55,7 @@ pub unsafe fn get_table( 34 => &DIGIT_TO_BASE34_SQUARED, 35 => &DIGIT_TO_BASE35_SQUARED, 36 => &DIGIT_TO_BASE36_SQUARED, - // SAFETY: This is safe as long as the radix is valid. - _ => unsafe { hint::unreachable_unchecked() }, + _ => unreachable!(), } } diff --git a/lexical/src/lib.rs b/lexical/src/lib.rs index 6078407a..46765de1 100644 --- a/lexical/src/lib.rs +++ b/lexical/src/lib.rs @@ -374,12 +374,9 @@ pub fn to_string_with_options( // SAFETY: safe since the buffer is of sufficient size. unsafe { let mut buf = Vec::::with_capacity(size); - let len = lexical_core::write_with_options::<_, FORMAT>( - n, - vector_as_slice(&mut buf), - options, - ) - .len(); + let len = + lexical_core::write_with_options::<_, FORMAT>(n, vector_as_slice(&mut buf), options) + .len(); buf.set_len(len); String::from_utf8_unchecked(buf) } From 64960288b441e4a8e48fc88c7104e8c868cf462d Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 9 Sep 2024 22:11:02 -0500 Subject: [PATCH 05/11] Enhance our comparison script. --- lexical-benchmark/compare.py | 54 +++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/lexical-benchmark/compare.py b/lexical-benchmark/compare.py index 81f9b36f..9d3415b0 100644 --- a/lexical-benchmark/compare.py +++ b/lexical-benchmark/compare.py @@ -1,15 +1,32 @@ #!/usr/bin/env python ''' - profiling - ========= + compare + ======= - Create a baseline for metrics between various tools. + Compare a baseline to new metrics from 2 criterion runs. - This finds all the results from criterion in the target directories, - and then concatenates them and joins various tooling into a single - file. + This uses 2 previously exported entries from `profiling.py` and then + exports them to a markdown file, of the following format, where the + percentage is `new / baseline`, so a value below 100% is better. - The file will be output to `/target/profiling.json` (by default). + Results + ======= + + | | core | lexical | + |:-:|:-:|:-:| + | parse_i128 | 99.83% | 100.21% | + | parse_i16 | 99.23% | 99.23% | + | parse_i32 | 99.1% | 98.07% | + | parse_i64 | 100.77% | 100.29% | + | parse_i8 | 101.14% | 98.64% | + | parse_u128 | 99.08% | 99.06% | + | parse_u16 | 99.48% | 98.64% | + | parse_u32 | 101.47% | 99.27% | + | parse_u64 | 100.28% | 101.1% | + | parse_u8 | 100.06% | 102.18% | + | parse_u128 | 99.31% | 100.02% | + | parse_u16 | 101.86% | 99.24% | + | parse_u32 | 100.97% | 100.96% | ''' import argparse @@ -52,8 +69,9 @@ new = json.load(fp) # grab our comparison names, so we know where to analyze +# TODO: This is broken... this needas to only have the set of the tools group = next(iter(baseline.values())) -tools = [i.rsplit('_')[-1] for i in group['mean']] +tools = sorted({i.rsplit('_')[-1] for i in group['mean']}) header = f'| | {" | ".join(tools)} |' center = f'|:{"-:|:" * len(tools)}-:|' @@ -74,14 +92,18 @@ def get_unit(x, y): output = 'Results\n=======\n\n' + header + '\n' + center + '\n' -for test, baseline_results in baseline.items(): - row = [] - for tool in tools: - new_results = new[test] - baseline_mean = [v for k, v in baseline_results['mean'].items() if k.endswith(f'_{tool}')][0] - new_mean = [v for k, v in new_results['mean'].items() if k.endswith(f'_{tool}')][0] - row.append(str(round(baseline_mean / new_mean * 100, 2)) + '%') - output += f'| {test} | {" | ".join(row)} |\n' +first_tool = tools[0] +for test_group, baseline_results in baseline.items(): + new_means = new[test_group]['mean'] + baseline_means = baseline_results['mean'] + tests = sorted([i[: -len(first_tool) - 1] for i in baseline_means if i.endswith(f'_{first_tool}')]) + for test in tests: + row = [] + for tool in tools: + baseline_mean = baseline_means[f'{test}_{tool}'] + new_mean = new_means[f'{test}_{tool}'] + row.append(str(round(baseline_mean / new_mean * 100, 2)) + '%') + output += f'| {test} | {" | ".join(row)} |\n' with args.output.open('w', encoding='utf-8') as file: print(output, file=file) From b8fcf5135588c6616b13663457fca3860765fef7 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 00:41:52 -0500 Subject: [PATCH 06/11] Remove unused unsafe. --- lexical-parse-float/src/number.rs | 24 ++++------ lexical-parse-float/src/slow.rs | 6 +-- lexical-parse-float/src/table_binary.rs | 38 ++++++--------- lexical-parse-float/src/table_decimal.rs | 12 ++--- lexical-parse-float/src/table_radix.rs | 61 +++++++++++------------- lexical-parse-integer/src/shared.rs | 2 +- lexical-write-float/src/compact.rs | 3 +- 7 files changed, 60 insertions(+), 86 deletions(-) diff --git a/lexical-parse-float/src/number.rs b/lexical-parse-float/src/number.rs index 63f2cff0..deba75cb 100644 --- a/lexical-parse-float/src/number.rs +++ b/lexical-parse-float/src/number.rs @@ -73,23 +73,19 @@ impl<'a> Number<'a> { // normal fast path let value = F::as_cast(self.mantissa); if self.exponent < 0 { - // SAFETY: safe, since the `exponent <= max_exponent`. - value / unsafe { F::pow_fast_path((-self.exponent) as _, radix) } + value / F::pow_fast_path((-self.exponent) as _, radix) } else { - // SAFETY: safe, since the `exponent <= max_exponent`. - value * unsafe { F::pow_fast_path(self.exponent as _, radix) } + value * F::pow_fast_path(self.exponent as _, radix) } } else { // disguised fast path let shift = self.exponent - max_exponent; - // SAFETY: safe, since `shift <= (max_disguised - max_exponent)`. - let int_power = unsafe { F::int_pow_fast_path(shift as usize, radix) }; + let int_power = F::int_pow_fast_path(shift as usize, radix); let mantissa = self.mantissa.checked_mul(int_power)?; if mantissa > F::MAX_MANTISSA_FAST_PATH { return None; } - // SAFETY: safe, since the `table.len() - 1 == max_exponent`. - F::as_cast(mantissa) * unsafe { F::pow_fast_path(max_exponent as _, radix) } + F::as_cast(mantissa) * F::pow_fast_path(max_exponent as _, radix) }; if self.is_negative { value = -value; @@ -116,20 +112,16 @@ impl<'a> Number<'a> { let mut exponent = self.exponent.abs(); if self.exponent < 0 { while exponent > max_exponent { - // SAFETY: safe, since pow_fast_path is always safe for max_exponent. - value /= unsafe { F::pow_fast_path(max_exponent as _, radix) }; + value /= F::pow_fast_path(max_exponent as _, radix); exponent -= max_exponent; } - // SAFETY: safe, since the `exponent < max_exponent`. - value /= unsafe { F::pow_fast_path(exponent as _, radix) }; + value /= F::pow_fast_path(exponent as _, radix); } else { while exponent > max_exponent { - // SAFETY: safe, since pow_fast_path is always safe for max_exponent. - value *= unsafe { F::pow_fast_path(max_exponent as _, radix) }; + value *= F::pow_fast_path(max_exponent as _, radix); exponent -= max_exponent; } - // SAFETY: safe, since the `exponent < max_exponent`. - value *= unsafe { F::pow_fast_path(exponent as _, radix) }; + value *= F::pow_fast_path(exponent as _, radix); } if self.is_negative { value = -value; diff --git a/lexical-parse-float/src/slow.rs b/lexical-parse-float/src/slow.rs index 04de82ce..5326a7a0 100644 --- a/lexical-parse-float/src/slow.rs +++ b/lexical-parse-float/src/slow.rs @@ -296,14 +296,10 @@ macro_rules! add_temporary { }; // Add a temporary where we won't read the counter results internally. - // - // # Safety - // - // Safe is `counter <= step`, or smaller than the table size. (@end $format:ident, $result:ident, $counter:ident, $value:ident) => { if $counter != 0 { // SAFETY: safe, since `counter <= step`, or smaller than the table size. - let small_power = unsafe { f64::int_pow_fast_path($counter, $format.radix()) }; + let small_power = f64::int_pow_fast_path($counter, $format.radix()); add_temporary!(@mul $result, small_power as Limb, $value); } }; diff --git a/lexical-parse-float/src/table_binary.rs b/lexical-parse-float/src/table_binary.rs index 42d98422..b6940a72 100644 --- a/lexical-parse-float/src/table_binary.rs +++ b/lexical-parse-float/src/table_binary.rs @@ -6,8 +6,6 @@ #[cfg(not(feature = "radix"))] use crate::table_decimal::*; -#[cfg(not(feature = "radix"))] -use lexical_util::assert::debug_assert_radix; use lexical_util::num::Float; // HELPERS @@ -21,20 +19,17 @@ use lexical_util::num::Float; /// than the table for the radix. #[inline(always)] #[cfg(not(feature = "radix"))] -pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { +pub const fn get_small_int_power(exponent: usize, radix: u32) -> u64 { // NOTE: don't check the radix since we also use it for half radix, or 5. - // TODO: Change this to take a format so we can validate it at compile time? - unsafe { - match radix { - 2 => get_small_int_power2(exponent), - 4 => get_small_int_power4(exponent), - 5 => get_small_int_power5(exponent), - 8 => get_small_int_power8(exponent), - 10 => get_small_int_power10(exponent), - 16 => get_small_int_power16(exponent), - 32 => get_small_int_power32(exponent), - _ => unreachable!(), - } + match radix { + 2 => get_small_int_power2(exponent), + 4 => get_small_int_power4(exponent), + 5 => get_small_int_power5(exponent), + 8 => get_small_int_power8(exponent), + 10 => get_small_int_power10(exponent), + 16 => get_small_int_power16(exponent), + 32 => get_small_int_power32(exponent), + _ => unreachable!(), } } @@ -42,8 +37,6 @@ pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { #[inline(always)] #[cfg(not(feature = "radix"))] pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { - // TODO: Change this to take a format so we can validate it at compile time? - debug_assert_radix(radix); match radix { 2 => get_small_f32_power2(exponent), 4 => get_small_f32_power4(exponent), @@ -64,7 +57,6 @@ pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { #[inline(always)] #[cfg(not(feature = "radix"))] pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { - debug_assert_radix(radix); match radix { 2 => get_small_f64_power2(exponent), 4 => get_small_f64_power4(exponent), @@ -83,7 +75,7 @@ pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { /// Get pre-computed int power of 2. #[inline(always)] -pub fn get_small_int_power2(exponent: usize) -> u64 { +pub const fn get_small_int_power2(exponent: usize) -> u64 { 1 << exponent } @@ -109,7 +101,7 @@ pub fn get_small_f64_power2(exponent: usize) -> f64 { /// Get pre-computed int power of 4. #[inline(always)] -pub fn get_small_int_power4(exponent: usize) -> u64 { +pub const fn get_small_int_power4(exponent: usize) -> u64 { get_small_int_power2(2 * exponent) } @@ -127,7 +119,7 @@ pub fn get_small_f64_power4(exponent: usize) -> f64 { /// Get pre-computed int power of 8. #[inline(always)] -pub fn get_small_int_power8(exponent: usize) -> u64 { +pub const fn get_small_int_power8(exponent: usize) -> u64 { get_small_int_power2(3 * exponent) } @@ -145,7 +137,7 @@ pub fn get_small_f64_power8(exponent: usize) -> f64 { /// Get pre-computed int power of 16. #[inline(always)] -pub fn get_small_int_power16(exponent: usize) -> u64 { +pub const fn get_small_int_power16(exponent: usize) -> u64 { get_small_int_power2(4 * exponent) } @@ -163,7 +155,7 @@ pub fn get_small_f64_power16(exponent: usize) -> f64 { /// Get pre-computed int power of 32. #[inline(always)] -pub fn get_small_int_power32(exponent: usize) -> u64 { +pub const fn get_small_int_power32(exponent: usize) -> u64 { get_small_int_power2(5 * exponent) } diff --git a/lexical-parse-float/src/table_decimal.rs b/lexical-parse-float/src/table_decimal.rs index 60bc15fd..9c0e24b6 100644 --- a/lexical-parse-float/src/table_decimal.rs +++ b/lexical-parse-float/src/table_decimal.rs @@ -6,8 +6,6 @@ #[cfg(not(feature = "radix"))] use crate::bigint::Limb; use crate::limits::{f32_exponent_limit, f64_exponent_limit, f64_mantissa_limit, u64_power_limit}; -#[cfg(not(feature = "power-of-two"))] -use lexical_util::assert::debug_assert_radix; use static_assertions::const_assert; // HELPERS @@ -16,7 +14,7 @@ use static_assertions::const_assert; /// Get lookup table for small int powers. #[inline(always)] #[cfg(not(feature = "power-of-two"))] -pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { +pub const fn get_small_int_power(exponent: usize, radix: u32) -> u64 { // NOTE: don't check the radix since we also use it for half radix, or 5. match radix { 5 => get_small_int_power5(exponent), @@ -29,7 +27,7 @@ pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { #[inline(always)] #[cfg(not(feature = "power-of-two"))] pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { - debug_assert_radix(radix); + _ = radix; get_small_f32_power10(exponent) } @@ -37,7 +35,7 @@ pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { #[inline(always)] #[cfg(not(feature = "power-of-two"))] pub fn get_small_f64_power(exponent: usize, radix: u32) -> f64 { - debug_assert_radix(radix); + _ = radix; get_small_f64_power10(exponent) } @@ -49,13 +47,13 @@ pub const fn get_large_int_power(_: u32) -> (&'static [Limb], u32) { /// Get pre-computed int power of 5. #[inline(always)] -pub fn get_small_int_power5(exponent: usize) -> u64 { +pub const fn get_small_int_power5(exponent: usize) -> u64 { SMALL_INT_POW5[exponent] } /// Get pre-computed int power of 10. #[inline(always)] -pub fn get_small_int_power10(exponent: usize) -> u64 { +pub const fn get_small_int_power10(exponent: usize) -> u64 { SMALL_INT_POW10[exponent] } diff --git a/lexical-parse-float/src/table_radix.rs b/lexical-parse-float/src/table_radix.rs index f554af0d..2df9a728 100644 --- a/lexical-parse-float/src/table_radix.rs +++ b/lexical-parse-float/src/table_radix.rs @@ -17,9 +17,7 @@ use static_assertions::const_assert; /// Get lookup table for 2 digit radix conversions. #[inline(always)] -pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { - debug_assert_radix(radix); - // TODO: Change this to take a format so we can validate it at compile time? +pub const fn get_small_int_power(exponent: usize, radix: u32) -> u64 { match radix { 2 => get_small_int_power2(exponent), 3 => get_small_int_power3(exponent), @@ -64,7 +62,6 @@ pub fn get_small_int_power(exponent: usize, radix: u32) -> u64 { #[inline(always)] pub fn get_small_f32_power(exponent: usize, radix: u32) -> f32 { debug_assert_radix(radix); - // TODO: Change this to take a format so we can validate it at compile time? match radix { 2 => get_small_f32_power2(exponent), 3 => get_small_f32_power3(exponent), @@ -175,7 +172,7 @@ pub const fn get_large_int_power(radix: u32) -> (&'static [Limb], u32) { /// Get pre-computed int power of 3. #[inline(always)] -pub fn get_small_int_power3(exponent: usize) -> u64 { +pub const fn get_small_int_power3(exponent: usize) -> u64 { SMALL_INT_POW3[exponent] } @@ -205,7 +202,7 @@ pub fn get_small_f64_power5(exponent: usize) -> f64 { /// Get pre-computed int power of 6. #[inline(always)] -pub fn get_small_int_power6(exponent: usize) -> u64 { +pub const fn get_small_int_power6(exponent: usize) -> u64 { SMALL_INT_POW6[exponent] } @@ -223,7 +220,7 @@ pub fn get_small_f64_power6(exponent: usize) -> f64 { /// Get pre-computed int power of 7. #[inline(always)] -pub fn get_small_int_power7(exponent: usize) -> u64 { +pub const fn get_small_int_power7(exponent: usize) -> u64 { SMALL_INT_POW7[exponent] } @@ -241,7 +238,7 @@ pub fn get_small_f64_power7(exponent: usize) -> f64 { /// Get pre-computed int power of 9. #[inline(always)] -pub fn get_small_int_power9(exponent: usize) -> u64 { +pub const fn get_small_int_power9(exponent: usize) -> u64 { SMALL_INT_POW9[exponent] } @@ -259,7 +256,7 @@ pub fn get_small_f64_power9(exponent: usize) -> f64 { /// Get pre-computed int power of 11. #[inline(always)] -pub fn get_small_int_power11(exponent: usize) -> u64 { +pub const fn get_small_int_power11(exponent: usize) -> u64 { SMALL_INT_POW11[exponent] } @@ -277,7 +274,7 @@ pub fn get_small_f64_power11(exponent: usize) -> f64 { /// Get pre-computed int power of 12. #[inline(always)] -pub fn get_small_int_power12(exponent: usize) -> u64 { +pub const fn get_small_int_power12(exponent: usize) -> u64 { SMALL_INT_POW12[exponent] } @@ -295,7 +292,7 @@ pub fn get_small_f64_power12(exponent: usize) -> f64 { /// Get pre-computed int power of 13. #[inline(always)] -pub fn get_small_int_power13(exponent: usize) -> u64 { +pub const fn get_small_int_power13(exponent: usize) -> u64 { SMALL_INT_POW13[exponent] } @@ -313,7 +310,7 @@ pub fn get_small_f64_power13(exponent: usize) -> f64 { /// Get pre-computed int power of 14. #[inline(always)] -pub fn get_small_int_power14(exponent: usize) -> u64 { +pub const fn get_small_int_power14(exponent: usize) -> u64 { SMALL_INT_POW14[exponent] } @@ -331,7 +328,7 @@ pub fn get_small_f64_power14(exponent: usize) -> f64 { /// Get pre-computed int power of 15. #[inline(always)] -pub fn get_small_int_power15(exponent: usize) -> u64 { +pub const fn get_small_int_power15(exponent: usize) -> u64 { SMALL_INT_POW15[exponent] } @@ -349,7 +346,7 @@ pub fn get_small_f64_power15(exponent: usize) -> f64 { /// Get pre-computed int power of 17. #[inline(always)] -pub fn get_small_int_power17(exponent: usize) -> u64 { +pub const fn get_small_int_power17(exponent: usize) -> u64 { SMALL_INT_POW17[exponent] } @@ -367,7 +364,7 @@ pub fn get_small_f64_power17(exponent: usize) -> f64 { /// Get pre-computed int power of 18. #[inline(always)] -pub fn get_small_int_power18(exponent: usize) -> u64 { +pub const fn get_small_int_power18(exponent: usize) -> u64 { SMALL_INT_POW18[exponent] } @@ -385,7 +382,7 @@ pub fn get_small_f64_power18(exponent: usize) -> f64 { /// Get pre-computed int power of 19. #[inline(always)] -pub fn get_small_int_power19(exponent: usize) -> u64 { +pub const fn get_small_int_power19(exponent: usize) -> u64 { SMALL_INT_POW19[exponent] } @@ -403,7 +400,7 @@ pub fn get_small_f64_power19(exponent: usize) -> f64 { /// Get pre-computed int power of 20. #[inline(always)] -pub fn get_small_int_power20(exponent: usize) -> u64 { +pub const fn get_small_int_power20(exponent: usize) -> u64 { SMALL_INT_POW20[exponent] } @@ -421,7 +418,7 @@ pub fn get_small_f64_power20(exponent: usize) -> f64 { /// Get pre-computed int power of 21. #[inline(always)] -pub fn get_small_int_power21(exponent: usize) -> u64 { +pub const fn get_small_int_power21(exponent: usize) -> u64 { SMALL_INT_POW21[exponent] } @@ -439,7 +436,7 @@ pub fn get_small_f64_power21(exponent: usize) -> f64 { /// Get pre-computed int power of 22. #[inline(always)] -pub fn get_small_int_power22(exponent: usize) -> u64 { +pub const fn get_small_int_power22(exponent: usize) -> u64 { SMALL_INT_POW22[exponent] } @@ -457,7 +454,7 @@ pub fn get_small_f64_power22(exponent: usize) -> f64 { /// Get pre-computed int power of 23. #[inline(always)] -pub fn get_small_int_power23(exponent: usize) -> u64 { +pub const fn get_small_int_power23(exponent: usize) -> u64 { SMALL_INT_POW23[exponent] } @@ -475,7 +472,7 @@ pub fn get_small_f64_power23(exponent: usize) -> f64 { /// Get pre-computed int power of 24. #[inline(always)] -pub fn get_small_int_power24(exponent: usize) -> u64 { +pub const fn get_small_int_power24(exponent: usize) -> u64 { SMALL_INT_POW24[exponent] } @@ -493,7 +490,7 @@ pub fn get_small_f64_power24(exponent: usize) -> f64 { /// Get pre-computed int power of 25. #[inline(always)] -pub fn get_small_int_power25(exponent: usize) -> u64 { +pub const fn get_small_int_power25(exponent: usize) -> u64 { SMALL_INT_POW25[exponent] } @@ -511,7 +508,7 @@ pub fn get_small_f64_power25(exponent: usize) -> f64 { /// Get pre-computed int power of 26. #[inline(always)] -pub fn get_small_int_power26(exponent: usize) -> u64 { +pub const fn get_small_int_power26(exponent: usize) -> u64 { SMALL_INT_POW26[exponent] } @@ -529,7 +526,7 @@ pub fn get_small_f64_power26(exponent: usize) -> f64 { /// Get pre-computed int power of 27. #[inline(always)] -pub fn get_small_int_power27(exponent: usize) -> u64 { +pub const fn get_small_int_power27(exponent: usize) -> u64 { SMALL_INT_POW27[exponent] } @@ -547,7 +544,7 @@ pub fn get_small_f64_power27(exponent: usize) -> f64 { /// Get pre-computed int power of 28. #[inline(always)] -pub fn get_small_int_power28(exponent: usize) -> u64 { +pub const fn get_small_int_power28(exponent: usize) -> u64 { SMALL_INT_POW28[exponent] } @@ -565,7 +562,7 @@ pub fn get_small_f64_power28(exponent: usize) -> f64 { /// Get pre-computed int power of 29. #[inline(always)] -pub fn get_small_int_power29(exponent: usize) -> u64 { +pub const fn get_small_int_power29(exponent: usize) -> u64 { SMALL_INT_POW29[exponent] } @@ -583,7 +580,7 @@ pub fn get_small_f64_power29(exponent: usize) -> f64 { /// Get pre-computed int power of 30. #[inline(always)] -pub fn get_small_int_power30(exponent: usize) -> u64 { +pub const fn get_small_int_power30(exponent: usize) -> u64 { SMALL_INT_POW30[exponent] } @@ -601,7 +598,7 @@ pub fn get_small_f64_power30(exponent: usize) -> f64 { /// Get pre-computed int power of 31. #[inline(always)] -pub fn get_small_int_power31(exponent: usize) -> u64 { +pub const fn get_small_int_power31(exponent: usize) -> u64 { SMALL_INT_POW31[exponent] } @@ -619,7 +616,7 @@ pub fn get_small_f64_power31(exponent: usize) -> f64 { /// Get pre-computed int power of 33. #[inline(always)] -pub fn get_small_int_power33(exponent: usize) -> u64 { +pub const fn get_small_int_power33(exponent: usize) -> u64 { SMALL_INT_POW33[exponent] } @@ -637,7 +634,7 @@ pub fn get_small_f64_power33(exponent: usize) -> f64 { /// Get pre-computed int power of 34. #[inline(always)] -pub fn get_small_int_power34(exponent: usize) -> u64 { +pub const fn get_small_int_power34(exponent: usize) -> u64 { SMALL_INT_POW34[exponent] } @@ -655,7 +652,7 @@ pub fn get_small_f64_power34(exponent: usize) -> f64 { /// Get pre-computed int power of 35. #[inline(always)] -pub fn get_small_int_power35(exponent: usize) -> u64 { +pub const fn get_small_int_power35(exponent: usize) -> u64 { SMALL_INT_POW35[exponent] } @@ -673,7 +670,7 @@ pub fn get_small_f64_power35(exponent: usize) -> f64 { /// Get pre-computed int power of 36. #[inline(always)] -pub fn get_small_int_power36(exponent: usize) -> u64 { +pub const fn get_small_int_power36(exponent: usize) -> u64 { SMALL_INT_POW36[exponent] } diff --git a/lexical-parse-integer/src/shared.rs b/lexical-parse-integer/src/shared.rs index 744bb3ec..ca5f74fe 100644 --- a/lexical-parse-integer/src/shared.rs +++ b/lexical-parse-integer/src/shared.rs @@ -283,7 +283,7 @@ macro_rules! algorithm { c.to_ascii_lowercase() == base_prefix.to_ascii_lowercase() }; if is_prefix { - // SAFETY: safe since we `byte.len() >= 1`. + // SAFETY: safe since we `byte.len() >= 1` (we got an item from peek). unsafe { iter.step_unchecked() }; if iter.is_done() { return into_error!(Empty, iter.cursor()); diff --git a/lexical-write-float/src/compact.rs b/lexical-write-float/src/compact.rs index 759effe4..82cf71ad 100644 --- a/lexical-write-float/src/compact.rs +++ b/lexical-write-float/src/compact.rs @@ -65,8 +65,7 @@ pub unsafe fn write_float( // Write our mantissa digits to a temporary buffer. let mut digits: [u8; 32] = [0u8; 32]; let (digit_count, kappa, carried) = if float == F::ZERO { - // SAFETY: safe since `digits.len() == 32`. - unsafe { index_unchecked_mut!(digits[0]) = b'0' }; + digits[0] = b'0'; (1, 0, false) } else { // SAFETY: safe since `digits.len()` is large enough to always hold From 7f634f9aecdc691a717a1e7ce68a7385185e4e83 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 08:00:20 -0500 Subject: [PATCH 07/11] Remove most unsafety in big int. The safety guarantees that aren't validated immediately have been removed (so only keeping the unsafe code in the hi bit extraction, which is checked right in the call), and this leads to ~3% degradation in performance in the worst cases, which is still 90% faster than core so it's well worth it. The unsafe code in the multiplication algorithms is the cause of the slowdown, but well worth it. --- lexical-parse-float/src/bigint.rs | 133 ++++++++++++++++-------------- lexical-util/src/num.rs | 12 +++ 2 files changed, 81 insertions(+), 64 deletions(-) diff --git a/lexical-parse-float/src/bigint.rs b/lexical-parse-float/src/bigint.rs index b26478db..552458cb 100644 --- a/lexical-parse-float/src/bigint.rs +++ b/lexical-parse-float/src/bigint.rs @@ -257,10 +257,11 @@ pub struct StackVec { } /// Extract the hi bits from the buffer. +/// +/// NOTE: Modifying this to remove unsafety which we statically +/// check directly in every caller leads to ~20% degradation in +/// performance. macro_rules! hi { - // # Safety - // - // Safe as long as the `stackvec.len() >= 1`. (@1 $self:ident, $rview:ident, $t:ident, $fn:ident) => {{ $fn(unsafe { index_unchecked!($rview[0]) as $t }) }}; @@ -347,7 +348,7 @@ impl StackVec { self.length = len as u16; } - /// The number of elements stored in the vector. + /// Get the number of elements stored in the vector. #[inline(always)] pub const fn len(&self) -> usize { self.length as usize @@ -510,13 +511,15 @@ impl StackVec { #[inline(always)] pub fn hi16(&self) -> (u16, bool) { let rview = self.rview(); - // SAFETY: the buffer must be at least length bytes long. - match self.len() { - 0 => (0, false), - 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi16_1), - 1 => hi!(@1 self, rview, u64, u64_to_hi16_1), - _ if LIMB_BITS == 32 => hi!(@nonzero2 self, rview, u32, u32_to_hi16_2), - _ => hi!(@nonzero2 self, rview, u64, u64_to_hi16_2), + // SAFETY: the buffer must be at least length bytes long which we check on the match. + unsafe { + match rview.len() { + 0 => (0, false), + 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi16_1), + 1 => hi!(@1 self, rview, u64, u64_to_hi16_1), + _ if LIMB_BITS == 32 => hi!(@nonzero2 self, rview, u32, u32_to_hi16_2), + _ => hi!(@nonzero2 self, rview, u64, u64_to_hi16_2), + } } } @@ -524,13 +527,15 @@ impl StackVec { #[inline(always)] pub fn hi32(&self) -> (u32, bool) { let rview = self.rview(); - // SAFETY: the buffer must be at least length bytes long. - match self.len() { - 0 => (0, false), - 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi32_1), - 1 => hi!(@1 self, rview, u64, u64_to_hi32_1), - _ if LIMB_BITS == 32 => hi!(@nonzero2 self, rview, u32, u32_to_hi32_2), - _ => hi!(@nonzero2 self, rview, u64, u64_to_hi32_2), + // SAFETY: the buffer must be at least length bytes long which we check on the match. + unsafe { + match rview.len() { + 0 => (0, false), + 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi32_1), + 1 => hi!(@1 self, rview, u64, u64_to_hi32_1), + _ if LIMB_BITS == 32 => hi!(@nonzero2 self, rview, u32, u32_to_hi32_2), + _ => hi!(@nonzero2 self, rview, u64, u64_to_hi32_2), + } } } @@ -538,15 +543,17 @@ impl StackVec { #[inline(always)] pub fn hi64(&self) -> (u64, bool) { let rview = self.rview(); - // SAFETY: the buffer must be at least length bytes long. - match self.len() { - 0 => (0, false), - 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi64_1), - 1 => hi!(@1 self, rview, u64, u64_to_hi64_1), - 2 if LIMB_BITS == 32 => hi!(@2 self, rview, u32, u32_to_hi64_2), - 2 => hi!(@2 self, rview, u64, u64_to_hi64_2), - _ if LIMB_BITS == 32 => hi!(@nonzero3 self, rview, u32, u32_to_hi64_3), - _ => hi!(@nonzero2 self, rview, u64, u64_to_hi64_2), + // SAFETY: the buffer must be at least length bytes long which we check on the match. + unsafe { + match rview.len() { + 0 => (0, false), + 1 if LIMB_BITS == 32 => hi!(@1 self, rview, u32, u32_to_hi64_1), + 1 => hi!(@1 self, rview, u64, u64_to_hi64_1), + 2 if LIMB_BITS == 32 => hi!(@2 self, rview, u32, u32_to_hi64_2), + 2 => hi!(@2 self, rview, u64, u64_to_hi64_2), + _ if LIMB_BITS == 32 => hi!(@nonzero3 self, rview, u32, u32_to_hi64_3), + _ => hi!(@nonzero2 self, rview, u64, u64_to_hi64_2), + } } } @@ -748,6 +755,18 @@ impl<'a, T: 'a> ReverseView<'a, T> { // We don't care if this wraps: the index is bounds-checked. self.inner.get(len.wrapping_sub(index + 1)) } + + /// Get the length of the inner buffer. + #[inline(always)] + pub const fn len(&self) -> usize { + self.inner.len() + } + + /// If the vector is empty. + #[inline(always)] + pub const fn is_empty(&self) -> bool { + self.inner.is_empty() + } } impl<'a, T> ops::Index for ReverseView<'a, T> { @@ -767,12 +786,12 @@ impl<'a, T> ops::Index for ReverseView<'a, T> { /// /// # Safety /// -/// Safe as long as `rindex <= x.len()`. +/// Safe as long as `rindex <= x.len()`. This is only called +/// where the type size is directly from the caller, and removing +/// it leads to a ~20% degradation in performance. #[inline(always)] pub unsafe fn nonzero(x: &[Limb], rindex: usize) -> bool { debug_assert!(rindex <= x.len()); - // TODO: Change to have no unsafe? - let len = x.len(); // SAFETY: safe if `rindex < x.len()`, since then `x.len() - rindex < x.len()`. let slc = unsafe { &index_unchecked!(x[..len - rindex]) }; @@ -959,8 +978,7 @@ pub fn pow(x: &mut StackVec, base: u32, mut exp: u32) - exp -= small_step; } if exp != 0 { - // SAFETY: safe, since `exp < small_step`. - let small_power = unsafe { f64::int_pow_fast_path(exp as usize, base) }; + let small_power = f64::int_pow_fast_path(exp as usize, base); small_mul(x, small_power as Limb)?; } Some(()) @@ -1000,9 +1018,9 @@ pub fn small_add_from( let mut index = start; let mut carry = y; while carry != 0 && index < x.len() { - // SAFETY: safe, since `index < x.len()`. - let result = scalar_add(unsafe { index_unchecked!(x[index]) }, carry); - unsafe { index_unchecked_mut!(x[index]) = result.0 }; + // NOTE: Don't need unsafety because the compiler will optimize it out. + let result = scalar_add(x[index], carry); + x[index] = result.0; carry = result.1 as Limb; index += 1; } @@ -1056,11 +1074,8 @@ pub fn large_add_from( // Iteratively add elements from y to x. let mut carry = false; for index in 0..y.len() { - // SAFETY: safe since `start + index < x.len()`. - // We panicked in `try_resize` if this wasn't true. - let xi = unsafe { &mut index_unchecked_mut!(x[start + index]) }; - // SAFETY: safe since `index < y.len()`. - let yi = unsafe { index_unchecked!(y[index]) }; + let xi = &mut x[start + index]; + let yi = y[index]; // Only one op of the two ops can overflow, since we added at max // Limb::max_value() + Limb::max_value(). Add the previous carry, @@ -1215,34 +1230,27 @@ pub fn large_quorem(x: &mut StackVec, y: &[Limb]) -> Li let mask = Limb::MAX as Wide; // Numerator is smaller the denominator, quotient always 0. - let m = x.len(); - let n = y.len(); - if m < n { + if x.len() < y.len() { return 0; } // Calculate our initial estimate for q. - // SAFETY: safe since `m > 0 && m == x.len()`, since `m > n && n > 0`. - let xm_1 = unsafe { index_unchecked!(x[m - 1]) }; - // SAFETY: safe since `n > 0 && n == y.len()`. - let yn_1 = unsafe { index_unchecked!(y[n - 1]) }; + let xm_1 = x[x.len() - 1]; + let yn_1 = y[y.len() - 1]; let mut q = xm_1 / (yn_1 + 1); // Need to calculate the remainder if we don't have a 0 quotient. if q != 0 { let mut borrow: Wide = 0; let mut carry: Wide = 0; - for j in 0..m { - // SAFETY: safe, since `j < n && n == y.len()`. - let yj = unsafe { index_unchecked!(y[j]) } as Wide; + for j in 0..x.len() { + let yj = y[j] as Wide; let p = yj * q as Wide + carry; carry = p >> LIMB_BITS; - // SAFETY: safe, since `j < m && m == x.len()`. - let xj = unsafe { index_unchecked!(x[j]) } as Wide; + let xj = x[j] as Wide; let t = xj.wrapping_sub(p & mask).wrapping_sub(borrow); borrow = (t >> LIMB_BITS) & 1; - // SAFETY: safe, since `j < m && m == x.len()`. - unsafe { index_unchecked_mut!(x[j]) = t as Limb }; + x[j] = t as Limb; } x.normalize(); } @@ -1252,17 +1260,14 @@ pub fn large_quorem(x: &mut StackVec, y: &[Limb]) -> Li q += 1; let mut borrow: Wide = 0; let mut carry: Wide = 0; - for j in 0..m { - // SAFETY: safe, since `j < n && n == y.len()`. - let yj = unsafe { index_unchecked!(y[j]) } as Wide; + for j in 0..x.len() { + let yj = y[j] as Wide; let p = yj + carry; carry = p >> LIMB_BITS; - // SAFETY: safe, since `j < m && m == x.len()`. - let xj = unsafe { index_unchecked!(x[j]) } as Wide; + let xj = x[j] as Wide; let t = xj.wrapping_sub(p & mask).wrapping_sub(borrow); borrow = (t >> LIMB_BITS) & 1; - // SAFETY: safe, since `j < m && m == x.len()`. - unsafe { index_unchecked_mut!(x[j]) = t as Limb }; + x[j] = t as Limb; } x.normalize(); } @@ -1333,12 +1338,12 @@ pub fn shl_limbs(x: &mut StackVec, n: usize) -> Option< None } else if !x.is_empty() { let len = n + x.len(); + let x_len = x.len(); + let ptr = x.as_mut_ptr(); + let src = ptr; // SAFE: since x is not empty, and `x.len() + n <= x.capacity()`. unsafe { // Move the elements. - let x_len = x.len(); - let ptr = x.as_mut_ptr(); - let src = ptr; let dst = ptr.add(n); ptr::copy(src, dst, x_len); // Write our 0s. diff --git a/lexical-util/src/num.rs b/lexical-util/src/num.rs index 91b4a140..8ffc6b84 100644 --- a/lexical-util/src/num.rs +++ b/lexical-util/src/num.rs @@ -30,6 +30,7 @@ pub trait AsPrimitive: Copy + PartialEq + PartialOrd + Send + Sync + Sized { fn as_f32(self) -> f32; fn as_f64(self) -> f64; fn from_u32(value: u32) -> Self; + fn from_u64(value: u64) -> Self; #[cfg(feature = "f16")] fn as_f16(self) -> f16; @@ -116,6 +117,11 @@ macro_rules! as_primitive { value as _ } + #[inline(always)] + fn from_u64(value: u64) -> Self { + value as _ + } + #[cfg(feature = "f16")] #[inline(always)] fn as_f16(self) -> f16 { @@ -212,6 +218,12 @@ macro_rules! half_as_primitive { Self::from_f32(value as _) } + #[inline(always)] + fn from_u64(value: u64) -> Self { + _ = value; + unimplemented!() + } + #[inline(always)] fn as_f16(self) -> f16 { f16::from_f32(self.as_f32()) From 862d5df07626427898f2ce13358fc814dba4f5b3 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 14:48:02 -0500 Subject: [PATCH 08/11] Remove length then unsafe index with get(0). Seems to improve performance on some edge case floats invoking this path by up to 30%. --- lexical-parse-float/src/bigint.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lexical-parse-float/src/bigint.rs b/lexical-parse-float/src/bigint.rs index 552458cb..0459264e 100644 --- a/lexical-parse-float/src/bigint.rs +++ b/lexical-parse-float/src/bigint.rs @@ -574,8 +574,9 @@ impl StackVec { #[inline(always)] pub fn from_u32(x: u32) -> Self { let mut vec = Self::new(); - assert!(1 <= vec.capacity()); - // SAFETY: safe since we can always add 1 item. + debug_assert!(1 <= vec.capacity()); + assert!(1 <= SIZE); + // SAFETY: safe since we can always add 1 item (validated in the asset). unsafe { vec.push_unchecked(x as Limb) }; vec.normalize(); vec @@ -585,9 +586,10 @@ impl StackVec { #[inline(always)] pub fn from_u64(x: u64) -> Self { let mut vec = Self::new(); - assert!(2 <= vec.capacity()); + debug_assert!(2 <= vec.capacity()); + assert!(2 <= SIZE); if LIMB_BITS == 32 { - // SAFETY: safe since we can always add 2 items. + // SAFETY: safe since we can always add 2 items (validated in the asset). unsafe { vec.push_unchecked(x as Limb); vec.push_unchecked((x >> 32) as Limb); @@ -693,6 +695,7 @@ impl ops::Deref for StackVec { type Target = [Limb]; #[inline(always)] fn deref(&self) -> &[Limb] { + debug_assert!(self.len() <= self.capacity()); // SAFETY: safe since `self.data[..self.len()]` must be initialized // and `self.len() <= self.capacity()`. unsafe { @@ -705,6 +708,7 @@ impl ops::Deref for StackVec { impl ops::DerefMut for StackVec { #[inline(always)] fn deref_mut(&mut self) -> &mut [Limb] { + debug_assert!(self.len() <= self.capacity()); // SAFETY: safe since `self.data[..self.len()]` must be initialized // and `self.len() <= self.capacity()`. unsafe { @@ -745,6 +749,7 @@ impl<'a, T: 'a> ReverseView<'a, T> { pub unsafe fn get_unchecked(&self, index: usize) -> &T { debug_assert!(index < self.inner.len()); let len = self.inner.len(); + // SAFETY: Safe as long as the index < length, so len - index - 1 >= 0 and <= len. unsafe { self.inner.get_unchecked(len - index - 1) } } @@ -1173,14 +1178,10 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option= 1. let mut z = StackVec::::try_from(x)?; - if !y.is_empty() { - // SAFETY: safe, since `y.len() > 0`. - let y0 = unsafe { index_unchecked!(y[0]) }; + if let Some(&y0) = y.get(0) { small_mul(&mut z, y0)?; - for index in 1..y.len() { - // SAFETY: safe, since `index < y.len()`. - let yi = unsafe { index_unchecked!(y[index]) }; + for (index, &yi) in y[1..].iter().enumerate() { if yi != 0 { let mut zi = StackVec::::try_from(x)?; small_mul(&mut zi, yi)?; @@ -1198,9 +1199,8 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option(x: &mut StackVec, y: &[Limb]) -> Option<()> { // Karatsuba multiplication never makes sense, so just use grade school // multiplication. - if y.len() == 1 { - // SAFETY: safe since `y.len() == 1`. - small_mul(x, unsafe { index_unchecked!(y[0]) })?; + if let Some(&y0) = y.get(0) { + small_mul(x, y0)?; } else { *x = long_mul(y, x)?; } From 62d3c21669126b4ceca54e547859ee7c8dfdcbec Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 17:46:51 -0500 Subject: [PATCH 09/11] Add in peak, read_if helpers and migrate to guaranteed safety variants. --- lexical-parse-float/src/bigint.rs | 4 +- lexical-parse-float/src/index.rs | 18 ------ lexical-parse-float/src/parse.rs | 103 ++++++++++++++---------------- lexical-util/src/iterator.rs | 24 +++++++ lexical-util/src/noskip.rs | 6 +- lexical-util/src/skip.rs | 6 +- 6 files changed, 82 insertions(+), 79 deletions(-) diff --git a/lexical-parse-float/src/bigint.rs b/lexical-parse-float/src/bigint.rs index 0459264e..1cd5d687 100644 --- a/lexical-parse-float/src/bigint.rs +++ b/lexical-parse-float/src/bigint.rs @@ -1178,7 +1178,7 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option= 1. let mut z = StackVec::::try_from(x)?; - if let Some(&y0) = y.get(0) { + if let Some(&y0) = y.first() { small_mul(&mut z, y0)?; for (index, &yi) in y[1..].iter().enumerate() { @@ -1199,7 +1199,7 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option(x: &mut StackVec, y: &[Limb]) -> Option<()> { // Karatsuba multiplication never makes sense, so just use grade school // multiplication. - if let Some(&y0) = y.get(0) { + if let Some(&y0) = y.first() { small_mul(x, y0)?; } else { *x = long_mul(y, x)?; diff --git a/lexical-parse-float/src/index.rs b/lexical-parse-float/src/index.rs index b0b5b152..77209d52 100644 --- a/lexical-parse-float/src/index.rs +++ b/lexical-parse-float/src/index.rs @@ -14,15 +14,6 @@ macro_rules! index_unchecked { }; } -/// Index a buffer and get a mutable reference, without bounds checking. -#[cfg(not(feature = "safe"))] -#[allow(unknown_lints, unused_macro_rules)] -macro_rules! index_unchecked_mut { - ($x:ident[$i:expr]) => { - *$x.get_unchecked_mut($i) - }; -} - /// Index a buffer, with bounds checking. #[cfg(feature = "safe")] macro_rules! index_unchecked { @@ -30,12 +21,3 @@ macro_rules! index_unchecked { $x[$i] }; } - -/// Index a buffer and get a mutable reference, with bounds checking. -#[cfg(feature = "safe")] -#[allow(unknown_lints, unused_macro_rules)] -macro_rules! index_unchecked_mut { - ($x:ident[$i:expr]) => { - $x[$i] - }; -} diff --git a/lexical-parse-float/src/parse.rs b/lexical-parse-float/src/parse.rs index 26082467..6b198924 100644 --- a/lexical-parse-float/src/parse.rs +++ b/lexical-parse-float/src/parse.rs @@ -157,39 +157,44 @@ parse_float_as_f32! { bf16 f16 } // different internally. Most of the code is reshared, so the duplicated // code is only like 30 lines. -macro_rules! parse_mantissa_sign { - ($byte:ident, $format:ident) => {{ - match $byte.integer_iter().peek() { - Some(&b'+') if !$format.no_positive_mantissa_sign() => (false, 1), - Some(&b'+') if $format.no_positive_mantissa_sign() => { - return Err(Error::InvalidPositiveSign($byte.cursor())); - }, - Some(&b'-') => (true, 1), - Some(_) if $format.required_mantissa_sign() => { - return Err(Error::MissingSign($byte.cursor())); - }, - _ => (false, 0), - } - }}; +/// Parse the sign from the buffer and advance the iterator to the next place. +#[inline(always)] +pub fn parse_mantissa_sign( + byte: &mut Bytes<'_, FORMAT>, + format: &NumberFormat, +) -> Result { + match byte.integer_iter().try_read() { + Some(&b'+') if !format.no_positive_mantissa_sign() => Ok(false), + Some(&b'+') if format.no_positive_mantissa_sign() => { + Err(Error::InvalidPositiveSign(byte.cursor() - 1)) + }, + Some(&b'-') => Ok(true), + Some(_) if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor() - 1)), + _ if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor())), + _ => Ok(false), + } } -macro_rules! parse_exponent_sign { - ($byte:ident, $format:ident) => {{ - match $byte.integer_iter().peek() { - Some(&b'+') if !$format.no_positive_exponent_sign() => (false, 1), - Some(&b'+') if $format.no_positive_exponent_sign() => { - return Err(Error::InvalidPositiveExponentSign($byte.cursor())); - }, - Some(&b'-') => (true, 1), - Some(_) if $format.required_mantissa_sign() => { - return Err(Error::MissingExponentSign($byte.cursor())); - }, - _ => (false, 0), - } - }}; +/// Parse the sign from the buffer and advance the iterator to the next place. +#[inline(always)] +pub fn parse_exponent_sign( + byte: &mut Bytes<'_, FORMAT>, + format: &NumberFormat, +) -> Result { + match byte.integer_iter().try_read() { + Some(&b'+') if !format.no_positive_exponent_sign() => Ok(false), + Some(&b'+') if format.no_positive_exponent_sign() => { + Err(Error::InvalidPositiveExponentSign(byte.cursor() - 1)) + }, + Some(&b'-') => Ok(true), + Some(_) if format.required_exponent_sign() => Err(Error::MissingSign(byte.cursor() - 1)), + _ if format.required_exponent_sign() => Err(Error::MissingExponentSign(byte.cursor())), + _ => Ok(false), + } } /// Utility to extract the result and handle any errors from parsing a `Number`. +/// TODO: Move to a function... macro_rules! parse_number { ( $format:ident, @@ -232,9 +237,7 @@ pub fn parse_complete( ) -> Result { let format = NumberFormat::<{ FORMAT }> {}; let mut byte = bytes.bytes::<{ FORMAT }>(); - let (is_negative, shift) = parse_mantissa_sign!(byte, format); - // SAFETY: safe since we shift at most one for a parsed sign byte. - unsafe { byte.step_by_unchecked(shift) }; + let is_negative = parse_mantissa_sign(&mut byte, &format)?; if byte.integer_iter().is_consumed() { return Err(Error::Empty(byte.cursor())); } @@ -269,9 +272,7 @@ pub fn fast_path_complete( ) -> Result { let format = NumberFormat::<{ FORMAT }> {}; let mut byte = bytes.bytes::<{ FORMAT }>(); - let (is_negative, shift) = parse_mantissa_sign!(byte, format); - // SAFETY: safe since we shift at most one for a parsed sign byte. - unsafe { byte.step_by_unchecked(shift) }; + let is_negative = parse_mantissa_sign(&mut byte, &format)?; if byte.integer_iter().is_consumed() { return Err(Error::Empty(byte.cursor())); } @@ -288,9 +289,7 @@ pub fn parse_partial( ) -> Result<(F, usize)> { let format = NumberFormat::<{ FORMAT }> {}; let mut byte = bytes.bytes::<{ FORMAT }>(); - let (is_negative, shift) = parse_mantissa_sign!(byte, format); - // SAFETY: safe since we shift at most one for a parsed sign byte. - unsafe { byte.step_by_unchecked(shift) }; + let is_negative = parse_mantissa_sign(&mut byte, &format)?; if byte.integer_iter().is_consumed() { return Err(Error::Empty(byte.cursor())); } @@ -325,6 +324,8 @@ pub fn parse_partial( Ok((to_native!(F, fp, is_negative), count)) } +// TODO(ahuszagh) Change to step_unchecked? + /// Parse a float using only the fast path as a partial parser. pub fn fast_path_partial( bytes: &[u8], @@ -332,9 +333,7 @@ pub fn fast_path_partial( ) -> Result<(F, usize)> { let format = NumberFormat::<{ FORMAT }> {}; let mut byte = bytes.bytes::<{ FORMAT }>(); - let (is_negative, shift) = parse_mantissa_sign!(byte, format); - // SAFETY: safe since we shift at most one for a parsed sign byte. - unsafe { byte.step_by_unchecked(shift) }; + let is_negative = parse_mantissa_sign(&mut byte, &format)?; if byte.integer_iter().is_consumed() { return Err(Error::Empty(byte.cursor())); } @@ -494,9 +493,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( let base_prefix = format.base_prefix(); let mut is_prefix = false; let mut iter = byte.integer_iter(); - if cfg!(feature = "format") && base_prefix != 0 && iter.peek() == Some(&b'0') { - // SAFETY: safe since `byte.len() >= 1`. - unsafe { iter.step_unchecked() }; + if cfg!(feature = "format") && base_prefix != 0 && iter.read_if(b'0') { // Check to see if the next character is the base prefix. // We must have a format like `0x`, `0d`, `0o`. Note: if let Some(&c) = iter.peek() { @@ -530,6 +527,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // Store the integer digits for slow-path algorithms. // SAFETY: safe, since `n_digits <= start.as_slice().len()`. + // TODO: Do we need unsafe here? debug_assert!(n_digits <= start.as_slice().len()); let integer_digits = unsafe { start.as_slice().get_unchecked(..n_digits) }; @@ -548,7 +546,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( let mut implicit_exponent: i64; let int_end = n_digits as i64; let mut fraction_digits = None; - if byte.first_is(decimal_point) { + if byte.peak_is(decimal_point) { // SAFETY: s cannot be empty due to first_is unsafe { byte.step_unchecked() }; let before = byte.clone(); @@ -587,9 +585,9 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // Handle scientific notation. let mut explicit_exponent = 0_i64; let is_exponent = if cfg!(feature = "format") && format.case_sensitive_exponent() { - byte.first_is(exponent_character) + byte.peak_is(exponent_character) } else { - byte.case_insensitive_first_is(exponent_character) + byte.case_insensitive_peek_is(exponent_character) }; if is_exponent { // Check float format syntax checks. @@ -605,12 +603,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // SAFETY: byte cannot be empty due to first_is unsafe { byte.step_unchecked() }; - let (is_negative, shift) = parse_exponent_sign!(byte, format); - // SAFETY: safe since we shift at most one for a parsed sign byte. - unsafe { byte.step_by_unchecked(shift) }; - if cfg!(feature = "format") && format.required_exponent_sign() && shift == 0 { - return Err(Error::MissingExponentSign(byte.cursor())); - } + let is_negative = parse_exponent_sign(&mut byte, &format)?; let before = byte.current_count(); parse_digits::<_, _, FORMAT>(byte.exponent_iter(), |digit| { @@ -639,9 +632,9 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( let base_suffix = format.base_suffix(); if cfg!(feature = "format") && base_suffix != 0 { let is_suffix = if cfg!(feature = "format") && format.case_sensitive_base_suffix() { - byte.first_is(base_suffix) + byte.peak_is(base_suffix) } else { - byte.case_insensitive_first_is(base_suffix) + byte.case_insensitive_peek_is(base_suffix) }; if is_suffix { // SAFETY: safe since `byte.len() >= 1`. @@ -681,7 +674,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // SAFETY: safe since zeros cannot be empty due to peek_is unsafe { zeros_integer.step_unchecked() }; } - if zeros.first_is(decimal_point) { + if zeros.peak_is(decimal_point) { // SAFETY: safe since zeros cannot be empty due to first_is unsafe { zeros.step_unchecked() }; } diff --git a/lexical-util/src/iterator.rs b/lexical-util/src/iterator.rs index 34211aca..43e72f89 100644 --- a/lexical-util/src/iterator.rs +++ b/lexical-util/src/iterator.rs @@ -87,6 +87,18 @@ pub unsafe trait BytesIter<'a>: Iterator { /// Peek the next value of the iterator, without consuming it. fn peek(&mut self) -> Option; + /// Peek the next value of the iterator, and step only if it exists. + #[inline(always)] + fn try_read(&mut self) -> Option { + if let Some(value) = self.peek() { + // SAFETY: the slice cannot be empty because we peeked a value. + unsafe { self.step_unchecked() }; + Some(value) + } else { + None + } + } + /// Check if the next element is a given value. #[inline(always)] fn peek_is(&mut self, value: u8) -> bool { @@ -97,6 +109,18 @@ pub unsafe trait BytesIter<'a>: Iterator { } } + /// Peek the next value and consume it if the read value matches the expected one. + #[inline(always)] + fn read_if(&mut self, value: u8) -> bool { + if Some(&value) == self.peek() { + // SAFETY: the slice cannot be empty because we peeked a value. + unsafe { self.step_unchecked() }; + true + } else { + false + } + } + /// Check if the next element is a given value without case sensitivity. #[inline(always)] fn case_insensitive_peek_is(&mut self, value: u8) -> bool { diff --git a/lexical-util/src/noskip.rs b/lexical-util/src/noskip.rs index 8c93557f..4f096a2c 100644 --- a/lexical-util/src/noskip.rs +++ b/lexical-util/src/noskip.rs @@ -143,9 +143,11 @@ impl<'a, const __: u128> Bytes<'a, __> { } } + // TODO: This looks like just the trait peak_is + /// Check if the next element is a given value. #[inline(always)] - pub fn first_is(&mut self, value: u8) -> bool { + pub fn peak_is(&mut self, value: u8) -> bool { if let Some(&c) = self.slc.get(self.index) { c == value } else { @@ -155,7 +157,7 @@ impl<'a, const __: u128> Bytes<'a, __> { /// Check if the next element is a given value without case sensitivity. #[inline(always)] - pub fn case_insensitive_first_is(&mut self, value: u8) -> bool { + pub fn case_insensitive_peek_is(&mut self, value: u8) -> bool { if let Some(&c) = self.slc.get(self.index) { c.to_ascii_lowercase() == value.to_ascii_lowercase() } else { diff --git a/lexical-util/src/skip.rs b/lexical-util/src/skip.rs index d663a94c..f7f1afa3 100644 --- a/lexical-util/src/skip.rs +++ b/lexical-util/src/skip.rs @@ -342,6 +342,8 @@ pub struct Bytes<'a, const FORMAT: u128> { count: usize, } +// TODO: Need a generic bytes trait tbh + impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { /// If each yielded value is adjacent in memory. pub const IS_CONTIGUOUS: bool = NumberFormat::<{ FORMAT }>::DIGIT_SEPARATOR == 0; @@ -465,7 +467,7 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { } /// Check if the next element is a given value. #[inline(always)] - pub fn first_is(&mut self, value: u8) -> bool { + pub fn peak_is(&mut self, value: u8) -> bool { // Don't assert not a digit separator, since this can occur when // a different component does not allow digit separators there. if let Some(&c) = self.slc.get(self.index) { @@ -477,7 +479,7 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { /// Check if the next element is a given value without case sensitivity. #[inline(always)] - pub fn case_insensitive_first_is(&mut self, value: u8) -> bool { + pub fn case_insensitive_peek_is(&mut self, value: u8) -> bool { // Don't assert not a digit separator, since this can occur when // a different component does not allow digit separators there. if let Some(&c) = self.slc.get(self.index) { From 8a7639c60b54bb57c44fc93627ca6882fd0b70c4 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 20:47:48 -0500 Subject: [PATCH 10/11] Remove unnecessary unsafe checks and fix a few minor format bugs. Introduce a self-contained `Buffer` trait that allows simpler comparison of values, and then remove more unsafety, especially in cases where the compiler can trivially provide the unsafety is sound. Then, also fix a minor bug with formatting where the mantissa radix negative sign check was used and not the exponent one. --- lexical-benchmark/compare.py | 1 - lexical-parse-float/src/bigint.rs | 9 ++- lexical-parse-float/src/parse.rs | 33 ++++----- lexical-parse-float/src/slow.rs | 1 + lexical-parse-integer/src/algorithm.rs | 1 + lexical-util/src/buffer.rs | 77 ++++++++++++++++++++ lexical-util/src/iterator.rs | 50 +++++++------ lexical-util/src/lib.rs | 1 + lexical-util/src/noskip.rs | 86 ++++++++++++---------- lexical-util/src/skip.rs | 99 +++++++++++++++++--------- lexical-util/tests/iterator_tests.rs | 1 + 11 files changed, 239 insertions(+), 120 deletions(-) create mode 100644 lexical-util/src/buffer.rs diff --git a/lexical-benchmark/compare.py b/lexical-benchmark/compare.py index 9d3415b0..b7cbb134 100644 --- a/lexical-benchmark/compare.py +++ b/lexical-benchmark/compare.py @@ -69,7 +69,6 @@ new = json.load(fp) # grab our comparison names, so we know where to analyze -# TODO: This is broken... this needas to only have the set of the tools group = next(iter(baseline.values())) tools = sorted({i.rsplit('_')[-1] for i in group['mean']}) header = f'| | {" | ".join(tools)} |' diff --git a/lexical-parse-float/src/bigint.rs b/lexical-parse-float/src/bigint.rs index 1cd5d687..34540322 100644 --- a/lexical-parse-float/src/bigint.rs +++ b/lexical-parse-float/src/bigint.rs @@ -1181,7 +1181,9 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option::try_from(x)?; small_mul(&mut zi, yi)?; @@ -1199,8 +1201,9 @@ pub fn long_mul(x: &[Limb], y: &[Limb]) -> Option(x: &mut StackVec, y: &[Limb]) -> Option<()> { // Karatsuba multiplication never makes sense, so just use grade school // multiplication. - if let Some(&y0) = y.first() { - small_mul(x, y0)?; + if y.len() == 1 { + // SAFETY: safe since `y.len() == 1`. + small_mul(x, unsafe { index_unchecked!(y[0]) })?; } else { *x = long_mul(y, x)?; } diff --git a/lexical-parse-float/src/parse.rs b/lexical-parse-float/src/parse.rs index 6b198924..b9a78c78 100644 --- a/lexical-parse-float/src/parse.rs +++ b/lexical-parse-float/src/parse.rs @@ -20,6 +20,7 @@ use crate::slow::slow_radix; use lexical_parse_integer::algorithm; #[cfg(feature = "f16")] use lexical_util::bf16::bf16; +use lexical_util::buffer::Buffer; use lexical_util::digit::{char_to_digit_const, char_to_valid_digit_const}; use lexical_util::error::Error; #[cfg(feature = "f16")] @@ -163,13 +164,12 @@ pub fn parse_mantissa_sign( byte: &mut Bytes<'_, FORMAT>, format: &NumberFormat, ) -> Result { - match byte.integer_iter().try_read() { + match byte.integer_iter().read_if(|c| *c == b'+' || *c == b'-') { Some(&b'+') if !format.no_positive_mantissa_sign() => Ok(false), Some(&b'+') if format.no_positive_mantissa_sign() => { Err(Error::InvalidPositiveSign(byte.cursor() - 1)) }, Some(&b'-') => Ok(true), - Some(_) if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor() - 1)), _ if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor())), _ => Ok(false), } @@ -181,13 +181,12 @@ pub fn parse_exponent_sign( byte: &mut Bytes<'_, FORMAT>, format: &NumberFormat, ) -> Result { - match byte.integer_iter().try_read() { + match byte.integer_iter().read_if(|c| *c == b'+' || *c == b'-') { Some(&b'+') if !format.no_positive_exponent_sign() => Ok(false), Some(&b'+') if format.no_positive_exponent_sign() => { Err(Error::InvalidPositiveExponentSign(byte.cursor() - 1)) }, Some(&b'-') => Ok(true), - Some(_) if format.required_exponent_sign() => Err(Error::MissingSign(byte.cursor() - 1)), _ if format.required_exponent_sign() => Err(Error::MissingExponentSign(byte.cursor())), _ => Ok(false), } @@ -324,8 +323,6 @@ pub fn parse_partial( Ok((to_native!(F, fp, is_negative), count)) } -// TODO(ahuszagh) Change to step_unchecked? - /// Parse a float using only the fast path as a partial parser. pub fn fast_path_partial( bytes: &[u8], @@ -493,7 +490,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( let base_prefix = format.base_prefix(); let mut is_prefix = false; let mut iter = byte.integer_iter(); - if cfg!(feature = "format") && base_prefix != 0 && iter.read_if(b'0') { + if cfg!(feature = "format") && base_prefix != 0 && iter.read_if(|c| *c == b'0').is_some() { // Check to see if the next character is the base prefix. // We must have a format like `0x`, `0d`, `0o`. Note: if let Some(&c) = iter.peek() { @@ -546,7 +543,7 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( let mut implicit_exponent: i64; let int_end = n_digits as i64; let mut fraction_digits = None; - if byte.peak_is(decimal_point) { + if byte.first_is(decimal_point) { // SAFETY: s cannot be empty due to first_is unsafe { byte.step_unchecked() }; let before = byte.clone(); @@ -585,9 +582,9 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // Handle scientific notation. let mut explicit_exponent = 0_i64; let is_exponent = if cfg!(feature = "format") && format.case_sensitive_exponent() { - byte.peak_is(exponent_character) + byte.first_is(exponent_character) } else { - byte.case_insensitive_peek_is(exponent_character) + byte.case_insensitive_first_is(exponent_character) }; if is_exponent { // Check float format syntax checks. @@ -631,10 +628,10 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // that the first character **is not** a digit separator. let base_suffix = format.base_suffix(); if cfg!(feature = "format") && base_suffix != 0 { - let is_suffix = if cfg!(feature = "format") && format.case_sensitive_base_suffix() { - byte.peak_is(base_suffix) + let is_suffix: bool = if cfg!(feature = "format") && format.case_sensitive_base_suffix() { + byte.first_is(base_suffix) } else { - byte.case_insensitive_peek_is(base_suffix) + byte.case_insensitive_first_is(base_suffix) }; if is_suffix { // SAFETY: safe since `byte.len() >= 1`. @@ -669,20 +666,16 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( n_digits -= step; let mut zeros = start.clone(); let mut zeros_integer = zeros.integer_iter(); - while zeros_integer.peek_is(b'0') { + while zeros_integer.read_if(|c| *c == b'0').is_some() { n_digits = n_digits.saturating_sub(1); - // SAFETY: safe since zeros cannot be empty due to peek_is - unsafe { zeros_integer.step_unchecked() }; } - if zeros.peak_is(decimal_point) { + if zeros.first_is(decimal_point) { // SAFETY: safe since zeros cannot be empty due to first_is unsafe { zeros.step_unchecked() }; } let mut zeros_fraction = zeros.fraction_iter(); - while zeros_fraction.peek_is(b'0') { + while zeros_fraction.read_if(|c| *c == b'0').is_some() { n_digits = n_digits.saturating_sub(1); - // SAFETY: safe since zeros cannot be empty due to peek_is - unsafe { zeros_fraction.step_unchecked() }; } // OVERFLOW diff --git a/lexical-parse-float/src/slow.rs b/lexical-parse-float/src/slow.rs index 5326a7a0..4ab286f7 100644 --- a/lexical-parse-float/src/slow.rs +++ b/lexical-parse-float/src/slow.rs @@ -16,6 +16,7 @@ use crate::shared; use core::cmp; #[cfg(not(feature = "compact"))] use lexical_parse_integer::algorithm; +use lexical_util::buffer::Buffer; use lexical_util::digit::char_to_valid_digit_const; #[cfg(feature = "radix")] use lexical_util::digit::digit_to_char_const; diff --git a/lexical-parse-integer/src/algorithm.rs b/lexical-parse-integer/src/algorithm.rs index ab6340b8..f4bfb9e5 100644 --- a/lexical-parse-integer/src/algorithm.rs +++ b/lexical-parse-integer/src/algorithm.rs @@ -11,6 +11,7 @@ #![cfg(not(feature = "compact"))] #![doc(hidden)] +use lexical_util::buffer::Buffer; use lexical_util::digit::char_to_digit_const; use lexical_util::format::NumberFormat; use lexical_util::iterator::{AsBytes, BytesIter}; diff --git a/lexical-util/src/buffer.rs b/lexical-util/src/buffer.rs new file mode 100644 index 00000000..342258d8 --- /dev/null +++ b/lexical-util/src/buffer.rs @@ -0,0 +1,77 @@ +//! Specialized buffer traits. +//! +//! The traits are for iterables containing bytes, and provide optimizations +//! which then can be used for contiguous or non-contiguous iterables, +//! including containers or iterators of any kind. + +#![cfg(feature = "parse")] + +/// A trait for working with iterables of bytes. +/// +/// These buffers can either be contiguous or not contiguous and provide +/// methods for reading data and accessing underlying data. The readers +/// can either be contiguous or non-contiguous, although performance and +/// some API methods may not be available for both. +pub trait Buffer<'a> { + /// Determine if the buffer is contiguous in memory. + const IS_CONTIGUOUS: bool; + + /// Get a ptr to the current start of the buffer. + fn as_ptr(&self) -> *const u8; + + /// Get a slice to the current start of the buffer. + fn as_slice(&self) -> &'a [u8]; + + /// Get if no bytes are available in the buffer. + /// + /// If this is an iterator, this is based left to be returned. + /// We do not necessarly know the length of the buffer + fn is_empty(&self) -> bool; + + /// Determine if the buffer is contiguous. + #[inline(always)] + fn is_contiguous(&self) -> bool { + Self::IS_CONTIGUOUS + } + + /// Peek the next value of the buffer, without checking bounds. + /// + /// # Safety + /// + /// Safe as long as there is at least a single valid value left in + /// the buffer. Note that the behavior of this may lead to out-of-bounds + /// access (for contiguous buffers) or panics (for non-contiguous + /// buffers). + unsafe fn first_unchecked(&self) -> &'a u8; + + /// Get the next value available without consuming it. + #[inline(always)] + fn first(&self) -> Option<&'a u8> { + if !self.is_empty() { + // SAFETY: safe since the buffer cannot be empty + unsafe { Some(self.first_unchecked()) } + } else { + None + } + } + + /// Check if the next element is a given value. + #[inline(always)] + fn first_is(&self, value: u8) -> bool { + if let Some(&c) = self.first() { + c == value + } else { + false + } + } + + /// Check if the next element is a given value without case sensitivity. + #[inline(always)] + fn case_insensitive_first_is(&self, value: u8) -> bool { + if let Some(&c) = self.first() { + c.to_ascii_lowercase() == value.to_ascii_lowercase() + } else { + false + } + } +} diff --git a/lexical-util/src/iterator.rs b/lexical-util/src/iterator.rs index 43e72f89..a1f19808 100644 --- a/lexical-util/src/iterator.rs +++ b/lexical-util/src/iterator.rs @@ -5,6 +5,8 @@ #![cfg(feature = "parse")] +pub use crate::buffer::Buffer; + // Re-export our digit iterators. #[cfg(not(feature = "format"))] pub use crate::noskip::{AsBytes, Bytes}; @@ -26,16 +28,7 @@ pub use crate::skip::{AsBytes, Bytes}; /// the methods for `read_32`, `read_64`, etc. check the bounds /// of the underlying contiguous buffer and is only called on /// contiguous buffers. -pub unsafe trait BytesIter<'a>: Iterator { - /// Determine if each yielded value is adjacent in memory. - const IS_CONTIGUOUS: bool; - - /// Get a ptr to the current start of the iterator. - fn as_ptr(&self) -> *const u8; - - /// Get a slice to the current start of the iterator. - fn as_slice(&self) -> &'a [u8]; - +pub unsafe trait BytesIter<'a>: Iterator + Buffer<'a> { /// Get the total number of elements in the underlying slice. fn length(&self) -> usize; @@ -68,14 +61,11 @@ pub unsafe trait BytesIter<'a>: Iterator { /// but weaker variant of `is_consumed()`. fn is_done(&self) -> bool; - /// Determine if the iterator is contiguous. - #[inline(always)] - fn is_contiguous(&self) -> bool { - Self::IS_CONTIGUOUS - } - /// Peek the next value of the iterator, without checking bounds. /// + /// Note that this can modify the internal state, by skipping digits + /// for iterators that find the first non-zero value, etc. + /// /// # Safety /// /// Safe as long as there is at least a single valid value left in @@ -85,7 +75,17 @@ pub unsafe trait BytesIter<'a>: Iterator { unsafe fn peek_unchecked(&mut self) -> Self::Item; /// Peek the next value of the iterator, without consuming it. - fn peek(&mut self) -> Option; + /// + /// Note that this can modify the internal state, by skipping digits + /// for iterators that find the first non-zero value, etc. + fn peek(&mut self) -> Option { + if !self.is_done() { + // SAFETY: safe since the buffer cannot be empty + unsafe { Some(self.peek_unchecked()) } + } else { + None + } + } /// Peek the next value of the iterator, and step only if it exists. #[inline(always)] @@ -111,13 +111,17 @@ pub unsafe trait BytesIter<'a>: Iterator { /// Peek the next value and consume it if the read value matches the expected one. #[inline(always)] - fn read_if(&mut self, value: u8) -> bool { - if Some(&value) == self.peek() { - // SAFETY: the slice cannot be empty because we peeked a value. - unsafe { self.step_unchecked() }; - true + fn read_if bool>(&mut self, pred: Pred) -> Option { + if let Some(peeked) = self.peek() { + if pred(peeked) { + // SAFETY: the slice cannot be empty because we peeked a value. + unsafe { self.step_unchecked() }; + Some(peeked) + } else { + None + } } else { - false + None } } diff --git a/lexical-util/src/lib.rs b/lexical-util/src/lib.rs index b51646aa..2c7db0ff 100644 --- a/lexical-util/src/lib.rs +++ b/lexical-util/src/lib.rs @@ -45,6 +45,7 @@ pub mod algorithm; pub mod ascii; pub mod assert; pub mod bf16; +pub mod buffer; pub mod constants; pub mod digit; pub mod div128; diff --git a/lexical-util/src/noskip.rs b/lexical-util/src/noskip.rs index 4f096a2c..00bb8ecd 100644 --- a/lexical-util/src/noskip.rs +++ b/lexical-util/src/noskip.rs @@ -5,7 +5,7 @@ #![cfg(all(feature = "parse", not(feature = "format")))] -use crate::iterator::BytesIter; +use crate::{buffer::Buffer, iterator::BytesIter}; use core::{mem, ptr}; // AS DIGITS @@ -37,9 +37,6 @@ pub struct Bytes<'a, const __: u128> { } impl<'a, const __: u128> Bytes<'a, __> { - /// If each yielded value is adjacent in memory. - pub const IS_CONTIGUOUS: bool = true; - /// Create new byte object. #[inline(always)] pub const fn new(slc: &'a [u8]) -> Self { @@ -49,20 +46,6 @@ impl<'a, const __: u128> Bytes<'a, __> { } } - /// Get a ptr to the current start of the iterator. - #[inline(always)] - pub fn as_ptr(&self) -> *const u8 { - self.as_slice().as_ptr() - } - - /// Get a slice to the current start of the iterator. - #[inline(always)] - pub fn as_slice(&self) -> &'a [u8] { - debug_assert!(self.index <= self.length()); - // SAFETY: safe since index must be in range. - unsafe { self.slc.get_unchecked(self.index..) } - } - /// Get the total number of elements in the underlying slice. #[inline(always)] pub fn length(&self) -> usize { @@ -143,26 +126,16 @@ impl<'a, const __: u128> Bytes<'a, __> { } } - // TODO: This looks like just the trait peak_is - /// Check if the next element is a given value. #[inline(always)] - pub fn peak_is(&mut self, value: u8) -> bool { - if let Some(&c) = self.slc.get(self.index) { - c == value - } else { - false - } + pub fn peek_is(&mut self, value: u8) -> bool { + self.first_is(value) } /// Check if the next element is a given value without case sensitivity. #[inline(always)] pub fn case_insensitive_peek_is(&mut self, value: u8) -> bool { - if let Some(&c) = self.slc.get(self.index) { - c.to_ascii_lowercase() == value.to_ascii_lowercase() - } else { - false - } + self.case_insensitive_first_is(value) } /// Get iterator over integer digits. @@ -225,6 +198,33 @@ impl<'a, const __: u128> Bytes<'a, __> { } } +impl<'a, const __: u128> Buffer<'a> for Bytes<'a, __> { + const IS_CONTIGUOUS: bool = true; + + #[inline(always)] + fn as_ptr(&self) -> *const u8 { + self.as_slice().as_ptr() + } + + #[inline(always)] + fn as_slice(&self) -> &'a [u8] { + debug_assert!(self.index <= self.length()); + // SAFETY: safe since index must be in range. + unsafe { self.slc.get_unchecked(self.index..) } + } + + #[inline(always)] + fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + + #[inline(always)] + unsafe fn first_unchecked(&self) -> &'a u8 { + // SAFETY: safe if there's at least 1 item in the buffer + unsafe { self.as_slice().get_unchecked(0) } + } +} + // DIGITS ITERATOR // --------------- @@ -234,7 +234,7 @@ pub struct BytesIterator<'a: 'b, 'b, const __: u128> { byte: &'b mut Bytes<'a, __>, } -unsafe impl<'a: 'b, 'b, const __: u128> BytesIter<'a> for BytesIterator<'a, 'b, __> { +impl<'a: 'b, 'b, const __: u128> Buffer<'a> for BytesIterator<'a, 'b, __> { const IS_CONTIGUOUS: bool = Bytes::<'a, __>::IS_CONTIGUOUS; #[inline(always)] @@ -247,6 +247,19 @@ unsafe impl<'a: 'b, 'b, const __: u128> BytesIter<'a> for BytesIterator<'a, 'b, self.byte.as_slice() } + #[inline(always)] + fn is_empty(&self) -> bool { + self.byte.is_done() + } + + #[inline(always)] + unsafe fn first_unchecked(&self) -> ::Item { + // SAFETY: safe if `self.cursor() < self.length()`. + unsafe { self.byte.slc.get_unchecked(self.byte.index) } + } +} + +unsafe impl<'a: 'b, 'b, const __: u128> BytesIter<'a> for BytesIterator<'a, 'b, __> { #[inline(always)] fn length(&self) -> usize { self.byte.length() @@ -282,17 +295,12 @@ unsafe impl<'a: 'b, 'b, const __: u128> BytesIter<'a> for BytesIterator<'a, 'b, #[inline(always)] unsafe fn peek_unchecked(&mut self) -> ::Item { // SAFETY: safe if `self.cursor() < self.length()`. - unsafe { self.byte.slc.get_unchecked(self.byte.index) } + unsafe { self.first_unchecked() } } #[inline(always)] fn peek(&mut self) -> Option<::Item> { - if self.byte.index < self.byte.slc.len() { - // SAFETY: the slice cannot be empty, so this is safe - Some(unsafe { self.peek_unchecked() }) - } else { - None - } + self.first() } #[inline(always)] diff --git a/lexical-util/src/skip.rs b/lexical-util/src/skip.rs index f7f1afa3..1a11eecc 100644 --- a/lexical-util/src/skip.rs +++ b/lexical-util/src/skip.rs @@ -37,6 +37,7 @@ #![cfg(all(feature = "format", feature = "parse"))] +use crate::buffer::Buffer; use crate::digit::char_is_digit_const; use crate::format::NumberFormat; use crate::format_flags as flags; @@ -342,12 +343,7 @@ pub struct Bytes<'a, const FORMAT: u128> { count: usize, } -// TODO: Need a generic bytes trait tbh - impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { - /// If each yielded value is adjacent in memory. - pub const IS_CONTIGUOUS: bool = NumberFormat::<{ FORMAT }>::DIGIT_SEPARATOR == 0; - /// Create new byte object. #[inline(always)] pub fn new(slc: &'a [u8]) -> Self { @@ -358,19 +354,6 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { } } - /// Get a ptr to the current start of the iterator. - #[inline(always)] - pub fn as_ptr(&self) -> *const u8 { - self.as_slice().as_ptr() - } - - /// Get a slice to the current start of the iterator. - #[inline(always)] - pub fn as_slice(&self) -> &'a [u8] { - // SAFETY: safe since index must be in range - unsafe { self.slc.get_unchecked(self.index..) } - } - /// Get the total number of elements in the underlying slice. #[inline(always)] pub fn length(&self) -> usize { @@ -465,12 +448,13 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { None } } + /// Check if the next element is a given value. #[inline(always)] - pub fn peak_is(&mut self, value: u8) -> bool { + pub fn peek_is(&mut self, value: u8) -> bool { // Don't assert not a digit separator, since this can occur when // a different component does not allow digit separators there. - if let Some(&c) = self.slc.get(self.index) { + if let Some(&c) = self.first() { c == value } else { false @@ -482,7 +466,7 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { pub fn case_insensitive_peek_is(&mut self, value: u8) -> bool { // Don't assert not a digit separator, since this can occur when // a different component does not allow digit separators there. - if let Some(&c) = self.slc.get(self.index) { + if let Some(&c) = self.first() { c.to_ascii_lowercase() == value.to_ascii_lowercase() } else { false @@ -565,6 +549,34 @@ impl<'a, const FORMAT: u128> Bytes<'a, FORMAT> { } } +impl<'a, const FORMAT: u128> Buffer<'a> for Bytes<'a, FORMAT> { + /// If each yielded value is adjacent in memory. + const IS_CONTIGUOUS: bool = NumberFormat::<{ FORMAT }>::DIGIT_SEPARATOR == 0; + + #[inline(always)] + fn as_ptr(&self) -> *const u8 { + self.as_slice().as_ptr() + } + + #[inline(always)] + fn as_slice(&self) -> &'a [u8] { + debug_assert!(self.index <= self.length()); + // SAFETY: safe since index must be in range. + unsafe { self.slc.get_unchecked(self.index..) } + } + + #[inline(always)] + fn is_empty(&self) -> bool { + self.index >= self.slc.len() + } + + #[inline(always)] + unsafe fn first_unchecked(&self) -> &'a u8 { + // SAFETY: safe if there's at least 1 item in the buffer + unsafe { self.as_slice().get_unchecked(0) } + } +} + // ITERATOR HELPERS // ---------------- @@ -634,8 +646,8 @@ macro_rules! skip_iterator_iterator_impl { }; } -/// Create base methods for the ByteIter block of a skip iterator. -macro_rules! skip_iterator_byteiter_base { +/// Create base methods for the Buffer block of a skip iterator. +macro_rules! skip_iterator_buffer_base { ($format:ident, $mask:ident) => { // It's contiguous if we don't skip over any values. // IE, the digit separator flags for the iterator over @@ -652,6 +664,22 @@ macro_rules! skip_iterator_byteiter_base { self.byte.as_slice() } + #[inline(always)] + fn is_empty(&self) -> bool { + self.byte.is_done() + } + + #[inline(always)] + unsafe fn first_unchecked(&self) -> ::Item { + // SAFETY: safe if `self.cursor() < self.length()`. + unsafe { self.byte.slc.get_unchecked(self.byte.index) } + } + }; +} + +/// Create base methods for the BytesIter block of a skip iterator. +macro_rules! skip_iterator_bytesiter_base { + () => { #[inline(always)] fn length(&self) -> usize { self.byte.length() @@ -684,11 +712,6 @@ macro_rules! skip_iterator_byteiter_base { self.byte.is_done() } - #[inline(always)] - fn is_contiguous(&self) -> bool { - Self::IS_CONTIGUOUS - } - #[inline(always)] unsafe fn peek_unchecked(&mut self) -> ::Item { self.peek().unwrap() @@ -721,10 +744,14 @@ macro_rules! skip_iterator_byteiter_base { } /// Create impl ByteIter block for skip iterator. -macro_rules! skip_iterator_byteiter_impl { +macro_rules! skip_iterator_bytesiter_impl { ($iterator:ident, $mask:ident, $i:ident, $l:ident, $t:ident, $c:ident) => { + impl<'a: 'b, 'b, const FORMAT: u128> Buffer<'a> for $iterator<'a, 'b, FORMAT> { + skip_iterator_buffer_base!(FORMAT, $mask); + } + unsafe impl<'a: 'b, 'b, const FORMAT: u128> BytesIter<'a> for $iterator<'a, 'b, FORMAT> { - skip_iterator_byteiter_base!(FORMAT, $mask); + skip_iterator_bytesiter_base!(); /// Peek the next value of the iterator, without consuming it. #[inline(always)] @@ -771,7 +798,7 @@ macro_rules! skip_iterator_byteiter_impl { skip_iterator!(IntegerBytesIterator, "Iterator that skips over digit separators in the integer."); skip_iterator_impl!(IntegerBytesIterator, mantissa_radix); skip_iterator_iterator_impl!(IntegerBytesIterator); -skip_iterator_byteiter_impl!( +skip_iterator_bytesiter_impl!( IntegerBytesIterator, INTEGER_DIGIT_SEPARATOR_FLAG_MASK, INTEGER_INTERNAL_DIGIT_SEPARATOR, @@ -786,7 +813,7 @@ skip_iterator_byteiter_impl!( skip_iterator!(FractionBytesIterator, "Iterator that skips over digit separators in the fraction."); skip_iterator_impl!(FractionBytesIterator, mantissa_radix); skip_iterator_iterator_impl!(FractionBytesIterator); -skip_iterator_byteiter_impl!( +skip_iterator_bytesiter_impl!( FractionBytesIterator, FRACTION_DIGIT_SEPARATOR_FLAG_MASK, FRACTION_INTERNAL_DIGIT_SEPARATOR, @@ -801,7 +828,7 @@ skip_iterator_byteiter_impl!( skip_iterator!(ExponentBytesIterator, "Iterator that skips over digit separators in the exponent."); skip_iterator_impl!(ExponentBytesIterator, exponent_radix); skip_iterator_iterator_impl!(ExponentBytesIterator); -skip_iterator_byteiter_impl!( +skip_iterator_bytesiter_impl!( ExponentBytesIterator, EXPONENT_DIGIT_SEPARATOR_FLAG_MASK, EXPONENT_INTERNAL_DIGIT_SEPARATOR, @@ -823,8 +850,12 @@ impl<'a: 'b, 'b, const FORMAT: u128> SpecialBytesIterator<'a, 'b, FORMAT> { is_digit_separator!(FORMAT); } +impl<'a: 'b, 'b, const FORMAT: u128> Buffer<'a> for SpecialBytesIterator<'a, 'b, FORMAT> { + skip_iterator_buffer_base!(FORMAT, SPECIAL_DIGIT_SEPARATOR); +} + unsafe impl<'a: 'b, 'b, const FORMAT: u128> BytesIter<'a> for SpecialBytesIterator<'a, 'b, FORMAT> { - skip_iterator_byteiter_base!(FORMAT, SPECIAL_DIGIT_SEPARATOR); + skip_iterator_bytesiter_base!(); /// Peek the next value of the iterator, without consuming it. #[inline(always)] diff --git a/lexical-util/tests/iterator_tests.rs b/lexical-util/tests/iterator_tests.rs index 813217f2..0c0ff22b 100644 --- a/lexical-util/tests/iterator_tests.rs +++ b/lexical-util/tests/iterator_tests.rs @@ -1,5 +1,6 @@ #![cfg(feature = "parse")] +use lexical_util::buffer::Buffer; use lexical_util::iterator::{AsBytes, Bytes, BytesIter}; #[test] From 13194a5cb2184b38e480ebcdc23bff27fab68098 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Tue, 10 Sep 2024 22:03:03 -0500 Subject: [PATCH 11/11] Minor performance tweaks. --- lexical-parse-float/src/parse.rs | 84 +++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/lexical-parse-float/src/parse.rs b/lexical-parse-float/src/parse.rs index b9a78c78..b60d13c6 100644 --- a/lexical-parse-float/src/parse.rs +++ b/lexical-parse-float/src/parse.rs @@ -158,35 +158,85 @@ parse_float_as_f32! { bf16 f16 } // different internally. Most of the code is reshared, so the duplicated // code is only like 30 lines. -/// Parse the sign from the buffer and advance the iterator to the next place. +/// Parse the sign from the leading digits. +/// +/// This routine does the following: +/// +/// 1. Parses the sign digit. +/// 2. Handles if positive signs before integers are not allowed. +/// 3. Handles negative signs if the type is unsigned. +/// 4. Handles if the sign is required, but missing. +/// 5. Handles if the iterator is empty, before or after parsing the sign. +/// 6. Handles if the iterator has invalid, leading zeros. +/// +/// Returns if the value is negative, or any values detected when +/// validating the input. #[inline(always)] pub fn parse_mantissa_sign( byte: &mut Bytes<'_, FORMAT>, format: &NumberFormat, ) -> Result { - match byte.integer_iter().read_if(|c| *c == b'+' || *c == b'-') { - Some(&b'+') if !format.no_positive_mantissa_sign() => Ok(false), + // NOTE: Using `read_if` with a predicate compiles badly and is very slow. + // Also, it's better to do the step_unchecked inside rather than get the step + // count and do `step_by_unchecked`. The compiler knows what to do better. + match byte.integer_iter().peek() { + Some(&b'+') if !format.no_positive_mantissa_sign() => { + // SAFETY: Safe, we peeked 1 byte. + unsafe { byte.step_unchecked() }; + Ok(false) + }, Some(&b'+') if format.no_positive_mantissa_sign() => { - Err(Error::InvalidPositiveSign(byte.cursor() - 1)) + Err(Error::InvalidPositiveSign(byte.cursor())) + }, + Some(&b'-') => { + // SAFETY: Safe, we peeked 1 byte. + unsafe { byte.step_unchecked() }; + Ok(true) }, - Some(&b'-') => Ok(true), + Some(_) if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor())), _ if format.required_mantissa_sign() => Err(Error::MissingSign(byte.cursor())), _ => Ok(false), } } -/// Parse the sign from the buffer and advance the iterator to the next place. +/// Parse the sign from the leading digits. +/// +/// This routine does the following: +/// +/// 1. Parses the sign digit. +/// 2. Handles if positive signs before integers are not allowed. +/// 3. Handles negative signs if the type is unsigned. +/// 4. Handles if the sign is required, but missing. +/// 5. Handles if the iterator is empty, before or after parsing the sign. +/// 6. Handles if the iterator has invalid, leading zeros. +/// +/// Returns if the value is negative, or any values detected when +/// validating the input. #[inline(always)] pub fn parse_exponent_sign( byte: &mut Bytes<'_, FORMAT>, format: &NumberFormat, ) -> Result { - match byte.integer_iter().read_if(|c| *c == b'+' || *c == b'-') { - Some(&b'+') if !format.no_positive_exponent_sign() => Ok(false), + // NOTE: Using `read_if` with a predicate compiles badly and is very slow. + // Also, it's better to do the step_unchecked inside rather than get the step + // count and do `step_by_unchecked`. The compiler knows what to do better. + match byte.integer_iter().peek() { + Some(&b'+') if !format.no_positive_exponent_sign() => { + // SAFETY: Safe, we peeked 1 byte. + unsafe { byte.step_unchecked() }; + Ok(false) + }, Some(&b'+') if format.no_positive_exponent_sign() => { - Err(Error::InvalidPositiveExponentSign(byte.cursor() - 1)) + Err(Error::InvalidPositiveExponentSign(byte.cursor())) + }, + Some(&b'-') => { + // SAFETY: Safe, we peeked 1 byte. + unsafe { byte.step_unchecked() }; + Ok(true) + }, + Some(_) if format.required_exponent_sign() => { + Err(Error::MissingExponentSign(byte.cursor())) }, - Some(&b'-') => Ok(true), _ if format.required_exponent_sign() => Err(Error::MissingExponentSign(byte.cursor())), _ => Ok(false), } @@ -487,10 +537,13 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( // INTEGER // Check to see if we have a valid base prefix. + // NOTE: `read_if` compiles poorly so use `peek` and then `step_unchecked`. let base_prefix = format.base_prefix(); let mut is_prefix = false; let mut iter = byte.integer_iter(); - if cfg!(feature = "format") && base_prefix != 0 && iter.read_if(|c| *c == b'0').is_some() { + if cfg!(feature = "format") && base_prefix != 0 && iter.peek() == Some(&b'0') { + // SAFETY: safe since `byte.len() >= 1`. + unsafe { iter.step_unchecked() }; // Check to see if the next character is the base prefix. // We must have a format like `0x`, `0d`, `0o`. Note: if let Some(&c) = iter.peek() { @@ -663,19 +716,24 @@ pub fn parse_partial_number<'a, const FORMAT: u128>( } // Check for leading zeros, and to see if we had a false overflow. + // NOTE: Once again, `read_if` is slow: do peek and step n_digits -= step; let mut zeros = start.clone(); let mut zeros_integer = zeros.integer_iter(); - while zeros_integer.read_if(|c| *c == b'0').is_some() { + while zeros_integer.peek_is(b'0') { n_digits = n_digits.saturating_sub(1); + // SAFETY: safe since zeros cannot be empty due to peek_is + unsafe { zeros_integer.step_unchecked() }; } if zeros.first_is(decimal_point) { // SAFETY: safe since zeros cannot be empty due to first_is unsafe { zeros.step_unchecked() }; } let mut zeros_fraction = zeros.fraction_iter(); - while zeros_fraction.read_if(|c| *c == b'0').is_some() { + while zeros_fraction.peek_is(b'0') { n_digits = n_digits.saturating_sub(1); + // SAFETY: safe since zeros cannot be empty due to peek_is + unsafe { zeros_fraction.step_unchecked() }; } // OVERFLOW