Skip to content

Implement Debug helpers via Cell - #159302

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
connortsui20:dyn-debug-helpers
Jul 17, 2026
Merged

Implement Debug helpers via Cell#159302
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
connortsui20:dyn-debug-helpers

Conversation

@connortsui20

@connortsui20connortsui20 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

View all comments

Related to #149745, but does not fix it (yet).

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 FnOncecan'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;pubstructPoint{pubx:u32,puby:u32,}impl fmt::DebugforPoint{fnfmt(&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.

@rustbotrustbot 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

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
ContributorAuthor

before.ll

Details
; ModuleID = 'repro.95a4099cea1845-cgu.0'
source_filename = "repro.95a4099cea1845-cgu.0"targetdatalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"targettriple = "arm64-apple-macosx11.0.0"@alloc_fe2ddbdc622a788f5416063d1e8d8ad4 = privateunnamed_addrconstant [3 x i8] c" { ", align1@alloc_94b00be069aafad82a2c6df764237b82 = privateunnamed_addrconstant [2 x i8] c", ", align1@alloc_556e4180596b5b612bb6ed6c0cbb55e1 = privateunnamed_addrconstant [2 x i8] c": ", align1@alloc_3690db001d3db01550bdb6f3f450de28 = privateunnamed_addrconstant [3 x i8] c" {\0A", align1@alloc_f7ac0c356c6ac6fcd03badb685e09ca2 = privateunnamed_addrconstant [2 x i8] c",\0A", align1@alloc_bf0ef947d182249ea2203578272460c5 = privateunnamed_addrconstant [5 x i8] c"Point", align1@alloc_37f62a15fae368a2423b3a16c75adcc9 = privateunnamed_addrconstant [1 x i8] c"x", align1@alloc_cd51bc84b49b21a3fdb2800731f25c21 = privateunnamed_addrconstant [1 x i8] c"y", align1; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>; Function Attrs: uwtabledefinealign8ptr@_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptralign8%self, ptr%name.0, i64%name.1, ptralign4%value_fmt) unnamed_addr #0 {
start:
%_8 = alloca [32 x i8], align8%op = alloca [32 x i8], align8%_4 = alloca [1 x i8], align1%0 = getelementptrinboundsi8, ptr%self, i648%1 = loadi8, ptr%0, align8%self1 = truncnuwi8%1toi1storeptr%self, ptr%op, align8%2 = getelementptrinboundsi8, ptr%op, i648storeptr%name.0, ptr%2, align8%3 = getelementptrinboundsi8, ptr%2, i648storei64%name.1, ptr%3, align8%4 = getelementptrinboundsi8, ptr%op, i6424storeptr%value_fmt, ptr%4, align8%_7 = zexti1%self1toi64%5 = truncnuwi64%_7toi1bri1%5, label%bb3, label%bb4
bb3: ; preds = %startstorei81, ptr%_4, align1brlabel%bb1
bb4: ; preds = %startcallvoid@llvm.memcpy.p0.p0.i64(ptralign8%_8, ptralign8%op, i6432, i1false)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>::{closure#0}%6 = callzeroexti1@_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmt0E0B1h_(ptralign8%_8) #4%7 = zexti1%6toi8storei8%7, ptr%_4, align1brlabel%bb1
bb1: ; preds = %bb3, %bb4%8 = getelementptrinboundsi8, ptr%self, i648%9 = loadi8, ptr%_4, align1%10 = truncnuwi8%9toi1%11 = zexti1%10toi8storei8%11, ptr%8, align8%12 = getelementptrinboundsi8, ptr%self, i649storei81, ptr%12, align1retptr%self
bb2: ; No predecessors!unreachable
}
; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>; Function Attrs: uwtabledefinealign8ptr@_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptralign8%self, ptr%name.0, i64%name.1, ptralign4%value_fmt) unnamed_addr #0 {
start:
%_8 = alloca [32 x i8], align8%op = alloca [32 x i8], align8%_4 = alloca [1 x i8], align1%0 = getelementptrinboundsi8, ptr%self, i648%1 = loadi8, ptr%0, align8%self1 = truncnuwi8%1toi1storeptr%self, ptr%op, align8%2 = getelementptrinboundsi8, ptr%op, i648storeptr%name.0, ptr%2, align8%3 = getelementptrinboundsi8, ptr%2, i648storei64%name.1, ptr%3, align8%4 = getelementptrinboundsi8, ptr%op, i6424storeptr%value_fmt, ptr%4, align8%_7 = zexti1%self1toi64%5 = truncnuwi64%_7toi1bri1%5, label%bb3, label%bb4
bb3: ; preds = %startstorei81, ptr%_4, align1brlabel%bb1
bb4: ; preds = %startcallvoid@llvm.memcpy.p0.p0.i64(ptralign8%_8, ptralign8%op, i6432, i1false)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>::{closure#0}%6 = callzeroexti1@_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmts_0E0B1h_(ptralign8%_8) #4%7 = zexti1%6toi8storei8%7, ptr%_4, align1brlabel%bb1
bb1: ; preds = %bb3, %bb4%8 = getelementptrinboundsi8, ptr%self, i648%9 = loadi8, ptr%_4, align1%10 = truncnuwi8%9toi1%11 = zexti1%10toi8storei8%11, ptr%8, align8%12 = getelementptrinboundsi8, ptr%self, i649storei81, ptr%12, align1retptr%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 uwtabledefinezeroexti1@_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmt0E0B1h_(ptralign8%_1) unnamed_addr #1personalityptr@rust_eh_personality {
start:
%0 = alloca [16 x i8], align8%_50 = alloca [1 x i8], align1%prefix = alloca [16 x i8], align8%writer = alloca [24 x i8], align8%state = alloca [1 x i8], align1%slot = alloca [24 x i8], align8%_0 = alloca [1 x i8], align1storei81, ptr%_50, align1%self = loadptr, ptr%_1, align8%_51 = loadptr, ptr%self, align8%1 = getelementptrinboundsi8, ptr%_51, i6416%_53 = loadi32, ptr%1, align8%_52 = andi32%_53, 8388608%2 = icmpeqi32%_52, 0bri1%2, label%bb11, label%bb1
bb11: ; preds = %start%_40 = loadptr, ptr%_1, align8%3 = getelementptrinboundsi8, ptr%_40, i649%4 = loadi8, ptr%3, align1%_24 = truncnuwi8%4toi1bri1%_24, label%bb12, label%bb13
bb1: ; preds = %start%_34 = loadptr, ptr%_1, align8%5 = getelementptrinboundsi8, ptr%_34, i649%6 = loadi8, ptr%5, align1%_3 = truncnuwi8%6toi1bri1%_3, label%bb5, label%bb2
bb13: ; preds = %bb11storeptr@alloc_fe2ddbdc622a788f5416063d1e8d8ad4, ptr%prefix, align8, !captures!4%7 = getelementptrinboundsi8, ptr%prefix, i648storei643, ptr%7, align8brlabel%bb14
bb12: ; preds = %bb11storeptr@alloc_94b00be069aafad82a2c6df764237b82, ptr%prefix, align8, !captures!4%8 = getelementptrinboundsi8, ptr%prefix, i648storei642, ptr%8, align8brlabel%bb14
bb14: ; preds = %bb12, %bb13%_41 = loadptr, ptr%_1, align8%_42 = loadptr, ptr%_41, align8%9 = loadptr, ptr%prefix, align8%10 = getelementptrinboundsi8, ptr%prefix, i648%11 = loadi64, ptr%10, align8; invoke <core::fmt::Formatter>::write_str%self1 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_42, ptr%9, i64%11)
tolabel%bb15unwindlabel%cleanup
bb24: ; preds = %cleanup%12 = loadi8, ptr%_50, align1%13 = truncnuwi8%12toi1bri1%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, 1storeptr%15, ptr%0, align8%17 = getelementptrinboundsi8, ptr%0, i648storei32%16, ptr%17, align8brlabel%bb24
bb15: ; preds = %bb14%_66 = zexti1%self1toi64%18 = truncnuwi64%_66toi1bri1%18, label%bb33, label%bb34
bb33: ; preds = %bb15storei81, ptr%_0, align1brlabel%bb20
bb34: ; preds = %bb15%_43 = loadptr, ptr%_1, align8%_44 = loadptr, ptr%_43, align8%19 = getelementptrinboundsi8, ptr%_1, i648%_45.0 = loadptr, ptr%19, align8%20 = getelementptrinboundsi8, ptr%19, i648%_45.1 = loadi64, ptr%20, align8; invoke <core::fmt::Formatter>::write_str%self2 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_44, ptr%_45.0, i64%_45.1)
tolabel%bb16unwindlabel%cleanup
bb16: ; preds = %bb34%_69 = zexti1%self2toi64%21 = truncnuwi64%_69toi1bri1%21, label%bb35, label%bb36
bb35: ; preds = %bb16storei81, ptr%_0, align1brlabel%bb20
bb36: ; preds = %bb16%_46 = loadptr, ptr%_1, align8%_47 = loadptr, ptr%_46, align8; invoke <core::fmt::Formatter>::write_str%self3 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_47, ptr@alloc_556e4180596b5b612bb6ed6c0cbb55e1, i642)
tolabel%bb17unwindlabel%cleanup
bb17: ; preds = %bb36%_72 = zexti1%self3toi64%22 = truncnuwi64%_72toi1bri1%22, label%bb37, label%bb38
bb37: ; preds = %bb17storei81, ptr%_0, align1brlabel%bb20
bb38: ; preds = %bb17storei80, ptr%_50, align1%23 = getelementptrinboundsi8, ptr%_1, i6424%_31 = loadptr, ptr%23, align8%_48 = loadptr, ptr%_1, align8%_49 = loadptr, ptr%_48, align8; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#0}%24 = invokezeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptralign4%_31, ptralign8%_49)
tolabel%bb18unwindlabel%cleanup
bb18: ; preds = %bb38%25 = zexti1%24toi8storei8%25, ptr%_0, align1brlabel%bb19
bb19: ; preds = %bb10, %bb18brlabel%bb21
bb20: ; preds = %bb33, %bb35, %bb37brlabel%bb40
bb40: ; preds = %bb39, %bb25, %bb20brlabel%bb21
bb2: ; preds = %bb1%_35 = loadptr, ptr%_1, align8%_36 = loadptr, ptr%_35, align8; invoke <core::fmt::Formatter>::write_str%self4 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_36, ptr@alloc_3690db001d3db01550bdb6f3f450de28, i643)
tolabel%bb3unwindlabel%cleanup
bb5: ; preds = %bb26, %bb1storeptrnull, ptr%slot, align8storei81, ptr%state, align1%_37 = loadptr, ptr%_1, align8%_38 = loadptr, ptr%_37, align8; invoke <core::fmt::builders::PadAdapter>::wrapinvokevoid@_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptrsret([24 x i8]) align8%writer, ptralign8%_38, ptralign8%slot, ptr%state)
tolabel%bb6unwindlabel%cleanup
bb3: ; preds = %bb2%_54 = zexti1%self4toi64%26 = truncnuwi64%_54toi1bri1%26, label%bb25, label%bb26
bb25: ; preds = %bb3storei81, ptr%_0, align1brlabel%bb40
bb26: ; preds = %bb3brlabel%bb5
bb6: ; preds = %bb5%27 = getelementptrinboundsi8, ptr%_1, i648%_39.0 = loadptr, ptr%27, align8%28 = getelementptrinboundsi8, ptr%27, i648%_39.1 = loadi64, ptr%28, align8; invoke <core::fmt::Formatter>::write_str%self5 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr%_39.0, i64%_39.1)
tolabel%bb7unwindlabel%cleanup
bb7: ; preds = %bb6%_57 = zexti1%self5toi64%29 = truncnuwi64%_57toi1bri1%29, label%bb27, label%bb28
bb27: ; preds = %bb7storei81, ptr%_0, align1brlabel%bb39
bb28: ; preds = %bb7; invoke <core::fmt::Formatter>::write_str%self6 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr@alloc_556e4180596b5b612bb6ed6c0cbb55e1, i642)
tolabel%bb8unwindlabel%cleanup
bb8: ; preds = %bb28%_60 = zexti1%self6toi64%30 = truncnuwi64%_60toi1bri1%30, label%bb29, label%bb30
bb29: ; preds = %bb8storei81, ptr%_0, align1brlabel%bb39
bb30: ; preds = %bb8storei80, ptr%_50, align1%31 = getelementptrinboundsi8, ptr%_1, i6424%_18 = loadptr, ptr%31, align8; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#0}%self7 = invokezeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptralign4%_18, ptralign8%writer)
tolabel%bb9unwindlabel%cleanup
bb9: ; preds = %bb30%_63 = zexti1%self7toi64%32 = truncnuwi64%_63toi1bri1%32, label%bb31, label%bb32
bb31: ; preds = %bb9storei81, ptr%_0, align1brlabel%bb21
bb32: ; preds = %bb9; invoke <core::fmt::Formatter>::write_str%33 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr@alloc_f7ac0c356c6ac6fcd03badb685e09ca2, i642)
tolabel%bb10unwindlabel%cleanup
bb10: ; preds = %bb32%34 = zexti1%33toi8storei8%34, ptr%_0, align1brlabel%bb19
bb21: ; preds = %bb40, %bb31, %bb19%35 = loadi8, ptr%_0, align1%36 = truncnuwi8%35toi1reti1%36
bb39: ; preds = %bb27, %bb29brlabel%bb40
bb4: ; No predecessors!unreachable
bb22: ; preds = %bb23, %bb24%37 = loadptr, ptr%0, align8%38 = getelementptrinboundsi8, ptr%0, i648%39 = loadi32, ptr%38, align8%40 = insertvalue { ptr, i32 } poison, ptr%37, 0%41 = insertvalue { ptr, i32 } %40, i32%39, 1resume { ptr, i32 } %41
bb23: ; preds = %bb24brlabel%bb22
}
; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>::{closure#0}; Function Attrs: inlinehint uwtabledefinezeroexti1@_RNCINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB8_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1h_5PointNtBa_5Debug3fmts_0E0B1h_(ptralign8%_1) unnamed_addr #1personalityptr@rust_eh_personality {
start:
%0 = alloca [16 x i8], align8%_50 = alloca [1 x i8], align1%prefix = alloca [16 x i8], align8%writer = alloca [24 x i8], align8%state = alloca [1 x i8], align1%slot = alloca [24 x i8], align8%_0 = alloca [1 x i8], align1storei81, ptr%_50, align1%self = loadptr, ptr%_1, align8%_51 = loadptr, ptr%self, align8%1 = getelementptrinboundsi8, ptr%_51, i6416%_53 = loadi32, ptr%1, align8%_52 = andi32%_53, 8388608%2 = icmpeqi32%_52, 0bri1%2, label%bb11, label%bb1
bb11: ; preds = %start%_40 = loadptr, ptr%_1, align8%3 = getelementptrinboundsi8, ptr%_40, i649%4 = loadi8, ptr%3, align1%_24 = truncnuwi8%4toi1bri1%_24, label%bb12, label%bb13
bb1: ; preds = %start%_34 = loadptr, ptr%_1, align8%5 = getelementptrinboundsi8, ptr%_34, i649%6 = loadi8, ptr%5, align1%_3 = truncnuwi8%6toi1bri1%_3, label%bb5, label%bb2
bb13: ; preds = %bb11storeptr@alloc_fe2ddbdc622a788f5416063d1e8d8ad4, ptr%prefix, align8, !captures!4%7 = getelementptrinboundsi8, ptr%prefix, i648storei643, ptr%7, align8brlabel%bb14
bb12: ; preds = %bb11storeptr@alloc_94b00be069aafad82a2c6df764237b82, ptr%prefix, align8, !captures!4%8 = getelementptrinboundsi8, ptr%prefix, i648storei642, ptr%8, align8brlabel%bb14
bb14: ; preds = %bb12, %bb13%_41 = loadptr, ptr%_1, align8%_42 = loadptr, ptr%_41, align8%9 = loadptr, ptr%prefix, align8%10 = getelementptrinboundsi8, ptr%prefix, i648%11 = loadi64, ptr%10, align8; invoke <core::fmt::Formatter>::write_str%self1 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_42, ptr%9, i64%11)
tolabel%bb15unwindlabel%cleanup
bb24: ; preds = %cleanup%12 = loadi8, ptr%_50, align1%13 = truncnuwi8%12toi1bri1%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, 1storeptr%15, ptr%0, align8%17 = getelementptrinboundsi8, ptr%0, i648storei32%16, ptr%17, align8brlabel%bb24
bb15: ; preds = %bb14%_66 = zexti1%self1toi64%18 = truncnuwi64%_66toi1bri1%18, label%bb33, label%bb34
bb33: ; preds = %bb15storei81, ptr%_0, align1brlabel%bb20
bb34: ; preds = %bb15%_43 = loadptr, ptr%_1, align8%_44 = loadptr, ptr%_43, align8%19 = getelementptrinboundsi8, ptr%_1, i648%_45.0 = loadptr, ptr%19, align8%20 = getelementptrinboundsi8, ptr%19, i648%_45.1 = loadi64, ptr%20, align8; invoke <core::fmt::Formatter>::write_str%self2 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_44, ptr%_45.0, i64%_45.1)
tolabel%bb16unwindlabel%cleanup
bb16: ; preds = %bb34%_69 = zexti1%self2toi64%21 = truncnuwi64%_69toi1bri1%21, label%bb35, label%bb36
bb35: ; preds = %bb16storei81, ptr%_0, align1brlabel%bb20
bb36: ; preds = %bb16%_46 = loadptr, ptr%_1, align8%_47 = loadptr, ptr%_46, align8; invoke <core::fmt::Formatter>::write_str%self3 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_47, ptr@alloc_556e4180596b5b612bb6ed6c0cbb55e1, i642)
tolabel%bb17unwindlabel%cleanup
bb17: ; preds = %bb36%_72 = zexti1%self3toi64%22 = truncnuwi64%_72toi1bri1%22, label%bb37, label%bb38
bb37: ; preds = %bb17storei81, ptr%_0, align1brlabel%bb20
bb38: ; preds = %bb17storei80, ptr%_50, align1%23 = getelementptrinboundsi8, ptr%_1, i6424%_31 = loadptr, ptr%23, align8%_48 = loadptr, ptr%_1, align8%_49 = loadptr, ptr%_48, align8; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#1}%24 = invokezeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptralign4%_31, ptralign8%_49)
tolabel%bb18unwindlabel%cleanup
bb18: ; preds = %bb38%25 = zexti1%24toi8storei8%25, ptr%_0, align1brlabel%bb19
bb19: ; preds = %bb10, %bb18brlabel%bb21
bb20: ; preds = %bb33, %bb35, %bb37brlabel%bb40
bb40: ; preds = %bb39, %bb25, %bb20brlabel%bb21
bb2: ; preds = %bb1%_35 = loadptr, ptr%_1, align8%_36 = loadptr, ptr%_35, align8; invoke <core::fmt::Formatter>::write_str%self4 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%_36, ptr@alloc_3690db001d3db01550bdb6f3f450de28, i643)
tolabel%bb3unwindlabel%cleanup
bb5: ; preds = %bb26, %bb1storeptrnull, ptr%slot, align8storei81, ptr%state, align1%_37 = loadptr, ptr%_1, align8%_38 = loadptr, ptr%_37, align8; invoke <core::fmt::builders::PadAdapter>::wrapinvokevoid@_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptrsret([24 x i8]) align8%writer, ptralign8%_38, ptralign8%slot, ptr%state)
tolabel%bb6unwindlabel%cleanup
bb3: ; preds = %bb2%_54 = zexti1%self4toi64%26 = truncnuwi64%_54toi1bri1%26, label%bb25, label%bb26
bb25: ; preds = %bb3storei81, ptr%_0, align1brlabel%bb40
bb26: ; preds = %bb3brlabel%bb5
bb6: ; preds = %bb5%27 = getelementptrinboundsi8, ptr%_1, i648%_39.0 = loadptr, ptr%27, align8%28 = getelementptrinboundsi8, ptr%27, i648%_39.1 = loadi64, ptr%28, align8; invoke <core::fmt::Formatter>::write_str%self5 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr%_39.0, i64%_39.1)
tolabel%bb7unwindlabel%cleanup
bb7: ; preds = %bb6%_57 = zexti1%self5toi64%29 = truncnuwi64%_57toi1bri1%29, label%bb27, label%bb28
bb27: ; preds = %bb7storei81, ptr%_0, align1brlabel%bb39
bb28: ; preds = %bb7; invoke <core::fmt::Formatter>::write_str%self6 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr@alloc_556e4180596b5b612bb6ed6c0cbb55e1, i642)
tolabel%bb8unwindlabel%cleanup
bb8: ; preds = %bb28%_60 = zexti1%self6toi64%30 = truncnuwi64%_60toi1bri1%30, label%bb29, label%bb30
bb29: ; preds = %bb8storei81, ptr%_0, align1brlabel%bb39
bb30: ; preds = %bb8storei80, ptr%_50, align1%31 = getelementptrinboundsi8, ptr%_1, i6424%_18 = loadptr, ptr%31, align8; invoke <repro::Point as core::fmt::Debug>::fmt::{closure#1}%self7 = invokezeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptralign4%_18, ptralign8%writer)
tolabel%bb9unwindlabel%cleanup
bb9: ; preds = %bb30%_63 = zexti1%self7toi64%32 = truncnuwi64%_63toi1bri1%32, label%bb31, label%bb32
bb31: ; preds = %bb9storei81, ptr%_0, align1brlabel%bb21
bb32: ; preds = %bb9; invoke <core::fmt::Formatter>::write_str%33 = invokezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8%writer, ptr@alloc_f7ac0c356c6ac6fcd03badb685e09ca2, i642)
tolabel%bb10unwindlabel%cleanup
bb10: ; preds = %bb32%34 = zexti1%33toi8storei8%34, ptr%_0, align1brlabel%bb19
bb21: ; preds = %bb40, %bb31, %bb19%35 = loadi8, ptr%_0, align1%36 = truncnuwi8%35toi1reti1%36
bb39: ; preds = %bb27, %bb29brlabel%bb40
bb4: ; No predecessors!unreachable
bb22: ; preds = %bb23, %bb24%37 = loadptr, ptr%0, align8%38 = getelementptrinboundsi8, ptr%0, i648%39 = loadi32, ptr%38, align8%40 = insertvalue { ptr, i32 } poison, ptr%37, 0%41 = insertvalue { ptr, i32 } %40, i32%39, 1resume { ptr, i32 } %41
bb23: ; preds = %bb24brlabel%bb22
}
; <repro::Point as core::fmt::Debug>::fmt::{closure#0}; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt0B4_(ptralign4%_1, ptralign8%f) unnamed_addr #1 {
start:
; call <u32 as core::fmt::Debug>::fmt%_0 = callzeroexti1@_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptralign4%_1, ptralign8%f) #4reti1%_0
}
; <repro::Point as core::fmt::Debug>::fmt::{closure#1}; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNCNvXCs36UsywoTqr_5reproNtB4_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmts_0B4_(ptralign4%_1, ptralign8%f) unnamed_addr #1 {
start:
%_3 = getelementptrinboundsi8, ptr%_1, i644; call <u32 as core::fmt::Debug>::fmt%_0 = callzeroexti1@_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptralign4%_3, ptralign8%f) #4reti1%_0
}
; <repro::Point as core::fmt::Debug>::fmt; Function Attrs: uwtabledefinezeroexti1@_RNvXCs36UsywoTqr_5reproNtB2_5PointNtNtCs2xrpM2fV95l_4core3fmt5Debug3fmt(ptralign4%self, ptralign8%f) unnamed_addr #0 {
start:
%_6 = alloca [16 x i8], align8; call <core::fmt::Formatter>::debug_structcallvoid@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter12debug_struct(ptrsret([16 x i8]) align8%_6, ptralign8%f, ptr@alloc_bf0ef947d182249ea2203578272460c5, i645)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>%_4 = callalign8ptr@_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptralign8%_6, ptr@alloc_37f62a15fae368a2423b3a16c75adcc9, i641, ptralign4%self)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>%_3 = callalign8ptr@_RINvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCs36UsywoTqr_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptralign8%_4, ptr@alloc_cd51bc84b49b21a3fdb2800731f25c21, i641, ptralign4%self)
; call <core::fmt::builders::DebugStruct>::finish%_0 = callzeroexti1@_RNvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB5_11DebugStruct6finish(ptralign8%_3)
reti1%_0
}
; <u32 as core::fmt::Debug>::fmt; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNvXsW_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_5Debug3fmtCs36UsywoTqr_5repro(ptralign4%self, ptralign8%f) unnamed_addr #1 {
start:
%_0 = alloca [1 x i8], align1%0 = getelementptrinboundsi8, ptr%f, i6416%_4 = loadi32, ptr%0, align8%_3 = andi32%_4, 33554432%1 = icmpeqi32%_3, 0bri1%1, label%bb2, label%bb1
bb2: ; preds = %start%2 = getelementptrinboundsi8, ptr%f, i6416%_6 = loadi32, ptr%2, align8%_5 = andi32%_6, 67108864%3 = icmpeqi32%_5, 0bri1%3, label%bb4, label%bb3
bb1: ; preds = %start; call <u32 as core::fmt::LowerHex>::fmt%4 = callzeroexti1@_RNvXsu_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8LowerHex3fmt(ptralign4%self, ptralign8%f)
%5 = zexti1%4toi8storei8%5, ptr%_0, align1brlabel%bb6
bb4: ; preds = %bb2; call <u32 as core::fmt::Display>::fmt%6 = callzeroexti1@_RNvXs8_NtNtNtCs2xrpM2fV95l_4core3fmt3num3impmNtB9_7Display3fmt(ptralign4%self, ptralign8%f)
%7 = zexti1%6toi8storei8%7, ptr%_0, align1brlabel%bb5
bb3: ; preds = %bb2; call <u32 as core::fmt::UpperHex>::fmt%8 = callzeroexti1@_RNvXsw_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8UpperHex3fmt(ptralign4%self, ptralign8%f)
%9 = zexti1%8toi8storei8%9, ptr%_0, align1brlabel%bb5
bb5: ; preds = %bb3, %bb4brlabel%bb6
bb6: ; preds = %bb1, %bb5%10 = loadi8, ptr%_0, align1%11 = truncnuwi8%10toi1reti1%11
}
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)declarevoid@llvm.memcpy.p0.p0.i64(ptrnoaliaswriteonly captures(none), ptrnoaliasreadonly captures(none), i64, i1 immarg) #2; Function Attrs: nounwind uwtabledeclarei32@rust_eh_personality(i32, i32, i64, ptr, ptr) unnamed_addr #3; <core::fmt::Formatter>::write_str; Function Attrs: uwtabledeclarezeroexti1@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter9write_str(ptralign8, ptr, i64) unnamed_addr #0; <core::fmt::builders::PadAdapter>::wrap; Function Attrs: uwtabledeclarevoid@_RNvMs_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB4_10PadAdapter4wrap(ptrsret([24 x i8]) align8, ptralign8, ptralign8, ptr) unnamed_addr #0; <core::fmt::Formatter>::debug_struct; Function Attrs: uwtabledeclarevoid@_RNvMsa_NtCs2xrpM2fV95l_4core3fmtNtB5_9Formatter12debug_struct(ptrsret([16 x i8]) align8, ptralign8, ptr, i64) unnamed_addr #0; <core::fmt::builders::DebugStruct>::finish; Function Attrs: uwtabledeclarezeroexti1@_RNvMs1_NtNtCs2xrpM2fV95l_4core3fmt8buildersNtB5_11DebugStruct6finish(ptralign8) unnamed_addr #0; <u32 as core::fmt::Display>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXs8_NtNtNtCs2xrpM2fV95l_4core3fmt3num3impmNtB9_7Display3fmt(ptralign4, ptralign8) unnamed_addr #0; <u32 as core::fmt::UpperHex>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXsw_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8UpperHex3fmt(ptralign4, ptralign8) unnamed_addr #0; <u32 as core::fmt::LowerHex>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXsu_NtNtCs2xrpM2fV95l_4core3fmt3nummNtB7_8LowerHex3fmt(ptralign4, ptralign8) unnamed_addr #0attributes #0 = { uwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #1 = { inlinehintuwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #3 = { nounwinduwtable"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 = !{i328, !"PIC Level", i322}
!1 = !{i327, !"uwtable", i322}
!2 = !{i327, !"frame-pointer", i321}
!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"targetdatalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"targettriple = "arm64-apple-macosx11.0.0"@vtable.0 = privateunnamed_addrconstant <{ [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_ }>, align8@vtable.1 = privateunnamed_addrconstant <{ [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_ }>, align8@alloc_bf0ef947d182249ea2203578272460c5 = privateunnamed_addrconstant [5 x i8] c"Point", align1@alloc_37f62a15fae368a2423b3a16c75adcc9 = privateunnamed_addrconstant [1 x i8] c"x", align1@alloc_cd51bc84b49b21a3fdb2800731f25c21 = privateunnamed_addrconstant [1 x i8] c"y", align1@alloc_67658ef8123446d70a96e7dbc7651d58 = privateunnamed_addrconstant [40 x i8] c"formatting closure called more than once", align1@alloc_703b1d677d4c28a67f1910a45c16864b = privateunnamed_addrconstant [33 x i8] c"library/core/src/fmt/builders.rs\00", align1@alloc_7fd203ad775cd664e43a5c0ae4634671 = privateunnamed_addrconstant <{ 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" }>, align8; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>; Function Attrs: uwtabledefinealign8ptr@_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptralign8%self, ptr%name.0, i64%name.1, ptralign4%value_fmt) unnamed_addr #0personalityptr@rust_eh_personality {
start:
%0 = alloca [16 x i8], align8%_6 = alloca [8 x i8], align8storeptr%value_fmt, ptr%_6, align8; invoke <core::fmt::builders::DebugStruct>::field%_0 = invokealign8ptr@_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptralign8%self, ptr%name.0, i64%name.1, ptr%_6, ptralign8@vtable.0)
tolabel%bb1unwindlabel%cleanup
bb3: ; preds = %cleanup%1 = loadptr, ptr%0, align8%2 = getelementptrinboundsi8, ptr%0, i648%3 = loadi32, ptr%2, align8%4 = insertvalue { ptr, i32 } poison, ptr%1, 0%5 = insertvalue { ptr, i32 } %4, i32%3, 1resume { ptr, i32 } %5
cleanup: ; preds = %start%6 = landingpad { ptr, i32 }
cleanup
%7 = extractvalue { ptr, i32 } %6, 0%8 = extractvalue { ptr, i32 } %6, 1storeptr%7, ptr%0, align8%9 = getelementptrinboundsi8, ptr%0, i648storei32%8, ptr%9, align8brlabel%bb3
bb1: ; preds = %startretptr%_0
}
; <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>; Function Attrs: uwtabledefinealign8ptr@_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptralign8%self, ptr%name.0, i64%name.1, ptralign4%value_fmt) unnamed_addr #0personalityptr@rust_eh_personality {
start:
%0 = alloca [16 x i8], align8%_6 = alloca [8 x i8], align8storeptr%value_fmt, ptr%_6, align8; invoke <core::fmt::builders::DebugStruct>::field%_0 = invokealign8ptr@_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptralign8%self, ptr%name.0, i64%name.1, ptr%_6, ptralign8@vtable.1)
tolabel%bb1unwindlabel%cleanup
bb3: ; preds = %cleanup%1 = loadptr, ptr%0, align8%2 = getelementptrinboundsi8, ptr%0, i648%3 = loadi32, ptr%2, align8%4 = insertvalue { ptr, i32 } poison, ptr%1, 0%5 = insertvalue { ptr, i32 } %4, i32%3, 1resume { ptr, i32 } %5
cleanup: ; preds = %start%6 = landingpad { ptr, i32 }
cleanup
%7 = extractvalue { ptr, i32 } %6, 0%8 = extractvalue { ptr, i32 } %6, 1storeptr%7, ptr%0, align8%9 = getelementptrinboundsi8, ptr%0, i648storei32%8, ptr%9, align8brlabel%bb3
bb1: ; preds = %startretptr%_0
}
; <repro::Point as core::fmt::Debug>::fmt::{closure#0}; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt0B4_(ptralign4%_1, ptralign8%f) unnamed_addr #1 {
start:
; call <u32 as core::fmt::Debug>::fmt%_0 = callzeroexti1@_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptralign4%_1, ptralign8%f) #4reti1%_0
}
; <repro::Point as core::fmt::Debug>::fmt::{closure#1}; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmts_0B4_(ptralign4%_1, ptralign8%f) unnamed_addr #1 {
start:
%_3 = getelementptrinboundsi8, ptr%_1, i644; call <u32 as core::fmt::Debug>::fmt%_0 = callzeroexti1@_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptralign4%_3, ptralign8%f) #4reti1%_0
}
; <repro::Point as core::fmt::Debug>::fmt; Function Attrs: uwtabledefinezeroexti1@_RNvXCseUiCQVDuJCY_5reproNtB2_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt(ptralign4%self, ptralign8%f) unnamed_addr #0 {
start:
%_6 = alloca [16 x i8], align8; call <core::fmt::Formatter>::debug_structcallvoid@_RNvMsa_NtCs1Jl4pBjAano_4core3fmtNtB5_9Formatter12debug_struct(ptrsret([16 x i8]) align8%_6, ptralign8%f, ptr@alloc_bf0ef947d182249ea2203578272460c5, i645)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#0}>%_4 = callalign8ptr@_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmt0EB1f_(ptralign8%_6, ptr@alloc_37f62a15fae368a2423b3a16c75adcc9, i641, ptralign4%self)
; call <core::fmt::builders::DebugStruct>::field_with::<<repro::Point as core::fmt::Debug>::fmt::{closure#1}>%_3 = callalign8ptr@_RINvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB6_11DebugStruct10field_withNCNvXCseUiCQVDuJCY_5reproNtB1f_5PointNtB8_5Debug3fmts_0EB1f_(ptralign8%_4, ptr@alloc_cd51bc84b49b21a3fdb2800731f25c21, i641, ptralign4%self)
; call <core::fmt::builders::DebugStruct>::finish%_0 = callzeroexti1@_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct6finish(ptralign8%_3)
reti1%_0
}
; <core::fmt::builders::DebugOnce<<repro::Point as core::fmt::Debug>::fmt::{closure#0}> as core::fmt::Debug>::fmt; Function Attrs: uwtabledefinezeroexti1@_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmt0EB1w_3fmtB10_(ptralign8%self, ptralign8%f) unnamed_addr #0 {
start:
%_3 = alloca [8 x i8], align8%0 = loadptr, ptr%self, align8storeptr%0, ptr%_3, align8storeptrnull, ptr%self, align8%1 = loadptr, ptr%_3, align8%2 = ptrtointptr%1toi64%3 = icmpeqi64%2, 0%_5 = selecti1%3, i640, i641%4 = truncnuwi64%_5toi1bri1%4, label%bb3, label%bb2
bb3: ; preds = %start%value_fmt = loadptr, ptr%_3, align8; call <repro::Point as core::fmt::Debug>::fmt::{closure#0}%_0 = callzeroexti1@_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmt0B4_(ptralign4%value_fmt, ptralign8%f) #4reti1%_0
bb2: ; preds = %start; call core::panicking::panic_fmtcallvoid@_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr@alloc_67658ef8123446d70a96e7dbc7651d58, ptrinttoptr (i6481toptr), ptralign8@alloc_7fd203ad775cd664e43a5c0ae4634671) #5unreachable
bb1: ; No predecessors!unreachable
}
; <core::fmt::builders::DebugOnce<<repro::Point as core::fmt::Debug>::fmt::{closure#1}> as core::fmt::Debug>::fmt; Function Attrs: uwtabledefinezeroexti1@_RNvXs1_NtNtCs1Jl4pBjAano_4core3fmt8buildersINtB5_9DebugOnceNCNvXCseUiCQVDuJCY_5reproNtB10_5PointNtB7_5Debug3fmts_0EB1w_3fmtB10_(ptralign8%self, ptralign8%f) unnamed_addr #0 {
start:
%_3 = alloca [8 x i8], align8%0 = loadptr, ptr%self, align8storeptr%0, ptr%_3, align8storeptrnull, ptr%self, align8%1 = loadptr, ptr%_3, align8%2 = ptrtointptr%1toi64%3 = icmpeqi64%2, 0%_5 = selecti1%3, i640, i641%4 = truncnuwi64%_5toi1bri1%4, label%bb3, label%bb2
bb3: ; preds = %start%value_fmt = loadptr, ptr%_3, align8; call <repro::Point as core::fmt::Debug>::fmt::{closure#1}%_0 = callzeroexti1@_RNCNvXCseUiCQVDuJCY_5reproNtB4_5PointNtNtCs1Jl4pBjAano_4core3fmt5Debug3fmts_0B4_(ptralign4%value_fmt, ptralign8%f) #4reti1%_0
bb2: ; preds = %start; call core::panicking::panic_fmtcallvoid@_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr@alloc_67658ef8123446d70a96e7dbc7651d58, ptrinttoptr (i6481toptr), ptralign8@alloc_7fd203ad775cd664e43a5c0ae4634671) #5unreachable
bb1: ; No predecessors!unreachable
}
; <u32 as core::fmt::Debug>::fmt; Function Attrs: inlinehint uwtabledefineinternalzeroexti1@_RNvXsW_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_5Debug3fmtCseUiCQVDuJCY_5repro(ptralign4%self, ptralign8%f) unnamed_addr #1 {
start:
%_0 = alloca [1 x i8], align1%0 = getelementptrinboundsi8, ptr%f, i6416%_4 = loadi32, ptr%0, align8%_3 = andi32%_4, 33554432%1 = icmpeqi32%_3, 0bri1%1, label%bb2, label%bb1
bb2: ; preds = %start%2 = getelementptrinboundsi8, ptr%f, i6416%_6 = loadi32, ptr%2, align8%_5 = andi32%_6, 67108864%3 = icmpeqi32%_5, 0bri1%3, label%bb4, label%bb3
bb1: ; preds = %start; call <u32 as core::fmt::LowerHex>::fmt%4 = callzeroexti1@_RNvXsu_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8LowerHex3fmt(ptralign4%self, ptralign8%f)
%5 = zexti1%4toi8storei8%5, ptr%_0, align1brlabel%bb6
bb4: ; preds = %bb2; call <u32 as core::fmt::Display>::fmt%6 = callzeroexti1@_RNvXs8_NtNtNtCs1Jl4pBjAano_4core3fmt3num3impmNtB9_7Display3fmt(ptralign4%self, ptralign8%f)
%7 = zexti1%6toi8storei8%7, ptr%_0, align1brlabel%bb5
bb3: ; preds = %bb2; call <u32 as core::fmt::UpperHex>::fmt%8 = callzeroexti1@_RNvXsw_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8UpperHex3fmt(ptralign4%self, ptralign8%f)
%9 = zexti1%8toi8storei8%9, ptr%_0, align1brlabel%bb5
bb5: ; preds = %bb3, %bb4brlabel%bb6
bb6: ; preds = %bb1, %bb5%10 = loadi8, ptr%_0, align1%11 = truncnuwi8%10toi1reti1%11
}
; Function Attrs: nounwind uwtabledeclarei32@rust_eh_personality(i32, i32, i64, ptr, ptr) unnamed_addr #2; <core::fmt::builders::DebugStruct>::field; Function Attrs: uwtabledeclarealign8ptr@_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct5field(ptralign8, ptr, i64, ptr, ptralign8) unnamed_addr #0; <core::fmt::Formatter>::debug_struct; Function Attrs: uwtabledeclarevoid@_RNvMsa_NtCs1Jl4pBjAano_4core3fmtNtB5_9Formatter12debug_struct(ptrsret([16 x i8]) align8, ptralign8, ptr, i64) unnamed_addr #0; <core::fmt::builders::DebugStruct>::finish; Function Attrs: uwtabledeclarezeroexti1@_RNvMs2_NtNtCs1Jl4pBjAano_4core3fmt8buildersNtB5_11DebugStruct6finish(ptralign8) unnamed_addr #0; core::panicking::panic_fmt; Function Attrs: cold noinline noreturn uwtabledeclarevoid@_RNvNtCs1Jl4pBjAano_4core9panicking9panic_fmt(ptr, ptr, ptralign8) unnamed_addr #3; <u32 as core::fmt::Display>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXs8_NtNtNtCs1Jl4pBjAano_4core3fmt3num3impmNtB9_7Display3fmt(ptralign4, ptralign8) unnamed_addr #0; <u32 as core::fmt::UpperHex>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXsw_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8UpperHex3fmt(ptralign4, ptralign8) unnamed_addr #0; <u32 as core::fmt::LowerHex>::fmt; Function Attrs: uwtabledeclarezeroexti1@_RNvXsu_NtNtCs1Jl4pBjAano_4core3fmt3nummNtB7_8LowerHex3fmt(ptralign4, ptralign8) unnamed_addr #0attributes #0 = { uwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #1 = { inlinehintuwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #2 = { nounwinduwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #3 = { cold noinlinenoreturnuwtable"frame-pointer"="non-leaf""probe-stack"="inline-asm""target-cpu"="apple-m1" }
attributes #4 = { inlinehint }
attributes #5 = { noinlinenoreturn }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!0 = !{i328, !"PIC Level", i322}
!1 = !{i327, !"uwtable", i322}
!2 = !{i327, !"frame-pointer", i321}
!3 = !{!"rustc version 1.99.0-dev"}

@connortsui20connortsui20 changed the title make debug builders with closures impl with dynImplement Debug helpers that take closures with &mut dyn FnMutJul 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.

@rustbotrustbot 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-borsBot 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
ContributorAuthor

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 threadlibrary/core/src/fmt/builders.rs Outdated
@rust-bors

rust-borsBot 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.

meanrangecount
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.

meanrangecount
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.

meanrangecount
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.

meanrangecount
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%)

@rustbotrustbot 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):

structDebugOnce<F>(Cell<Option<F>>);impl<F> fmt::DebugforDebugOnce<F>whereF:FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,{fnfmt(&self,f:&mut fmt::Formatter<'_>) -> fmt::Result{ifletSome(x) = self.0.replace(None){return(x)(f);}Ok(())}}// ...pubfnfield_with<F>(&mutself,name:&str,value_fmt:F) -> &mutSelfwhereF:FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,{self.field(name,&DebugOnce(Cell::new(Some(value_fmt))))}

@tgross35

Copy link
Copy Markdown
Member

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

No need, all yours :)

@connortsui20

Copy link
Copy Markdown
ContributorAuthor

@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 countfieldfield_with (main)field_with (this PR)
3 fields30846351
6 fields541686696
9 fields7825261041
(per field)12420172.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-kruppehanna-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 threadlibrary/core/src/fmt/builders.rs Outdated
Comment threadlibrary/core/src/fmt/builders.rs Outdated
@rustbotrustbot 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

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>
@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.

meanrangecount
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.

meanrangecount
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.

meanrangecount
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%)

@rustbotrustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 15, 2026
Comment threadlibrary/core/src/fmt/builders.rs

@tgross35tgross35Jul 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there any way we could reasonably cover this in a codegen test?

View changes since the review

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.

I've asked myself that on other changes to reduce monomorphization, and I never came up with a satisfying answer. Matching against the structure of calling some shared monomorphic function is the closest thing, but that's not really reasonable because it's either way too unspecific or way too sensitive to details like function names.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would probably be a good idea to add a benchmark using these functions to https://github.com/rust-lang/rustc-perf/tree/2e1f10b49224c069b9fdca7fe8d1e78790e8c62b/collector, assuming the differences here make it to optimized binary size.

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.

I don’t think this is worth it in the grand scheme of things. We shouldn’t have an API that’s an unavoidable code size footgun to use at all, but also, this one API isn’t likely to be a sufficiently interesting benchmark for the purpose of rustc-perf, compared to everything else we could be measuring with the limited benchmarking budget. If this API eventually makes its way into a popular application/library, we may measure it that way in a more realistic setting.

Also, yeah, it doesn’t necessarily show up in optimized binary size. The examples we’ve had so far get handled well by function merging so they act mostly like a single field_with call. It’s likely possible to write a microbenchmark that doesn’t have so much obviously identical code, but the real risk is death by a thousand cuts across a large code base, which is hard to model in an artificial benchmark.

@hanna-kruppe

Copy link
Copy Markdown
Contributor

I repeated the previous experiment with the Cell approach (last column is new, rest is copied from my last comment):

Field countfieldfield_with (main)field_with (dyn FnMut)field_with (Cell)
3 fields30846351273
6 fields541686696540
9 fields7825261041807
(per field)12420172.589

That's better but honestly still not great. Although a lot of the difference is due to debug info related overhead (the .dbg.spill allocas). It's not like we can really avoid that, but I set profile.dev.debug = "none" to measure the effect:

Field countfieldfield_with (main)field_with (Cell)
3 fields28550151
6 fields521096298
9 fields781642445
(per field)818249

And a lot of the remaining IR is the aforementioned (1) Option juggling, and (2) landing pads for unwinding that don't seem to end up doing anything. The debug info issues and the landing pads really looks like opportunities for rustc to emit better code in dev builds. But (1) can potentially be improved...

@hanna-kruppe

Copy link
Copy Markdown
Contributor

As another point of comparison, I extended my test with a variant that uses fmt::from_fn with the same closure (see updated gist). field_with is also significantly worse than from_fn (still measuring cargo llvm-lines tally on commit b727274):

Field countfrom_fnfield_with
3108273
6210540
9312807
(lines / field)3489

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.take() {
Some(value_fmt) => value_fmt(f),
None => panic!("formatting closure called more than once"),

@camsteffencamsteffenJul 16, 2026

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.

Are we sure that the panic is optimized out? If not, this should probably return Ok? Even if by some chance this branch were reached, probably better not to panic anyways.

View changes since the review

@hanna-kruppe

Copy link
Copy Markdown
Contributor

On the one hand, this PR in its current state is a good contribution that I'd be happy to merge: it significantly reduces the cost of DebugStruct::field_with and friends in a very natural and clean way without regressing DebugStruct::field performance.

On the other hand, I don't feel this fully fixes #149745. field_with still emits quite a lot more code (both relatively and in absolute terms) than field or even than fmt::from_fn, especially in the default dev profile. And that's despite only measuring for simple cases where the closure itself is almost as cheap as can be (it's not a ZST due to having captures, but the captured value is small, there's no drop glue, the code in the closure is tiny). And while I have some ideas for reducing that cost, it's not obvious to me how much they'll help and they're much more complicated. So I'd have to try them out and would like another libs reviewer's judgement on whether such complexity is acceptable.

So @connortsui20 I would propose merging this PR minus the "Fixes #149745" bit. And then someone will need to do further work in another PR to fully close that issue. If you're up for it, I can sketch the ideas I have and you try them out. If not, I might try my hand at that myself, after having thought a bunch about it, but I can't promise when I'll get around to it. Does that sound okay to you?

@connortsui20

Copy link
Copy Markdown
ContributorAuthor

@hanna-kruppe that sounds good! Though I will admit that it is probably better if you or someone else takes over as I am not super familiar with LLVM and codegen in general. I have updated the PR description.

@connortsui20

Copy link
Copy Markdown
ContributorAuthor

Also, I think we are at the point where the review comments are not something I can actively fix without a lot of help, feel free to commit directly to this PR!

@hanna-kruppe

Copy link
Copy Markdown
Contributor

I’ll take the musings about further improvements to the issue, then. Thanks for your work on this!

@bors r+ rollup

@rust-bors

rust-borsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📌 Commit b727274 has been approved by hanna-kruppe

It is now in the queue for this repository.

@rust-borsrust-borsBot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 16, 2026
jhpratt added a commit to jhpratt/rust that referenced this pull request Jul 17, 2026
…hanna-kruppe
Implement `Debug` helpers via `Cell`
Related to rust-lang#149745, but does not fix it (yet).
Following @jmillikin's [suggestion](rust-lang#159302 (comment)), 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](rust-lang#149745 (comment)), hence the `Cell<Option<..>>` stuff in `DebugOnce`.
###### repro.rs
```rust
#![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()
}
}
```
```bash
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](rust-lang#117729 (comment)) for stabilizing `debug_closure_helpers`, so it should unblock rust-lang#146099.
@jhprattjhpratt mentioned this pull request Jul 17, 2026
rust-borsBot pushed a commit that referenced this pull request Jul 17, 2026
Rollup of 10 pull requests
Successful merges:
- #156977 (interpret: properly check for inhabitedness of nested references)
- #159365 (fix: point at method call chain when a return-position `impl Trait` assoc type diverges)
- #159402 (Clarify safety requirements for SIMD shl/shr and masked load/store)
- #159410 (rustdoc: remove old `--emit` types)
- #159302 (Implement `Debug` helpers via `Cell`)
- #159386 (add a fallback for `fmuladdf*`)
- #159391 (Update tests for LLVM 23)
- #159400 (Update books)
- #159401 (Gate `tests/debuginfo/function-call.rs` on min GDB 15.1)
- #159404 ([aarch64][win] Pass oversized c-variadic args indirectly on Arm64EC)
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jul 17, 2026
…hanna-kruppe
Implement `Debug` helpers via `Cell`
Related to rust-lang#149745, but does not fix it (yet).
Following @jmillikin's [suggestion](rust-lang#159302 (comment)), 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](rust-lang#149745 (comment)), hence the `Cell<Option<..>>` stuff in `DebugOnce`.
###### repro.rs
```rust
#![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()
}
}
```
```bash
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](rust-lang#117729 (comment)) for stabilizing `debug_closure_helpers`, so it should unblock rust-lang#146099.
rust-borsBot pushed a commit that referenced this pull request Jul 17, 2026
…uwer
Rollup of 10 pull requests
Successful merges:
- #159365 (fix: point at method call chain when a return-position `impl Trait` assoc type diverges)
- #159402 (Clarify safety requirements for SIMD shl/shr and masked load/store)
- #159410 (rustdoc: remove old `--emit` types)
- #159302 (Implement `Debug` helpers via `Cell`)
- #159386 (add a fallback for `fmuladdf*`)
- #159391 (Update tests for LLVM 23)
- #159400 (Update books)
- #159401 (Gate `tests/debuginfo/function-call.rs` on min GDB 15.1)
- #159404 ([aarch64][win] Pass oversized c-variadic args indirectly on Arm64EC)
- #159405 (Manually implement Clone for GrowableBitSet)
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jul 17, 2026
…hanna-kruppe
Implement `Debug` helpers via `Cell`
Related to rust-lang#149745, but does not fix it (yet).
Following @jmillikin's [suggestion](rust-lang#159302 (comment)), 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](rust-lang#149745 (comment)), hence the `Cell<Option<..>>` stuff in `DebugOnce`.
###### repro.rs
```rust
#![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()
}
}
```
```bash
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](rust-lang#117729 (comment)) for stabilizing `debug_closure_helpers`, so it should unblock rust-lang#146099.
rust-borsBot pushed a commit that referenced this pull request Jul 17, 2026
…uwer
Rollup of 16 pull requests
Successful merges:
- #150732 (Convert `-Ctarget-cpu` into a target-modifier for AVR, AMDGCN and NVPTX )
- #159301 (Update Enzyme to handle LLVM23)
- #159365 (fix: point at method call chain when a return-position `impl Trait` assoc type diverges)
- #159402 (Clarify safety requirements for SIMD shl/shr and masked load/store)
- #159410 (rustdoc: remove old `--emit` types)
- #158398 (Comment about empty run_passes, fixup of #158040)
- #158843 (Fix ICE in `write_interface` when the interface file can't be written)
- #159302 (Implement `Debug` helpers via `Cell`)
- #159332 (Honor field-level lint attributes in non_snake_case)
- #159386 (add a fallback for `fmuladdf*`)
- #159391 (Update tests for LLVM 23)
- #159400 (Update books)
- #159401 (Gate `tests/debuginfo/function-call.rs` on min GDB 15.1)
- #159404 ([aarch64][win] Pass oversized c-variadic args indirectly on Arm64EC)
- #159405 (Manually implement Clone for GrowableBitSet)
- #159415 (rustdoc: rename the doc parts metadata params)
rust-borsBot pushed a commit that referenced this pull request Jul 17, 2026
…uwer
Rollup of 17 pull requests
Successful merges:
- #159301 (Update Enzyme to handle LLVM23)
- #159365 (fix: point at method call chain when a return-position `impl Trait` assoc type diverges)
- #159402 (Clarify safety requirements for SIMD shl/shr and masked load/store)
- #159408 (rustc_data_structures: Expand documentation for rustc jobserver APIs)
- #159410 (rustdoc: remove old `--emit` types)
- #158398 (Comment about empty run_passes, fixup of #158040)
- #158843 (Fix ICE in `write_interface` when the interface file can't be written)
- #159302 (Implement `Debug` helpers via `Cell`)
- #159332 (Honor field-level lint attributes in non_snake_case)
- #159340 (Rename `errors.rs` file to `diagnostics.rs` (14/N))
- #159386 (add a fallback for `fmuladdf*`)
- #159391 (Update tests for LLVM 23)
- #159400 (Update books)
- #159401 (Gate `tests/debuginfo/function-call.rs` on min GDB 15.1)
- #159404 ([aarch64][win] Pass oversized c-variadic args indirectly on Arm64EC)
- #159405 (Manually implement Clone for GrowableBitSet)
- #159415 (rustdoc: rename the doc parts metadata params)
@rust-bors
rust-borsBot merged commit 0864f09 into rust-lang:mainJul 17, 2026
14 checks passed
@rustbotrustbot added this to the 1.99.0 milestone Jul 17, 2026
rust-timer added a commit that referenced this pull request Jul 17, 2026
Rollup merge of #159302 - connortsui20:dyn-debug-helpers, r=hanna-kruppe
Implement `Debug` helpers via `Cell`
Related to #149745, but does not fix it (yet).
Following @jmillikin's [suggestion](#159302 (comment)), 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](#149745 (comment)), hence the `Cell<Option<..>>` stuff in `DebugOnce`.
###### repro.rs
```rust
#![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()
}
}
```
```bash
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](#117729 (comment)) for stabilizing `debug_closure_helpers`, so it should unblock #146099.
Kobzol pushed a commit to Kobzol/rustc_codegen_cranelift that referenced this pull request Jul 23, 2026
…uwer
Rollup of 17 pull requests
Successful merges:
- rust-lang/rust#159301 (Update Enzyme to handle LLVM23)
- rust-lang/rust#159365 (fix: point at method call chain when a return-position `impl Trait` assoc type diverges)
- rust-lang/rust#159402 (Clarify safety requirements for SIMD shl/shr and masked load/store)
- rust-lang/rust#159408 (rustc_data_structures: Expand documentation for rustc jobserver APIs)
- rust-lang/rust#159410 (rustdoc: remove old `--emit` types)
- rust-lang/rust#158398 (Comment about empty run_passes, fixup of rust-lang/rust#158040)
- rust-lang/rust#158843 (Fix ICE in `write_interface` when the interface file can't be written)
- rust-lang/rust#159302 (Implement `Debug` helpers via `Cell`)
- rust-lang/rust#159332 (Honor field-level lint attributes in non_snake_case)
- rust-lang/rust#159340 (Rename `errors.rs` file to `diagnostics.rs` (14/N))
- rust-lang/rust#159386 (add a fallback for `fmuladdf*`)
- rust-lang/rust#159391 (Update tests for LLVM 23)
- rust-lang/rust#159400 (Update books)
- rust-lang/rust#159401 (Gate `tests/debuginfo/function-call.rs` on min GDB 15.1)
- rust-lang/rust#159404 ([aarch64][win] Pass oversized c-variadic args indirectly on Arm64EC)
- rust-lang/rust#159405 (Manually implement Clone for GrowableBitSet)
- rust-lang/rust#159415 (rustdoc: rename the doc parts metadata params)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-borsStatus: Waiting on bors to run and complete tests. Bors will change the label on completion.T-libsRelevant 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.

7 participants

@connortsui20@rustbot@hanna-kruppe@rust-timer@jmillikin@tgross35@camsteffen