autodiff: silent gradient corruption (dx: 0) when differentiating through coroutines/generators in debug mode
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Code
#![feature(coroutines, coroutine_trait, autodiff, stmt_expr_attributes)]
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
use std::autodiff::autodiff_reverse;
#[autodiff_reverse(df, Active, Active)]
fn f(x: f32) -> f32 {
let mut coroutine = #[coroutine] move || {
let val = x;
yield val;
yield val * 3.0;
};
let mut pin = Pin::new(&mut coroutine);
let mut sum = 0.0;
match pin.as_mut().resume(()) {
CoroutineState::Yielded(v) => sum += v,
CoroutineState::Complete(_) => {}
}
match pin.as_mut().resume(()) {
CoroutineState::Yielded(v) => sum += v,
CoroutineState::Complete(_) => {}
}
sum
}
fn main() {
let (val, dx) = df(3.0, 1.0);
// f(x) = x + 3x = 4x
// Expected dx: 4.0
// Actual dx: 0.0
println!("val: {}, dx: {}", val, dx);
}
Build/run:
rustc -Z autodiff=Enable -C lto=fat test_coroutine.rs
./test_coroutine
What happened
The code compiles without any errors or warnings, runs without panicking, but the returned gradient is silently wrong:
val: 12, dx: 0
Expected dx: 4.0 (for f(x) = x + 3x = 4x); actual dx: 0.0. This reproduces with both self-referential and non-self-referential coroutines (i.e. it does not appear to depend on the self-referential-generator/noalias issue tracked in #63818 — a plain, non-self-referential #[coroutine] closure with no internal borrows reproduces the same silent dx: 0).
My working hypothesis (not confirmed against the generated LLVM IR): the compiler does not emit type-tree/activity metadata for the compiler-generated coroutine state struct, so Enzyme's activity analysis treats the struct's fields as inactive and drops gradient tracking through it entirely, without that being surfaced as an error.
Because this produces no diagnostic of any kind, it's a silent-correctness issue rather than a crash — code using #[coroutine]/generators/async blocks inside a differentiated function will currently get plausible-looking but wrong gradients.
Meta
rustc --version --verbose:
rustc 1.99.0-nightly (daf2e5e18 2026-07-13)
host: x86_64-unknown-linux-gnu (WSL2 Ubuntu)
Built with rustup component add rustc-dev llvm-tools-preview enzyme on the same nightly, -Z autodiff=Enable -C lto=fat.
Suggested fix direction
At minimum, reject (with a diagnostic) differentiation through functions whose MIR references a coroutine/generator state type, until type-tree/activity metadata propagation for these types is implemented — silent wrong answers seem strictly worse than a compile error here.
I wrote a small third-party static checker (ad-safety, MIR-level rustc_private lint) that currently rejects this pattern at compile time as a stopgap, in case the reproducer is useful: https://github.com/sangmorg1-debug/ad-safety
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the reproducer in test_coroutine.rs with the stated rustc and autodiff flags, then inspect the generated LLVM IR and compiler handling of coroutine state types. Compare the gradient path through the coroutine with the expected dx of 4.0; done means either correct gradient tracking or a diagnostic rejecting unsupported coroutine/generator differentiation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100