Skip to content

Implement Debug helpers via Cell#159302

Open
connortsui20 wants to merge 1 commit into
rust-lang:mainfrom
connortsui20:dyn-debug-helpers
Open

Implement Debug helpers via Cell#159302
connortsui20 wants to merge 1 commit into
rust-lang:mainfrom
connortsui20:dyn-debug-helpers

Conversation

@connortsui20

@connortsui20 connortsui20 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

View all comments

Fixes #149745.

Following @jmillikin's suggestion, the builder logic now lives in the pre-existing non-generic methods taking &dyn fmt::Debug (where it used to exist), and each *_with method wraps its closure in a private DebugOnce struct that implements Debug by calling the closure, so the body is compiled once.

Just for context: A dyn FnOnce can't be called behind a reference, hence the Cell<Option<..>> stuff in DebugOnce.

repro.rs
#![feature(debug_closure_helpers)]
#![crate_type = "lib"]

use core::fmt;

pub struct Point {
    pub x: u32,
    pub y: u32,
}

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Point")
            .field_with("x", |f| self.x.fmt(f))
            .field_with("y", |f| self.y.fmt(f))
            .finish()
    }
}
rustc +nightly --edition=2024 --emit=llvm-ir -Copt-level=0 -o before.ll repro.rs
rustc +stage1  --edition=2024 --emit=llvm-ir -Copt-level=0 -o after.ll  repro.rs

On the repro above, I saw the LLVM IR drop from 739 to 268 lines (an earlier version of this PR using &mut dyn FnMut measured 372). Since the stable &dyn fmt::Debug methods no longer go through any closure indirection, this should also avoid the potential runtime regression in the derive(Debug) microbenchmark.

This was the only remaining blocker for stabilizing debug_closure_helpers, so it should unblock #146099.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 14, 2026
@rustbot

rustbot commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

tgross35 is currently at their maximum review capacity.
They may take a while to respond.

@connortsui20

connortsui20 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

before.ll

Details
; ModuleID = 'repro.95a4099cea1845-cgu.0'
source_filename = "repro.95a4099cea1845-cgu.0"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx11.0.0"

@alloc_fe2ddbdc622a788f5416063d1e8d8ad4 = private unnamed_addr constant [3 x i8] c" { ", align 1
@alloc_94b00be069aafad82a2c6df764237b82 = private unnamed_addr constant [2 x i8] c", ", align 1
@alloc_556e4180596b5b612bb6ed6c0cbb55e1 = private unnamed_addr constant [2 x i8] c": ", align 1
@alloc_3690db001d3db01550bdb6f3f450de28 = private unnamed_addr constant [3 x i8] c" {\0A", align 1
@alloc_f7ac0c356c6ac6fcd03badb685e09ca2 = private unnamed_addr constant [2 x i8] c",\0A", align 1
@alloc_bf0ef947d182249ea2203578272460c5 = private unnamed_addr constant [5 x i8] c"Point", align 1
@alloc_37f62a15fae368a2423b3a16c75adcc9 = private unnamed_addr constant [1 x i8] c"x", align 1
@alloc_cd51bc84b49b21a3fdb2800731f25c21 = private unnamed_addr constant [1 x i8] c"y", align 1

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>
; Function Attrs: uwtable
define align 8 ptr @_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr align 4 %value_fmt) unnamed_addr #0 {
start:
  %_8 = alloca [32 x i8], align 8
  %op = alloca [32 x i8], align 8
  %_4 = alloca [1 x i8], align 1
  %0 = getelementptr inbounds i8, ptr %self, i64 8
  %1 = load i8, ptr %0, align 8
  %self1 = trunc nuw i8 %1 to i1
  store ptr %self, ptr %op, align 8
  %2 = getelementptr inbounds i8, ptr %op, i64 8
  store ptr %name.0, ptr %2, align 8
  %3 = getelementptr inbounds i8, ptr %2, i64 8
  store i64 %name.1, ptr %3, align 8
  %4 = getelementptr inbounds i8, ptr %op, i64 24
  store ptr %value_fmt, ptr %4, align 8
  %_7 = zext i1 %self1 to i64
  %5 = trunc nuw i64 %_7 to i1
  br i1 %5, label %bb3, label %bb4

bb3:                                              ; preds = %start
  store i8 1, ptr %_4, align 1
  br label %bb1

bb4:                                              ; preds = %start
  call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_8, ptr align 8 %op, i64 32, i1 false)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>::{closure#0}
  %6 = call zeroext i1 @_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmt0E0B1h_(ptr align 8 %_8) #4
  %7 = zext i1 %6 to i8
  store i8 %7, ptr %_4, align 1
  br label %bb1

bb1:                                              ; preds = %bb3, %bb4
  %8 = getelementptr inbounds i8, ptr %self, i64 8
  %9 = load i8, ptr %_4, align 1
  %10 = trunc nuw i8 %9 to i1
  %11 = zext i1 %10 to i8
  store i8 %11, ptr %8, align 8
  %12 = getelementptr inbounds i8, ptr %self, i64 9
  store i8 1, ptr %12, align 1
  ret ptr %self

bb2:                                              ; No predecessors!
  unreachable
}

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>
; Function Attrs: uwtable
define align 8 ptr @_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr align 4 %value_fmt) unnamed_addr #0 {
start:
  %_8 = alloca [32 x i8], align 8
  %op = alloca [32 x i8], align 8
  %_4 = alloca [1 x i8], align 1
  %0 = getelementptr inbounds i8, ptr %self, i64 8
  %1 = load i8, ptr %0, align 8
  %self1 = trunc nuw i8 %1 to i1
  store ptr %self, ptr %op, align 8
  %2 = getelementptr inbounds i8, ptr %op, i64 8
  store ptr %name.0, ptr %2, align 8
  %3 = getelementptr inbounds i8, ptr %2, i64 8
  store i64 %name.1, ptr %3, align 8
  %4 = getelementptr inbounds i8, ptr %op, i64 24
  store ptr %value_fmt, ptr %4, align 8
  %_7 = zext i1 %self1 to i64
  %5 = trunc nuw i64 %_7 to i1
  br i1 %5, label %bb3, label %bb4

bb3:                                              ; preds = %start
  store i8 1, ptr %_4, align 1
  br label %bb1

bb4:                                              ; preds = %start
  call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_8, ptr align 8 %op, i64 32, i1 false)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>::{closure#0}
  %6 = call zeroext i1 @_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmts_0E0B1h_(ptr align 8 %_8) #4
  %7 = zext i1 %6 to i8
  store i8 %7, ptr %_4, align 1
  br label %bb1

bb1:                                              ; preds = %bb3, %bb4
  %8 = getelementptr inbounds i8, ptr %self, i64 8
  %9 = load i8, ptr %_4, align 1
  %10 = trunc nuw i8 %9 to i1
  %11 = zext i1 %10 to i8
  store i8 %11, ptr %8, align 8
  %12 = getelementptr inbounds i8, ptr %self, i64 9
  store i8 1, ptr %12, align 1
  ret ptr %self

bb2:                                              ; No predecessors!
  unreachable
}

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>::{closure#0}
; Function Attrs: inlinehint uwtable
define zeroext i1 @_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmt0E0B1h_(ptr align 8 %_1) unnamed_addr #1 personality ptr @rust_eh_personality {
start:
  %0 = alloca [16 x i8], align 8
  %_50 = alloca [1 x i8], align 1
  %prefix = alloca [16 x i8], align 8
  %writer = alloca [24 x i8], align 8
  %state = alloca [1 x i8], align 1
  %slot = alloca [24 x i8], align 8
  %_0 = alloca [1 x i8], align 1
  store i8 1, ptr %_50, align 1
  %self = load ptr, ptr %_1, align 8
  %_51 = load ptr, ptr %self, align 8
  %1 = getelementptr inbounds i8, ptr %_51, i64 16
  %_53 = load i32, ptr %1, align 8
  %_52 = and i32 %_53, 8388608
  %2 = icmp eq i32 %_52, 0
  br i1 %2, label %bb11, label %bb1

bb11:                                             ; preds = %start
  %_40 = load ptr, ptr %_1, align 8
  %3 = getelementptr inbounds i8, ptr %_40, i64 9
  %4 = load i8, ptr %3, align 1
  %_24 = trunc nuw i8 %4 to i1
  br i1 %_24, label %bb12, label %bb13

bb1:                                              ; preds = %start
  %_34 = load ptr, ptr %_1, align 8
  %5 = getelementptr inbounds i8, ptr %_34, i64 9
  %6 = load i8, ptr %5, align 1
  %_3 = trunc nuw i8 %6 to i1
  br i1 %_3, label %bb5, label %bb2

bb13:                                             ; preds = %bb11
  store ptr @alloc_fe2ddbdc622a788f5416063d1e8d8ad4, ptr %prefix, align 8, !captures !4
  %7 = getelementptr inbounds i8, ptr %prefix, i64 8
  store i64 3, ptr %7, align 8
  br label %bb14

bb12:                                             ; preds = %bb11
  store ptr @alloc_94b00be069aafad82a2c6df764237b82, ptr %prefix, align 8, !captures !4
  %8 = getelementptr inbounds i8, ptr %prefix, i64 8
  store i64 2, ptr %8, align 8
  br label %bb14

bb14:                                             ; preds = %bb12, %bb13
  %_41 = load ptr, ptr %_1, align 8
  %_42 = load ptr, ptr %_41, align 8
  %9 = load ptr, ptr %prefix, align 8
  %10 = getelementptr inbounds i8, ptr %prefix, i64 8
  %11 = load i64, ptr %10, align 8
; invoke <core::fmt::Formatter>::write_str
  %self1 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_42, ptr %9, i64 %11)
          to label %bb15 unwind label %cleanup

bb24:                                             ; preds = %cleanup
  %12 = load i8, ptr %_50, align 1
  %13 = trunc nuw i8 %12 to i1
  br i1 %13, label %bb23, label %bb22

cleanup:                                          ; preds = %bb32, %bb30, %bb28, %bb6, %bb5, %bb2, %bb38, %bb36, %bb34, %bb14
  %14 = landingpad { ptr, i32 }
          cleanup
  %15 = extractvalue { ptr, i32 } %14, 0
  %16 = extractvalue { ptr, i32 } %14, 1
  store ptr %15, ptr %0, align 8
  %17 = getelementptr inbounds i8, ptr %0, i64 8
  store i32 %16, ptr %17, align 8
  br label %bb24

bb15:                                             ; preds = %bb14
  %_66 = zext i1 %self1 to i64
  %18 = trunc nuw i64 %_66 to i1
  br i1 %18, label %bb33, label %bb34

bb33:                                             ; preds = %bb15
  store i8 1, ptr %_0, align 1
  br label %bb20

bb34:                                             ; preds = %bb15
  %_43 = load ptr, ptr %_1, align 8
  %_44 = load ptr, ptr %_43, align 8
  %19 = getelementptr inbounds i8, ptr %_1, i64 8
  %_45.0 = load ptr, ptr %19, align 8
  %20 = getelementptr inbounds i8, ptr %19, i64 8
  %_45.1 = load i64, ptr %20, align 8
; invoke <core::fmt::Formatter>::write_str
  %self2 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_44, ptr %_45.0, i64 %_45.1)
          to label %bb16 unwind label %cleanup

bb16:                                             ; preds = %bb34
  %_69 = zext i1 %self2 to i64
  %21 = trunc nuw i64 %_69 to i1
  br i1 %21, label %bb35, label %bb36

bb35:                                             ; preds = %bb16
  store i8 1, ptr %_0, align 1
  br label %bb20

bb36:                                             ; preds = %bb16
  %_46 = load ptr, ptr %_1, align 8
  %_47 = load ptr, ptr %_46, align 8
; invoke <core::fmt::Formatter>::write_str
  %self3 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_47, ptr @alloc_556e4180596b5b612bb6ed6c0cbb55e1, i64 2)
          to label %bb17 unwind label %cleanup

bb17:                                             ; preds = %bb36
  %_72 = zext i1 %self3 to i64
  %22 = trunc nuw i64 %_72 to i1
  br i1 %22, label %bb37, label %bb38

bb37:                                             ; preds = %bb17
  store i8 1, ptr %_0, align 1
  br label %bb20

bb38:                                             ; preds = %bb17
  store i8 0, ptr %_50, align 1
  %23 = getelementptr inbounds i8, ptr %_1, i64 24
  %_31 = load ptr, ptr %23, align 8
  %_48 = load ptr, ptr %_1, align 8
  %_49 = load ptr, ptr %_48, align 8
; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#0}
  %24 = invoke zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptr align 4 %_31, ptr align 8 %_49)
          to label %bb18 unwind label %cleanup

bb18:                                             ; preds = %bb38
  %25 = zext i1 %24 to i8
  store i8 %25, ptr %_0, align 1
  br label %bb19

bb19:                                             ; preds = %bb10, %bb18
  br label %bb21

bb20:                                             ; preds = %bb33, %bb35, %bb37
  br label %bb40

bb40:                                             ; preds = %bb39, %bb25, %bb20
  br label %bb21

bb2:                                              ; preds = %bb1
  %_35 = load ptr, ptr %_1, align 8
  %_36 = load ptr, ptr %_35, align 8
; invoke <core::fmt::Formatter>::write_str
  %self4 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_36, ptr @alloc_3690db001d3db01550bdb6f3f450de28, i64 3)
          to label %bb3 unwind label %cleanup

bb5:                                              ; preds = %bb26, %bb1
  store ptr null, ptr %slot, align 8
  store i8 1, ptr %state, align 1
  %_37 = load ptr, ptr %_1, align 8
  %_38 = load ptr, ptr %_37, align 8
; invoke <core::fmt::builders::PadAdapter>::wrap
  invoke void @_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptr sret([24 x i8]) align 8 %writer, ptr align 8 %_38, ptr align 8 %slot, ptr %state)
          to label %bb6 unwind label %cleanup

bb3:                                              ; preds = %bb2
  %_54 = zext i1 %self4 to i64
  %26 = trunc nuw i64 %_54 to i1
  br i1 %26, label %bb25, label %bb26

bb25:                                             ; preds = %bb3
  store i8 1, ptr %_0, align 1
  br label %bb40

bb26:                                             ; preds = %bb3
  br label %bb5

bb6:                                              ; preds = %bb5
  %27 = getelementptr inbounds i8, ptr %_1, i64 8
  %_39.0 = load ptr, ptr %27, align 8
  %28 = getelementptr inbounds i8, ptr %27, i64 8
  %_39.1 = load i64, ptr %28, align 8
; invoke <core::fmt::Formatter>::write_str
  %self5 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr %_39.0, i64 %_39.1)
          to label %bb7 unwind label %cleanup

bb7:                                              ; preds = %bb6
  %_57 = zext i1 %self5 to i64
  %29 = trunc nuw i64 %_57 to i1
  br i1 %29, label %bb27, label %bb28

bb27:                                             ; preds = %bb7
  store i8 1, ptr %_0, align 1
  br label %bb39

bb28:                                             ; preds = %bb7
; invoke <core::fmt::Formatter>::write_str
  %self6 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr @alloc_556e4180596b5b612bb6ed6c0cbb55e1, i64 2)
          to label %bb8 unwind label %cleanup

bb8:                                              ; preds = %bb28
  %_60 = zext i1 %self6 to i64
  %30 = trunc nuw i64 %_60 to i1
  br i1 %30, label %bb29, label %bb30

bb29:                                             ; preds = %bb8
  store i8 1, ptr %_0, align 1
  br label %bb39

bb30:                                             ; preds = %bb8
  store i8 0, ptr %_50, align 1
  %31 = getelementptr inbounds i8, ptr %_1, i64 24
  %_18 = load ptr, ptr %31, align 8
; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#0}
  %self7 = invoke zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptr align 4 %_18, ptr align 8 %writer)
          to label %bb9 unwind label %cleanup

bb9:                                              ; preds = %bb30
  %_63 = zext i1 %self7 to i64
  %32 = trunc nuw i64 %_63 to i1
  br i1 %32, label %bb31, label %bb32

bb31:                                             ; preds = %bb9
  store i8 1, ptr %_0, align 1
  br label %bb21

bb32:                                             ; preds = %bb9
; invoke <core::fmt::Formatter>::write_str
  %33 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr @alloc_f7ac0c356c6ac6fcd03badb685e09ca2, i64 2)
          to label %bb10 unwind label %cleanup

bb10:                                             ; preds = %bb32
  %34 = zext i1 %33 to i8
  store i8 %34, ptr %_0, align 1
  br label %bb19

bb21:                                             ; preds = %bb40, %bb31, %bb19
  %35 = load i8, ptr %_0, align 1
  %36 = trunc nuw i8 %35 to i1
  ret i1 %36

bb39:                                             ; preds = %bb27, %bb29
  br label %bb40

bb4:                                              ; No predecessors!
  unreachable

bb22:                                             ; preds = %bb23, %bb24
  %37 = load ptr, ptr %0, align 8
  %38 = getelementptr inbounds i8, ptr %0, i64 8
  %39 = load i32, ptr %38, align 8
  %40 = insertvalue { ptr, i32 } poison, ptr %37, 0
  %41 = insertvalue { ptr, i32 } %40, i32 %39, 1
  resume { ptr, i32 } %41

bb23:                                             ; preds = %bb24
  br label %bb22
}

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>::{closure#0}
; Function Attrs: inlinehint uwtable
define zeroext i1 @_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmts_0E0B1h_(ptr align 8 %_1) unnamed_addr #1 personality ptr @rust_eh_personality {
start:
  %0 = alloca [16 x i8], align 8
  %_50 = alloca [1 x i8], align 1
  %prefix = alloca [16 x i8], align 8
  %writer = alloca [24 x i8], align 8
  %state = alloca [1 x i8], align 1
  %slot = alloca [24 x i8], align 8
  %_0 = alloca [1 x i8], align 1
  store i8 1, ptr %_50, align 1
  %self = load ptr, ptr %_1, align 8
  %_51 = load ptr, ptr %self, align 8
  %1 = getelementptr inbounds i8, ptr %_51, i64 16
  %_53 = load i32, ptr %1, align 8
  %_52 = and i32 %_53, 8388608
  %2 = icmp eq i32 %_52, 0
  br i1 %2, label %bb11, label %bb1

bb11:                                             ; preds = %start
  %_40 = load ptr, ptr %_1, align 8
  %3 = getelementptr inbounds i8, ptr %_40, i64 9
  %4 = load i8, ptr %3, align 1
  %_24 = trunc nuw i8 %4 to i1
  br i1 %_24, label %bb12, label %bb13

bb1:                                              ; preds = %start
  %_34 = load ptr, ptr %_1, align 8
  %5 = getelementptr inbounds i8, ptr %_34, i64 9
  %6 = load i8, ptr %5, align 1
  %_3 = trunc nuw i8 %6 to i1
  br i1 %_3, label %bb5, label %bb2

bb13:                                             ; preds = %bb11
  store ptr @alloc_fe2ddbdc622a788f5416063d1e8d8ad4, ptr %prefix, align 8, !captures !4
  %7 = getelementptr inbounds i8, ptr %prefix, i64 8
  store i64 3, ptr %7, align 8
  br label %bb14

bb12:                                             ; preds = %bb11
  store ptr @alloc_94b00be069aafad82a2c6df764237b82, ptr %prefix, align 8, !captures !4
  %8 = getelementptr inbounds i8, ptr %prefix, i64 8
  store i64 2, ptr %8, align 8
  br label %bb14

bb14:                                             ; preds = %bb12, %bb13
  %_41 = load ptr, ptr %_1, align 8
  %_42 = load ptr, ptr %_41, align 8
  %9 = load ptr, ptr %prefix, align 8
  %10 = getelementptr inbounds i8, ptr %prefix, i64 8
  %11 = load i64, ptr %10, align 8
; invoke <core::fmt::Formatter>::write_str
  %self1 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_42, ptr %9, i64 %11)
          to label %bb15 unwind label %cleanup

bb24:                                             ; preds = %cleanup
  %12 = load i8, ptr %_50, align 1
  %13 = trunc nuw i8 %12 to i1
  br i1 %13, label %bb23, label %bb22

cleanup:                                          ; preds = %bb32, %bb30, %bb28, %bb6, %bb5, %bb2, %bb38, %bb36, %bb34, %bb14
  %14 = landingpad { ptr, i32 }
          cleanup
  %15 = extractvalue { ptr, i32 } %14, 0
  %16 = extractvalue { ptr, i32 } %14, 1
  store ptr %15, ptr %0, align 8
  %17 = getelementptr inbounds i8, ptr %0, i64 8
  store i32 %16, ptr %17, align 8
  br label %bb24

bb15:                                             ; preds = %bb14
  %_66 = zext i1 %self1 to i64
  %18 = trunc nuw i64 %_66 to i1
  br i1 %18, label %bb33, label %bb34

bb33:                                             ; preds = %bb15
  store i8 1, ptr %_0, align 1
  br label %bb20

bb34:                                             ; preds = %bb15
  %_43 = load ptr, ptr %_1, align 8
  %_44 = load ptr, ptr %_43, align 8
  %19 = getelementptr inbounds i8, ptr %_1, i64 8
  %_45.0 = load ptr, ptr %19, align 8
  %20 = getelementptr inbounds i8, ptr %19, i64 8
  %_45.1 = load i64, ptr %20, align 8
; invoke <core::fmt::Formatter>::write_str
  %self2 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_44, ptr %_45.0, i64 %_45.1)
          to label %bb16 unwind label %cleanup

bb16:                                             ; preds = %bb34
  %_69 = zext i1 %self2 to i64
  %21 = trunc nuw i64 %_69 to i1
  br i1 %21, label %bb35, label %bb36

bb35:                                             ; preds = %bb16
  store i8 1, ptr %_0, align 1
  br label %bb20

bb36:                                             ; preds = %bb16
  %_46 = load ptr, ptr %_1, align 8
  %_47 = load ptr, ptr %_46, align 8
; invoke <core::fmt::Formatter>::write_str
  %self3 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_47, ptr @alloc_556e4180596b5b612bb6ed6c0cbb55e1, i64 2)
          to label %bb17 unwind label %cleanup

bb17:                                             ; preds = %bb36
  %_72 = zext i1 %self3 to i64
  %22 = trunc nuw i64 %_72 to i1
  br i1 %22, label %bb37, label %bb38

bb37:                                             ; preds = %bb17
  store i8 1, ptr %_0, align 1
  br label %bb20

bb38:                                             ; preds = %bb17
  store i8 0, ptr %_50, align 1
  %23 = getelementptr inbounds i8, ptr %_1, i64 24
  %_31 = load ptr, ptr %23, align 8
  %_48 = load ptr, ptr %_1, align 8
  %_49 = load ptr, ptr %_48, align 8
; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#1}
  %24 = invoke zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptr align 4 %_31, ptr align 8 %_49)
          to label %bb18 unwind label %cleanup

bb18:                                             ; preds = %bb38
  %25 = zext i1 %24 to i8
  store i8 %25, ptr %_0, align 1
  br label %bb19

bb19:                                             ; preds = %bb10, %bb18
  br label %bb21

bb20:                                             ; preds = %bb33, %bb35, %bb37
  br label %bb40

bb40:                                             ; preds = %bb39, %bb25, %bb20
  br label %bb21

bb2:                                              ; preds = %bb1
  %_35 = load ptr, ptr %_1, align 8
  %_36 = load ptr, ptr %_35, align 8
; invoke <core::fmt::Formatter>::write_str
  %self4 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %_36, ptr @alloc_3690db001d3db01550bdb6f3f450de28, i64 3)
          to label %bb3 unwind label %cleanup

bb5:                                              ; preds = %bb26, %bb1
  store ptr null, ptr %slot, align 8
  store i8 1, ptr %state, align 1
  %_37 = load ptr, ptr %_1, align 8
  %_38 = load ptr, ptr %_37, align 8
; invoke <core::fmt::builders::PadAdapter>::wrap
  invoke void @_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptr sret([24 x i8]) align 8 %writer, ptr align 8 %_38, ptr align 8 %slot, ptr %state)
          to label %bb6 unwind label %cleanup

bb3:                                              ; preds = %bb2
  %_54 = zext i1 %self4 to i64
  %26 = trunc nuw i64 %_54 to i1
  br i1 %26, label %bb25, label %bb26

bb25:                                             ; preds = %bb3
  store i8 1, ptr %_0, align 1
  br label %bb40

bb26:                                             ; preds = %bb3
  br label %bb5

bb6:                                              ; preds = %bb5
  %27 = getelementptr inbounds i8, ptr %_1, i64 8
  %_39.0 = load ptr, ptr %27, align 8
  %28 = getelementptr inbounds i8, ptr %27, i64 8
  %_39.1 = load i64, ptr %28, align 8
; invoke <core::fmt::Formatter>::write_str
  %self5 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr %_39.0, i64 %_39.1)
          to label %bb7 unwind label %cleanup

bb7:                                              ; preds = %bb6
  %_57 = zext i1 %self5 to i64
  %29 = trunc nuw i64 %_57 to i1
  br i1 %29, label %bb27, label %bb28

bb27:                                             ; preds = %bb7
  store i8 1, ptr %_0, align 1
  br label %bb39

bb28:                                             ; preds = %bb7
; invoke <core::fmt::Formatter>::write_str
  %self6 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr @alloc_556e4180596b5b612bb6ed6c0cbb55e1, i64 2)
          to label %bb8 unwind label %cleanup

bb8:                                              ; preds = %bb28
  %_60 = zext i1 %self6 to i64
  %30 = trunc nuw i64 %_60 to i1
  br i1 %30, label %bb29, label %bb30

bb29:                                             ; preds = %bb8
  store i8 1, ptr %_0, align 1
  br label %bb39

bb30:                                             ; preds = %bb8
  store i8 0, ptr %_50, align 1
  %31 = getelementptr inbounds i8, ptr %_1, i64 24
  %_18 = load ptr, ptr %31, align 8
; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#1}
  %self7 = invoke zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptr align 4 %_18, ptr align 8 %writer)
          to label %bb9 unwind label %cleanup

bb9:                                              ; preds = %bb30
  %_63 = zext i1 %self7 to i64
  %32 = trunc nuw i64 %_63 to i1
  br i1 %32, label %bb31, label %bb32

bb31:                                             ; preds = %bb9
  store i8 1, ptr %_0, align 1
  br label %bb21

bb32:                                             ; preds = %bb9
; invoke <core::fmt::Formatter>::write_str
  %33 = invoke zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8 %writer, ptr @alloc_f7ac0c356c6ac6fcd03badb685e09ca2, i64 2)
          to label %bb10 unwind label %cleanup

bb10:                                             ; preds = %bb32
  %34 = zext i1 %33 to i8
  store i8 %34, ptr %_0, align 1
  br label %bb19

bb21:                                             ; preds = %bb40, %bb31, %bb19
  %35 = load i8, ptr %_0, align 1
  %36 = trunc nuw i8 %35 to i1
  ret i1 %36

bb39:                                             ; preds = %bb27, %bb29
  br label %bb40

bb4:                                              ; No predecessors!
  unreachable

bb22:                                             ; preds = %bb23, %bb24
  %37 = load ptr, ptr %0, align 8
  %38 = getelementptr inbounds i8, ptr %0, i64 8
  %39 = load i32, ptr %38, align 8
  %40 = insertvalue { ptr, i32 } poison, ptr %37, 0
  %41 = insertvalue { ptr, i32 } %40, i32 %39, 1
  resume { ptr, i32 } %41

bb23:                                             ; preds = %bb24
  br label %bb22
}

; <repro::Point as core::fmt::Debug>::fmt::{closure#0}
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptr align 4 %_1, ptr align 8 %f) unnamed_addr #1 {
start:
; call <u32 as core::fmt::Debug>::fmt
  %_0 = call zeroext i1 @_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptr align 4 %_1, ptr align 8 %f) #4
  ret i1 %_0
}

; <repro::Point as core::fmt::Debug>::fmt::{closure#1}
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptr align 4 %_1, ptr align 8 %f) unnamed_addr #1 {
start:
  %_3 = getelementptr inbounds i8, ptr %_1, i64 4
; call <u32 as core::fmt::Debug>::fmt
  %_0 = call zeroext i1 @_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptr align 4 %_3, ptr align 8 %f) #4
  ret i1 %_0
}

; <repro::Point as core::fmt::Debug>::fmt
; Function Attrs: uwtable
define zeroext i1 @_RNvXCs36UsywoTqr_5reproNtB2_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt(ptr align 4 %self, ptr align 8 %f) unnamed_addr #0 {
start:
  %_6 = alloca [16 x i8], align 8
; call <core::fmt::Formatter>::debug_struct
  call void @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter12debug_struct(ptr sret([16 x i8]) align 8 %_6, ptr align 8 %f, ptr @alloc_bf0ef947d182249ea2203578272460c5, i64 5)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>
  %_4 = call align 8 ptr @_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptr align 8 %_6, ptr @alloc_37f62a15fae368a2423b3a16c75adcc9, i64 1, ptr align 4 %self)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>
  %_3 = call align 8 ptr @_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptr align 8 %_4, ptr @alloc_cd51bc84b49b21a3fdb2800731f25c21, i64 1, ptr align 4 %self)
; call <core::fmt::builders::DebugStruct>::finish
  %_0 = call zeroext i1 @_RNvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB5_11DebugStruct6finish(ptr align 8 %_3)
  ret i1 %_0
}

; <u32 as core::fmt::Debug>::fmt
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptr align 4 %self, ptr align 8 %f) unnamed_addr #1 {
start:
  %_0 = alloca [1 x i8], align 1
  %0 = getelementptr inbounds i8, ptr %f, i64 16
  %_4 = load i32, ptr %0, align 8
  %_3 = and i32 %_4, 33554432
  %1 = icmp eq i32 %_3, 0
  br i1 %1, label %bb2, label %bb1

bb2:                                              ; preds = %start
  %2 = getelementptr inbounds i8, ptr %f, i64 16
  %_6 = load i32, ptr %2, align 8
  %_5 = and i32 %_6, 67108864
  %3 = icmp eq i32 %_5, 0
  br i1 %3, label %bb4, label %bb3

bb1:                                              ; preds = %start
; call <u32 as core::fmt::LowerHex>::fmt
  %4 = call zeroext i1 @_RNvXsu_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8LowerHex3fmt(ptr align 4 %self, ptr align 8 %f)
  %5 = zext i1 %4 to i8
  store i8 %5, ptr %_0, align 1
  br label %bb6

bb4:                                              ; preds = %bb2
; call <u32 as core::fmt::Display>::fmt
  %6 = call zeroext i1 @_RNvXs8_NtNtNtCs2xrpM2fV95l_4core3fmt3num3impmNtB9_7Display3fmt(ptr align 4 %self, ptr align 8 %f)
  %7 = zext i1 %6 to i8
  store i8 %7, ptr %_0, align 1
  br label %bb5

bb3:                                              ; preds = %bb2
; call <u32 as core::fmt::UpperHex>::fmt
  %8 = call zeroext i1 @_RNvXsw_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8UpperHex3fmt(ptr align 4 %self, ptr align 8 %f)
  %9 = zext i1 %8 to i8
  store i8 %9, ptr %_0, align 1
  br label %bb5

bb5:                                              ; preds = %bb3, %bb4
  br label %bb6

bb6:                                              ; preds = %bb1, %bb5
  %10 = load i8, ptr %_0, align 1
  %11 = trunc nuw i8 %10 to i1
  ret i1 %11
}

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #2

; Function Attrs: nounwind uwtable
declare i32 @rust_eh_personality(i32, i32, i64, ptr, ptr) unnamed_addr #3

; <core::fmt::Formatter>::write_str
; Function Attrs: uwtable
declare zeroext i1 @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptr align 8, ptr, i64) unnamed_addr #0

; <core::fmt::builders::PadAdapter>::wrap
; Function Attrs: uwtable
declare void @_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptr sret([24 x i8]) align 8, ptr align 8, ptr align 8, ptr) unnamed_addr #0

; <core::fmt::Formatter>::debug_struct
; Function Attrs: uwtable
declare void @_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter12debug_struct(ptr sret([16 x i8]) align 8, ptr align 8, ptr, i64) unnamed_addr #0

; <core::fmt::builders::DebugStruct>::finish
; Function Attrs: uwtable
declare zeroext i1 @_RNvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB5_11DebugStruct6finish(ptr align 8) unnamed_addr #0

; <u32 as core::fmt::Display>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXs8_NtNtNtCs2xrpM2fV95l_4core3fmt3num3impmNtB9_7Display3fmt(ptr align 4, ptr align 8) unnamed_addr #0

; <u32 as core::fmt::UpperHex>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXsw_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8UpperHex3fmt(ptr align 4, ptr align 8) unnamed_addr #0

; <u32 as core::fmt::LowerHex>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXsu_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8LowerHex3fmt(ptr align 4, ptr align 8) unnamed_addr #0

attributes #0 = { uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #1 = { inlinehint uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #3 = { nounwind uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #4 = { inlinehint }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{i32 7, !"frame-pointer", i32 1}
!3 = !{!"rustc version 1.99.0-nightly (77cf889bc 2026-07-12)"}
!4 = !{!"address", !"read_provenance"}

after.ll

Details
; ModuleID = 'repro.ada4be50ab0d3186-cgu.0'
source_filename = "repro.ada4be50ab0d3186-cgu.0"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx11.0.0"

@vtable.0 = private unnamed_addr constant <{ [24 x i8], ptr }> <{ [24 x i8] c"\00\00\00\00\00\00\00\00\08\00\00\00\00\00\00\00\08\00\00\00\00\00\00\00", ptr @_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmt0EB1w_3fmtB10_ }>, align 8
@vtable.1 = private unnamed_addr constant <{ [24 x i8], ptr }> <{ [24 x i8] c"\00\00\00\00\00\00\00\00\08\00\00\00\00\00\00\00\08\00\00\00\00\00\00\00", ptr @_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmts_0EB1w_3fmtB10_ }>, align 8
@alloc_bf0ef947d182249ea2203578272460c5 = private unnamed_addr constant [5 x i8] c"Point", align 1
@alloc_37f62a15fae368a2423b3a16c75adcc9 = private unnamed_addr constant [1 x i8] c"x", align 1
@alloc_cd51bc84b49b21a3fdb2800731f25c21 = private unnamed_addr constant [1 x i8] c"y", align 1
@alloc_67658ef8123446d70a96e7dbc7651d58 = private unnamed_addr constant [40 x i8] c"formatting closure called more than once", align 1
@alloc_703b1d677d4c28a67f1910a45c16864b = private unnamed_addr constant [33 x i8] c"library/core/src/fmt/builders.rs\00", align 1
@alloc_7fd203ad775cd664e43a5c0ae4634671 = private unnamed_addr constant <{ ptr, [16 x i8] }> <{ ptr @alloc_703b1d677d4c28a67f1910a45c16864b, [16 x i8] c" \00\00\00\00\00\00\00H\00\00\00\15\00\00\00" }>, align 8

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>
; Function Attrs: uwtable
define align 8 ptr @_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr align 4 %value_fmt) unnamed_addr #0 personality ptr @rust_eh_personality {
start:
  %0 = alloca [16 x i8], align 8
  %_6 = alloca [8 x i8], align 8
  store ptr %value_fmt, ptr %_6, align 8
; invoke <core::fmt::builders::DebugStruct>::field
  %_0 = invoke align 8 ptr @_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr %_6, ptr align 8 @vtable.0)
          to label %bb1 unwind label %cleanup

bb3:                                              ; preds = %cleanup
  %1 = load ptr, ptr %0, align 8
  %2 = getelementptr inbounds i8, ptr %0, i64 8
  %3 = load i32, ptr %2, align 8
  %4 = insertvalue { ptr, i32 } poison, ptr %1, 0
  %5 = insertvalue { ptr, i32 } %4, i32 %3, 1
  resume { ptr, i32 } %5

cleanup:                                          ; preds = %start
  %6 = landingpad { ptr, i32 }
          cleanup
  %7 = extractvalue { ptr, i32 } %6, 0
  %8 = extractvalue { ptr, i32 } %6, 1
  store ptr %7, ptr %0, align 8
  %9 = getelementptr inbounds i8, ptr %0, i64 8
  store i32 %8, ptr %9, align 8
  br label %bb3

bb1:                                              ; preds = %start
  ret ptr %_0
}

; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>
; Function Attrs: uwtable
define align 8 ptr @_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr align 4 %value_fmt) unnamed_addr #0 personality ptr @rust_eh_personality {
start:
  %0 = alloca [16 x i8], align 8
  %_6 = alloca [8 x i8], align 8
  store ptr %value_fmt, ptr %_6, align 8
; invoke <core::fmt::builders::DebugStruct>::field
  %_0 = invoke align 8 ptr @_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptr align 8 %self, ptr %name.0, i64 %name.1, ptr %_6, ptr align 8 @vtable.1)
          to label %bb1 unwind label %cleanup

bb3:                                              ; preds = %cleanup
  %1 = load ptr, ptr %0, align 8
  %2 = getelementptr inbounds i8, ptr %0, i64 8
  %3 = load i32, ptr %2, align 8
  %4 = insertvalue { ptr, i32 } poison, ptr %1, 0
  %5 = insertvalue { ptr, i32 } %4, i32 %3, 1
  resume { ptr, i32 } %5

cleanup:                                          ; preds = %start
  %6 = landingpad { ptr, i32 }
          cleanup
  %7 = extractvalue { ptr, i32 } %6, 0
  %8 = extractvalue { ptr, i32 } %6, 1
  store ptr %7, ptr %0, align 8
  %9 = getelementptr inbounds i8, ptr %0, i64 8
  store i32 %8, ptr %9, align 8
  br label %bb3

bb1:                                              ; preds = %start
  ret ptr %_0
}

; <repro::Point as core::fmt::Debug>::fmt::{closure#0}
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt0B4_(ptr align 4 %_1, ptr align 8 %f) unnamed_addr #1 {
start:
; call <u32 as core::fmt::Debug>::fmt
  %_0 = call zeroext i1 @_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptr align 4 %_1, ptr align 8 %f) #4
  ret i1 %_0
}

; <repro::Point as core::fmt::Debug>::fmt::{closure#1}
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmts_0B4_(ptr align 4 %_1, ptr align 8 %f) unnamed_addr #1 {
start:
  %_3 = getelementptr inbounds i8, ptr %_1, i64 4
; call <u32 as core::fmt::Debug>::fmt
  %_0 = call zeroext i1 @_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptr align 4 %_3, ptr align 8 %f) #4
  ret i1 %_0
}

; <repro::Point as core::fmt::Debug>::fmt
; Function Attrs: uwtable
define zeroext i1 @_RNvXCseUiCQVDuJCY_5reproNtB2_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt(ptr align 4 %self, ptr align 8 %f) unnamed_addr #0 {
start:
  %_6 = alloca [16 x i8], align 8
; call <core::fmt::Formatter>::debug_struct
  call void @_RNvMsa_NtCs1Jl4pBjAano_4core3fmtNtB5_9Formatter12debug_struct(ptr sret([16 x i8]) align 8 %_6, ptr align 8 %f, ptr @alloc_bf0ef947d182249ea2203578272460c5, i64 5)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>
  %_4 = call align 8 ptr @_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptr align 8 %_6, ptr @alloc_37f62a15fae368a2423b3a16c75adcc9, i64 1, ptr align 4 %self)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>
  %_3 = call align 8 ptr @_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptr align 8 %_4, ptr @alloc_cd51bc84b49b21a3fdb2800731f25c21, i64 1, ptr align 4 %self)
; call <core::fmt::builders::DebugStruct>::finish
  %_0 = call zeroext i1 @_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct6finish(ptr align 8 %_3)
  ret i1 %_0
}

; <core::fmt::builders::DebugOnce<<repro::Point as core::fmt::Debug>::fmt::{closure#0}> as core::fmt::Debug>::fmt
; Function Attrs: uwtable
define zeroext i1 @_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmt0EB1w_3fmtB10_(ptr align 8 %self, ptr align 8 %f) unnamed_addr #0 {
start:
  %_3 = alloca [8 x i8], align 8
  %0 = load ptr, ptr %self, align 8
  store ptr %0, ptr %_3, align 8
  store ptr null, ptr %self, align 8
  %1 = load ptr, ptr %_3, align 8
  %2 = ptrtoint ptr %1 to i64
  %3 = icmp eq i64 %2, 0
  %_5 = select i1 %3, i64 0, i64 1
  %4 = trunc nuw i64 %_5 to i1
  br i1 %4, label %bb3, label %bb2

bb3:                                              ; preds = %start
  %value_fmt = load ptr, ptr %_3, align 8
; call <repro::Point as core::fmt::Debug>::fmt::{closure#0}
  %_0 = call zeroext i1 @_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt0B4_(ptr align 4 %value_fmt, ptr align 8 %f) #4
  ret i1 %_0

bb2:                                              ; preds = %start
; call core::panicking::panic_fmt
  call void @_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr @alloc_67658ef8123446d70a96e7dbc7651d58, ptr inttoptr (i64 81 to ptr), ptr align 8 @alloc_7fd203ad775cd664e43a5c0ae4634671) #5
  unreachable

bb1:                                              ; No predecessors!
  unreachable
}

; <core::fmt::builders::DebugOnce<<repro::Point as core::fmt::Debug>::fmt::{closure#1}> as core::fmt::Debug>::fmt
; Function Attrs: uwtable
define zeroext i1 @_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmts_0EB1w_3fmtB10_(ptr align 8 %self, ptr align 8 %f) unnamed_addr #0 {
start:
  %_3 = alloca [8 x i8], align 8
  %0 = load ptr, ptr %self, align 8
  store ptr %0, ptr %_3, align 8
  store ptr null, ptr %self, align 8
  %1 = load ptr, ptr %_3, align 8
  %2 = ptrtoint ptr %1 to i64
  %3 = icmp eq i64 %2, 0
  %_5 = select i1 %3, i64 0, i64 1
  %4 = trunc nuw i64 %_5 to i1
  br i1 %4, label %bb3, label %bb2

bb3:                                              ; preds = %start
  %value_fmt = load ptr, ptr %_3, align 8
; call <repro::Point as core::fmt::Debug>::fmt::{closure#1}
  %_0 = call zeroext i1 @_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmts_0B4_(ptr align 4 %value_fmt, ptr align 8 %f) #4
  ret i1 %_0

bb2:                                              ; preds = %start
; call core::panicking::panic_fmt
  call void @_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr @alloc_67658ef8123446d70a96e7dbc7651d58, ptr inttoptr (i64 81 to ptr), ptr align 8 @alloc_7fd203ad775cd664e43a5c0ae4634671) #5
  unreachable

bb1:                                              ; No predecessors!
  unreachable
}

; <u32 as core::fmt::Debug>::fmt
; Function Attrs: inlinehint uwtable
define internal zeroext i1 @_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptr align 4 %self, ptr align 8 %f) unnamed_addr #1 {
start:
  %_0 = alloca [1 x i8], align 1
  %0 = getelementptr inbounds i8, ptr %f, i64 16
  %_4 = load i32, ptr %0, align 8
  %_3 = and i32 %_4, 33554432
  %1 = icmp eq i32 %_3, 0
  br i1 %1, label %bb2, label %bb1

bb2:                                              ; preds = %start
  %2 = getelementptr inbounds i8, ptr %f, i64 16
  %_6 = load i32, ptr %2, align 8
  %_5 = and i32 %_6, 67108864
  %3 = icmp eq i32 %_5, 0
  br i1 %3, label %bb4, label %bb3

bb1:                                              ; preds = %start
; call <u32 as core::fmt::LowerHex>::fmt
  %4 = call zeroext i1 @_RNvXsu_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8LowerHex3fmt(ptr align 4 %self, ptr align 8 %f)
  %5 = zext i1 %4 to i8
  store i8 %5, ptr %_0, align 1
  br label %bb6

bb4:                                              ; preds = %bb2
; call <u32 as core::fmt::Display>::fmt
  %6 = call zeroext i1 @_RNvXs8_NtNtNtCs1Jl4pBjAano_4core3fmt3num3impmNtB9_7Display3fmt(ptr align 4 %self, ptr align 8 %f)
  %7 = zext i1 %6 to i8
  store i8 %7, ptr %_0, align 1
  br label %bb5

bb3:                                              ; preds = %bb2
; call <u32 as core::fmt::UpperHex>::fmt
  %8 = call zeroext i1 @_RNvXsw_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8UpperHex3fmt(ptr align 4 %self, ptr align 8 %f)
  %9 = zext i1 %8 to i8
  store i8 %9, ptr %_0, align 1
  br label %bb5

bb5:                                              ; preds = %bb3, %bb4
  br label %bb6

bb6:                                              ; preds = %bb1, %bb5
  %10 = load i8, ptr %_0, align 1
  %11 = trunc nuw i8 %10 to i1
  ret i1 %11
}

; Function Attrs: nounwind uwtable
declare i32 @rust_eh_personality(i32, i32, i64, ptr, ptr) unnamed_addr #2

; <core::fmt::builders::DebugStruct>::field
; Function Attrs: uwtable
declare align 8 ptr @_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptr align 8, ptr, i64, ptr, ptr align 8) unnamed_addr #0

; <core::fmt::Formatter>::debug_struct
; Function Attrs: uwtable
declare void @_RNvMsa_NtCs1Jl4pBjAano_4core3fmtNtB5_9Formatter12debug_struct(ptr sret([16 x i8]) align 8, ptr align 8, ptr, i64) unnamed_addr #0

; <core::fmt::builders::DebugStruct>::finish
; Function Attrs: uwtable
declare zeroext i1 @_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct6finish(ptr align 8) unnamed_addr #0

; core::panicking::panic_fmt
; Function Attrs: cold noinline noreturn uwtable
declare void @_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr, ptr, ptr align 8) unnamed_addr #3

; <u32 as core::fmt::Display>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXs8_NtNtNtCs1Jl4pBjAano_4core3fmt3num3impmNtB9_7Display3fmt(ptr align 4, ptr align 8) unnamed_addr #0

; <u32 as core::fmt::UpperHex>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXsw_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8UpperHex3fmt(ptr align 4, ptr align 8) unnamed_addr #0

; <u32 as core::fmt::LowerHex>::fmt
; Function Attrs: uwtable
declare zeroext i1 @_RNvXsu_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8LowerHex3fmt(ptr align 4, ptr align 8) unnamed_addr #0

attributes #0 = { uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #1 = { inlinehint uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #2 = { nounwind uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #3 = { cold noinline noreturn uwtable "frame-pointer"="non-leaf" "probe-stack"="inline-asm" "target-cpu"="apple-m1" }
attributes #4 = { inlinehint }
attributes #5 = { noinline noreturn }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{i32 7, !"frame-pointer", i32 1}
!3 = !{!"rustc version 1.99.0-dev"}

@connortsui20 connortsui20 changed the title make debug builders with closures impl with dyn Implement Debug helpers that take closures with &mut dyn FnMut Jul 14, 2026
@hanna-kruppe

hanna-kruppe commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

r? me (happy to hand this back to @tgross35 if that’s preferred)

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 14, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 14, 2026
Implement `Debug` helpers that take closures with `&mut dyn FnMut`
@connortsui20

connortsui20 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Oh thats fine @hanna-kruppe I just didn't know if you were in the rotation (I've only ever directly requested from 2 other people total in the past 🤣)

Comment thread library/core/src/fmt/builders.rs Outdated
@rust-bors

rust-bors Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: d43d144 (d43d144f0793e0ced6f82ce6a8679ce7f56a007d)
Base parent: da80ed0 (da80ed0708a09dc096c184345d6eb42cbcd50a1e)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (d43d144): comparison URL.

Overall result: ❌✅ regressions and improvements - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.2% [0.2%, 0.2%] 1
Improvements ✅
(primary)
-0.4% [-0.4%, -0.3%] 2
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) -0.4% [-0.4%, -0.3%] 2

Max RSS (memory usage)

Results (secondary 3.0%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
3.0% [3.0%, 3.0%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Cycles

Results (secondary -2.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.7% [-3.2%, -2.2%] 2
All ❌✅ (primary) - - 0

Binary size

Results (primary -0.2%, secondary 0.3%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.3% [0.0%, 0.6%] 2
Improvements ✅
(primary)
-0.2% [-0.2%, -0.2%] 4
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) -0.2% [-0.2%, -0.2%] 4

Bootstrap: 491.202s -> 491.87s (0.14%)
Artifact size: 389.32 MiB -> 389.43 MiB (0.03%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 14, 2026
@jmillikin

Copy link
Copy Markdown
Contributor

optional: As discussed in the other thread, since the pre-existing fn field(&mut self, name: &str, value: &dyn fmt::Debug) already has the right signature, you can put the logic back into that function (where it used to exist) and use Cell<Option<..>> to shrink the LLVM IR further (372 -> 292). Adding a helper struct to avoid a per-call closure is also slightly helpful (292 -> 273):

struct DebugOnce<F>(Cell<Option<F>>);

impl<F> fmt::Debug for DebugOnce<F>
where
    F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(x) = self.0.replace(None) {
            return (x)(f);
        }
        Ok(())
    }
}

// ...

    pub fn field_with<F>(&mut self, name: &str, value_fmt: F) -> &mut Self
    where
        F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
    {
        self.field(name, &DebugOnce(Cell::new(Some(value_fmt))))
    }

@tgross35

Copy link
Copy Markdown
Contributor

r? me (happy to hand this back to @tgross35 if that’s preferred)

No need, all yours :)

@connortsui20

Copy link
Copy Markdown
Contributor Author

@jmillikin could you help me understand why using this Cell via a helper struct reduces the LLVM IR by a whole 30%? I'm surprised that adding an additional type here reduces the LLVM complexity (rather than increase it). Is this a missing optimization / workaround? Or something else? And is the indirection simply deduplicating some logic?

@hanna-kruppe

Copy link
Copy Markdown
Contributor

I wrote a bigger test program to compare the growth as number of field / field_with calls increases (code here). Note that I'm measuring the tally of cargo llvm-lines because that more closely approximates "number of LLVM IR instructions", by only counting lines inside function definition that plausibly look like they start a new instruction (no labels, debug metadata, continuation lines of invoke/switch, blank lines, etc.).

Field count field field_with (main) field_with (this PR)
3 fields 30 846 351
6 fields 54 1686 696
9 fields 78 2526 1041
(per field) 12 420 172.5

The "per field" column is just the extra lines of 6 vs 3 fields and 9 vs 6 fields divided by 3. Not sure why it's not an integer. Don't read too much into it.

I also skimmed the LLVM IR generated for field_with with this PR. Some things that stand out:

  1. The field_with<F> instantiations themselves do quite a lot of setup to spill things to the stack, set up the closure object on the stack, and do apparently no-op cleanup on panics.
  2. The FnMut closure itself has also quite a lot of unnecessary gunk, including debug spills, Option juggling, potentially panicking from the expect, and finally setting up the call to the actually interesting closure.
  3. For some reason (maybe to support Box<dyn FnMut> -> Box<dyn FnOnce> casts?) the vtables for the FnMut closures include a bunch of unnecessary shim code that ultimately calls the actual code of the closure.

I guess @jmillikin's approach side-steps the third problem by not involving closure trait objects.

@hanna-kruppe hanna-kruppe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By my measurements above, this PR does improve things significantly. But DebugStruct::field_with vs field still seems to be a ca 10x difference in LLVM IR per call site despite that improvement. I'd like to take the improvements anyway and try to squeeze out more gains in a follow up PR, but it looks like there's a runtime performance regression right now. So if you're up for it, I'd suggest trying the approach described by @jmillikin.

View changes since this review

Comment thread library/core/src/fmt/builders.rs Outdated
Comment thread library/core/src/fmt/builders.rs Outdated
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 15, 2026
@rustbot

rustbot commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20 connortsui20 changed the title Implement Debug helpers that take closures with &mut dyn FnMut Implement Debug helpers via Cell Jul 15, 2026
@connortsui20

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 15, 2026
@hanna-kruppe

Copy link
Copy Markdown
Contributor

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 15, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 15, 2026
@rust-bors

rust-bors Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: fcf8e93 (fcf8e931bacd85b876c9aab2a50c3808624ac846)
Base parent: df1ae0f (df1ae0f7dd168513de2d89beb2cb7a95d4fdc118)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (fcf8e93): comparison URL.

Overall result: ❌ regressions - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.2% [0.2%, 0.2%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results (primary 1.0%, secondary 4.6%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.0% [1.0%, 1.0%] 1
Regressions ❌
(secondary)
4.6% [4.2%, 4.9%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 1.0% [1.0%, 1.0%] 1

Cycles

Results (primary 1.8%, secondary 0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.8% [1.8%, 1.8%] 1
Regressions ❌
(secondary)
4.7% [4.7%, 4.7%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.2% [-2.4%, -2.1%] 2
All ❌✅ (primary) 1.8% [1.8%, 1.8%] 1

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 487.651s -> 489.459s (0.37%)
Artifact size: 389.37 MiB -> 389.96 MiB (0.15%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DebugStruct::field_with and friends are code size footguns

7 participants