rust-lang / rust-lang/rust

LLVM optimization often (always?) results of loss of closure upvar debug info

Open
#141,712 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-closures A-debuginfo A-LLVM C-bug WG-llvm
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I'm filing this here for tracking even though this is largely/entirely an LLVM issue (llvm/llvm-project#141559, NB: the title of that issue is not accurate at the time of filing this one)

use std::{env, process};
use std::str::FromStr;

fn main() {
    let needle = env::var("NEEDLE").unwrap();
    let ret = i64::from_str(&env::var("RETURN").unwrap()).unwrap();
    let ret2 = i64::from_str(&env::var("RETURN2").unwrap()).unwrap();

    let check = |foobar| {
        if foobar == needle {
            process::exit(ret as i32 - ret2 as i32);
            return true;
        }
        false
    };

    for s in env::args() {
        if check(s) {
            unreachable!();
        }
    }
}

This program has been reduced from iterator heavy code but intentionally made weird in certain ways to defeat the optimizer (getting upvar values from env vars), highlight the issue with the closure (eschewing the use of the Iterator trait), and to force the rustc-generated type capturing the upvars to be of sufficient size.

rustc's MIR optimizations do not inline the closure check, leaving that for LLVM to do. Once LLVM inlines it, SROA then sets to work on the upvar-capturing-type and splits it into its components. LLVM does correctly move the debug value definitions for the upvars within the closure to the points at which the equivalent variables in the outer function are defined, and it does correctly rewrite the DWARF expressions to "$rsp plus offset plus deref".

Where LLVM falls down is that the LiveDebugValues pass does not propagate the value definition through basic blocks where the corresponding variable is not in scope (unless the entire block is "artificial"). This essentially guarantees that the value will not be propagated through to the closure and will be lost.

There is some discussion in the linked LLVM issue of how to fix this and what the performance costs might be.

@rustbot label +A-llvm +A-debuginfo +WG-llvm

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 with the reduced Rust program in the issue and the linked LLVM issue llvm/llvm-project#141559. Investigate the interaction between LLVM inlining, SROA, and the LiveDebugValues pass, focusing on why closure upvar debug values are not propagated through out-of-scope blocks. Done means the closure's upvar debug information is preserved after optimization.

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
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.