+#[derive(Debug, Default)]
+pub struct TlsDtorsState(TlsDtorsStatePriv);
+
+#[derive(Debug, Default)]
+enum TlsDtorsStatePriv {
+ #[default]
+ Init,
+ PthreadDtors(RunningDtorState),
+ Done,
+}
+
+impl TlsDtorsState {
+ pub fn on_stack_empty<'tcx>(
+ &mut self,
+ this: &mut MiriInterpCx<'_, 'tcx>,
+ ) -> InterpResult<'tcx, Poll<()>> {
+ use TlsDtorsStatePriv::*;
+ match &mut self.0 {
+ Init => {
+ match this.tcx.sess.target.os.as_ref() {
+ "linux" | "freebsd" | "android" => {
+ // Run the pthread dtors.
+ self.0 = PthreadDtors(Default::default());
+ }
+ "macos" => {
+ // The macOS thread wide destructor runs "before any TLS slots get
+ // freed", so do that first.
+ this.schedule_macos_tls_dtor()?;
+ // When the stack is empty again, go on with the pthread dtors.
+ self.0 = PthreadDtors(Default::default());
+ }
+ "windows" => {
+ // Run the special magic hook.
+ this.schedule_windows_tls_dtors()?;
+ // And move to the final state.
+ self.0 = Done;
+ }
+ "wasi" | "none" => {
+ // No OS, no TLS dtors.
+ // FIXME: should we do something on wasi?
+ self.0 = Done;
+ }
+ os => {
+ throw_unsup_format!(
+ "the TLS machinery does not know how to handle OS `{os}`"
+ );
+ }
+ }
+ }
+ PthreadDtors(state) => {
+ match this.schedule_next_pthread_tls_dtor(state)? {
+ Poll::Pending => {} // just keep going
+ Poll::Ready(()) => self.0 = Done,
+ }
+ }
+ Done => {
+ this.machine.tls.delete_all_thread_tls(this.get_active_thread());
+ return Ok(Poll::Ready(()));
+ }
+ }
+
+ Ok(Poll::Pending)
+ }
+}
+