-
Notifications
You must be signed in to change notification settings - Fork 413
Add support for global constructors (i.e. life before main) #4459
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| //! Implement global constructors. | ||
| use std::task::Poll; | ||
|
|
||
| use rustc_abi::ExternAbi; | ||
| use rustc_target::spec::BinaryFormat; | ||
|
|
||
| use crate::*; | ||
|
|
||
| #[derive(Debug, Default)] | ||
| pub struct GlobalCtorState<'tcx>(GlobalCtorStatePriv<'tcx>); | ||
|
|
||
| #[derive(Debug, Default)] | ||
| enum GlobalCtorStatePriv<'tcx> { | ||
| #[default] | ||
| Init, | ||
| /// The list of constructor functions that we still have to call. | ||
| Ctors(Vec<ImmTy<'tcx>>), | ||
| Done, | ||
| } | ||
|
|
||
| impl<'tcx> GlobalCtorState<'tcx> { | ||
| pub fn on_stack_empty( | ||
| &mut self, | ||
| this: &mut MiriInterpCx<'tcx>, | ||
| ) -> InterpResult<'tcx, Poll<()>> { | ||
| use GlobalCtorStatePriv::*; | ||
| let new_state = 'new_state: { | ||
| match &mut self.0 { | ||
| Init => { | ||
| let this = this.eval_context_mut(); | ||
|
|
||
| // Lookup constructors from the relevant magic link section. | ||
| let ctors = match this.tcx.sess.target.binary_format { | ||
| // Read the CRT library section on Windows. | ||
| BinaryFormat::Coff => | ||
| this.lookup_link_section(|section| section == ".CRT$XCU")?, | ||
|
|
||
| // Read the `__mod_init_func` section on macOS. | ||
| BinaryFormat::MachO => | ||
| this.lookup_link_section(|section| { | ||
| let mut parts = section.splitn(3, ','); | ||
| let (segment_name, section_name, section_type) = | ||
| (parts.next(), parts.next(), parts.next()); | ||
|
|
||
| segment_name == Some("__DATA") | ||
| && section_name == Some("__mod_init_func") | ||
| // The `mod_init_funcs` directive ensures that the `S_MOD_INIT_FUNC_POINTERS` flag | ||
| // is set on the section, but it is not strictly required. | ||
| && matches!(section_type, None | Some("mod_init_funcs")) | ||
ibraheemdev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| })?, | ||
|
|
||
| // Read the standard `.init_array` section on platforms that use ELF, or WASM, | ||
| // which supports the same linker directive. | ||
| // FIXME: Add support for `.init_array.N`. | ||
ibraheemdev marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| BinaryFormat::Elf | BinaryFormat::Wasm => | ||
| this.lookup_link_section(|section| section == ".init_array")?, | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Other platforms have no global ctor support. | ||
| _ => break 'new_state Done, | ||
| }; | ||
|
|
||
| break 'new_state Ctors(ctors); | ||
| } | ||
| Ctors(ctors) => { | ||
| if let Some(ctor) = ctors.pop() { | ||
| let this = this.eval_context_mut(); | ||
|
|
||
| let ctor = ctor.to_scalar().to_pointer(this)?; | ||
| let thread_callback = this.get_ptr_fn(ctor)?.as_instance()?; | ||
|
|
||
| // The signature of this function is `unsafe extern "C" fn()`. | ||
| this.call_function( | ||
| thread_callback, | ||
| ExternAbi::C { unwind: false }, | ||
| &[], | ||
| None, | ||
| ReturnContinuation::Stop { cleanup: true }, | ||
| )?; | ||
|
|
||
| return interp_ok(Poll::Pending); // we stay in this state (but `ctors` got shorter) | ||
| } | ||
|
|
||
| // No more constructors to run. | ||
| break 'new_state Done; | ||
| } | ||
| Done => return interp_ok(Poll::Ready(())), | ||
| } | ||
| }; | ||
|
|
||
| self.0 = new_state; | ||
| interp_ok(Poll::Pending) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | ||||||
|
|
||||||
| static COUNT: AtomicUsize = AtomicUsize::new(0); | ||||||
|
|
||||||
| unsafe extern "C" fn ctor() { | ||||||
| COUNT.fetch_add(1, Ordering::Relaxed); | ||||||
| } | ||||||
|
|
||||||
| macro_rules! ctor { | ||||||
| ($ident:ident = $ctor:ident) => { | ||||||
| #[cfg_attr( | ||||||
| all(any( | ||||||
| target_os = "linux", | ||||||
| target_os = "android", | ||||||
| target_os = "dragonfly", | ||||||
| target_os = "freebsd", | ||||||
| target_os = "haiku", | ||||||
| target_os = "illumos", | ||||||
| target_os = "netbsd", | ||||||
| target_os = "openbsd", | ||||||
| target_os = "solaris", | ||||||
| target_os = "none", | ||||||
| target_family = "wasm", | ||||||
| )), | ||||||
| link_section = ".init_array" | ||||||
| )] | ||||||
| #[cfg_attr(windows, link_section = ".CRT$XCU")] | ||||||
| #[cfg_attr( | ||||||
| any(target_os = "macos", target_os = "ios"), | ||||||
| link_section = "__DATA,__mod_init_func" | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Mach-O requires the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From the Mach-O reference, it looks like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs seem to say that S_MOD_INIT_FUNC_POINTERS indicates init functions, which sounds to me like the flag is required to designate a section of init functions?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the flag is implied on For this specific test I don't think it matters much whether we specify the flag given that we support it either way, but I can add it if you prefer.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it is questionable whether the version without the extra tag should work (as opposed to "happens to work due to undocumented LLVM accidents"), then at the very least we need a FIXME comment here clarifying this.
Do you have a link for that?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LLVM currently implies the flag for Edit: llvm/llvm-project@cb307a2 seems to have introduced this behavior as part of moving away from using the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added a comment, that should suffice for now -- thanks for the links! |
||||||
| )] | ||||||
| #[used] | ||||||
| static $ident: unsafe extern "C" fn() = $ctor; | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| ctor! { CTOR1 = ctor } | ||||||
| ctor! { CTOR2 = ctor } | ||||||
| ctor! { CTOR3 = ctor } | ||||||
|
|
||||||
| fn main() { | ||||||
| assert_eq!(COUNT.load(Ordering::Relaxed), 3, "ctors did not run"); | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.