|
1 | 1 | # How to contribute
|
2 | 2 |
|
3 |
| -- Pick your favorite math function from the [issue tracker]. |
4 |
| -- Look for the C implementation of the function in the [MUSL source code][src]. |
5 |
| -- Copy paste the C code into a Rust file in the `src/math` directory and adjust |
6 |
| - `src/math/mod.rs` accordingly. Also, uncomment the corresponding trait method |
7 |
| - in `src/lib.rs`. |
8 |
| -- Write some simple tests in your module (using `#[test]`) |
9 |
| -- Run `cargo test` to make sure it works. Full tests are only run when enabling |
10 |
| - features, see [Testing](#testing) below. |
11 |
| -- Send us a pull request! Make sure to run `cargo fmt` on your code before |
12 |
| - sending the PR. Also include "closes #42" in the PR description to close the |
13 |
| - corresponding issue. |
14 |
| -- :tada: |
15 |
| - |
16 |
| -[issue tracker]: https://github.com/rust-lang/libm/issues |
17 |
| -[src]: https://git.musl-libc.org/cgit/musl/tree/src/math |
18 |
| -[`src/math/truncf.rs`]: https://github.com/rust-lang/libm/blob/master/src/math/truncf.rs |
19 |
| - |
20 |
| -Check [PR #65] for an example. |
21 |
| - |
22 |
| -[PR #65]: https://github.com/rust-lang/libm/pull/65 |
| 3 | +## compiler-builtins |
| 4 | + |
| 5 | +1. From the [pending list](compiler-builtins/README.md#progress), pick one or |
| 6 | + more intrinsics. |
| 7 | +2. Port the version from [`compiler-rt`] and, if applicable, their |
| 8 | + [tests][rt-tests]. Note that this crate has generic implementations for a lot |
| 9 | + of routines, which may be usable without porting the entire implementation. |
| 10 | +3. Add a test to `builtins-test`, comparing the behavior of the ported |
| 11 | + intrinsic(s) with their implementation on the testing host. |
| 12 | +4. Add the intrinsic to `builtins-test-intrinsics/src/main.rs` to verify it can |
| 13 | + be linked on all targets. |
| 14 | +5. Send a Pull Request (PR) :tada:. |
| 15 | + |
| 16 | +[`compiler-rt`]: https://github.com/llvm/llvm-project/tree/b6820c35c59a4da3e59c11f657093ffbd79ae1db/compiler-rt/lib/builtins |
| 17 | +[rt-tests]: https://github.com/llvm/llvm-project/tree/b6820c35c59a4da3e59c11f657093ffbd79ae1db/compiler-rt/test/builtins |
| 18 | + |
| 19 | +## Porting Reminders |
| 20 | + |
| 21 | +1. [Rust][prec-rust] and [C][prec-c] have slightly different operator |
| 22 | + precedence. C evaluates comparisons (`== !=`) before bitwise operations |
| 23 | + (`& | ^`), while Rust evaluates the other way. |
| 24 | +2. C assumes wrapping operations everywhere. Rust panics on overflow when in |
| 25 | + debug mode. Consider using the [Wrapping][wrap-ty] type or the explicit |
| 26 | + [wrapping_*][wrap-fn] functions where applicable. |
| 27 | +3. Note [C implicit casts][casts], especially integer promotion. Rust is much |
| 28 | + more explicit about casting, so be sure that any cast which affects the |
| 29 | + output is ported to the Rust implementation. |
| 30 | +4. Rust has [many functions][i32] for integer or floating point manipulation in |
| 31 | + the standard library. Consider using one of these functions rather than |
| 32 | + porting a new one. |
| 33 | + |
| 34 | +[prec-rust]: https://doc.rust-lang.org/reference/expressions.html#expression-precedence |
| 35 | +[prec-c]: http://en.cppreference.com/w/c/language/operator_precedence |
| 36 | +[wrap-ty]: https://doc.rust-lang.org/core/num/struct.Wrapping.html |
| 37 | +[wrap-fn]: https://doc.rust-lang.org/std/primitive.i32.html#method.wrapping_add |
| 38 | +[casts]: http://en.cppreference.com/w/cpp/language/implicit_conversion |
| 39 | +[i32]: https://doc.rust-lang.org/std/primitive.i32.html |
23 | 40 |
|
24 | 41 | ## Tips and tricks
|
25 | 42 |
|
26 |
| -- *IMPORTANT* The code in this crate will end up being used in the `core` crate so it can **not** |
27 |
| - have any external dependencies (other than `core` itself). |
28 |
| - |
29 |
| -- Only use relative imports within the `math` directory / module, e.g. `use self::fabs::fabs` or |
30 |
| -`use super::k_cos`. Absolute imports from core are OK, e.g. `use core::u64`. |
31 |
| - |
32 |
| -- To reinterpret a float as an integer use the `to_bits` method. The MUSL code uses the |
33 |
| - `GET_FLOAT_WORD` macro, or a union, to do this operation. |
34 |
| - |
35 |
| -- To reinterpret an integer as a float use the `f32::from_bits` constructor. The MUSL code uses the |
36 |
| - `SET_FLOAT_WORD` macro, or a union, to do this operation. |
37 |
| - |
| 43 | +- _IMPORTANT_ The code in this crate will end up being used in the `core` crate |
| 44 | + so it can **not** have any external dependencies (other than a subset of |
| 45 | + `core` itself). |
| 46 | +- Only use relative imports within the `math` directory / module, e.g. |
| 47 | + `use self::fabs::fabs` or `use super::k_cos`. Absolute imports from core are |
| 48 | + OK, e.g. `use core::u64`. |
| 49 | +- To reinterpret a float as an integer use the `to_bits` method. The MUSL code |
| 50 | + uses the `GET_FLOAT_WORD` macro, or a union, to do this operation. |
| 51 | +- To reinterpret an integer as a float use the `f32::from_bits` constructor. The |
| 52 | + MUSL code uses the `SET_FLOAT_WORD` macro, or a union, to do this operation. |
38 | 53 | - You may use other methods from core like `f64::is_nan`, etc. as appropriate.
|
39 |
| - |
40 |
| -- If you're implementing one of the private double-underscore functions, take a look at the |
41 |
| - "source" name in the comment at the top for an idea for alternate naming. For example, `__sin` |
42 |
| - was renamed to `k_sin` after the FreeBSD source code naming. Do `use` these private functions in |
43 |
| - `mod.rs`. |
44 |
| - |
45 |
| -- You may encounter weird literals like `0x1p127f` in the MUSL code. These are hexadecimal floating |
46 |
| - point literals. Rust (the language) doesn't support these kind of literals. This crate provides |
47 |
| - two macros, `hf32!` and `hf64!`, which convert string literals to floats at compile time. |
| 54 | +- Rust does not have hex float literals. This crate provides two `hf16!`, |
| 55 | + `hf32!`, `hf64!`, and `hf128!` which convert string literals to floats at |
| 56 | + compile time. |
48 | 57 |
|
49 | 58 | ```rust
|
50 | 59 | assert_eq!(hf32!("0x1.ffep+8").to_bits(), 0x43fff000);
|
51 | 60 | assert_eq!(hf64!("0x1.ffep+8").to_bits(), 0x407ffe0000000000);
|
52 | 61 | ```
|
53 | 62 |
|
54 |
| -- Rust code panics on arithmetic overflows when not optimized. You may need to use the [`Wrapping`] |
55 |
| - newtype to avoid this problem, or individual methods like [`wrapping_add`]. |
| 63 | +- Rust code panics on arithmetic overflows when not optimized. You may need to |
| 64 | + use the [`Wrapping`] newtype to avoid this problem, or individual methods like |
| 65 | + [`wrapping_add`]. |
56 | 66 |
|
57 | 67 | [`Wrapping`]: https://doc.rust-lang.org/std/num/struct.Wrapping.html
|
58 | 68 | [`wrapping_add`]: https://doc.rust-lang.org/std/primitive.u32.html#method.wrapping_add
|
59 | 69 |
|
60 | 70 | ## Testing
|
61 | 71 |
|
62 |
| -Normal tests can be executed with: |
| 72 | +Testing for these crates can be somewhat complex, so feel free to rely on CI. |
| 73 | + |
| 74 | +The easiest way replicate CI testing is using Docker. This can be done by |
| 75 | +running `./ci/run-docker.sh [target]`. If no target is specified, all targets |
| 76 | +will be run. |
| 77 | + |
| 78 | +Tests can also be run without Docker: |
63 | 79 |
|
64 | 80 | ```sh
|
65 |
| -# Tests against musl require that the submodule is up to date. |
| 81 | +# Run basic tests |
| 82 | +# |
| 83 | +# --no-default-features always needs to be passed, an unfortunate limitation |
| 84 | +# since the `#![compiler_builtins]` feature is enabled by default. |
| 85 | +cargo test --workspace --no-default-features |
| 86 | + |
| 87 | +# Test with all interesting features |
| 88 | +cargo test --workspace --no-default-features \ |
| 89 | + --features arch,unstable-float,unstable-intrinsics,mem |
| 90 | + |
| 91 | +# Run with more detailed tests for libm |
| 92 | +cargo test --workspace --no-default-features \ |
| 93 | + --features arch,unstable-float,unstable-intrinsics,mem \ |
| 94 | + --features build-mpfr,build-musl \ |
| 95 | + --profile release-checked |
| 96 | +``` |
| 97 | + |
| 98 | +The multiprecision tests use the [`rug`] crate for bindings to MPFR. MPFR can be |
| 99 | +difficult to build on non-Unix systems, refer to [`gmp_mpfr_sys`] for help. |
| 100 | + |
| 101 | +`build-musl` does not build with MSVC, Wasm, or Thumb. |
| 102 | + |
| 103 | +[`rug`]: https://docs.rs/rug/latest/rug/ |
| 104 | +[`gmp_mpfr_sys`]: https://docs.rs/gmp-mpfr-sys/1.6.4/gmp_mpfr_sys/ |
| 105 | + |
| 106 | +In order to run all tests, some dependencies may be required: |
| 107 | + |
| 108 | +```sh |
| 109 | +# Allow testing compiler-builtins |
| 110 | +./ci/download-compiler-rt.sh |
| 111 | + |
| 112 | +# Optional, initialize musl for `--features build-musl` |
66 | 113 | git submodule init
|
67 | 114 | git submodule update
|
68 | 115 |
|
69 | 116 | # `--release` ables more test cases
|
70 | 117 | cargo test --release
|
71 | 118 | ```
|
72 | 119 |
|
73 |
| -If you are on a system that cannot build musl or MPFR, passing |
74 |
| -`--no-default-features` will run some limited tests. |
| 120 | +### Extensive tests |
75 | 121 |
|
76 |
| -The multiprecision tests use the [`rug`] crate for bindings to MPFR. MPFR can |
77 |
| -be difficult to build on non-Unix systems, refer to [`gmp_mpfr_sys`] for help. |
| 122 | +Libm also has tests that are exhaustive (for single-argument `f32` and 1- or 2- |
| 123 | +argument `f16`) or extensive (for all other float and argument combinations). |
| 124 | +These take quite a long time to run, but are launched in CI when relevant files |
| 125 | +are changed. |
78 | 126 |
|
79 |
| -`build-musl` does not build with MSVC, Wasm, or Thumb. |
| 127 | +Exhaustive tests can be selected by passing an environment variable: |
80 | 128 |
|
81 |
| -[`rug`]: https://docs.rs/rug/latest/rug/ |
82 |
| -[`gmp_mpfr_sys`]: https://docs.rs/gmp-mpfr-sys/1.6.4/gmp_mpfr_sys/ |
| 129 | +```sh |
| 130 | +LIBM_EXTENSIVE_TESTS=sqrt,sqrtf cargo test --features build-mpfr \ |
| 131 | + --test z_extensive \ |
| 132 | + --profile release-checked |
| 133 | + |
| 134 | +# Run all tests for one type |
| 135 | +LIBM_EXTENSIVE_TESTS=all_f16 cargo test ... |
| 136 | + |
| 137 | +# Ensure `f64` tests can run exhaustively. Estimated completion test for a |
| 138 | +# single test is 57306 years on my machine so this may be worth skipping. |
| 139 | +LIBM_EXTENSIVE_TESTS=all LIBM_EXTENSIVE_ITERATIONS=18446744073709551615 cargo test ... |
| 140 | +``` |
| 141 | + |
| 142 | +## Benchmarking |
| 143 | + |
| 144 | +Regular walltime benchmarks can be run with `cargo bench`: |
| 145 | + |
| 146 | +```sh |
| 147 | +cargo bench --no-default-features \ |
| 148 | + --features arch,unstable-float,unstable-intrinsics,mem \ |
| 149 | + --features benchmarking-reports |
| 150 | +``` |
| 151 | + |
| 152 | +There are also benchmarks that check instruction count behind the `icount` |
| 153 | +feature. These require [`iai-callgrind-runner`] (via Cargo) and [Valgrind] |
| 154 | +to be installed, which means these only run on limited platforms. |
| 155 | + |
| 156 | +Instruction count benchmarks are run as part of CI to flag performance |
| 157 | +regresions. |
| 158 | + |
| 159 | +```sh |
| 160 | +cargo bench --no-default-features \ |
| 161 | + --features arch,unstable-float,unstable-intrinsics,mem \ |
| 162 | + --features icount \ |
| 163 | + --bench icount --bench mem_icount |
| 164 | +``` |
| 165 | + |
| 166 | +[`iai-callgrind-runner`]: https://crates.io/crates/iai-callgrind-runner |
| 167 | +[Valgrind]: https://valgrind.org/ |
0 commit comments