LLVM's APFloat
(aka llvm::APFloat
) software floating-point (or "softfloat")
library was first ported to Rust (and named rustc_apfloat
) back in 2017,
in the Rust pull request rust-lang/rust#43554
,
as part of an effort to expand Rust compile-time capabilities without sacrificing
determinism (and therefore soundness, if the type-system was involved).
Note: while using the original C++ llvm::APFloat
directly would've been an option,
certain high-level API design differences made in the Rust port, without behavioral impact
(C++ raw pointers and dynamic allocations vs Rust generics, traits and #![no_std]
),
made the Rust port more appealing from a determinism standpoint (mostly thanks to
lacking all 3 of: unsafe
code, host floating-point use, std
access - and only
allocating to handle the arbitrary precision needed for conversions to/from decimal),
even though there was a chance it had correctness issues unique to it.
However, that port had a fatal flaw: it was added to the rust-lang/rust
repository
without its unique licensing status (as a port of a C++ library with its own license)
being properly tracked, communicated, taken into account, etc.
The end result was years of limbo, mostly chronicled in the Rust issue
rust-lang/rust#55993
, in which
the in-tree port couldn't really receive proper updated or even maintenance, due
due to its unclear status.
This repository (rust-lang/rustc_apfloat
) is the result of a 2022 plan on
the relevant Zulip topic, fully put into motion during 2023:
- the
git
history of the in-treecompiler/rustc_apfloat
library was extracted
(see the separaterustc_apfloat-git-history-extraction
repository for more details) - only commits that were both necessary and had clear copyright status, were kept
- any missing functionality or bug fixes, would have to be either be re-contributed,
or rebuilt from the ground up (mostly the latter ended up being done, see below)
Most changes since the original port had been aesthetic (e.g. spell-checking, rustfmt
),
so little was lost in the process.
Starting from that much smaller "trusted" base:
- everything could use LLVM's new (since 2019) license, "
Apache-2.0 WITH LLVM-exception
"
(see the "Licensing" section below and/or LICENSE-DETAILS.md for more details) - new facilities were built (benchmarks, and a fuzzer comparing Rust/C++/hardware)
- excessive testing was performed (via a combination of fuzzing and bruteforce search)
- latent bugs were discovered (e.g. LLVM issues #63895 and #63938)
- the port has been forwarded in time, to include upstream (
llvm/llvm-project
) changes
tollvm::APFloat
over the years (since 2017), removing the need for selective backports
As this is, for the time being, a "living port", tracking upstream (llvm/llvm-project
)
llvm::APFloat
changes, the rustc_apfloat
crate will have versions of the form:
0.X.Y+llvm-ZZZZZZZZZZZZ
X
is always bumped after semver-incompatible API changes,
or when updating the upstream (llvm/llvm-project
) commit the port is based onY
is only bumped when other parts of the version don't need to be (e.g. for bug fixes)+llvm-ZZZZZZZZZZZZ
is "version metadata" (which Cargo itself ignores),
andZZZZZZZZZZZZ
always holds the first 12 hexadecimal digits of
the upstream (llvm/llvm-project
)git
commit hash the port is based on
rustc_apfloat
contains ports of all the tests from the C++ llvm::APFloat
code,
in tests/ieee.rs
and tests/ppc.rs
.
For tests specific to rustc_apfloat
(without C++ equivalents), tests/downstream.rs
is used (which mainly contains tests for now-fixed bugs, found by fuzzing).
As llvm::APFloat
tests are far from comprehensive, the only option for in-depth
comparisons between the original C++ code and the Rust port (and between them and
hardware floating-point behavior) is to employ fuzzing.
The fuzzing infrastructure lives in fuzz/
and requires cargo-afl
, but also
involves an automated build of the original C++ llvm::APFloat
code with clang
(to be able to instrument it via LLVM, in the same way cargo-afl
does for the
Rust code), and has been prototyped and tested on Linux (and is unlikely to work
on other platforms, or even some Linux distros, though it mostly assumes UNIX).
Example usage:
(TODO: maybe move this to fuzz/README.md
and/or expand on it)
# Install `cargo-afl` (used below to build/run the fuzzing binary).
cargo install cargo-afl
# Build the fuzzing binary (`target/release/rustc_apfloat-fuzz`).
cargo afl build -p rustc_apfloat-fuzz --release
# Seed the inputs for a run `foo` (while not ideal, even this one minimal input works).
mkdir fuzz/in-foo && echo > fuzz/in-foo/empty
# Start the fuzzing run `foo`, which should bring up the AFL++ progress TUI
# (see also `cargo run -p rustc_apfloat-fuzz` for extra flags available).
cargo afl fuzz -i fuzz/in-foo -o fuzz/out-foo target/release/rustc_apfloat-fuzz
To visualize the fuzzing testcases, you can use the decode
subcommand:
cargo run -p rustc_apfloat-fuzz decode fuzz/out-foo/default/crashes/*
(this will work even while cargo afl fuzz
, i.e. AFL++, is running)
This project is licensed under the Apache 2 with LLVM exception license. For a more complete discussion of this project's licensing, see LICENSE-DETAILS.md.