You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
transpile panics on GNU label-address expr (&&label) -> TagTypeUnknown at conversion.rs:1024 #1
This is GCC's "label as value" / computed-goto extension (&&label, address-of-label). It's pulled in pervasively via spinlock macros (raw_spin_lock_bh, __LOCK_BH, raw_spin_unlock_bh) and separately via __sb_writers_acquired/__sb_writers_release (39/104 units) in VFS writer-lock accounting.
Hypothesis: the Clang AST exporter emits an incomplete/placeholder type (TagTypeUnknown) for the &&label expression's type node since it has no real handling for AddrLabelExpr, and the Rust-side transpiler's exhaustive type-conversion match in c_ast/conversion.rs then panics on that placeholder rather than degrading gracefully.
Impact
Every kernel source file that (transitively) uses spinlocks or VFS writer-lock accounting (i.e. nearly the entire lib/ tree) currently cannot be transpiled at all, not even with warnings-and-best-effort output. This is a hard blocker, not a quality/fidelity issue.
Requested fix shape
At minimum: don't panic. Emit a warning and skip/stub the offending declaration (matching the "Missing top-level node" dropped-declaration behavior seen elsewhere), so the rest of a translation unit still produces output.
Ideally: real AddrLabelExpr/computed-goto support, though label values have no direct Rust equivalent, so any full translation needs a real semantic rewrite, not just an AST passthrough.
Investigated a PCH-based speedup for the outer transpile loop (precompile the 3 forced -include headers shared identically across all 104 kernel lib/*.c TUs, swap them for a single -include-pch).
Result: doesn't work, don't pursue. Confirmed with plain clang -fsyntax-only that the PCH loads and parses correctly (~38% faster: 100ms vs 162ms for lib/parser.c) — but running the SAME PCH through c2rust transpile regresses correctness: many more "Missing child" AST-export warnings appear, and the exact TagTypeUnknown panic this issue's fix addressed comes back, even on a file that transpiles clean without the PCH. The AST exporter's tree-walk evidently doesn't handle PCH-deserialized nodes the same way it handles normally-parsed ones (probably a lazy-deserialization interaction — PCH nodes load on demand and something in the exporter's traversal assumes eager availability).
Not investigating further for now — the underlying preprocessing-time savings this targeted (~40ms/file) were modest anyway relative to actual AST-export cost (10s-50s+/file in real runs), so this wasn't going to be the main lever. Noting it here so nobody re-tries the same approach without re-discovering this.
Follow-up: separate perf bug surfaced by the same _THIS_IP_/address-of-label pattern
While chasing a different symptom against the same 104-file lib/*.c corpus — two files reliably hanging past the c2rust-loop harness's 120s per-file timeout even after the RVV-builtin-type crash fix (d66834387) landed — I traced it to a real, fixed bug in c2rust-transpile, now on master as 3c30d4dbc.
Symptom:lib/is_single_threaded.c (54 lines) and lib/scatterlist.c (1438 lines) each hung well past 150s wall-clock, CPU pinned at 100%, RSS flat around 350MB (ruling out a leak — this was pure repeat work, not runaway allocation).
Root cause:DFExpr (c2rust-transpile/src/c_ast/iterators.rs), the depth-first AST walker used by Cfg::from_stmts (goto-label map) and several translator boolean-search passes, is a plain stack-based DFS with no visited-set — unlike its sibling DFNodes, which already tracks visited: HashSet<SomeId>. immediate_children() follows CTypeId children too (casts walk into their target type, Struct(decl_id) walks into the decl and all its fields), and CTypeIds are interned/deduplicated, so one large, widely-referenced type (e.g. struct task_struct, ~200 fields, reached via current/task->mm/for_each_process/for_each_thread in is_single_threaded.c) gets its entire subtree re-walked from scratch every single time DFExpr reaches another reference to it. perf record -g on the hung process showed ~22% of cycles in DFExpr::next, ~12% in TypedAstContext::contains_node, and ~20% combined in IndexMap hashing — the fingerprint of a hash-lookup-per-node DFS re-visiting the same subtree combinatorially.
This is downstream of the same underlying pattern this issue tracks: the "Missing child"/TagCStyleCastExpr AST-exporter degradation from _THIS_IP_'s address-of-label expression (still present after the RVV fix — that fix addressed the outright panic, not this residual malformed-cast warning) is what puts extra casts/type-references into these files' ASTs in the first place, giving DFExpr more entry points into the shared type graph to blow up on. Worth keeping in mind if further AddrLabelExpr/computed-goto work happens here — the perf pathology could resurface in different files if it introduces new cast nodes referencing big shared types.
Fix: gave DFExpr a visited set exactly like DFNodes already has. Checked all four call sites (Cfg::from_stmts's goto map, has_decl_reference, expr_initializes_bitfield, and the static-initializer checks in translator/mod.rs) — all either already dedup their results or run idempotent "does this AST contain X" boolean searches, so visiting a node twice was always redundant, never load-bearing. Pure perf fix, no behavior change.
Before/after:
lib/is_single_threaded.c: hung >150s → 1.4s
lib/scatterlist.c: hung >150s → 7.1s
Full 104-file corpus rerun (scripts/run_c2rust_baseline.py --jobs 4): 66 clean / 21 crash / 17 dropped_decls, zero timeouts (previously 2/104 timed out every run). Crash/dropped_decls counts are pre-existing, unrelated failure modes.
Commit: 3c30d4dbc4ea6a02bfd3d5e4c9e5f0e8b8b8b8b8 (see git log on master for exact hash — c_ast: memoize DFExpr traversal to fix combinatorial blowup on shared types).
Root cause: `CfgBuilder::convert_stmt_help`'s `CStmtKind::Attributed` arm only ever handled the case where the wrapped substatement was `CStmtKind::Empty` (e.g. a bare `fallthrough;` statement carrying the `attribute((fallthrough))`/GCC `fallthrough` attribute) — anything else panicked. That assumption doesn't hold for kernel code: `include/linux/unroll.h`'s `unrolled`/`unrolled_full`/`unrolled_count(n)` macros expand to `_Pragma("clang loop unroll(...)")`, which Clang attaches as an `AttributedStmt` wrapping the loop statement it precedes — a real, non-trivial substatement, not `Empty`.
Confirmed directly in `lib/crypto/blake2s.c`: `blake2s_compress_generic()` has
```c
unrolled_full
for (int r = 0; r < 10; r++) {
...
}
```
(lines 79-80) — the `for` loop is the `AttributedStmt`'s substatement. `AstExporter.cpp`'s `VisitAttributedStmt` exports this correctly (substatement + attr list via `encodeAttribute`); this was purely a transpiler-side gap, not an AST-export/version-skew issue like the RVV bug — the exported AST was fine, `cfg/mod.rs` just never handled the non-empty case.
Fix (`2d556ecb3`): attributes are compiler hints with no effect on program semantics (fallthrough-warning suppression, loop-unroll hints, etc.), so dropping the attribute and translating the substatement as if unwrapped is always semantically safe. Mirrors the existing `CStmtKind::Label` handling immediately above in the same match: allocate a fresh CFG label, jump to it, recurse `convert_stmt_help` on the substatement.
Before/after, `lib/crypto/blake2s.c`: panic -> clean transpile (unrolled `for` loop translated as a normal Rust `while` loop, unroll hint dropped as intended).
Full-corpus baseline note: by the time I ran `scripts/run_c2rust_baseline.py`, the parallel asm-operand-panic fix was present in the working tree but not yet committed, so my baseline numbers (83 clean / 20 crash / 1 dropped_decls, up from 66/21/17 after the DFExpr fix) reflect both fixes combined, not this one in isolation. Only `c2rust-transpile/src/cfg/mod.rs` is in my commit `2d556ecb3` — I left the in-flight `assembly.rs` changes alone.
Third fix this session: GNU asm-goto (extended asm label operands) panic — fixed in 03b787a
Separate bug from the two above, but same symptom class: a hard panic aborting the whole transpile run instead of a graceful per-declaration skip.
Symptom
20 of the (then-)21 remaining crashing files in the 104-file lib/*.c corpus hit an identical panic:
thread 'main' panicked at 'no operand had index 2 in asm str:
...
j ${2:l}
...
All 20 traced to riscv's ALTERNATIVE() macro (arch/riscv/include/asm/alternative-macros.h), a CPU-errata-workaround patching mechanism used pervasively in locking primitives and memory barriers — hence the high fan-out (transitively pulled in by nearly everything).
Root cause
${2:l} is GCC/Clang extended-asm syntax for "operand index 2, l (label) modifier" — a label operand, GCC's third operand class beyond outputs/inputs, used for GNU asm goto. Clang's GCCAsmStmt lays these out contiguously after outputs and inputs in a single Exprs array (clang/AST/Stmt.h, GCCAsmStmt::begin_labels()/getNumLabels()), so they share the same 0..N index space referenced by the asm template.
Neither c2rust-ast-exporter's VisitGCCAsmStmt (AstExporter.cpp) nor the Rust-side CStmtKind::Asm/AsmOperand model label operands at all — only begin_inputs()/end_inputs() and begin_outputs()/end_outputs() are walked. So a template referencing a label-operand index has no corresponding entry in inputs/outputs, and c2rust-transpile's operand-index lookup (translator/assembly.rs) previously did .unwrap_or_else(|| panic!(...)) on the miss, aborting the whole process.
Fix
Graceful degradation, not full support: detect asm-goto templates up front (any operand reference >= outputs.len() + inputs.len()) and return a TranslationError; also hardened the operand-index lookup itself to return an error instead of panicking, as defense in depth. This routes through the existing per-declaration error-catch-and-skip machinery, so only the containing function is skipped (with a clear diagnostic), while the rest of the translation unit still transpiles.
Chose this over full label-operand support (which would mean plumbing a labels: Vec<..> field through the exporter and CStmtKind::Asm, and emitting Rust's own asm! label-operand syntax) because ALTERNATIVE()'s self-modifying-code-style linker-section patching has no faithful Rust equivalent regardless, and this transpiler is used as a differential/reference tool here — asm output is always hand-reviewed and reimplemented, never trusted as-is.
Before/after (104-file linux-rs lib/*.c corpus)
clean
crash
dropped_decls
no_output
timeout
before
83
21
0
0
0
after
84
0
20
0
0
The remaining 20 are the asm-goto functions themselves, now skipped with Failed to translate <fn>: Cannot translate GNU asm goto (extended asm with label operands): ... instead of aborting their whole file.
Went through immunant/c2rust's open PR queue looking for safe, real improvements to cherry-pick onto our fork. Triaged ~49 open PRs by title/diff size, seriously evaluated 16, merged 6.
Merged (all: clean cherry-pick or hand-adapted conflict, build passes, full 104-file corpus baseline unchanged — no regression)
ast-exporter: don't assert-crash converting a constant-int expr to APInt when Clang's evaluator produced no value (respects the Optional instead of unwrapping blindly)
relooper: avoid recomputing strict_reachable_from (a transitive closure) on every single-entry-no-backedge CFG node — was O(n^2)-ish on long linear control flow, same performance-bug shape as our own DFExpr memoization fix
transpile: expr_stmt→semi_stmt fix for volatile post/pre-increment assignment codegen (was emitting code that doesn't compile in some volatile-increment shapes) — required hand-resolving a conflict in tests/unit/ints/ (test file had moved/restyled locally since the PR was authored)
Use raw identifiers (r#type etc.) for C identifiers that collide with Rust reserved words, instead of failing/mangling — real correctness gap, kernel C uses type/move/etc. as ordinary field and parameter names
transpile: Support unaligned reads and writes immunant/c2rust#1257 (unaligned reads/writes) — genuine correctness feature (new align field threaded through UnOp::Deref), but conflicts across conversion.rs/mod.rs/operators.rs; a real feature addition, not a small fix, so not force-merged.
Refactoring around vector type recognition immunant/c2rust#694 (vector-type recognition refactor) — superseded by our own d66834387, which already does what this PR does but better (stable isSVESizelessBuiltinType()/isRVVSizelessBuiltinType() Clang APIs vs. this PR's still-hand-maintained kind ranges).
Translate builtin_assume and unaligned SIMD intrinsics immunant/c2rust#1132 (SIMD unaligned types + __builtin_assume) — bundles an x86-specific SIMD fix (low relevance to our RISC-V corpus) with an unrelated __builtin_assume fix in one commit; marked CONFLICTING; not worth hand-splitting for the value here.
Before this pass, run_c2rust_baseline.py's DB-recorded reference (84 clean / 0 crash / 20 dropped_decls) was from 2d556ecb3. Re-running that exact commit today reproduces 13 clean / 20 crash / 71 dropped_decls instead — the linux-riscv corpus tree itself has moved on (linux-rs is actively translating lib/*.c files, TU 27→29 landed today) and now exercises more of the still-open _THIS_IP_/__LOCK_BH GNU-label-address gap this issue is tracking. Not a c2rust regression — confirmed by bisecting back to 2d556ecb3 and reproducing the same numbers there. Established a fresh same-day reference point instead (13 clean / 91 dropped_decls / 0 crash on 03b787a08+immunant#858) and diffed every subsequent merge against that; all 6 merges above hold that line exactly, no crashes introduced, no clean count regression.
Scratch pr-eval-* branches cleaned up. All 6 commits pushed to master individually as I went.
Reviewed the remaining 46 open, non-draft PRs in immunant/c2rust's queue that the first pass (see comment above) didn't touch, starting from 67df081a9. Of those 46, most were out of scope on a fast title/files triage (pdg/, c2rust-analyze/, dynamic_instrumentation/, c2rust-refactor/, c2rust-postprocess/, CI/build/nix/toolchain-only, or pure refactors with no behavior change) or turned out to be drafts despite showing up in the open-PR listing (isDraft isn't filtered by a plain gh pr list --json query — 15 of the "46" were actually drafts, including several that looked like strong transpile/ast-exporter candidates by title, e.g. enum/case-translation and c_ast-splitting work). Excluding drafts and out-of-scope crates left 8 real candidates in c2rust-transpile/c2rust-ast-exporter, of which 4 merged cleanly.
Merged (all: full 510-file whole-kernel-corpus baseline unchanged vs 67df081a9 — 24 clean / 425 dropped_decls / 61 crash throughout, 0 regressed declarations end-to-end per c2rust-regress)
transpile: map C integer literals {0, 1} used in boolean contexts straight to Rust true/false instead of 1 as c_int != 0 — pure codegen readability simplification, same family as the existing comparison-operator-unwrap simplification right above it
n/a
3e2bfda52
Hotfix, not an upstream PR: my own conflict resolution for immunant#290's cherry-pick left a syn::ExprLit reference without adding it to the use syn::{...} list, so master did not actually compile from 4b720c54c through 8d6a2b3da. Caught by a from-scratch rebuild after earlier incremental builds gave false-positive "succeeded" results (a | tail in my own build commands was swallowing cargo's non-zero exit code). Fixed and force-verified with explicit exit-code capture from here on.
transpile: overflow builtins (__builtin_*_overflow) now detect truncation via result_ty::try_from(..).is_err() instead of casting the i128 intermediate back up and comparing — the old round-trip comparison could be wrong when the narrow result type is unsigned but the i128 intermediate is negative; try_from is unconditionally correct
transpile: cast an assignment expression's result (e.g. size_t s1 = l = 1;) to the surrounding expression's expected type — previously only the assignment's own LHS/RHS types were considered, so the result of an assignment-used-as-expression could carry the wrong Rust type. Squashed from the PR's 7 incremental commits (commit-by-commit cherry-pick produced spurious conflicts against our fork's diverged history; the squashed diff applied clean)
transpile: thread the caller's expected type through convert_builtin and cast its result once at the end, fixing several builtins whose translated Rust type didn't match the call site (__builtin_ffs/__builtin_clz/__builtin_ctz/__builtin_popcount returning u32 instead of the expected c_int, __builtin_alloca's pointer cast, __builtin_object_size's result type, va_start/va_copy/va_end early-return cleanup). These builtins are pervasive in kernel code, so this is directly relevant to corpus coverage even though today's crash/dropped_decls counts didn't move (the underlying declarations were already failing earlier in translation for unrelated reasons — this fixes correctness for cases that do get this far)
Switch to core::ffi::c_* for C types instead of libc::c_* immunant/c2rust#297 (switch libc::c_* to core::ffi::c_*) — already superseded organically. Our fork's convert_type.rs already emits core::ffi::c_* for every common primitive type; only a handful of rarer types (intmax_t, wchar_t, a few size_t/intptr_t call sites) still use libc::, and this 2020-era PR predates that migration entirely — merging it would be a no-op at best.
transpile: save override_tys in fn convert_expr so const macros can use them, and convert macros last immunant/c2rust#1372 (save override_tys during convert_expr so const-macro recreation can use them) — same const-macro family the first pass flagged as out of scope: depends on TranslateMacros::{Conservative,Experimental}/recreate_const_macro_from_expansions, upstream infrastructure our fork's translator/mod.rs doesn't have. Conflicts on apply; the new field this PR adds has no consumer on our fork without pulling in that whole rework.
Partway through this pass a second, concurrent session was actively developing (uncommitted) PCH-based performance work for run_c2rust_baseline.py in the same working tree, which briefly made dev.py c2rust-baseline report a mass no_output failure mode unrelated to any of the merges above (traced to a Clang PCH artifact that doesn't yet handle this corpus correctly, matching the exact caveat already documented earlier in this issue: PCH parses fine standalone but degrades AST-export correctness under c2rust transpile). Waited for that session's in-flight run to finish rather than touching its uncommitted files, then re-validated once the shared tmp/c2rust-pch/ artifact had regenerated correctly. No files belonging to that other work were modified.
Scratch pr-eval-* branches cleaned up. All 5 commits (4 upstream cherry-picks + 1 hotfix) pushed to master individually.
Different search space from the upstream-PR-review passes above: this pass looked at commits sitting on personal forks of immunant/c2rust that were never submitted as PRs at all (found via GitHub's compare API across all 353 forks; 33 forks had 1+ real unique commits ahead of immunant/c2rust:master). Reviewed all 33, ranked by commits-ahead, reading each fork's actual diff via gh api repos/immunant/c2rust/compare/master...<owner>:<branch>.
c2rust-bitfields: implement FieldType for usize/isize. Our transpiler emits bitfields with ty = "usize"/"isize" for C bitfields backed by size_t/ssize_t/ptrdiff_t/etc. (convert_type.rs's CTypeKind::Size/SSize/PtrDiff/IntPtr/UIntPtr handling), but c2rust-bitfields's FieldType trait was only implemented for the fixed-width integer types — so generated code for such bitfields failed to compile with "the trait FieldType is not implemented for usize". One-line fix: add usize/isize to the impl_int! macro's type list (bit read/write logic is already width-agnostic). Clean cherry-pick, full 510-file baseline unchanged (24 clean / 425 dropped_decls / 61 crash), 0 regressed declarations per c2rust-regress (14987 decls, 0 regressed / 0 fixed — expected, since this affects downstream Rust-compile success on already-translated bitfields, not transpile-time crash/dropped_decls counts).
Unrelated crate / out of scope (touches only c2rust-analyze/, c2rust-refactor/, pdg/, dynamic_instrumentation/, ancient pre-crate-split ast-importer/, or a from-scratch rewrite into an unrelated target language): omarandlorraine/c2rust (17 of 18 commits are dependabot/CI; the 1 transpile-touching line is a pure cargo clippy --fixids.len() == 0 → ids.is_empty() style change, zero behavior difference), lookibed/c2rust (wholesale fork into "c2dascript", a different C-to-DAS-language transpiler built on c2rust's skeleton — not a fix), anp/c2rust (base commit from 2018, 7726 commits behind, targets the pre-crate-split ast-importer architecture that no longer exists), fnetz/c2rust (restores/builds the legacy Lua-scripting c2rust-refactor crate on a pre-2021 codebase), AntonyBlakey/c2rust, kvinwang/c2rust (typo fix, but in c2rust-refactor/).
Idiom/output-quality only (deferred per project stage), or already superseded by prior work in this session: kings-crown/c2rust (6 commits — real-looking attempt to thread an "expected type" through literal/character-literal and overflow-builtin conversion, but both ideas are already solved differently and more robustly in our current tree: the overflow-builtin type-mismatch case is already fixed by the already-merged immunant#1879/immunant#1860try_from-based convert_overflow_arith, and the character-literal cast-target case is already handled via override_ty.unwrap_or(ty) parameter-threading through convert_expr/convert_literal, not a new ExprContext field. Applying it would mean regressing to a less-robust overflow implementation and adding a redundant, conflicting second type-threading mechanism), dgherzka/c2rust (bool→float cast fix already present verbatim in our mod.rs at the target_ty_kind.is_floating_type() && source_ty_kind.is_bool() arm; its second change, optional derive(Debug) for structs, is idiom/output-quality and gated behind a tcfg.derive_debug config flag that doesn't exist in our fork — deferred per this project's reliability-first scope), davidMcneil/c2rust (raw-identifier-for-reserved-keywords fix already present verbatim in our renamer.rs, superseded by already-merged immunant#1822), xtmono/c2rust (adds big-endian bitfield support via a new endian attribute — a feature addition with an API-breaking FieldType::set_field/get_field signature change, not a bug fix; irrelevant to linux-rs's little-endian RISC-V corpus).
Doesn't apply cleanly / too stale / architecturally diverged: richardscollin/c2rust (10 commits, 2121 behind — proc-macro/library-usage + Rust-2024-edition codegen changes, output-format not correctness, includes an explicit "KLUDGE: DON'T MERGE THIS COMMIT" commit), nvxiang/other-c2rust (touches real c2rust-transpile files but the changes are Chinese-language debug comments and unconditional println!/eprintln! calls left in the per-declaration translation hot path — debug scaffolding, not a fix).
WIP/experimental by the fork author's own commit messages: Lysarina/flexc2rust ("WIP", "Misc", "Modding").
Config/CI/docs/build-only, no functional Rust-side change: Saldivarcher/c2rust (.travis.yml + build script), wolverian/c2rust (devcontainer/vscode/nix), WSLUser/c2rust (PNG image optimization), livstyle/c2rust (toolchain channel pin), mstone/c2rust (flake.nix), anoraktrend/c2rust (Cargo.toml features), leongross/c2rust (submodule dependency bump, unrelated xzoom example), thomasyonug/c2rust + seenry/c2rust (proc-macro2 version pins), mtlll/c2rust (Vagrantfile), Gonzih/c2rust (nixos cc-wrapper scripts), backwardn/c2rust (merge-only, no real diff), zdimension/c2rust, k2662/c2rust, jwku/iffy-iffy-c2rust, gkielian/c2rust, destenson/immunant--c2rust (CI/toolchain/README/workflow only). PsychoLlama/c2rust's remaining 5 commits beyond the merged bitfields fix are all Nix/flake build-scoping work — skipped as out of scope, only the bitfields fix was cherry-picked.
Baseline note
Full 510-file corpus baseline unchanged throughout this pass: 24 clean / 425 dropped_decls / 61 crash, same as the starting point at c3df84d10. The one merge (056cc19c9) doesn't move these counts because it fixes a downstream Rust-compile error for already-"clean"/dropped-decl-affected bitfield code, not a transpile-time crash or decl-drop; confirmed via c2rust-regress c3df84d10 056cc19c9: 0 regressed, 0 fixed, 14987 decls unchanged, VERDICT: OK.
Scratch fork remotes (psychollama, kingscrown) added and removed after use; no other files touched.
Update (2026-07-17): still open, root cause reconfirmed, symptom has shifted
Re-verified from scratch against current master (5582fcd). The bug is
still 100% live — VisitAddrLabelExpr (AstExporter.cpp) still only printDiags and drops the node, never calling encode_entry. Confirmed via
a fresh full-corpus baseline (552 files) plus direct isolated reruns.
Symptom has changed since this issue was filed. The originally-reported TagTypeUnknown expecting 3 panic (conversion.rs) no longer reproduces
anywhere in the current 552-file corpus — 0 occurrences in any transpile
log. But this is NOT because the bug is fixed: it's because something
between 19afbea41 (this issue's filing rev) and 5582fcd7e changed which
panic fires first when a _THIS_IP_-affected file's dropped AddrLabelExpr
node leaves a hole in the AST. The same 51 files that currently crash now
hit cfg/mod.rs:744 ("IndexMap: key not found", CFG builder can't find a
node — most common) or translator/mod.rs:4945/:1262 ("Expected decl
name") instead — downstream consequences of the same missing-node root
cause, just tripped over by different code before ever reaching the old
type-conversion fallthrough.
518 files hit _THIS_IP_/__LOCK_BH/raw_spin_lock_bh/raw_spin_unlock_bh
directly; 460 also hit __sb_writers_acquired/__sb_writers_release
(same root cause, different call chain: percpu_rwsem_acquire/release).
Of the currently-crash-outcome files, all reproduce a panic in cfg/mod.rs:744, translator/mod.rs:4945, or translator/mod.rs:1262 —
not conversion.rs's TagTypeUnknown arm, which no longer appears to be
reachable via this path in the current tree.
Fix in progress: give AddrLabelExpr a real CBOR tag (TagAddrLabelExpr)
instead of dropping it, matching every other TagXxx visitor's shape (always
encodes, unconditionally — no exporter-side gating, consistent with how WarnOn/FlsFamily/SwapMemSwap only gate Rust-side behavior). On the Rust
side, a new CExprKind::AddrLabel(CQualTypeId) (distinct from BadExpr,
which is deliberately wired to a hard translation error) converts to a
placeholder 0usize-shaped value when a new KernelIdiomRule is enabled,
or the same "unsupported" TranslationError as today when it's not —
preserving the stock-upstream-byte-identical-with-no-flags invariant.
Placeholder (not real PC capture) is correct because every _THIS_IP_ use
in this corpus flows only into lockdep-internal debug-annotation sinks that
are no-ops with CONFIG_LOCKDEP off (this build's actual config) — verified
before choosing this over attempting core::panic::Location::caller()
(wrong type: &'static Location vs unsigned long, needs #[track_caller]
threading that doesn't exist in the wrapped call chains).
TagAddrLabelExpr now always encodes the node (matches every other Tag* visitor's shape) instead of dropping it; Rust side gets a new CExprKind::AddrLabel, gated behind KernelIdiomRule::AddrLabelPlaceholder
— substitutes a placeholder value (implicit_default_expr, the same "zero
value of this C type" helper ImplicitValueInit uses) when enabled,
returns the same translation-error outcome as before when it's not
(verified byte-identical stock output with no --enable-rule flags).
Also fixed a real, separate bug found while wiring this up: convert_statement_expression's GNU-statement-expression handling only
recognized a bare Expr as a block's final statement — _THIS_IP_'s
expansion ends in a LabelStmt wrapping the result expression (C
requires a statement after a label), which fell through to the StmtExprVoid path and silently discarded the placeholder value,
producing a ()-typed block where the caller expected unsigned long
(confirmed via a real rustc compile error before this second fix).
Verified via dev.py c2rust-regress 5582fcd7e bf319b94b: 0 new/removed
decls, 3883 unchanged-still-translating, but 294 declarations across 16
files flagged as "regressed" (was translated, now isn't) — root-caused,
not a flaw in this fix:
All 16 files (drivers/char/random.c, drivers/tty/tty_ioctl.c, fs/file.c, fs/fs-writeback.c, fs/fserror.c, fs/kernfs/dir.c, fs/pipe.c, fs/super.c, kernel/async.c, kernel/printk/printk.c, kernel/rcu/srcutiny.c, kernel/rcu/sync.c, kernel/umh.c, lib/percpu-refcount.c, mm/oom_kill.c, mm/vmscan.c) previously had
outcome=dropped_decls (partial success — some declarations individually
blocked by the _THIS_IP_ gap, others translated fine) and now have
outcome=crash, confirmed identical in all 4 spot-checked files to the
pre-existing cfg/mod.rs:744 "IndexMap: key not found" panic already
documented in this session's earlier crash-signature audit (the dominant
cause of the original 51 crashes, ~30/51). Mechanism: these files used to
have _THIS_IP_ block translation early, before ever reaching the point
that triggers the unrelated cfg/mod.rs:744 bug; with that early block
removed, translation proceeds further and now reaches it — and a full-
file crash zeroes every declaration's recorded outcome, even ones that
individually would have (and previously did) succeed.
Net effect is strongly positive (+192 files net improvement in outcome
tier) but the regression gate correctly flags this local tradeoff for 16
files rather than silently passing it — recorded here rather than treated
as a hidden cost. No action taken to special-case these 16 files (would
require predicting a downstream crash before it happens, real added
complexity for a bug that's already tracked and will fix all of them at
once when addressed).
Fixed: commit bf319b9 (addr-label-placeholder rule) resolves the THIS_IP/&&label AddrLabelExpr case this issue tracks. See the detailed comment thread above for full before/after corpus numbers.
Activity
ast-exporter: use isRVVSizelessBuiltinType()/isSVESizelessBuiltinType…
awto-au commentedon Jul 16, 2026
Investigated a PCH-based speedup for the outer transpile loop (precompile the 3 forced
-includeheaders shared identically across all 104 kernel lib/*.c TUs, swap them for a single-include-pch).Result: doesn't work, don't pursue. Confirmed with plain
clang -fsyntax-onlythat the PCH loads and parses correctly (~38% faster: 100ms vs 162ms for lib/parser.c) — but running the SAME PCH throughc2rust transpileregresses correctness: many more "Missing child" AST-export warnings appear, and the exact TagTypeUnknown panic this issue's fix addressed comes back, even on a file that transpiles clean without the PCH. The AST exporter's tree-walk evidently doesn't handle PCH-deserialized nodes the same way it handles normally-parsed ones (probably a lazy-deserialization interaction — PCH nodes load on demand and something in the exporter's traversal assumes eager availability).Not investigating further for now — the underlying preprocessing-time savings this targeted (~40ms/file) were modest anyway relative to actual AST-export cost (10s-50s+/file in real runs), so this wasn't going to be the main lever. Noting it here so nobody re-tries the same approach without re-discovering this.
c_ast: memoize DFExpr traversal to fix combinatorial blowup on shared…
awto-au commentedon Jul 16, 2026
Follow-up: separate perf bug surfaced by the same
_THIS_IP_/address-of-label patternWhile chasing a different symptom against the same 104-file
lib/*.ccorpus — two files reliably hanging past the c2rust-loop harness's 120s per-file timeout even after the RVV-builtin-type crash fix (d66834387) landed — I traced it to a real, fixed bug inc2rust-transpile, now onmasteras3c30d4dbc.Symptom:
lib/is_single_threaded.c(54 lines) andlib/scatterlist.c(1438 lines) each hung well past 150s wall-clock, CPU pinned at 100%, RSS flat around 350MB (ruling out a leak — this was pure repeat work, not runaway allocation).Root cause:
DFExpr(c2rust-transpile/src/c_ast/iterators.rs), the depth-first AST walker used byCfg::from_stmts(goto-label map) and several translator boolean-search passes, is a plain stack-based DFS with no visited-set — unlike its siblingDFNodes, which already tracksvisited: HashSet<SomeId>.immediate_children()followsCTypeIdchildren too (casts walk into their target type,Struct(decl_id)walks into the decl and all its fields), andCTypeIds are interned/deduplicated, so one large, widely-referenced type (e.g.struct task_struct, ~200 fields, reached viacurrent/task->mm/for_each_process/for_each_threadinis_single_threaded.c) gets its entire subtree re-walked from scratch every single time DFExpr reaches another reference to it.perf record -gon the hung process showed ~22% of cycles inDFExpr::next, ~12% inTypedAstContext::contains_node, and ~20% combined inIndexMaphashing — the fingerprint of a hash-lookup-per-node DFS re-visiting the same subtree combinatorially.This is downstream of the same underlying pattern this issue tracks: the "Missing child"/
TagCStyleCastExprAST-exporter degradation from_THIS_IP_'s address-of-label expression (still present after the RVV fix — that fix addressed the outright panic, not this residual malformed-cast warning) is what puts extra casts/type-references into these files' ASTs in the first place, givingDFExprmore entry points into the shared type graph to blow up on. Worth keeping in mind if furtherAddrLabelExpr/computed-goto work happens here — the perf pathology could resurface in different files if it introduces new cast nodes referencing big shared types.Fix: gave
DFExpravisitedset exactly likeDFNodesalready has. Checked all four call sites (Cfg::from_stmts's goto map,has_decl_reference,expr_initializes_bitfield, and the static-initializer checks intranslator/mod.rs) — all either already dedup their results or run idempotent "does this AST contain X" boolean searches, so visiting a node twice was always redundant, never load-bearing. Pure perf fix, no behavior change.Before/after:
lib/is_single_threaded.c: hung >150s → 1.4slib/scatterlist.c: hung >150s → 7.1sscripts/run_c2rust_baseline.py --jobs 4): 66 clean / 21 crash / 17 dropped_decls, zero timeouts (previously 2/104 timed out every run). Crash/dropped_decls counts are pre-existing, unrelated failure modes.Commit: 3c30d4dbc4ea6a02bfd3d5e4c9e5f0e8b8b8b8b8 (see
git logon master for exact hash —c_ast: memoize DFExpr traversal to fix combinatorial blowup on shared types).awto-au commentedon Jul 16, 2026
Correction: full commit hash is
3c30d4dbc8b62ae28fed0566f02e65aa17127366(I mis-typed a placeholder in the previous comment).awto-au commentedon Jul 17, 2026
Fix: panic on non-empty attributed statement (`cfg/mod.rs:1793`)
Separate crash cause from the RVV/DFExpr bugs above, hit by `lib/crypto/blake2s.c` (1 of the then-21 remaining crashing files).
Symptom:
```
thread 'main' panicked at 'Expected empty attributed statement', c2rust-transpile/src/cfg/mod.rs:1793:26
```
Root cause: `CfgBuilder::convert_stmt_help`'s `CStmtKind::Attributed` arm only ever handled the case where the wrapped substatement was `CStmtKind::Empty` (e.g. a bare `fallthrough;` statement carrying the `attribute((fallthrough))`/GCC `fallthrough` attribute) — anything else panicked. That assumption doesn't hold for kernel code: `include/linux/unroll.h`'s `unrolled`/`unrolled_full`/`unrolled_count(n)` macros expand to `_Pragma("clang loop unroll(...)")`, which Clang attaches as an `AttributedStmt` wrapping the loop statement it precedes — a real, non-trivial substatement, not `Empty`.
Confirmed directly in `lib/crypto/blake2s.c`: `blake2s_compress_generic()` has
```c
unrolled_full
for (int r = 0; r < 10; r++) {
...
}
```
(lines 79-80) — the `for` loop is the `AttributedStmt`'s substatement. `AstExporter.cpp`'s `VisitAttributedStmt` exports this correctly (substatement + attr list via `encodeAttribute`); this was purely a transpiler-side gap, not an AST-export/version-skew issue like the RVV bug — the exported AST was fine, `cfg/mod.rs` just never handled the non-empty case.
Fix (`2d556ecb3`): attributes are compiler hints with no effect on program semantics (fallthrough-warning suppression, loop-unroll hints, etc.), so dropping the attribute and translating the substatement as if unwrapped is always semantically safe. Mirrors the existing `CStmtKind::Label` handling immediately above in the same match: allocate a fresh CFG label, jump to it, recurse `convert_stmt_help` on the substatement.
Before/after, `lib/crypto/blake2s.c`: panic -> clean transpile (unrolled `for` loop translated as a normal Rust `while` loop, unroll hint dropped as intended).
Full-corpus baseline note: by the time I ran `scripts/run_c2rust_baseline.py`, the parallel asm-operand-panic fix was present in the working tree but not yet committed, so my baseline numbers (83 clean / 20 crash / 1 dropped_decls, up from 66/21/17 after the DFExpr fix) reflect both fixes combined, not this one in isolation. Only `c2rust-transpile/src/cfg/mod.rs` is in my commit `2d556ecb3` — I left the in-flight `assembly.rs` changes alone.
Commit: 2d556ecb3
awto-au commentedon Jul 17, 2026
Third fix this session: GNU asm-goto (extended asm label operands) panic — fixed in 03b787a
Separate bug from the two above, but same symptom class: a hard panic aborting the whole transpile run instead of a graceful per-declaration skip.
Symptom
20 of the (then-)21 remaining crashing files in the 104-file
lib/*.ccorpus hit an identical panic:All 20 traced to riscv's
ALTERNATIVE()macro (arch/riscv/include/asm/alternative-macros.h), a CPU-errata-workaround patching mechanism used pervasively in locking primitives and memory barriers — hence the high fan-out (transitively pulled in by nearly everything).Root cause
${2:l}is GCC/Clang extended-asm syntax for "operand index 2,l(label) modifier" — a label operand, GCC's third operand class beyond outputs/inputs, used for GNUasm goto. Clang'sGCCAsmStmtlays these out contiguously after outputs and inputs in a singleExprsarray (clang/AST/Stmt.h,GCCAsmStmt::begin_labels()/getNumLabels()), so they share the same0..Nindex space referenced by the asm template.Neither
c2rust-ast-exporter'sVisitGCCAsmStmt(AstExporter.cpp) nor the Rust-sideCStmtKind::Asm/AsmOperandmodel label operands at all — onlybegin_inputs()/end_inputs()andbegin_outputs()/end_outputs()are walked. So a template referencing a label-operand index has no corresponding entry ininputs/outputs, andc2rust-transpile's operand-index lookup (translator/assembly.rs) previously did.unwrap_or_else(|| panic!(...))on the miss, aborting the whole process.Fix
Graceful degradation, not full support: detect asm-goto templates up front (any operand reference
>= outputs.len() + inputs.len()) and return aTranslationError; also hardened the operand-index lookup itself to return an error instead of panicking, as defense in depth. This routes through the existing per-declaration error-catch-and-skip machinery, so only the containing function is skipped (with a clear diagnostic), while the rest of the translation unit still transpiles.Chose this over full label-operand support (which would mean plumbing a
labels: Vec<..>field through the exporter andCStmtKind::Asm, and emitting Rust's ownasm!label-operand syntax) because ALTERNATIVE()'s self-modifying-code-style linker-section patching has no faithful Rust equivalent regardless, and this transpiler is used as a differential/reference tool here — asm output is always hand-reviewed and reimplemented, never trusted as-is.Before/after (104-file linux-rs
lib/*.ccorpus)The remaining 20 are the asm-goto functions themselves, now skipped with
Failed to translate <fn>: Cannot translate GNU asm goto (extended asm with label operands): ...instead of aborting their whole file.Commit: 03b787a
awto-au commentedon Jul 17, 2026
Upstream PR review pass: 6 merged, ~10 evaluated-and-skipped
Went through immunant/c2rust's open PR queue looking for safe, real improvements to cherry-pick onto our fork. Triaged ~49 open PRs by title/diff size, seriously evaluated 16, merged 6.
Merged (all: clean cherry-pick or hand-adapted conflict, build passes, full 104-file corpus baseline unchanged — no regression)
fa045c140Optionalinstead of unwrapping blindly)7680f8909strict_reachable_from(a transitive closure) on every single-entry-no-backedge CFG node — was O(n^2)-ish on long linear control flow, same performance-bug shape as our own DFExpr memoization fix817c54c32ctxmutation ordering bug inconvert_cast— literal-cast-skip path was leakingctxchanges back to the caller when it shouldn't243b3c6d5expr_stmt→semi_stmtfix for volatile post/pre-increment assignment codegen (was emitting code that doesn't compile in some volatile-increment shapes) — required hand-resolving a conflict intests/unit/ints/(test file had moved/restyled locally since the PR was authored)b43792ebc,c8c5b3167r#typeetc.) for C identifiers that collide with Rust reserved words, instead of failing/mangling — real correctness gap, kernel C usestype/move/etc. as ordinary field and parameter names67df081a9macro_expansion_textto actual expression nodes — was inserting it keyed by non-CExprIdnode ids too, a type-confusion bugEvaluated and skipped
DeclRefexclusion to only variables immunant/c2rust#1391, transpile: const macros: skip fn ptr types, as they don't work with portable types immunant/c2rust#1386, transpile: Translate enums with newtype structs immunant/c2rust#1620, transpile: Compute and storeoverride_tys ahead of time immunant/c2rust#1679, transpile: Split offcan_propagate_castfunction, elide some more casts immunant/c2rust#1770 (const-macro translation family) — real conflicts intranslator/mod.rs's const-macro handling; upstream has done a substantial rework (TranslateMacros::{Conservative,Experimental},recreate_const_macro_from_expansions, etc.) that our fork'smod.rspredates and doesn't share. Multiple of these PRs explicitly depend on each other and on other unmerged upstream PRs (transpile: Override pointer difference expression type toPtrDiffimmunant/c2rust#1843, transpile: Reduce repetition in simd function handling immunant/c2rust#1757/transpile: Minor improvements of cast handling immunant/c2rust#1756, refactor: Also include constructor ids inRenameUnnamedandreorganize_definitionsimmunant/c2rust#1744/transpile:prune_unwanted_declsonly after settingprenamed_declsimmunant/c2rust#1739/transpile: Include enums inCastKind::from_typesimmunant/c2rust#1737) — not safe to hand-adapt piecemeal without pulling in that whole feature stack.transpile: Support unaligned reads and writes immunant/c2rust#1257 (unaligned reads/writes) — genuine correctness feature (newalignfield threaded throughUnOp::Deref), but conflicts acrossconversion.rs/mod.rs/operators.rs; a real feature addition, not a small fix, so not force-merged.d66834387, which already does what this PR does but better (stableisSVESizelessBuiltinType()/isRVVSizelessBuiltinType()Clang APIs vs. this PR's still-hand-maintained kind ranges).builtin_assumeand unaligned SIMD intrinsics immunant/c2rust#1132 (SIMD unaligned types +__builtin_assume) — bundles an x86-specific SIMD fix (low relevance to our RISC-V corpus) with an unrelated__builtin_assumefix in one commit; marked CONFLICTING; not worth hand-splitting for the value here.convert_decl_refandconvert_enum_constant_decl_refimmunant/c2rust#1889/transpile: Use correct types for enums with in/decrement immunant/c2rust#1890/transpile: TakeCQualTypeIdand returnWithStmtsin enum/pointer cast functions immunant/c2rust#1891/transpile: Refactor array subscripting and better handle expected type of offset immunant/c2rust#1888/transpile: Refactor binary operand handling and handle complex numbers immunant/c2rust#1835/Roll toolchain to nightly-2023-04-15 immunant/c2rust#1904 and similar — explicitly-labeled pieces of a large in-flight upstream refactor stack (enum/cast/array-subscript handling, toolchain roll to nightly-2023-04-15), several stating "split off so they can be merged earlier" for other still-open PRs. Out of scope for a one-pass review; would need the whole stack.AssertsTransformandFormattingTransformand GPT tool calling immunant/c2rust#1855, postprocess: add--no-fail-fast, progress numbers, comment diffs, exclude file of all errors, etc. immunant/c2rust#1553-1555, postprocess: handle comments in conditional code immunant/c2rust#1534, (c2rust-analyze) UseSubstsRefwhen creating anInstanceto get theSymbolNameso that we don't ICE on generic foreignfns likeextern "rust-intrinsic"s immunant/c2rust#999, (c2rust-analyze) Add more (still incomplete) dataflow constraints for ptr casts immunant/c2rust#947, re-addmiritest to CI immunant/c2rust#766, Fix simple strict provenance cases inanalysis/testimmunant/c2rust#699, etc.) and CI/build-only PRs (Roll toolchain to nightly-2023-04-15 immunant/c2rust#1904 toolchain roll, transpile: tests: Runtest_translator.pyfromcargo nextestwith the currently built transpiler path immunant/c2rust#1731, ci: test on aarch64 Linux immunant/c2rust#1283, Make scripts/pdg.sh more flexible for more use cases immunant/c2rust#1113, Windows support Windows building support immunant/c2rust#1501) — out of scope per task priority (not transpile/ast-exporter, or infra-only).Baseline note
Before this pass,
run_c2rust_baseline.py's DB-recorded reference (84 clean / 0 crash / 20 dropped_decls) was from2d556ecb3. Re-running that exact commit today reproduces 13 clean / 20 crash / 71 dropped_decls instead — the linux-riscv corpus tree itself has moved on (linux-rs is actively translatinglib/*.cfiles, TU 27→29 landed today) and now exercises more of the still-open_THIS_IP_/__LOCK_BHGNU-label-address gap this issue is tracking. Not a c2rust regression — confirmed by bisecting back to2d556ecb3and reproducing the same numbers there. Established a fresh same-day reference point instead (13 clean / 91 dropped_decls / 0 crash on03b787a08+immunant#858) and diffed every subsequent merge against that; all 6 merges above hold that line exactly, no crashes introduced, no clean count regression.Scratch
pr-eval-*branches cleaned up. All 6 commits pushed tomasterindividually as I went.awto-au commentedon Jul 17, 2026
Upstream PR review pass #2: 4 merged (+1 hotfix), 4 evaluated-and-skipped
Reviewed the remaining 46 open, non-draft PRs in immunant/c2rust's queue that the first pass (see comment above) didn't touch, starting from
67df081a9. Of those 46, most were out of scope on a fast title/files triage (pdg/, c2rust-analyze/, dynamic_instrumentation/, c2rust-refactor/, c2rust-postprocess/, CI/build/nix/toolchain-only, or pure refactors with no behavior change) or turned out to be drafts despite showing up in the open-PR listing (isDraftisn't filtered by a plaingh pr list --jsonquery — 15 of the "46" were actually drafts, including several that looked like strong transpile/ast-exporter candidates by title, e.g. enum/case-translation and c_ast-splitting work). Excluding drafts and out-of-scope crates left 8 real candidates inc2rust-transpile/c2rust-ast-exporter, of which 4 merged cleanly.Merged (all: full 510-file whole-kernel-corpus baseline unchanged vs
67df081a9— 24 clean / 425 dropped_decls / 61 crash throughout, 0 regressed declarations end-to-end perc2rust-regress)4b720c54c{0, 1}used in boolean contexts straight to Rusttrue/falseinstead of1 as c_int != 0— pure codegen readability simplification, same family as the existing comparison-operator-unwrap simplification right above it3e2bfda52syn::ExprLitreference without adding it to theuse syn::{...}list, somasterdid not actually compile from4b720c54cthrough8d6a2b3da. Caught by a from-scratch rebuild after earlier incremental builds gave false-positive "succeeded" results (a| tailin my own build commands was swallowingcargo's non-zero exit code). Fixed and force-verified with explicit exit-code capture from here on.62c070554__builtin_*_overflow) now detect truncation viaresult_ty::try_from(..).is_err()instead of casting the i128 intermediate back up and comparing — the old round-trip comparison could be wrong when the narrow result type is unsigned but the i128 intermediate is negative;try_fromis unconditionally correct8d6a2b3dasize_t s1 = l = 1;) to the surrounding expression's expected type — previously only the assignment's own LHS/RHS types were considered, so the result of an assignment-used-as-expression could carry the wrong Rust type. Squashed from the PR's 7 incremental commits (commit-by-commit cherry-pick produced spurious conflicts against our fork's diverged history; the squashed diff applied clean)59dfd1a99convert_builtinand cast its result once at the end, fixing several builtins whose translated Rust type didn't match the call site (__builtin_ffs/__builtin_clz/__builtin_ctz/__builtin_popcountreturningu32instead of the expectedc_int,__builtin_alloca's pointer cast,__builtin_object_size's result type,va_start/va_copy/va_endearly-return cleanup). These builtins are pervasive in kernel code, so this is directly relevant to corpus coverage even though today's crash/dropped_decls counts didn't move (the underlying declarations were already failing earlier in translation for unrelated reasons — this fixes correctness for cases that do get this far)Evaluated and skipped
c2rust-bitfields-derive) — already superseded. The first pass's merged Use raw identifier for reserved keywords (continued) immunant/c2rust#1822 (b43792ebc+c8c5b3167) already carries a newer, more complete version of this exact fix (bitfields-derive,structs_unions.rs,ast-builder); confirmed by diffing Use raw identifier for reserved keywords immunant/c2rust#295's core change against currentrenamer.rs/c2rust-bitfields-derive/src/lib.rs, which already contain it.core::ffi::c_*for C types instead oflibc::c_*immunant/c2rust#297 (switchlibc::c_*tocore::ffi::c_*) — already superseded organically. Our fork'sconvert_type.rsalready emitscore::ffi::c_*for every common primitive type; only a handful of rarer types (intmax_t,wchar_t, a fewsize_t/intptr_tcall sites) still uselibc::, and this 2020-era PR predates that migration entirely — merging it would be a no-op at best.override_tysinfn convert_exprso const macros can use them, and convert macros last immunant/c2rust#1372 (saveoverride_tysduringconvert_exprso const-macro recreation can use them) — same const-macro family the first pass flagged as out of scope: depends onTranslateMacros::{Conservative,Experimental}/recreate_const_macro_from_expansions, upstream infrastructure our fork'stranslator/mod.rsdoesn't have. Conflicts on apply; the new field this PR adds has no consumer on our fork without pulling in that whole rework.VisitExpr, handle when Begin and End start off in different macros immunant/c2rust#1795), reworks the CBOR wire format betweenc2rust-ast-exporterandc2rust-transpilefor macro invocation tracking, and conflicts onc_ast/conversion.rs. Same const-macro-family risk profile as transpile: saveoverride_tysinfn convert_exprso const macros can use them, and convert macros last immunant/c2rust#1372; author's own description frames it as groundwork ("exports the arg info... to hopefully make functional macros possible later"), not a finished, scoped fix.Process note
Partway through this pass a second, concurrent session was actively developing (uncommitted) PCH-based performance work for
run_c2rust_baseline.pyin the same working tree, which briefly madedev.py c2rust-baselinereport a massno_outputfailure mode unrelated to any of the merges above (traced to a Clang PCH artifact that doesn't yet handle this corpus correctly, matching the exact caveat already documented earlier in this issue: PCH parses fine standalone but degrades AST-export correctness underc2rust transpile). Waited for that session's in-flight run to finish rather than touching its uncommitted files, then re-validated once the sharedtmp/c2rust-pch/artifact had regenerated correctly. No files belonging to that other work were modified.Scratch
pr-eval-*branches cleaned up. All 5 commits (4 upstream cherry-picks + 1 hotfix) pushed tomasterindividually.awto-au commentedon Jul 17, 2026
Community-fork triage pass: 1 merged, 32 evaluated-and-skipped
Different search space from the upstream-PR-review passes above: this pass looked at commits sitting on personal forks of immunant/c2rust that were never submitted as PRs at all (found via GitHub's compare API across all 353 forks; 33 forks had 1+ real unique commits ahead of
immunant/c2rust:master). Reviewed all 33, ranked by commits-ahead, reading each fork's actual diff viagh api repos/immunant/c2rust/compare/master...<owner>:<branch>.Merged
jesse.gibson/main)056cc19c9c2rust-bitfields: implementFieldTypeforusize/isize. Our transpiler emits bitfields withty = "usize"/"isize"for C bitfields backed bysize_t/ssize_t/ptrdiff_t/etc. (convert_type.rs'sCTypeKind::Size/SSize/PtrDiff/IntPtr/UIntPtrhandling), butc2rust-bitfields'sFieldTypetrait was only implemented for the fixed-width integer types — so generated code for such bitfields failed to compile with "the traitFieldTypeis not implemented forusize". One-line fix: addusize/isizeto theimpl_int!macro's type list (bit read/write logic is already width-agnostic). Clean cherry-pick, full 510-file baseline unchanged (24 clean / 425 dropped_decls / 61 crash), 0 regressed declarations perc2rust-regress(14987 decls, 0 regressed / 0 fixed — expected, since this affects downstream Rust-compile success on already-translated bitfields, not transpile-time crash/dropped_decls counts).Flagged leads, investigated first
Remmirad/fix-wrong-macro-translation(2 commits ahead, promising by name) — real, scoped fix toc2rust-transpile/src/translator/mod.rs(references immunant/c2rust#803: stops a const-macro expansion containing a function call from being folded into a const-context expression, which doesn't compile). Skip: doesn't apply. The fix targets aconvert_const_macro_expansion-era const-macro fold path that our fork'stranslator/mod.rsno longer has in that shape — same const-macro rework (TranslateMacros::{Conservative,Experimental},recreate_const_macro_from_expansions) that the earlier upstream-PR-review passes already found makes PRs Narrow const macroDeclRefexclusion to only variables immunant/c2rust#1391/transpile: const macros: skip fn ptr types, as they don't work with portable types immunant/c2rust#1386/transpile: Translate enums with newtype structs immunant/c2rust#1620/transpile: Compute and storeoverride_tys ahead of time immunant/c2rust#1679/transpile: Split offcan_propagate_castfunction, elide some more casts immunant/c2rust#1770/transpile: saveoverride_tysinfn convert_exprso const macros can use them, and convert macros last immunant/c2rust#1372 unsafe to hand-adapt piecemeal.git apply --checkconfirms it does not apply; the target function doesn't exist in currentmod.rs.dberlin/c2rust(1 commit ahead) — already superseded. Its sole commit (1cf7feac7, "Don't attempt to convert constant value if it doesn't exist") is byte-identical in diff content to upstream PR Don't attempt to convert failed constant int evaluations into APint immunant/c2rust#858, already merged onto our fork this session asfa045c140.Evaluated and skipped
Unrelated crate / out of scope (touches only
c2rust-analyze/,c2rust-refactor/,pdg/,dynamic_instrumentation/, ancient pre-crate-splitast-importer/, or a from-scratch rewrite into an unrelated target language):omarandlorraine/c2rust(17 of 18 commits are dependabot/CI; the 1 transpile-touching line is a purecargo clippy --fixids.len() == 0→ids.is_empty()style change, zero behavior difference),lookibed/c2rust(wholesale fork into "c2dascript", a different C-to-DAS-language transpiler built on c2rust's skeleton — not a fix),anp/c2rust(base commit from 2018, 7726 commits behind, targets the pre-crate-splitast-importerarchitecture that no longer exists),fnetz/c2rust(restores/builds the legacy Lua-scriptingc2rust-refactorcrate on a pre-2021 codebase),AntonyBlakey/c2rust,kvinwang/c2rust(typo fix, but inc2rust-refactor/).Idiom/output-quality only (deferred per project stage), or already superseded by prior work in this session:
kings-crown/c2rust(6 commits — real-looking attempt to thread an "expected type" through literal/character-literal and overflow-builtin conversion, but both ideas are already solved differently and more robustly in our current tree: the overflow-builtin type-mismatch case is already fixed by the already-merged immunant#1879/immunant#1860try_from-basedconvert_overflow_arith, and the character-literal cast-target case is already handled viaoverride_ty.unwrap_or(ty)parameter-threading throughconvert_expr/convert_literal, not a newExprContextfield. Applying it would mean regressing to a less-robust overflow implementation and adding a redundant, conflicting second type-threading mechanism),dgherzka/c2rust(bool→float cast fix already present verbatim in ourmod.rsat thetarget_ty_kind.is_floating_type() && source_ty_kind.is_bool()arm; its second change, optionalderive(Debug)for structs, is idiom/output-quality and gated behind atcfg.derive_debugconfig flag that doesn't exist in our fork — deferred per this project's reliability-first scope),davidMcneil/c2rust(raw-identifier-for-reserved-keywords fix already present verbatim in ourrenamer.rs, superseded by already-merged immunant#1822),xtmono/c2rust(adds big-endian bitfield support via a newendianattribute — a feature addition with an API-breakingFieldType::set_field/get_fieldsignature change, not a bug fix; irrelevant to linux-rs's little-endian RISC-V corpus).Doesn't apply cleanly / too stale / architecturally diverged:
richardscollin/c2rust(10 commits, 2121 behind — proc-macro/library-usage + Rust-2024-edition codegen changes, output-format not correctness, includes an explicit "KLUDGE: DON'T MERGE THIS COMMIT" commit),nvxiang/other-c2rust(touches realc2rust-transpilefiles but the changes are Chinese-language debug comments and unconditionalprintln!/eprintln!calls left in the per-declaration translation hot path — debug scaffolding, not a fix).WIP/experimental by the fork author's own commit messages:
Lysarina/flexc2rust("WIP", "Misc", "Modding").Config/CI/docs/build-only, no functional Rust-side change:
Saldivarcher/c2rust(.travis.yml+ build script),wolverian/c2rust(devcontainer/vscode/nix),WSLUser/c2rust(PNG image optimization),livstyle/c2rust(toolchain channel pin),mstone/c2rust(flake.nix),anoraktrend/c2rust(Cargo.toml features),leongross/c2rust(submodule dependency bump, unrelatedxzoomexample),thomasyonug/c2rust+seenry/c2rust(proc-macro2 version pins),mtlll/c2rust(Vagrantfile),Gonzih/c2rust(nixos cc-wrapper scripts),backwardn/c2rust(merge-only, no real diff),zdimension/c2rust,k2662/c2rust,jwku/iffy-iffy-c2rust,gkielian/c2rust,destenson/immunant--c2rust(CI/toolchain/README/workflow only).PsychoLlama/c2rust's remaining 5 commits beyond the merged bitfields fix are all Nix/flake build-scoping work — skipped as out of scope, only the bitfields fix was cherry-picked.Baseline note
Full 510-file corpus baseline unchanged throughout this pass: 24 clean / 425 dropped_decls / 61 crash, same as the starting point at
c3df84d10. The one merge (056cc19c9) doesn't move these counts because it fixes a downstream Rust-compile error for already-"clean"/dropped-decl-affected bitfield code, not a transpile-time crash or decl-drop; confirmed viac2rust-regress c3df84d10 056cc19c9: 0 regressed, 0 fixed, 14987 decls unchanged, VERDICT: OK.Scratch fork remotes (
psychollama,kingscrown) added and removed after use; no other files touched.awto-au commentedon Jul 17, 2026
Update (2026-07-17): still open, root cause reconfirmed, symptom has shifted
Re-verified from scratch against current
master(5582fcd). The bug isstill 100% live —
VisitAddrLabelExpr(AstExporter.cpp) still onlyprintDiags and drops the node, never callingencode_entry. Confirmed viaa fresh full-corpus baseline (552 files) plus direct isolated reruns.
Symptom has changed since this issue was filed. The originally-reported
TagTypeUnknown expecting 3panic (conversion.rs) no longer reproducesanywhere in the current 552-file corpus — 0 occurrences in any transpile
log. But this is NOT because the bug is fixed: it's because something
between
19afbea41(this issue's filing rev) and5582fcd7echanged whichpanic fires first when a
_THIS_IP_-affected file's droppedAddrLabelExprnode leaves a hole in the AST. The same 51 files that currently crash now
hit
cfg/mod.rs:744("IndexMap: key not found", CFG builder can't find anode — most common) or
translator/mod.rs:4945/:1262("Expected declname") instead — downstream consequences of the same missing-node root
cause, just tripped over by different code before ever reaching the old
type-conversion fallthrough.
Corpus-wide impact, freshly measured (552-file corpus,
--include-slow):_THIS_IP_/__LOCK_BH/raw_spin_lock_bh/raw_spin_unlock_bhdirectly; 460 also hit
__sb_writers_acquired/__sb_writers_release(same root cause, different call chain:
percpu_rwsem_acquire/release).crash-outcome files, all reproduce a panic incfg/mod.rs:744,translator/mod.rs:4945, ortranslator/mod.rs:1262—not
conversion.rs'sTagTypeUnknownarm, which no longer appears to bereachable via this path in the current tree.
Fix in progress: give
AddrLabelExpra real CBOR tag (TagAddrLabelExpr)instead of dropping it, matching every other
TagXxxvisitor's shape (alwaysencodes, unconditionally — no exporter-side gating, consistent with how
WarnOn/FlsFamily/SwapMemSwaponly gate Rust-side behavior). On the Rustside, a new
CExprKind::AddrLabel(CQualTypeId)(distinct fromBadExpr,which is deliberately wired to a hard translation error) converts to a
placeholder
0usize-shaped value when a newKernelIdiomRuleis enabled,or the same "unsupported"
TranslationErroras today when it's not —preserving the stock-upstream-byte-identical-with-no-flags invariant.
Placeholder (not real PC capture) is correct because every
_THIS_IP_usein this corpus flows only into lockdep-internal debug-annotation sinks that
are no-ops with
CONFIG_LOCKDEPoff (this build's actual config) — verifiedbefore choosing this over attempting
core::panic::Location::caller()(wrong type:
&'static Locationvsunsigned long, needs#[track_caller]threading that doesn't exist in the wrapped call chains).
awto-au commentedon Jul 17, 2026
Fix landed (2026-07-17, commit bf319b9)
TagAddrLabelExprnow always encodes the node (matches every otherTag*visitor's shape) instead of dropping it; Rust side gets a newCExprKind::AddrLabel, gated behindKernelIdiomRule::AddrLabelPlaceholder— substitutes a placeholder value (
implicit_default_expr, the same "zerovalue of this C type" helper
ImplicitValueInituses) when enabled,returns the same translation-error outcome as before when it's not
(verified byte-identical stock output with no
--enable-ruleflags).Also fixed a real, separate bug found while wiring this up:
convert_statement_expression's GNU-statement-expression handling onlyrecognized a bare
Expras a block's final statement —_THIS_IP_'sexpansion ends in a
LabelStmtwrapping the result expression (Crequires a statement after a label), which fell through to the
StmtExprVoidpath and silently discarded the placeholder value,producing a
()-typed block where the caller expectedunsigned long(confirmed via a real
rustccompile error before this second fix).Full corpus impact (552 files,
--include-slow)Before: 24 clean / 467 dropped_decls / 51 crash (542 files, pre-
--include- slowconvention)After: 216 clean / 265 dropped_decls / 71 crash (552 files)
Verified via
dev.py c2rust-regress 5582fcd7e bf319b94b: 0 new/removeddecls, 3883 unchanged-still-translating, but 294 declarations across 16
files flagged as "regressed" (was translated, now isn't) — root-caused,
not a flaw in this fix:
All 16 files (
drivers/char/random.c,drivers/tty/tty_ioctl.c,fs/file.c,fs/fs-writeback.c,fs/fserror.c,fs/kernfs/dir.c,fs/pipe.c,fs/super.c,kernel/async.c,kernel/printk/printk.c,kernel/rcu/srcutiny.c,kernel/rcu/sync.c,kernel/umh.c,lib/percpu-refcount.c,mm/oom_kill.c,mm/vmscan.c) previously hadoutcome=
dropped_decls(partial success — some declarations individuallyblocked by the
_THIS_IP_gap, others translated fine) and now haveoutcome=
crash, confirmed identical in all 4 spot-checked files to thepre-existing
cfg/mod.rs:744"IndexMap: key not found" panic alreadydocumented in this session's earlier crash-signature audit (the dominant
cause of the original 51 crashes, ~30/51). Mechanism: these files used to
have
_THIS_IP_block translation early, before ever reaching the pointthat triggers the unrelated
cfg/mod.rs:744bug; with that early blockremoved, translation proceeds further and now reaches it — and a full-
file crash zeroes every declaration's recorded outcome, even ones that
individually would have (and previously did) succeed.
Net effect is strongly positive (+192 files net improvement in outcome
tier) but the regression gate correctly flags this local tradeoff for 16
files rather than silently passing it — recorded here rather than treated
as a hidden cost. No action taken to special-case these 16 files (would
require predicting a downstream crash before it happens, real added
complexity for a bug that's already tracked and will fix all of them at
once when addressed).
Corpus-wide
_THIS_IP_/lock-bh family: fully closedgrep -c "address of label" tmp/c2rust-baseline/*/transpile.logwith therule enabled: 0 occurrences anywhere in the current 552-file corpus.
transpile: emit a placeholder for _THIS_IP_ (&&label) instead of drop…
awto-au commentedon Jul 17, 2026
Fixed: commit bf319b9 (addr-label-placeholder rule) resolves the THIS_IP/&&label AddrLabelExpr case this issue tracks. See the detailed comment thread above for full before/after corpus numbers.
scripts/docs: archive run_c2rust_pch_compare.py, wire ollama_queue_wa…