zkat / miette

Fancy extension for std::error::Error with pretty, detailed diagnostic printing.
https://docs.rs/miette
Apache License 2.0
1.85k stars 106 forks source link

Improve compilation speed by reducing expanded llvm lines #373

Closed Boshen closed 1 month ago

Boshen commented 1 month ago

My project is growing with lots of miette errors, and hence instantiating a lot of code.

Using --timings, we see oxc_linter is slow on codegen (the purple part).

image

The crate currently contains 353 miette errors. cargo-llvm-lines displays

cargo llvm-lines -p oxc_linter --lib --release

  Lines                 Copies               Function name
  -----                 ------               -------------
  830350                33438                (TOTAL)
   29252 (3.5%,  3.5%)    808 (2.4%,  2.4%)  <alloc::boxed::Box<T,A> as core::ops::drop::Drop>::drop
   23298 (2.8%,  6.3%)    353 (1.1%,  3.5%)  miette::eyreish::error::object_downcast
   19062 (2.3%,  8.6%)    706 (2.1%,  5.6%)  core::error::Error::type_id
   12610 (1.5%, 10.1%)     65 (0.2%,  5.8%)  alloc::raw_vec::RawVec<T,A>::grow_amortized
   12002 (1.4%, 11.6%)    706 (2.1%,  7.9%)  miette::eyreish::ptr::Own<T>::boxed
    9215 (1.1%, 12.7%)    115 (0.3%,  8.2%)  core::iter::traits::iterator::Iterator::try_fold
    9150 (1.1%, 13.8%)      1 (0.0%,  8.2%)  oxc_linter::rules::RuleEnum::read_json
    8825 (1.1%, 14.9%)    353 (1.1%,  9.3%)  <miette::eyreish::error::ErrorImpl<E> as core::error::Error>::source
    8822 (1.1%, 15.9%)    353 (1.1%, 10.3%)  miette::eyreish::error::<impl miette::eyreish::Report>::construct
    8119 (1.0%, 16.9%)    353 (1.1%, 11.4%)  miette::eyreish::error::object_ref
    8119 (1.0%, 17.9%)    353 (1.1%, 12.5%)  miette::eyreish::error::object_ref_stderr
    7413 (0.9%, 18.8%)    353 (1.1%, 13.5%)  <miette::eyreish::error::ErrorImpl<E> as core::fmt::Display>::fmt
    7413 (0.9%, 19.7%)    353 (1.1%, 14.6%)  miette::eyreish::ptr::Own<T>::new
    6669 (0.8%, 20.5%)     39 (0.1%, 14.7%)  alloc::raw_vec::RawVec<T,A>::try_allocate_in
    6173 (0.7%, 21.2%)    353 (1.1%, 15.7%)  miette::eyreish::error::<impl miette::eyreish::Report>::from_std
    6027 (0.7%, 21.9%)     70 (0.2%, 16.0%)  <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter
    6001 (0.7%, 22.7%)    353 (1.1%, 17.0%)  miette::eyreish::error::object_drop
    6001 (0.7%, 23.4%)    353 (1.1%, 18.1%)  miette::eyreish::error::object_drop_front
    5648 (0.7%, 24.1%)    353 (1.1%, 19.1%)  <miette::eyreish::error::ErrorImpl<E> as core::fmt::Debug>::fmt

It's totalling more than 50k llvm lines, and is putting pressure on rustc codegen (the purple part on oxc_linter in the image above.


It's pretty obvious by looking at https://github.com/zkat/miette/blob/main/src/eyreish/error.rs, the generics can expand out to lots of code.

I wonder if there are any newer Rust patterns that could keep the lines down.

zkat commented 1 month ago

oh interesting. I wonder if anyhow or eyre have run into this, too, since that's where that code comes from.

Boshen commented 1 month ago

Interesting. I went thorough all the issues in anyhow and eyre and found no discussions on this.

Partially related, I see there are newer changes to https://github.com/dtolnay/anyhow/blob/master/src/error.rs and https://github.com/eyre-rs/eyre/blob/master/eyre/src/error.rs, may I copy some of the relevant changes over? For example I see one commit adding #[cold] attributes.


Now a quick investigation on the generated llvm ir:

; miette::eyreish::error::object_downcast
; Function Attrs: uwtable
define ptr @_ZN6miette7eyreish5error15object_downcast17h036d603efeb56cf6E(ptr %e, i64 %0, i64 %1) unnamed_addr #1 {
start:
  %_5 = alloca %"core::any::TypeId", align 8
  %_0 = alloca ptr, align 8
  %target = alloca %"core::any::TypeId", align 8
  store i64 %0, ptr %target, align 8
  %2 = getelementptr inbounds i8, ptr %target, i64 8
  store i64 %1, ptr %2, align 8
; call core::any::TypeId::of
  %3 = call { i64, i64 } @_ZN4core3any6TypeId2of17h7dd1498b91903dceE()
  %4 = extractvalue { i64, i64 } %3, 0
  %5 = extractvalue { i64, i64 } %3, 1
  store i64 %4, ptr %_5, align 8
  %6 = getelementptr inbounds i8, ptr %_5, i64 8
  store i64 %5, ptr %6, align 8
; call <core::any::TypeId as core::cmp::PartialEq>::eq
  %_3 = call zeroext i1 @"_ZN58_$LT$core..any..TypeId$u20$as$u20$core..cmp..PartialEq$GT$2eq17h3ec8c0e5dcb956b2E"(ptr align 8 %_5, ptr align 8 %target)
  br i1 %_3, label %bb3, label %bb9

bb9:                                              ; preds = %start
  store ptr null, ptr %_0, align 8
  br label %bb10

bb3:                                              ; preds = %start
; call miette::eyreish::ptr::Ref<T>::cast
  %unerased = call ptr @"_ZN6miette7eyreish3ptr12Ref$LT$T$GT$4cast17hb52e031c6b285058E"(ptr %e)
; call miette::eyreish::ptr::Ref<T>::as_ptr
  %_13 = call ptr @"_ZN6miette7eyreish3ptr12Ref$LT$T$GT$6as_ptr17h4a06fcac3ef26180E"(ptr %unerased)
  %_12 = getelementptr inbounds i8, ptr %_13, i64 24
; call core::ptr::non_null::NonNull<T>::new_unchecked
  %_10 = call ptr @"_ZN4core3ptr8non_null16NonNull$LT$T$GT$13new_unchecked17h0f63ba3cd42f3548E"(ptr %_12)
; call miette::eyreish::ptr::Ref<T>::from_raw
  %_9 = call ptr @"_ZN6miette7eyreish3ptr12Ref$LT$T$GT$8from_raw17h17a12303579934dfE"(ptr %_10)
; call miette::eyreish::ptr::Ref<T>::cast
  %_8 = call ptr @"_ZN6miette7eyreish3ptr12Ref$LT$T$GT$4cast17h228ea4cc31de5266E"(ptr %_9)
  store ptr %_8, ptr %_0, align 8
  br label %bb10

bb10:                                             ; preds = %bb3, %bb9
  %7 = load ptr, ptr %_0, align 8, !noundef !3
  ret ptr %7
}

353 copies of this is pretty big, I'll find some other time to investigate further.

Boshen commented 1 month ago

One way to mitigate this is to merge all the error structs into a single num 🤔

Ok I think I'm doing it all wrong, I shouldn't define so many diagnostics, I should define one LinterDiagnostic instead and reuse it through out the codebase ...

Boshen commented 1 month ago

Ok it was pointed out that I totally missed MietteDiagnostic from the docs for the past 2 years ...