A tool for refactoring LLVM libc math functions into header-only implementations.
When working on LLVM's libc, math functions live in .cpp files under libc/src/math/generic/. The goal of this tool is to move those implementations into header-only support files under libc/src/__support/math/, generate shared wrappers, and update all the build system files (CMake and Bazel) accordingly.
In short, given a function name like fabsf, the tool will:
- Extract the implementation from
libc/src/math/generic/fabsf.cppintolibc/src/__support/math/fabsf.h - Rewrite the original
.cppto just call the new header-only version - Create a shared wrapper at
libc/shared/math/fabsf.h - Add the include to
libc/shared/math.h - Update
libc/src/__support/math/CMakeLists.txtwith the new header library target - Update
libc/src/math/generic/CMakeLists.txtto depend on the new support target - Update the Bazel
BUILD.bazelfile with the support library and math function deps - Add a test case to
libc/test/shared/shared_math_test.cpp
cargo build --releaseThe binary ends up at target/release/refactinator.
cargo run -- --func <function_name> --root <path_to_llvm_project>For example:
cargo run -- --func fabsf --root ~/llvm-projectIf you omit --root, it defaults to the current directory.
Some functions can't be constexpr (e.g., they touch errno or use runtime-only intrinsics). Pass -n or --no-constexpr to skip adding the constexpr qualifier to the extracted functions:
cargo run -- --func lgammaf --root ~/llvm-project -n| Flag | Short | Description |
|---|---|---|
--func |
-f |
Name of the math function to refactor (required) |
--root |
-r |
Path to the LLVM project root (default: .) |
--no-constexpr |
-n |
Don't add constexpr to the extracted implementation |
There's a refactor.sh script that handles the full workflow: syncing with upstream, creating a branch, running the tool on a list of functions, formatting, and committing.
Create a text file with one function name per line. Lines starting with # are comments, empty lines are ignored.
# trig family
sinf
cosf
tanf
./refactor.sh functions.txt [family_name]functions.txt- path to your functions filefamily_name- optional; used for the branch name and commit message. If omitted, it's inferred from the filename (e.g.,trig.txt→ familytrig)
This will:
- Sync your local
mainwithupstream/main - Create (or reuse) a branch called
refactor_<family>_family - Run the refactinator on each function in the file
- Format the code with
clang-formatandbuildifier - Commit everything
If you've already done the refactor and just want to re-run the tests:
./refactor.sh -tThis runs the shared math test suite against both GCC and LLVM builds.
Once you're happy with the refactor:
./refactor.sh functions.txt -por if you're already on the right branch and just want to publish:
./refactor.sh -p family_nameThis will:
- Format and commit any remaining changes
- Create a tracking issue on
llvm/llvm-project - Force-push your branch to your fork
- Open a pull request against
llvm/llvm-project
Requires the GitHub CLI (gh) to be installed and authenticated.
| Flag | Description |
|---|---|
-n |
Pass --no-constexpr to the refactinator |
-t / --tests |
Only run tests, skip everything else |
-p / --publish |
Commit + create GitHub issue + open PR |
The tool parses the C++ source using tree-sitter. It looks for LLVM_LIBC_FUNCTION(ret_type, name, (args)) macros and converts them into LIBC_INLINE constexpr ret_type name(args) signatures. Other helper functions in the file get LIBC_INLINE constexpr added to them as well.
The extracted code is wrapped in the appropriate include guards, license header, and namespace math {} block.
The tool checks if the original .cpp file uses float16 or float128 types and automatically:
- Adds the right macro includes (
float16-macros.h,float128.h) - Wraps the code in
#ifdef LIBC_TYPES_HAS_FLOAT16/#ifdef LIBC_TYPES_HAS_FLOAT128guards - Adds the corresponding CMake and Bazel dependencies
CMake: The tool parses the existing add_entrypoint_object block to extract dependencies, filters out the ones relevant for the support header, and creates a new add_header_library target. It also updates the original entrypoint to depend on the new support library.
Bazel: Similar logic - it creates a libc_support_library target and updates the libc_math_function entry to include it in additional_deps. It tries to match CMake dependency names to existing Bazel target names by searching the BUILD file for matching header paths.
The tool appends a test invocation to the appropriate TEST(LlvmLibcSharedMathTest, ...) block in shared_math_test.cpp. It infers the right literal types (e.g., 0.0f for float, 0.0 for double, 0x0p+0f16 for float16) based on the function name suffix.
The shell script has two paths hardcoded at the top:
LLVM_PROJECT_DIR="$HOME/Documents/llvm-project"
REFACTINATOR_DIR="$HOME/refactinator"Change these to match your setup before using the script.
- Always review the generated code before committing. The tool does its best, but C++ is C++.
- The Bazel dependency resolution is heuristic-based - it searches for header paths in the BUILD file to find target names. If a target doesn't exist yet, it falls back to a naming convention.
- Run
clang-formatafter the tool (the script does this automatically). The tool doesn't try to perfectly format the output.