Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print spans where tags are created and invalidated #2030

Merged
merged 6 commits into from
May 14, 2022

Conversation

saethlin
Copy link
Member

@saethlin saethlin commented Mar 17, 2022

5225225 called this "automatic tag tracking" and I think that may be a reasonable description, but I would like to kill tag tracking as a primary use of Miri if possible. Tag tracking isn't always possible; for example if the UB is only detected with isolation off and the failing tag is made unstable by removing isolation. (also it's bad UX to run the tool twice)

This is just one of the things we can do with #2024

The memory usage of this is shockingly low, I think because the memory usage of Miri is driven by allocations where each byte ends up with its own very large stack. The memory usage in this change is linear with the number of tags, not tags * bytes. If memory usage gets out of control we can cap the number of events we save per allocation, from experience we tend to only use the most recent few in diagnostics but of course there's no guarantee of that so if we can manage to keep everything that would be best.

In many cases now I can tell exactly what these codebases are doing wrong just from the new outputs here, which I think is extremely cool.

New helps generated with plain old cargo miri test on rust-argon2 v1.0.0:

test argon2::tests::single_thread_verification_multi_lane_hash ... error: Undefined Behavior: trying to reborrow <1485898> for Unique permission at alloc110523[0x0], but that tag does not exist in the borrow stack for this location
   --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/mem/manually_drop.rs:89:9
    |
89  |         slot.value
    |         ^^^^^^^^^^
    |         |
    |         trying to reborrow <1485898> for Unique permission at alloc110523[0x0], but that tag does not exist in the borrow stack for this location
    |         this error occurs as part of a reborrow at alloc110523[0x0..0x20]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <1485898> was created by a retag at offsets [0x0..0x20]
   --> src/memory.rs:42:13
    |
42  |             vec.push(unsafe { &mut (*ptr) });
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <1485898> was later invalidated at offsets [0x0..0x20]
   --> src/memory.rs:42:31
    |
42  |             vec.push(unsafe { &mut (*ptr) });
    |                               ^^^^^^^^^^^

And with -Zmiri-tag-raw-pointers on slab v0.4.5

error: Undefined Behavior: trying to reborrow <2915> for Unique permission at alloc1418[0x0], but that tag does not exist in the borrow stack for this location
   --> /tmp/slab-0.4.5/src/lib.rs:835:16
    |
835 |         match (&mut *ptr1, &mut *ptr2) {
    |                ^^^^^^^^^^
    |                |
    |                trying to reborrow <2915> for Unique permission at alloc1418[0x0], but that tag does not exist in the borrow stack for this location
    |                this error occurs as part of a reborrow at alloc1418[0x0..0x10]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <2915> was created by a retag at offsets [0x0..0x10]
   --> /tmp/slab-0.4.5/src/lib.rs:833:20
    |
833 |         let ptr1 = self.entries.get_unchecked_mut(key1) as *mut Entry<T>;
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <2915> was later invalidated at offsets [0x0..0x20]
   --> /tmp/slab-0.4.5/src/lib.rs:834:20
    |
834 |         let ptr2 = self.entries.get_unchecked_mut(key2) as *mut Entry<T>;
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And without raw pointer tagging, cargo miri test on half v1.8.2

error: Undefined Behavior: trying to reborrow <untagged> for Unique permission at alloc1340[0x0], but that tag only grants SharedReadOnly permission for this location
   --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/slice/raw.rs:141:9
    |
141 |         &mut *ptr::slice_from_raw_parts_mut(data, len)
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |         |
    |         trying to reborrow <untagged> for Unique permission at alloc1340[0x0], but that tag only grants SharedReadOnly permission for this location
    |         this error occurs as part of a reborrow at alloc1340[0x0..0x6]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: tag was most recently created at offsets [0x0..0x6]
   --> /tmp/half-1.8.2/src/slice.rs:309:22
    |
309 |         let length = self.len();
    |                      ^^^^^^^^^^
help: this tag was also created here at offsets [0x0..0x6]
   --> /tmp/half-1.8.2/src/slice.rs:308:23
    |
308 |         let pointer = self.as_ptr() as *mut u16;
    |                       ^^^^^^^^^^^^^

The second suggestion is close to guesswork, but from experience it tends to be correct (as in, it tends to locate the pointer the user wanted) more often that it doesn't.

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

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

Afaict this set of information will just keep growing indefinitely. At this point I think we could try a refactoring where SbTag actually is an Rc and we store the new tracking data in the heap allocation.

There were also some smaller cleanups you did (around mutable references and refcell borrowing) that I think can be pulled out into separate PRs or commits?

src/stacked_borrows.rs Outdated Show resolved Hide resolved
@saethlin
Copy link
Member Author

Afaict this set of information will just keep growing indefinitely.

That is correct, but in this implementation we could store a finite amount of data per allocation. Anecdotally, the problematic tags often seem to be created recently.

At this point I think we could try a refactoring where SbTag actually is an Rc and we store the new tracking data in the heap allocation.

Can you elaborate on what you mean by this? Tags have a sort of obvious reference-counted-like behavior, but the problem we have at the moment is that we cannot clean up tags until the whole allocation goes away. Unless we can remove tags from a borrow stack because they are deemed inaccessible, I don't think there's any room for improvement.

There were also some smaller cleanups you did

This PR contains bits of two other PRs that are required to make it work. I think talking about cleanups will be a lot easier once those land and I can cleanly rebase onto the exact revisions that land.

@oli-obk
Copy link
Contributor

oli-obk commented Mar 17, 2022

Unless we can remove tags from a borrow stack because they are deemed inaccessible, I don't think there's any room for improvement.

My hope was that we could make the ones in the borrow stack be a Weak and once anything else referencing that tag is dropped, it becomes dangling. We can then regularly remove all dangling ones from the stack.

@bors
Copy link
Collaborator

bors commented Mar 17, 2022

☔ The latest upstream changes (presumably #1971) made this pull request unmergeable. Please resolve the merge conflicts.

@saethlin
Copy link
Member Author

saethlin commented Mar 17, 2022

In this comment, I concluded we need something like what you're proposing but I didn't think to use Weak: #1935 (comment)

So Weak fixes the problem where we need to be notified of references or pointers going away, but it doesn't fix the problem with ptr-int-ptr casts. I think we need a way to leak the Rc part for tags which have been cast to integers.

@oli-obk
Copy link
Contributor

oli-obk commented Mar 17, 2022

I think we need a way to leak the Rc part for tags which have been cast to integers.

we could keep them around in a list just to be able to tell people at the end about all the leaked tags

@saethlin
Copy link
Member Author

My concern is figuring out that a tag has been leaked. I'm just not sure how to implement that.

src/eval.rs Outdated
.map(|frame| frame.current_span())
.unwrap_or(rustc_span::DUMMY_SP);
if let Some(sb) = ecx.memory.extra.stacked_borrows.as_mut() {
sb.get_mut().current_span = current_span;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if there is some reasonable way we could just have the span available where it is currently missing? I assume this is for memory_read and memory_written and it seems not unreasonable to give them a TyCtxtAt which contains a span.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not entirely sure what you're getting at, but I have so many feelings about this code.

I've generally tried to avoid modifying rustc to provide the functionality I want here. But we definitely need some more state somewhere, I feel like I'm starting to shove state where it doesn't really belong.

This code produces DUMMY_SP when there isn't a local span available. I'm not sure what span to select when there is nothing local. The topmost span? And this code is a bit constrained because it needs the array of local crates available to deduce which span it should be considering.

Copy link
Member

Choose a reason for hiding this comment

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

There is ecx.cur_span() to get the best approximation of the "current" span.

However, I did not realize that you are deliberately computing another span than that here. The problem is that inside the read/write handlers we cannot have access to the entire machine state since there are outstanding borrows on particular allocations. We could have access to the stack though... that might not even require changes to rustc, if we can move the threads field from Evaluator to MemoryExtra. (There is not really a conceptual distinction between those two, we just put things into MemoryExtra whenever that is required.)

Copy link
Member

Choose a reason for hiding this comment

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

Here is how I think you can entirely avoid this "set_cur_span" thing: RalfJung@a20fb71

Copy link
Member Author

Choose a reason for hiding this comment

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

I need to thread a bit more logic through to have access to the local crate list, but the logic is factored better!

@bors
Copy link
Collaborator

bors commented Mar 18, 2022

☔ The latest upstream changes (presumably #2024) made this pull request unmergeable. Please resolve the merge conflicts.

@saethlin saethlin force-pushed the track-alloc-history branch 2 times, most recently from 2ad160b to 2ad417e Compare March 19, 2022 00:31
@saethlin
Copy link
Member Author

saethlin commented Mar 19, 2022

After much searching, I have found an example where these diagnostics do very poorly. stack-buf 0.1.6

---- src/vec.rs - vec::StackVec<T,N>::swap_remove (line 481) stdout ----
Test executable failed (exit code 1).

stderr:
error: Undefined Behavior: trying to reborrow <untagged> for Unique permission at alloc1071[0x10], but that tag does not exist in the borrow stack for this location
   --> /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:531:19
    |
531 |         mem::swap(&mut *dst, &mut src); // cannot overlap
    |                   ^^^^^^^^^
    |                   |
    |                   trying to reborrow <untagged> for Unique permission at alloc1071[0x10], but that tag does not exist in the borrow stack for this location
    |                   this error occurs as part of a reborrow at alloc1071[0x10..0x20]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: tag was most recently created at offsets [0x0..0x40]
   --> /tmp/stack-buf-0.1.6/src/vec.rs:257:9
    |
257 |         self.vec.as_mut_ptr() as _
    |         ^^^^^^^^^^^^^^^^^^^^^
help: tag was later invalidated at offsets [0x0..0x48]
   --> /tmp/stack-buf-0.1.6/src/vec.rs:281:5
    |
281 | /     pub unsafe fn set_len(&mut self, length: usize) {
282 | |         debug_assert!(length <= self.capacity());
283 | |         self.len = length as Size;
284 | |     }
    | |_____^
help: this tag was also created here at offsets [0x0..0x40]
   --> /tmp/stack-buf-0.1.6/src/vec.rs:251:9
    |
251 |         self.vec.as_ptr() as _
    |         ^^^^^^^^^^^^^^^^^
    = note: inside `std::ptr::replace::<&str>` at /home/ben/.rustup/toolchains/miri/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:531:19
note: inside `stack_buf::StackVec::<&str, 4_usize>::swap_remove` at /tmp/stack-buf-0.1.6/src/vec.rs:514:13
   --> /tmp/stack-buf-0.1.6/src/vec.rs:514:13
    |
514 |             ptr::replace(hole, last)
    |             ^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `main::_doctest_main_src_vec_rs_481_0` at src/vec.rs:8:12
   --> src/vec.rs:486:12
    |
8   | assert_eq!(v.swap_remove(1), "bar");
    |            ^^^^^^^^^^^^^^^^
note: inside `main` at src/vec.rs:13:3
   --> src/vec.rs:491:3
    |
13  | } _doctest_main_src_vec_rs_481_0() }
    |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This is some classic invalidation. People do this sort of thing all the time, but my attempt to diagnose this has gone wrong in many ways.

  1. The identification of where the invalidation occurs should be perfect, but the span we select is of the definition of set_len, not the call to it. Which is completely baffling.
  2. The created spans are supposed to locate where the author of this thought they were created a valid pointer for this operation. The first attempt is correct. But this crate implements a fn as_mut_ptr by calling self.as_mut_ptr which makes for hard reading. It would be totally awesome if I could have pointed them to the self.as_mut_ptr on the line right above the set_len which invalidates the pointer.

The span for set_len is so confusing I'm willing to call it wrong. The invalidation occurs because this is a

pub struct StackVec<T, const N: usize> {
    vec: [MaybeUninit<T>; N],
    len: Size,
}

So the retag of self as Unique which occurs to call self.set_len is the source of the invalidation. But it's very confusing to me that we end up in a Frame whose current_span points at the entire function when that's happening.

For what it's worth, I had to search through 61 crates that I know contain SB issues before I found one where this PR's diagnostics emitted something confusing.

@RalfJung
Copy link
Member

Is it possible that the invalidation happens at a Retag? These are synthesized by a MIR transform and might not have great spans. Though considering that you are skipping non-local frames, the result still seems to be odd.

@saethlin
Copy link
Member Author

saethlin commented Mar 19, 2022

I'm pretty sure it does. I minimized the code into this

fn main() {
    let mut x = 0i32;
    let z = &mut x as *mut i32;
    x.do_bad();
    unsafe {
        let _oof = *z;
    }
}

trait Bad {
    fn do_bad(&mut self) {
        // who knows
    }
}

impl Bad for i32 {}

Which emits this

error: Undefined Behavior: attempting a read access using <untagged> at alloc953[0x0], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:6:20
   |
6  |         let _oof = *z;
   |                    ^^
   |                    |
   |                    attempting a read access using <untagged> at alloc953[0x0], but that tag does not exist in the borrow stack for this location
   |                    this error occurs as part of an access at alloc953[0x0..0x4]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: tag was most recently created at offsets [0x0..0x4]
  --> src/main.rs:3:13
   |
3  |     let z = &mut x as *mut i32;
   |             ^^^^^^
help: tag was later invalidated at offsets [0x0..0x4]
  --> src/main.rs:11:5
   |
11 | /     fn do_bad(&mut self) {
12 | |         // who knows
13 | |     }
   | |_____^
   = note: inside `main` at src/main.rs:6:20

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

So I think the problem here, at least in my opinion, is that the interpreter stack has a frame which says its span is the entire called function during a RetagKind::FnEntry, but my mental model is that the retag is associated with the call site. I think we should be able to point at either the call site or the specific parameter in the function signature which is being retagged.

@RalfJung
Copy link
Member

RalfJung commented Mar 19, 2022

We certainly cannot point at the call site, the retag is part of the callee operations (that is crucial for its role in optimizations, and also pragmatically speaking when that span is set we have no idea who will call this function).

The span for the retag is determined here.

@saethlin
Copy link
Member Author

We certainly cannot point at the call site

Ah, yes. I think that we should probably surface the kind of retag that's happening here in a diagnostic, which may help avoid confusion. If nothing else it helps people get to the part of the linked SB reference that explains what's going on. I agree with the FIXME, maybe I'll try fixing it.

But more broadly, I'm thinking about helping people fix their code. Pointing at the argument is technically correct, but the change that needs to be made to fix the aliasing in this situation is at the call site. So for diagnostic purposes, we could back up a stack frame when picking the current local span if we are inside a FnEntry retag. But of course really the problem here is crossing a function boundary, so I wonder if there's similar lack of useful information with the span selection during retagging that happens upon function return.

@RalfJung
Copy link
Member

the change that needs to be made to fix the aliasing in this situation is at the call site

How universally true is that for FnEntry retag failures?

@saethlin
Copy link
Member Author

How universally true

I'm at 1/1 so far. Currently trying to get this branch into my bot setup so that I can do a proper search. It might be a while, but I'm determined to know.

@saethlin saethlin force-pushed the track-alloc-history branch 2 times, most recently from 7f37fa6 to 50f19e3 Compare April 2, 2022 16:44
@saethlin
Copy link
Member Author

saethlin commented Apr 2, 2022

I wrote a little script that looks for invalidation errors where the first line of the span contains fn. This found 13 crates. Of those, I think 11 are best fixed at the call site, and only is an issue with the callee.

Best fixed at the call site:
stack-buf 0.1.6 (calling set_len too early)
staticvec 0.11.4 (&->*, &mut invalidation)
refpool 0.4.3 (invalidation on first iteration of loop)
bsn1 0.4.0 (calling set_len too early)
mbox 0.6.0 (Unable to free due to a call to drop_in_place)
arraydeque 0.4.5 (&->*, &mut invalidation)
sized-chunks 0.6.5 (&->*, &mut invalidation)
nalgebra 0.30.1 (&->*, &mut invalidation)
heapless 0.7.10 (&->*, &mut invalidation)
arrayvec 0.7.2 (&->*, &mut invalidation)

Best fixed in the function definition:
ash 0.37.0+1.3.209 (Passing a &mut T and instantly converting to *mut T. Argument should be *mut T)

Structurally problematic:
trees 0.4.2 (Some kind of linked data structure invalidation problem)

One thing that sticks out to me is that a lot of these are this pattern:

ptr::copy(arr.get_unchecked(src), arr.get_unchecked_mut(dst));

where the author wants to pass two pointers into their container to a function, so they call two functions to get them where the receiver of the second one is &mut self, invalidating the first pointer. But the snippet above doesn't generate the FnEntry span pattern, because the functions that are entered aren't considered local.


Mostly posting this to share my progress, still thinking on what to do with this information to be more helpful in diagnostics.

@bors
Copy link
Collaborator

bors commented Apr 5, 2022

☔ The latest upstream changes (presumably #2047) made this pull request unmergeable. Please resolve the merge conflicts.

@RalfJung
Copy link
Member

RalfJung commented Apr 5, 2022

I merged MemoryExtra and Machine into one type, which hopefully helps with some of the trouble you had here accessing global state inside Stacked Borrows.

In particular, memory_read and friends can now access all threads' stacks.

* Pass a ThreadInfo down to grant/access to get the current span lazily
* Rename add_* to log_* for clarity
* Hoist borrow_mut calls out of loops by tweaking the for_each signature
* Explain the parameters of check_protector a bit more
src/machine.rs Outdated Show resolved Hide resolved
src/stacked_borrows.rs Outdated Show resolved Hide resolved
@@ -111,7 +118,12 @@ pub struct GlobalStateInner {
tracked_call_ids: HashSet<CallId>,
/// Whether to track raw pointers.
tag_raw: bool,
/// Extra per-allocation information
extras: HashMap<AllocId, AllocHistory>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should at least try to move extras into Stacks. It looks to me like the log_* functions could actually be implemented on Stacks.

src/stacked_borrows/diagnostics.rs Show resolved Hide resolved
src/stacked_borrows/diagnostics.rs Show resolved Hide resolved
src/stacked_borrows.rs Outdated Show resolved Hide resolved
src/stacked_borrows/diagnostics.rs Show resolved Hide resolved
extras.current_time += 1;
}

fn get_stack_history(
Copy link
Contributor

Choose a reason for hiding this comment

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

the use of iterator combinators vs for loops and and_then/map vs if let feels very random in this function and I keep expecting a specific choice to have a reason.

Maybe create one function per TagHistory field and use ? more within these functions?

* Store the local crates in an Rc<[CrateNum]>
* Move all the allocation history into Stacks
* Clean up the implementation of get_logs_relevant_to a bit
@oli-obk
Copy link
Contributor

oli-obk commented May 14, 2022

are the output examples at the top of the PR still up-to-date?

r=me with those updated unless they are up to date

@@ -764,8 +797,16 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
Permission::SharedReadWrite
};
let item = Item { perm, tag: new_tag, protector };
stacked_borrows.for_each(range, |offset, stack| {
stack.grant(orig_tag, item, (alloc_id, range, offset), &*global)
let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut();
Copy link
Member

Choose a reason for hiding this comment

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

This moves a borrow into the closure, which is probably bad for performance.
Is it possible to keep it at the old place, or does that lead to multiple borrows?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah it does lead to multiple borrows. I actually attempted this transformation before in a previous commit then I amended it out when CI failed.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, then please add a comment explaining that.

@RalfJung
Copy link
Member

r=me with those updated unless they are up to date

Same here. Would be good to also double-check that the benchmarks did not get any worse with these refactorings.

@saethlin
Copy link
Member Author

Without raw pointer tagging, I cannot measure a regression. With it, I measure a regression of ~5% in mse, and nothing above noise in serde1.

@oli-obk
Copy link
Contributor

oli-obk commented May 14, 2022

@bors r+

@bors
Copy link
Collaborator

bors commented May 14, 2022

📌 Commit 8ff0aac has been approved by oli-obk

@bors
Copy link
Collaborator

bors commented May 14, 2022

⌛ Testing commit 8ff0aac with merge 98c8c8f...

@oli-obk
Copy link
Contributor

oli-obk commented May 14, 2022

Thanks for checking! Very excited for this PR being in miri proper

@bors
Copy link
Collaborator

bors commented May 14, 2022

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing 98c8c8f to master...

@bors bors merged commit 98c8c8f into rust-lang:master May 14, 2022
@saethlin saethlin deleted the track-alloc-history branch May 15, 2022 04:01
@RalfJung
Copy link
Member

Thanks a ton for pushing this through, @saethlin. :-)

FWIW I hope to make raw ptr tagging the default once permissive provenance works, so if there is something we can do about those 5% that would be great...

@saethlin
Copy link
Member Author

My instinct is that the 5% could be recovered if instead of a Vec<Event> that's pushed to indefinitely, we store a VecDeque<Event> and cap the size. Presently I don't know what a good size is and I'm hesitant to just pick one because if it's too small the diagnostics will be incomplete. But I do have a growing library of crates which can serve as real-world test cases, so I think it makes sense to do some experiments to pick a good size cap once we've taken a shot at some of the bigger performance opportunities.

@saethlin
Copy link
Member Author

The lazy current span calculation computes the current span once per invalidated tag, potentially computing it many times in one interpreter step 🤦

@RalfJung
Copy link
Member

Oh, good point.
A FnMut could encapsulate some local state to cache the result...

bors added a commit that referenced this pull request May 23, 2022
Factor current-span logic into a caching handle

After #2030 and while working on #1935 it became quite clear that we need to do some caching here, because some retag operations generate many calls to `log_invalidation`, and would thus search the current thread's stack _many_ times for a local crate. This caching fixes that. This handle type also has the nice benefit of tucking away all the `ThreadManager` + `CrateNum` logic.
@jrmuizel
Copy link

This was very helpful in fixing fitzgen/bacon-rajan-cc#37. Thanks for doing it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants