rust-lang / rust-lang/rust

autodiff: silent gradient corruption (dx: 0) when differentiating through coroutines/generators in debug mode

Open
#159,268 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug F-autodiff I-miscompile requires-nightly
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.