about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/std/src/sys/pal/windows/thread_local_key.rs1
-rw-r--r--src/tools/miri/src/bin/miri.rs41
-rw-r--r--src/tools/miri/src/concurrency/thread.rs2
-rw-r--r--src/tools/miri/src/eval.rs8
-rw-r--r--src/tools/miri/src/helpers.rs74
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs113
-rw-r--r--src/tools/miri/src/shims/tls.rs112
7 files changed, 205 insertions, 146 deletions
diff --git a/library/std/src/sys/pal/windows/thread_local_key.rs b/library/std/src/sys/pal/windows/thread_local_key.rs
index 1d9cb316a45..4c00860dae3 100644
--- a/library/std/src/sys/pal/windows/thread_local_key.rs
+++ b/library/std/src/sys/pal/windows/thread_local_key.rs
@@ -276,6 +276,7 @@ unsafe fn register_dtor(key: &'static StaticKey) {
 // the address of the symbol to ensure it sticks around.
 
 #[link_section = ".CRT$XLB"]
+#[cfg_attr(miri, used)] // Miri only considers explicitly `#[used]` statics for `lookup_link_section`
 pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, c::LPVOID) =
     on_tls_callback;
 
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 8fffb91542f..64e2c13a451 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -32,8 +32,9 @@ use rustc_driver::Compilation;
 use rustc_hir::{self as hir, Node};
 use rustc_interface::interface::Config;
 use rustc_middle::{
-    middle::exported_symbols::{
-        ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel,
+    middle::{
+        codegen_fn_attrs::CodegenFnAttrFlags,
+        exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel},
     },
     query::LocalCrate,
     ty::TyCtxt,
@@ -136,6 +137,7 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
             config.override_queries = Some(|_, local_providers| {
                 // `exported_symbols` and `reachable_non_generics` provided by rustc always returns
                 // an empty result if `tcx.sess.opts.output_types.should_codegen()` is false.
+                // In addition we need to add #[used] symbols to exported_symbols for `lookup_link_section`.
                 local_providers.exported_symbols = |tcx, LocalCrate| {
                     let reachable_set = tcx.with_stable_hashing_context(|hcx| {
                         tcx.reachable_set(()).to_sorted(&hcx, true)
@@ -160,19 +162,28 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
                                 })
                                 if !tcx.generics_of(local_def_id).requires_monomorphization(tcx)
                             );
-                            (is_reachable_non_generic
-                                && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator())
-                            .then_some((
-                                ExportedSymbol::NonGeneric(local_def_id.to_def_id()),
-                                // Some dummy `SymbolExportInfo` here. We only use
-                                // `exported_symbols` in shims/foreign_items.rs and the export info
-                                // is ignored.
-                                SymbolExportInfo {
-                                    level: SymbolExportLevel::C,
-                                    kind: SymbolExportKind::Text,
-                                    used: false,
-                                },
-                            ))
+                            if !is_reachable_non_generic {
+                                return None;
+                            }
+                            let codegen_fn_attrs = tcx.codegen_fn_attrs(local_def_id);
+                            if codegen_fn_attrs.contains_extern_indicator()
+                                || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED)
+                                || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)
+                            {
+                                Some((
+                                    ExportedSymbol::NonGeneric(local_def_id.to_def_id()),
+                                    // Some dummy `SymbolExportInfo` here. We only use
+                                    // `exported_symbols` in shims/foreign_items.rs and the export info
+                                    // is ignored.
+                                    SymbolExportInfo {
+                                        level: SymbolExportLevel::C,
+                                        kind: SymbolExportKind::Text,
+                                        used: false,
+                                    },
+                                ))
+                            } else {
+                                None
+                            }
                         }),
                     )
                 }
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index d0d73bb1b34..d1136272f01 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -158,7 +158,7 @@ pub struct Thread<'mir, 'tcx> {
 }
 
 pub type StackEmptyCallback<'mir, 'tcx> =
-    Box<dyn FnMut(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, Poll<()>>>;
+    Box<dyn FnMut(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, Poll<()>> + 'tcx>;
 
 impl<'mir, 'tcx> Thread<'mir, 'tcx> {
     /// Get the name of the current thread if it was set.
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index f7edbdb9c5e..df0ede1e1b6 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -192,18 +192,18 @@ impl Default for MiriConfig {
 
 /// The state of the main thread. Implementation detail of `on_main_stack_empty`.
 #[derive(Default, Debug)]
-enum MainThreadState {
+enum MainThreadState<'tcx> {
     #[default]
     Running,
-    TlsDtors(tls::TlsDtorsState),
+    TlsDtors(tls::TlsDtorsState<'tcx>),
     Yield {
         remaining: u32,
     },
     Done,
 }
 
-impl MainThreadState {
-    fn on_main_stack_empty<'tcx>(
+impl<'tcx> MainThreadState<'tcx> {
+    fn on_main_stack_empty(
         &mut self,
         this: &mut MiriInterpCx<'_, 'tcx>,
     ) -> InterpResult<'tcx, Poll<()>> {
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 6e320b60eec..55be4a43792 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -9,16 +9,21 @@ use rand::RngCore;
 
 use rustc_apfloat::ieee::{Double, Single};
 use rustc_apfloat::Float;
-use rustc_hir::def::{DefKind, Namespace};
-use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
+use rustc_hir::{
+    def::{DefKind, Namespace},
+    def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE},
+};
 use rustc_index::IndexVec;
+use rustc_middle::middle::dependency_format::Linkage;
+use rustc_middle::middle::exported_symbols::ExportedSymbol;
 use rustc_middle::mir;
 use rustc_middle::ty::{
     self,
     layout::{LayoutOf, TyAndLayout},
     FloatTy, IntTy, Ty, TyCtxt, UintTy,
 };
-use rustc_span::{def_id::CrateNum, sym, Span, Symbol};
+use rustc_session::config::CrateType;
+use rustc_span::{sym, Span, Symbol};
 use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants};
 use rustc_target::spec::abi::Abi;
 
@@ -142,6 +147,38 @@ fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option<Namespace>)
     None
 }
 
+/// Call `f` for each exported symbol.
+pub fn iter_exported_symbols<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>,
+) -> InterpResult<'tcx> {
+    // `dependency_formats` includes all the transitive informations needed to link a crate,
+    // which is what we need here since we need to dig out `exported_symbols` from all transitive
+    // dependencies.
+    let dependency_formats = tcx.dependency_formats(());
+    let dependency_format = dependency_formats
+        .iter()
+        .find(|(crate_type, _)| *crate_type == CrateType::Executable)
+        .expect("interpreting a non-executable crate");
+    for cnum in iter::once(LOCAL_CRATE).chain(dependency_format.1.iter().enumerate().filter_map(
+        |(num, &linkage)| {
+            // We add 1 to the number because that's what rustc also does everywhere it
+            // calls `CrateNum::new`...
+            #[allow(clippy::arithmetic_side_effects)]
+            (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
+        },
+    )) {
+        // We can ignore `_export_info` here: we are a Rust crate, and everything is exported
+        // from a Rust crate.
+        for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
+            if let ExportedSymbol::NonGeneric(def_id) = symbol {
+                f(cnum, def_id)?;
+            }
+        }
+    }
+    Ok(())
+}
+
 /// Convert a softfloat type to its corresponding hostfloat type.
 pub trait ToHost {
     type HostFloat;
@@ -1180,6 +1217,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         }
         Ok(())
     }
+
+    /// Lookup an array of immediates stored as a linker section of name `name`.
+    fn lookup_link_section(
+        &mut self,
+        name: &str,
+    ) -> InterpResult<'tcx, Vec<ImmTy<'tcx, Provenance>>> {
+        let this = self.eval_context_mut();
+        let tcx = this.tcx.tcx;
+
+        let mut array = vec![];
+
+        iter_exported_symbols(tcx, |_cnum, def_id| {
+            let attrs = tcx.codegen_fn_attrs(def_id);
+            let Some(link_section) = attrs.link_section else {
+                return Ok(());
+            };
+            if link_section.as_str() == name {
+                let instance = ty::Instance::mono(tcx, def_id);
+                let const_val = this.eval_global(instance).unwrap_or_else(|err| {
+                    panic!(
+                        "failed to evaluate static in required link_section: {def_id:?}\n{err:?}"
+                    )
+                });
+                let val = this.read_immediate(&const_val)?;
+                array.push(val);
+            }
+            Ok(())
+        })?;
+
+        Ok(array)
+    }
 }
 
 impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index a25d377f3a7..6b0797f6da1 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -2,17 +2,10 @@ use std::{collections::hash_map::Entry, io::Write, iter, path::Path};
 
 use rustc_apfloat::Float;
 use rustc_ast::expand::allocator::AllocatorKind;
-use rustc_hir::{
-    def::DefKind,
-    def_id::{CrateNum, LOCAL_CRATE},
-};
-use rustc_middle::middle::{
-    codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
-    exported_symbols::ExportedSymbol,
-};
+use rustc_hir::{def::DefKind, def_id::CrateNum};
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::mir;
 use rustc_middle::ty;
-use rustc_session::config::CrateType;
 use rustc_span::Symbol;
 use rustc_target::{
     abi::{Align, Size},
@@ -174,74 +167,48 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             Entry::Vacant(e) => {
                 // Find it if it was not cached.
                 let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None;
-                // `dependency_formats` includes all the transitive informations needed to link a crate,
-                // which is what we need here since we need to dig out `exported_symbols` from all transitive
-                // dependencies.
-                let dependency_formats = tcx.dependency_formats(());
-                let dependency_format = dependency_formats
-                    .iter()
-                    .find(|(crate_type, _)| *crate_type == CrateType::Executable)
-                    .expect("interpreting a non-executable crate");
-                for cnum in iter::once(LOCAL_CRATE).chain(
-                    dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
-                        // We add 1 to the number because that's what rustc also does everywhere it
-                        // calls `CrateNum::new`...
-                        #[allow(clippy::arithmetic_side_effects)]
-                        (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
-                    }),
-                ) {
-                    // We can ignore `_export_info` here: we are a Rust crate, and everything is exported
-                    // from a Rust crate.
-                    for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
-                        if let ExportedSymbol::NonGeneric(def_id) = symbol {
-                            let attrs = tcx.codegen_fn_attrs(def_id);
-                            let symbol_name = if let Some(export_name) = attrs.export_name {
-                                export_name
-                            } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
-                                tcx.item_name(def_id)
+                helpers::iter_exported_symbols(tcx, |cnum, def_id| {
+                    let attrs = tcx.codegen_fn_attrs(def_id);
+                    let symbol_name = if let Some(export_name) = attrs.export_name {
+                        export_name
+                    } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
+                        tcx.item_name(def_id)
+                    } else {
+                        // Skip over items without an explicitly defined symbol name.
+                        return Ok(());
+                    };
+                    if symbol_name == link_name {
+                        if let Some((original_instance, original_cnum)) = instance_and_crate {
+                            // Make sure we are consistent wrt what is 'first' and 'second'.
+                            let original_span = tcx.def_span(original_instance.def_id()).data();
+                            let span = tcx.def_span(def_id).data();
+                            if original_span < span {
+                                throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
+                                    link_name,
+                                    first: original_span,
+                                    first_crate: tcx.crate_name(original_cnum),
+                                    second: span,
+                                    second_crate: tcx.crate_name(cnum),
+                                });
                             } else {
-                                // Skip over items without an explicitly defined symbol name.
-                                continue;
-                            };
-                            if symbol_name == link_name {
-                                if let Some((original_instance, original_cnum)) = instance_and_crate
-                                {
-                                    // Make sure we are consistent wrt what is 'first' and 'second'.
-                                    let original_span =
-                                        tcx.def_span(original_instance.def_id()).data();
-                                    let span = tcx.def_span(def_id).data();
-                                    if original_span < span {
-                                        throw_machine_stop!(
-                                            TerminationInfo::MultipleSymbolDefinitions {
-                                                link_name,
-                                                first: original_span,
-                                                first_crate: tcx.crate_name(original_cnum),
-                                                second: span,
-                                                second_crate: tcx.crate_name(cnum),
-                                            }
-                                        );
-                                    } else {
-                                        throw_machine_stop!(
-                                            TerminationInfo::MultipleSymbolDefinitions {
-                                                link_name,
-                                                first: span,
-                                                first_crate: tcx.crate_name(cnum),
-                                                second: original_span,
-                                                second_crate: tcx.crate_name(original_cnum),
-                                            }
-                                        );
-                                    }
-                                }
-                                if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
-                                    throw_ub_format!(
-                                        "attempt to call an exported symbol that is not defined as a function"
-                                    );
-                                }
-                                instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
+                                throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
+                                    link_name,
+                                    first: span,
+                                    first_crate: tcx.crate_name(cnum),
+                                    second: original_span,
+                                    second_crate: tcx.crate_name(original_cnum),
+                                });
                             }
                         }
+                        if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
+                            throw_ub_format!(
+                                "attempt to call an exported symbol that is not defined as a function"
+                            );
+                        }
+                        instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
                     }
-                }
+                    Ok(())
+                })?;
 
                 e.insert(instance_and_crate.map(|ic| ic.0))
             }
diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs
index 7f929d9a91e..d25bae1cdc0 100644
--- a/src/tools/miri/src/shims/tls.rs
+++ b/src/tools/miri/src/shims/tls.rs
@@ -219,61 +219,76 @@ impl VisitProvenance for TlsData<'_> {
 }
 
 #[derive(Debug, Default)]
-pub struct TlsDtorsState(TlsDtorsStatePriv);
+pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
 
 #[derive(Debug, Default)]
-enum TlsDtorsStatePriv {
+enum TlsDtorsStatePriv<'tcx> {
     #[default]
     Init,
     PthreadDtors(RunningDtorState),
+    /// For Windows Dtors, we store the list of functions that we still have to call.
+    /// These are functions from the magic `.CRT$XLB` linker section.
+    WindowsDtors(Vec<ImmTy<'tcx, Provenance>>),
     Done,
 }
 
-impl TlsDtorsState {
-    pub fn on_stack_empty<'tcx>(
+impl<'tcx> TlsDtorsState<'tcx> {
+    pub fn on_stack_empty(
         &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());
+        let new_state = 'new_state: {
+            match &mut self.0 {
+                Init => {
+                    match this.tcx.sess.target.os.as_ref() {
+                        "linux" | "freebsd" | "android" => {
+                            // Run the pthread dtors.
+                            break 'new_state 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.
+                            break 'new_state PthreadDtors(Default::default());
+                        }
+                        "windows" => {
+                            // Determine which destructors to run.
+                            let dtors = this.lookup_windows_tls_dtors()?;
+                            // And move to the final state.
+                            break 'new_state WindowsDtors(dtors);
+                        }
+                        _ => {
+                            // No TLS dtor support.
+                            // FIXME: should we do something on wasi?
+                            break 'new_state Done;
+                        }
                     }
-                    "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;
+                }
+                PthreadDtors(state) => {
+                    match this.schedule_next_pthread_tls_dtor(state)? {
+                        Poll::Pending => return Ok(Poll::Pending), // just keep going
+                        Poll::Ready(()) => break 'new_state Done,
                     }
-                    _ => {
-                        // No TLS dtor support.
-                        // FIXME: should we do something on wasi?
-                        self.0 = Done;
+                }
+                WindowsDtors(dtors) => {
+                    if let Some(dtor) = dtors.pop() {
+                        this.schedule_windows_tls_dtor(dtor)?;
+                        return Ok(Poll::Pending); // we stay in this state (but `dtors` got shorter)
+                    } else {
+                        // No more destructors to run.
+                        break 'new_state Done;
                     }
                 }
-            }
-            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(()));
                 }
             }
-            Done => {
-                this.machine.tls.delete_all_thread_tls(this.get_active_thread());
-                return Ok(Poll::Ready(()));
-            }
-        }
+        };
 
+        self.0 = new_state;
         Ok(Poll::Pending)
     }
 }
@@ -282,22 +297,19 @@ impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'m
 trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     /// Schedule TLS destructors for Windows.
     /// On windows, TLS destructors are managed by std.
-    fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> {
+    fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<ImmTy<'tcx, Provenance>>> {
         let this = self.eval_context_mut();
 
         // Windows has a special magic linker section that is run on certain events.
-        // Instead of searching for that section and supporting arbitrary hooks in there
-        // (that would be basically https://github.com/rust-lang/miri/issues/450),
-        // we specifically look up the static in libstd that we know is placed
-        // in that section.
-        if !this.have_module(&["std"]) {
-            // Looks like we are running in a `no_std` crate.
-            // That also means no TLS dtors callback to call.
-            return Ok(());
-        }
-        let thread_callback =
-            this.eval_windows("thread_local_key", "p_thread_callback").to_pointer(this)?;
-        let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?;
+        // We don't support most of that, but just enough to make thread-local dtors in `std` work.
+        Ok(this.lookup_link_section(".CRT$XLB")?)
+    }
+
+    fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let dtor = dtor.to_scalar().to_pointer(this)?;
+        let thread_callback = this.get_ptr_fn(dtor)?.as_instance()?;
 
         // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
         // but std treats both the same.
@@ -305,7 +317,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
         // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
-        // but both are ignored by std
+        // but both are ignored by std.
         this.call_function(
             thread_callback,
             Abi::System { unwind: false },