rllvm 0.4.7

A tool to build whole-program LLVM bitcode files
Documentation
# Engineering Guidance

Repository guidance for coding agents.

## Scope

`rllvm-cc`, `rllvm-cxx`, and `rllvm-rustc` capture LLVM bitcode alongside normal
builds. `rllvm-get-bc` extracts it; `rllvm-info` inspects a module. Helpers:
`rllvm-init` and `rllvm-completions`.

`rules_rllvm` is a separate Bazel-only project that does not use these binaries.
Do not change anything here to serve it.

## Commands

```bash
cargo build
cargo test --all
cargo test parsing_lto                    # one test by name
cargo clippy --all-targets -- -D warnings  # CI gate
cargo fmt --all --check                   # CI gate
```

LLVM/Clang is required: `brew install llvm` or
`apt install llvm llvm-dev clang libclang-dev`. Rust bitcode needs compatible
LLVM readers; `rustc -vV` reports its LLVM version.

For manual wrapper checks, set `RLLVM_CONFIG` to a scratch configuration so the
run never changes `~/.rllvm/config.toml`. Put `--rllvm-verbose=3` before compiler
arguments to log subcommands. Use temporary sources and out-of-tree builds when
checking another repository, such as nghttp2.

## Contracts to preserve

Source paths in this section are relative to `src/`.

### Compiler arguments

Every compiler-owned flag reaches the real compiler, including `-c`, `-v`,
`--help`, and `--version`. Wrapper options are long-only and prefixed
`--rllvm-`. Diagnostics go to stderr. Build systems use compiler stdout for
identification, preprocessing, and queries.

`arg_parser.rs` separates compile and link arguments using the tables in
`constants.rs`. Arity controls consumption independently of the handler: a wrong
arity swallows the next argument. Flags needed in both phases, such as `-pthread`
and `-arch`, must reach secondary compilations and relinks. Recognize `-oFILE`
before filename patterns while preserving both forms of `-object-file-name`.
The fallback stays total: `is_object_file()` returns `Ok(false)` for unrecognized
arguments.

### Response files and command transport

`utils/response_file.rs` follows Clang's GNU UTF-8 response syntax. Nested paths
resolve from the process working directory; nonexistent `@` names remain literal
so linker values such as `@rpath/...` survive. Repeated references are not cycles.
Check tokenizer changes against real Clang, including quotes, escapes, BOMs, and
whitespace.

`CompilerArgsInfo::input_args()` retains original argv for the real compiler;
classification and internal consumers use expanded arguments. In particular,
save-temps ownership must inspect `expanded_args()`.

Generated commands and marker compilations must handle OS argument-size limits
through the shared transport helper. Preserve `OsStr` bytes and direct empty
arguments: GNU response files discard quoted empties, so empty argv entries stay
inline between response-file segments. Temporary files must outlive the child.

### Artifact identity and recorded paths

`arg_parser.rs` and `utils/path_utils.rs` derive C/C++ artifacts from the source,
requested output, compiler, and compilation settings. Keep separate variants
distinct even in a shared `bitcode_store_path`. Wrapper constructors and builders
must retain the actual compiler so public `args().artifact_filepaths()` queries
match the paths generated by the wrapper. The public path hash stays stable.

Each object's dedicated section records a **newline-terminated** bitcode path.
Linkers concatenate these sections; losing the separator silently corrupts
multi-file extraction. Every writer uses the same `bitcode_root` resolution.

Section names are rllvm's own: `__RLLVM,__rllvm_bc` on Mach-O, `.rllvm_bc`
elsewhere. Do not rename them to LLVM's `.llvmbc` or `.llvmcmd`: wasm-ld discards
those names. Every Mach-O writer must set `no_dead_strip`; preserve the user's
dead-stripping flags.

Embedding prefers `llvm-objcopy`. The `object`-crate rebuild can lose unmodelled
load commands, so do not remove that preference.

### Cache validity and file ownership

`cache.rs` validates hits against fresh preprocessed input, current dependency
contents, command/compiler identity, working directory, and environment. A prior
depfile alone misses newly selected headers and changed `__has_include` results.
If current inputs cannot be validated, generate uncached bitcode. This is not a
general compiler cache for side inputs absent from preprocessing/dependencies.

Only the user's original compilation may write its dependency outputs. Use
`without_dependency_flags()` for secondary compilations and markers; cache
validation uses private output and dependency files.

Publish cache entries atomically. Partial merges own a unique temporary directory.
Archive extraction builds a fresh archive beside the destination and replaces it
only after success: `llvm-ar rs` against an existing output retains stale members.
Cleanup must never remove preexisting files merely because their names match.

### LTO

Dispatch on object content, not just `-flto`: fat LTO produces a native object.
For bitcode objects, `lto.rs` and `compiler_wrapper/llvm/lto_marker.rs` record paths
through module assembly. Keep the `.ascii` newline and two-layer C/assembler
escaping. A `used` global introduces NUL termination and allocation/section-merging
problems. Fat objects need the path in both their native and bitcode halves.

Save-temps eligibility includes combined source/link invocations. Queries,
non-linking actions, and configure-only mode must not stage a linker marker.
Build markers for the requested target/language, then reset `-x` to `none` before
appending the marker object after the user's inputs. Universal builds remain
unsupported.

When the user requests linker temporaries, preserve them, including the selected
module: copy it to the retained rllvm path rather than renaming it away. Full LTO
provides a merged module; ThinLTO does not. Mixed marker/save-temps inputs require
the existing diagnostic rather than silently merging a translation unit twice.

### Rust and inspection

`compiler_wrapper/llvm/rustc_args.rs` handles both explicit `-o` and Cargo's
`--out-dir`, crate name, and extra filename. Make future bitcode paths absolute
without canonicalizing a file that rustc has not created yet. Linked crates carry
paths through a marker; archive members are patched after compilation. Never patch
a finished Rust binary: doing so invalidates its Darwin code signature.

Cargo supplies the real compiler path in `RUSTC_WRAPPER` mode. Configured rustc and
`RLLVM_REAL_RUSTC` selection apply to direct invocation. Metadata-only and
procedural-macro invocations pass through without capture.

Test `rllvm-info` against real `llvm-dis` output as well as literal fixtures.
Basic-block labels can have quoted names and trailing predecessor comments; the
entry block can be implicit. Inspecting a binary currently uses only its first
recorded module, when available; whole-program inspection uses the extracted `.bc`.

Link mode deliberately performs repeated compilations (#51). Changing that is a
separate behavior/performance task, not incidental cleanup.

## Tests and coordination

- Integration tests use the `rllvm()` helper and isolated `RLLVM_CONFIG` files.
  Never make a test depend on the developer's home configuration.
- Name tests after behavior, without a `test_` prefix. Confirm new regressions
  fail before their fix, at the layer that can actually break. Prefer native and
  extracted behavior checks over merely asserting that a file exists.
- Use separate worktrees and `fix/` or `feat/` branches for independent issue work.
  Keep each issue's change reviewable in its own PR. Order dependent work and make
  stacked PR bases explicit.
- After a parent is squash-merged, rebase only the dependent commits onto current
  `main`. Retargeting the PR alone can leave the parent's changes in its diff.
- Parallel workers use separate Cargo target directories and bounded job counts.
  Do not move a built target directory: integration binaries contain absolute
  paths to the executables they invoke.
- Performance measurements require coordinated, otherwise idle resources and
  recorded build/cache conditions. Parallel correctness runs are not benchmarks.
- Run checks appropriate to the change. Reuse valid results for unchanged code;
  do not repeat full suites without a new change or unresolved concern.
- Honor the requested stopping point. Clean up task-owned temporary files and
  merged worktrees when requested, preserving uncommitted user work.

## Conventions and documentation

- Rust edition 2024, MSRV 1.88.
- Library code returns `Result` using the `thiserror` enum in `error.rs`; avoid
  exiting or panicking. Logging uses `tracing`.
- `constants.rs` is internal. Public items in `utils/` are public API; use
  `pub(crate)` for internal helpers.
- The `docs/` directory is intentionally excluded.
- `README.md` is the user-facing source of truth. `site/build.py` generates
  `site/index.md`; do not edit or commit that generated page. Validate links and
  site generation when changing the README.

Issues, PRs, and comments use the project's voice: problem, cause, fix,
verification. Follow the repository templates and omit conversational framing.

Commits and PR titles use Conventional Commits (`fix:`, `feat:`, `docs:`, etc.).
Keep commit bodies short, and leave them empty in most cases.
Releases derive from these types; see [RELEASING.md](RELEASING.md). Below 1.0,
`feat:`/`fix:` bump the patch and `feat!:` bumps the minor. Mark breaking changes.
Do not bump `version` by hand. Pushing a tag does not trigger a release.