Skip to content

Commit fe090db

Browse files
authored
Merge pull request rust-lang#4459 from ibraheemdev/ibraheem/global-ctor
Add support for global constructors (i.e. life before main)
2 parents ed212ff + a7818ab commit fe090db

File tree

8 files changed

+216
-30
lines changed

8 files changed

+216
-30
lines changed

src/tools/miri/src/concurrency/thread.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,8 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
677677
fn run_on_stack_empty(&mut self) -> InterpResult<'tcx, Poll<()>> {
678678
let this = self.eval_context_mut();
679679
// Inform GenMC that a thread has finished all user code. GenMC needs to know this for scheduling.
680+
// FIXME(GenMC): Thread-local destructors *are* user code, so this is odd. Also now that we
681+
// support pre-main constructors, it can get called there as well.
680682
if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
681683
let thread_id = this.active_thread();
682684
genmc_ctx.handle_thread_stack_empty(thread_id);

src/tools/miri/src/eval.rs

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ use rustc_abi::ExternAbi;
1111
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1212
use rustc_hir::def::Namespace;
1313
use rustc_hir::def_id::DefId;
14-
use rustc_middle::ty::layout::LayoutCx;
14+
use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutCx};
1515
use rustc_middle::ty::{self, Ty, TyCtxt};
1616
use rustc_session::config::EntryFnType;
1717

1818
use crate::concurrency::GenmcCtx;
1919
use crate::concurrency::thread::TlsAllocAction;
2020
use crate::diagnostics::report_leaks;
21-
use crate::shims::tls;
21+
use crate::shims::{global_ctor, tls};
2222
use crate::*;
2323

2424
#[derive(Copy, Clone, Debug)]
@@ -216,9 +216,17 @@ impl Default for MiriConfig {
216216
}
217217

218218
/// The state of the main thread. Implementation detail of `on_main_stack_empty`.
219-
#[derive(Default, Debug)]
219+
#[derive(Debug)]
220220
enum MainThreadState<'tcx> {
221-
#[default]
221+
GlobalCtors {
222+
ctor_state: global_ctor::GlobalCtorState<'tcx>,
223+
/// The main function to call.
224+
entry_id: DefId,
225+
entry_type: MiriEntryFnType,
226+
/// Arguments passed to `main`.
227+
argc: ImmTy<'tcx>,
228+
argv: ImmTy<'tcx>,
229+
},
222230
Running,
223231
TlsDtors(tls::TlsDtorsState<'tcx>),
224232
Yield {
@@ -234,6 +242,15 @@ impl<'tcx> MainThreadState<'tcx> {
234242
) -> InterpResult<'tcx, Poll<()>> {
235243
use MainThreadState::*;
236244
match self {
245+
GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => {
246+
match ctor_state.on_stack_empty(this)? {
247+
Poll::Pending => {} // just keep going
248+
Poll::Ready(()) => {
249+
call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?;
250+
*self = Running;
251+
}
252+
}
253+
}
237254
Running => {
238255
*self = TlsDtors(Default::default());
239256
}
@@ -309,13 +326,6 @@ pub fn create_ecx<'tcx>(
309326
MiriMachine::new(config, layout_cx, genmc_ctx),
310327
);
311328

312-
// Some parts of initialization require a full `InterpCx`.
313-
MiriMachine::late_init(&mut ecx, config, {
314-
let mut state = MainThreadState::default();
315-
// Cannot capture anything GC-relevant here.
316-
Box::new(move |m| state.on_main_stack_empty(m))
317-
})?;
318-
319329
// Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
320330
let sentinel =
321331
helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
@@ -326,15 +336,9 @@ pub fn create_ecx<'tcx>(
326336
);
327337
}
328338

329-
// Setup first stack frame.
330-
let entry_instance = ty::Instance::mono(tcx, entry_id);
331-
332-
// First argument is constructed later, because it's skipped for `miri_start.`
333-
334-
// Second argument (argc): length of `config.args`.
339+
// Compute argc and argv from `config.args`.
335340
let argc =
336341
ImmTy::from_int(i64::try_from(config.args.len()).unwrap(), ecx.machine.layouts.isize);
337-
// Third argument (`argv`): created from `config.args`.
338342
let argv = {
339343
// Put each argument in memory, collect pointers.
340344
let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
@@ -359,7 +363,7 @@ pub fn create_ecx<'tcx>(
359363
ecx.write_immediate(arg, &place)?;
360364
}
361365
ecx.mark_immutable(&argvs_place);
362-
// Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
366+
// Store `argc` and `argv` for macOS `_NSGetArg{c,v}`, and for the GC to see them.
363367
{
364368
let argc_place =
365369
ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
@@ -374,7 +378,7 @@ pub fn create_ecx<'tcx>(
374378
ecx.machine.argv = Some(argv_place.ptr());
375379
}
376380
// Store command line as UTF-16 for Windows `GetCommandLineW`.
377-
{
381+
if tcx.sess.target.os == "windows" {
378382
// Construct a command string with all the arguments.
379383
let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
380384

@@ -395,11 +399,43 @@ pub fn create_ecx<'tcx>(
395399
ImmTy::from_immediate(imm, layout)
396400
};
397401

402+
// Some parts of initialization require a full `InterpCx`.
403+
MiriMachine::late_init(&mut ecx, config, {
404+
let mut main_thread_state = MainThreadState::GlobalCtors {
405+
entry_id,
406+
entry_type,
407+
argc,
408+
argv,
409+
ctor_state: global_ctor::GlobalCtorState::default(),
410+
};
411+
412+
// Cannot capture anything GC-relevant here.
413+
// `argc` and `argv` *are* GC_relevant, but they also get stored in `machine.argc` and
414+
// `machine.argv` so we are good.
415+
Box::new(move |m| main_thread_state.on_main_stack_empty(m))
416+
})?;
417+
418+
interp_ok(ecx)
419+
}
420+
421+
// Call the entry function.
422+
fn call_main<'tcx>(
423+
ecx: &mut MiriInterpCx<'tcx>,
424+
entry_id: DefId,
425+
entry_type: MiriEntryFnType,
426+
argc: ImmTy<'tcx>,
427+
argv: ImmTy<'tcx>,
428+
) -> InterpResult<'tcx, ()> {
429+
let tcx = ecx.tcx();
430+
431+
// Setup first stack frame.
432+
let entry_instance = ty::Instance::mono(tcx, entry_id);
433+
398434
// Return place (in static memory so that it does not count as leak).
399435
let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
400436
ecx.machine.main_fn_ret_place = Some(ret_place.clone());
401-
// Call start function.
402437

438+
// Call start function.
403439
match entry_type {
404440
MiriEntryFnType::Rustc(EntryFnType::Main { .. }) => {
405441
let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| {
@@ -409,7 +445,7 @@ pub fn create_ecx<'tcx>(
409445
let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
410446
let start_instance = ty::Instance::try_resolve(
411447
tcx,
412-
typing_env,
448+
ecx.typing_env(),
413449
start_id,
414450
tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]),
415451
)
@@ -427,7 +463,7 @@ pub fn create_ecx<'tcx>(
427463
ExternAbi::Rust,
428464
&[
429465
ImmTy::from_scalar(
430-
Scalar::from_pointer(main_ptr, &ecx),
466+
Scalar::from_pointer(main_ptr, ecx),
431467
// FIXME use a proper fn ptr type
432468
ecx.machine.layouts.const_raw_ptr,
433469
),
@@ -450,7 +486,7 @@ pub fn create_ecx<'tcx>(
450486
}
451487
}
452488

453-
interp_ok(ecx)
489+
interp_ok(())
454490
}
455491

456492
/// Evaluates the entry function specified by `entry_id`.

src/tools/miri/src/helpers.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,8 +1235,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12351235
interp_ok(())
12361236
}
12371237

1238-
/// Lookup an array of immediates stored as a linker section of name `name`.
1239-
fn lookup_link_section(&mut self, name: &str) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> {
1238+
/// Lookup an array of immediates from any linker sections matching the provided predicate.
1239+
fn lookup_link_section(
1240+
&mut self,
1241+
include_name: impl Fn(&str) -> bool,
1242+
) -> InterpResult<'tcx, Vec<ImmTy<'tcx>>> {
12401243
let this = self.eval_context_mut();
12411244
let tcx = this.tcx.tcx;
12421245

@@ -1247,7 +1250,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12471250
let Some(link_section) = attrs.link_section else {
12481251
return interp_ok(());
12491252
};
1250-
if link_section.as_str() == name {
1253+
if include_name(link_section.as_str()) {
12511254
let instance = ty::Instance::mono(tcx, def_id);
12521255
let const_val = this.eval_global(instance).unwrap_or_else(|err| {
12531256
panic!(
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! Implement global constructors.
2+
3+
use std::task::Poll;
4+
5+
use rustc_abi::ExternAbi;
6+
use rustc_target::spec::BinaryFormat;
7+
8+
use crate::*;
9+
10+
#[derive(Debug, Default)]
11+
pub struct GlobalCtorState<'tcx>(GlobalCtorStatePriv<'tcx>);
12+
13+
#[derive(Debug, Default)]
14+
enum GlobalCtorStatePriv<'tcx> {
15+
#[default]
16+
Init,
17+
/// The list of constructor functions that we still have to call.
18+
Ctors(Vec<ImmTy<'tcx>>),
19+
Done,
20+
}
21+
22+
impl<'tcx> GlobalCtorState<'tcx> {
23+
pub fn on_stack_empty(
24+
&mut self,
25+
this: &mut MiriInterpCx<'tcx>,
26+
) -> InterpResult<'tcx, Poll<()>> {
27+
use GlobalCtorStatePriv::*;
28+
let new_state = 'new_state: {
29+
match &mut self.0 {
30+
Init => {
31+
let this = this.eval_context_mut();
32+
33+
// Lookup constructors from the relevant magic link section.
34+
let ctors = match this.tcx.sess.target.binary_format {
35+
// Read the CRT library section on Windows.
36+
BinaryFormat::Coff =>
37+
this.lookup_link_section(|section| section == ".CRT$XCU")?,
38+
39+
// Read the `__mod_init_func` section on macOS.
40+
BinaryFormat::MachO =>
41+
this.lookup_link_section(|section| {
42+
let mut parts = section.splitn(3, ',');
43+
let (segment_name, section_name, section_type) =
44+
(parts.next(), parts.next(), parts.next());
45+
46+
segment_name == Some("__DATA")
47+
&& section_name == Some("__mod_init_func")
48+
// The `mod_init_funcs` directive ensures that the
49+
// `S_MOD_INIT_FUNC_POINTERS` flag is set on the section. LLVM
50+
// adds this automatically so we currently do not require it.
51+
// FIXME: is this guaranteed LLVM behavior? If not, we shouldn't
52+
// implicitly add it here. Also see
53+
// <https://github.com/rust-lang/miri/pull/4459#discussion_r2200115629>.
54+
&& matches!(section_type, None | Some("mod_init_funcs"))
55+
})?,
56+
57+
// Read the standard `.init_array` section on platforms that use ELF, or WASM,
58+
// which supports the same linker directive.
59+
// FIXME: Add support for `.init_array.N` and `.ctors`?
60+
BinaryFormat::Elf | BinaryFormat::Wasm =>
61+
this.lookup_link_section(|section| section == ".init_array")?,
62+
63+
// Other platforms have no global ctor support.
64+
_ => break 'new_state Done,
65+
};
66+
67+
break 'new_state Ctors(ctors);
68+
}
69+
Ctors(ctors) => {
70+
if let Some(ctor) = ctors.pop() {
71+
let this = this.eval_context_mut();
72+
73+
let ctor = ctor.to_scalar().to_pointer(this)?;
74+
let thread_callback = this.get_ptr_fn(ctor)?.as_instance()?;
75+
76+
// The signature of this function is `unsafe extern "C" fn()`.
77+
this.call_function(
78+
thread_callback,
79+
ExternAbi::C { unwind: false },
80+
&[],
81+
None,
82+
ReturnContinuation::Stop { cleanup: true },
83+
)?;
84+
85+
return interp_ok(Poll::Pending); // we stay in this state (but `ctors` got shorter)
86+
}
87+
88+
// No more constructors to run.
89+
break 'new_state Done;
90+
}
91+
Done => return interp_ok(Poll::Ready(())),
92+
}
93+
};
94+
95+
self.0 = new_state;
96+
interp_ok(Poll::Pending)
97+
}
98+
}

src/tools/miri/src/shims/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod x86;
1414
pub mod env;
1515
pub mod extern_static;
1616
pub mod foreign_items;
17+
pub mod global_ctor;
1718
pub mod io_error;
1819
pub mod os_str;
1920
pub mod panic;

src/tools/miri/src/shims/tls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
302302

303303
// Windows has a special magic linker section that is run on certain events.
304304
// We don't support most of that, but just enough to make thread-local dtors in `std` work.
305-
interp_ok(this.lookup_link_section(".CRT$XLB")?)
305+
interp_ok(this.lookup_link_section(|section| section == ".CRT$XLB")?)
306306
}
307307

308308
fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx>) -> InterpResult<'tcx> {

src/tools/miri/tests/pass/alloc-access-tracking.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![no_std]
22
#![no_main]
3-
//@compile-flags: -Zmiri-track-alloc-id=20 -Zmiri-track-alloc-accesses -Cpanic=abort
4-
//@normalize-stderr-test: "id 20" -> "id $$ALLOC"
3+
//@compile-flags: -Zmiri-track-alloc-id=19 -Zmiri-track-alloc-accesses -Cpanic=abort
4+
//@normalize-stderr-test: "id 19" -> "id $$ALLOC"
55
//@only-target: linux # alloc IDs differ between OSes (due to extern static allocations)
66

77
extern "Rust" {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use std::sync::atomic::{AtomicUsize, Ordering};
2+
3+
static COUNT: AtomicUsize = AtomicUsize::new(0);
4+
5+
unsafe extern "C" fn ctor() {
6+
COUNT.fetch_add(1, Ordering::Relaxed);
7+
}
8+
9+
#[rustfmt::skip]
10+
macro_rules! ctor {
11+
($ident:ident = $ctor:ident) => {
12+
#[cfg_attr(
13+
all(any(
14+
target_os = "linux",
15+
target_os = "android",
16+
target_os = "dragonfly",
17+
target_os = "freebsd",
18+
target_os = "haiku",
19+
target_os = "illumos",
20+
target_os = "netbsd",
21+
target_os = "openbsd",
22+
target_os = "solaris",
23+
target_os = "none",
24+
target_family = "wasm",
25+
)),
26+
link_section = ".init_array"
27+
)]
28+
#[cfg_attr(windows, link_section = ".CRT$XCU")]
29+
#[cfg_attr(
30+
any(target_os = "macos", target_os = "ios"),
31+
// We do not set the `mod_init_funcs` flag here since ctor/inventory also do not do
32+
// that. See <https://github.com/rust-lang/miri/pull/4459#discussion_r2200115629>.
33+
link_section = "__DATA,__mod_init_func"
34+
)]
35+
#[used]
36+
static $ident: unsafe extern "C" fn() = $ctor;
37+
};
38+
}
39+
40+
ctor! { CTOR1 = ctor }
41+
ctor! { CTOR2 = ctor }
42+
ctor! { CTOR3 = ctor }
43+
44+
fn main() {
45+
assert_eq!(COUNT.load(Ordering::Relaxed), 3, "ctors did not run");
46+
}

0 commit comments

Comments
 (0)