mmtk/util/
options.rs

1use crate::util::constants::LOG_BYTES_IN_MBYTE;
2use crate::util::os::*;
3use crate::util::Address;
4use std::default::Default;
5use std::fmt::Debug;
6use std::str::FromStr;
7use strum_macros::EnumString;
8
9/// The default stress factor. This is set to the max usize,
10/// which means we will never trigger a stress GC for the default value.
11pub const DEFAULT_STRESS_FACTOR: usize = usize::MAX;
12
13/// The zeroing approach to use for new object allocations.
14/// Affects each plan differently.
15#[derive(Copy, Clone, EnumString, Debug)]
16pub enum NurseryZeroingOptions {
17    /// Zeroing with normal temporal write.
18    Temporal,
19    /// Zeroing with cache-bypassing non-temporal write.
20    Nontemporal,
21    /// Zeroing with a separate zeroing thread.
22    Concurrent,
23    /// An adaptive approach using both non-temporal write and a concurrent zeroing thread.
24    Adaptive,
25}
26
27/// Select a GC plan for MMTk.
28#[derive(Copy, Clone, EnumString, Debug, PartialEq, Eq)]
29pub enum PlanSelector {
30    /// Allocation only without a collector. This is usually used for debugging.
31    /// Similar to OpenJDK epsilon (<https://openjdk.org/jeps/318>).
32    NoGC,
33    /// A semi-space collector, which divides the heap into two spaces and
34    /// copies the live objects into the other space for every GC.
35    SemiSpace,
36    /// A generational collector that uses a copying nursery, and the semi-space policy as its mature space.
37    GenCopy,
38    /// A generational collector that uses a copying nursery, and Immix as its mature space.
39    GenImmix,
40    /// A mark-sweep collector, which marks live objects and sweeps dead objects during GC.
41    MarkSweep,
42    /// A debugging collector that allocates memory at page granularity, and protects pages for dead objects
43    /// to prevent future access.
44    PageProtect,
45    /// A mark-region collector that allows an opportunistic defragmentation mechanism.
46    Immix,
47    /// A mark-compact collector that implements the Lisp-2 compaction algorithm.
48    Lisp2,
49    /// A mark-compact collector that uses offset-vector bitmaps.
50    OVC,
51    /// An Immix collector that uses a sticky mark bit to allow generational behaviors without a copying nursery.
52    StickyImmix,
53    /// Concurrent non-moving immix using SATB
54    ConcurrentImmix,
55}
56
57/// MMTk option for perf events
58///
59/// The format is
60/// ```
61/// <event> ::= <event-name> "," <pid> "," <cpu>
62/// <events> ::= <event> ";" <events> | <event> | ""
63/// ```
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct PerfEventOptions {
66    /// A vector of perf events in tuples of (event name, PID, CPU)
67    pub events: Vec<(String, i32, i32)>,
68}
69
70impl PerfEventOptions {
71    fn parse_perf_events(events: &str) -> Result<Vec<(String, i32, i32)>, String> {
72        events
73            .split(';')
74            .filter(|e| !e.is_empty())
75            .map(|e| {
76                let e: Vec<&str> = e.split(',').collect();
77                if e.len() != 3 {
78                    Err("Please supply (event name, pid, cpu)".into())
79                } else {
80                    let event_name = e[0].into();
81                    let pid = e[1]
82                        .parse()
83                        .map_err(|_| String::from("Failed to parse cpu"))?;
84                    let cpu = e[2]
85                        .parse()
86                        .map_err(|_| String::from("Failed to parse cpu"))?;
87                    Ok((event_name, pid, cpu))
88                }
89            })
90            .collect()
91    }
92}
93
94impl FromStr for PerfEventOptions {
95    type Err = String;
96
97    fn from_str(s: &str) -> Result<Self, Self::Err> {
98        PerfEventOptions::parse_perf_events(s).map(|events| PerfEventOptions { events })
99    }
100}
101
102/// The default min nursery size. This does not affect the actual space we create as nursery. It is
103/// only used in the GC trigger check.
104#[cfg(target_pointer_width = "64")]
105pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
106/// The default max nursery size. This does not affect the actual space we create as nursery. It is
107/// only used in the GC trigger check.
108#[cfg(target_pointer_width = "64")]
109pub const DEFAULT_MAX_NURSERY: usize = (1 << 20) << LOG_BYTES_IN_MBYTE;
110
111/// The default min nursery size. This does not affect the actual space we create as nursery. It is
112/// only used in the GC trigger check.
113#[cfg(target_pointer_width = "32")]
114pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
115/// The default max nursery size for 32 bits.
116pub const DEFAULT_MAX_NURSERY_32: usize = 32 << LOG_BYTES_IN_MBYTE;
117/// The default max nursery size. This does not affect the actual space we create as nursery. It is
118/// only used in the GC trigger check.
119#[cfg(target_pointer_width = "32")]
120pub const DEFAULT_MAX_NURSERY: usize = DEFAULT_MAX_NURSERY_32;
121
122/// The default min nursery size proportional to the current heap size
123pub const DEFAULT_PROPORTIONAL_MIN_NURSERY: f64 = 0.25;
124/// The default max nursery size proportional to the current heap size
125pub const DEFAULT_PROPORTIONAL_MAX_NURSERY: f64 = 1.0;
126
127fn always_valid<T>(_: &T) -> bool {
128    true
129}
130
131/// Error when setting an option by option name and option value as strings.
132enum SetOptionByStringError {
133    /// The option name does not exist.
134    InvalidKey,
135    /// Error when converting the value from string.
136    ValueParseError,
137    /// The value failed validation.
138    ValueValidationError,
139}
140
141/// An MMTk option of a given type.
142/// This type allows us to store some metadata for the option. To get the value of an option,
143/// you can simply dereference it (for example, *options.threads).
144#[derive(Clone)]
145pub struct MMTKOption<T: Debug + Clone + FromStr> {
146    /// The actual value for the option
147    value: T,
148    /// The validator to ensure the value is valid.
149    validator: fn(&T) -> bool,
150    /// Whether this option has been explicitly set by the user, as opposed to using its built-in default.
151    was_set: bool,
152}
153
154impl<T: Debug + Clone + FromStr> MMTKOption<T> {
155    /// Create a new MMTKOption
156    pub fn new(value: T, validator: fn(&T) -> bool) -> Self {
157        // FIXME: We should enable the following check to make sure the initial value is valid.
158        // However, we cannot enable it now. For options like perf events, the validator checks
159        // if the perf event feature is enabled. So when the perf event features are not enabled,
160        // the validator will fail whatever value we try to set (including the initial value).
161        // Ideally, we conditionally compile options based on the feature. But options! macro
162        // does not allow attributes in it, so we cannot conditionally compile options.
163        // let is_valid = validator(&value);
164        // assert!(
165        //     is_valid,
166        //     "Unable to create MMTKOption: initial value {:?} is invalid",
167        //     value
168        // );
169        MMTKOption {
170            value,
171            validator,
172            was_set: false,
173        }
174    }
175
176    /// Set the option to the given value. Returns true if the value is valid, and we set the option to the value.
177    pub fn set(&mut self, value: T) -> bool {
178        if (self.validator)(&value) {
179            self.value = value;
180            self.was_set = true;
181            return true;
182        }
183        false
184    }
185
186    /// Return true if this option has been explicitly set by the user (rather than left at its built-in default).
187    pub fn was_set(&self) -> bool {
188        self.was_set
189    }
190}
191
192// Dereference an option to get its value.
193impl<T: Debug + Clone + FromStr> std::ops::Deref for MMTKOption<T> {
194    type Target = T;
195
196    fn deref(&self) -> &Self::Target {
197        &self.value
198    }
199}
200
201macro_rules! options {
202    ($($(#[$outer:meta])*$name:ident: $type:ty [$validator:expr] = $default:expr),*,) => [
203        options!($(#[$outer])*$($name: $type [$validator] = $default),*);
204    ];
205    ($($(#[$outer:meta])*$name:ident: $type:ty [$validator:expr] = $default:expr),*) => [
206        /// Options for an MMTk instance.  It affects many aspects of the behavior of the MMTk
207        /// instance, including the number of GC worker threads, the GC plan to use, etc.
208        ///
209        /// Options are set by the VM binding before creating an instance of MMTk.  The VM binding
210        /// usually parses command line options, environment variables, configuration files, etc.,
211        /// to determine the options.  MMTk also provides the [`Options::read_env_var_settings`]
212        /// method which reads environment variables of the form `MMTK_*` and set options.  It can
213        /// be convenient in the early development stage of a VM binding.
214        #[derive(Clone)]
215        pub struct Options {
216            $($(#[$outer])*pub $name: MMTKOption<$type>),*
217        }
218
219        impl Options {
220            /// Set an option and run its validator for its value.
221            fn set_from_string_inner(&mut self, s: &str, val: &str) -> Result<(), SetOptionByStringError> {
222                match s {
223                    // Parse the given value from str (by env vars or by calling process()) to the right type
224                    $(stringify!($name) => {
225                        let Ok(typed_val) = val.parse::<$type>() else {
226                            return Err(SetOptionByStringError::ValueParseError);
227                        };
228
229                        if !self.$name.set(typed_val) {
230                            return Err(SetOptionByStringError::ValueValidationError);
231                        }
232
233                        Ok(())
234                    })*
235                    _ => Err(SetOptionByStringError::InvalidKey)
236                }
237            }
238
239            /// Create an `Options` instance with built-in default settings.
240            fn new() -> Self {
241                Options {
242                    $($name: MMTKOption::new($default, $validator)),*
243                }
244            }
245        }
246    ]
247}
248
249impl Default for Options {
250    /// By default, `Options` instance is created with built-in default settings.
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256impl Options {
257    /// Set an option by name and value as strings.  Returns true if the option is successfully set;
258    /// false otherwise.
259    ///
260    /// *WARNING*: This method involves string parsing which is not necessary in most cases. If you
261    /// can use [`MMTKOption::set`] directly, do it.  For example,
262    ///
263    /// ```rust
264    /// let mut builder = MMTKBuilder::new();
265    /// builder.options.threads.set(4);
266    /// builder.options.plan.set(PlanSelector::GenImmix);
267    ///
268    /// // All `T` in `MMTKOption<T>` implement `FromStr`.
269    /// builder.options.plan.set(user_input1.parse()?);
270    /// builder.options.thread_affinity.set(user_input2.parse()?);
271    /// ```
272    ///
273    /// Only use this method if the option name is also provided as strings, e.g. from command line
274    /// options or environment variables.
275    ///
276    /// Arguments:
277    /// * `s`: The name of the option, same as the field name.
278    /// * `val`: The value of the option, as a string.  It will be parsed by `FromStr::from_str`.
279    pub fn set_from_string(&mut self, s: &str, val: &str) -> bool {
280        self.set_from_string_inner(s, val).is_ok()
281    }
282
283    /// Set options in bulk by names and values as strings.
284    ///
285    /// Returns true if all the options are set successfully.
286    ///
287    /// Panics if the `options` argument contains any unrecognized keys.  Returns false if any
288    /// option given in the `options` argument cannot be set due to parsing errors or validation
289    /// errors.
290    ///
291    /// Arguments:
292    /// * `options`: a string that is key value pairs separated by white spaces or commas, e.g.
293    ///   `threads=1 stress_factor=4096`, or `threads=1,stress_factor=4096`. Each key-value pair
294    ///   will be set via [`Options::set_from_string`].
295    pub fn set_bulk_from_string(&mut self, options: &str) -> bool {
296        for opt in options.replace(',', " ").split_ascii_whitespace() {
297            let kv_pair: Vec<&str> = opt.split('=').collect();
298            if kv_pair.len() != 2 {
299                return false;
300            }
301
302            let key = kv_pair[0];
303            let val = kv_pair[1];
304            if let Err(e) = self.set_from_string_inner(key, val) {
305                match e {
306                    SetOptionByStringError::InvalidKey => {
307                        panic!("Invalid Options key: {}", key);
308                    }
309                    SetOptionByStringError::ValueParseError => {
310                        eprintln!("Warn: unable to set {}={:?}. Can't parse value. Default value will be used.", key, val);
311                    }
312                    SetOptionByStringError::ValueValidationError => {
313                        eprintln!("Warn: unable to set {}={:?}. Invalid value. Default value will be used.", key, val);
314                    }
315                }
316                return false;
317            }
318        }
319
320        true
321    }
322
323    /// Read options from environment variables, and apply those settings to self.
324    ///
325    /// If we have environment variables that start with `MMTK_` and match any option (such as
326    /// `MMTK_STRESS_FACTOR`), we set the option to its value (if it is a valid value).
327    pub fn read_env_var_settings(&mut self) {
328        const PREFIX: &str = "MMTK_";
329        for (key, val) in std::env::vars() {
330            // strip the prefix, and get the lower case string
331            if let Some(rest_of_key) = key.strip_prefix(PREFIX) {
332                let lowercase: &str = &rest_of_key.to_lowercase();
333                if let Err(e) = self.set_from_string_inner(lowercase, &val) {
334                    match e {
335                        SetOptionByStringError::InvalidKey => {
336                            /* Silently skip unrecognized keys. */
337                        }
338                        SetOptionByStringError::ValueParseError => {
339                            eprintln!("Warn: unable to set {}={:?}. Can't parse value. Default value will be used.", key, val);
340                        }
341                        SetOptionByStringError::ValueValidationError => {
342                            eprintln!("Warn: unable to set {}={:?}. Invalid value. Default value will be used.", key, val);
343                        }
344                    }
345                }
346            }
347        }
348    }
349
350    /// Check if the options are set for stress GC. If either stress_factor or analysis_factor is set,
351    /// we should do stress GC.
352    pub fn is_stress_test_gc_enabled(&self) -> bool {
353        *self.stress_factor != DEFAULT_STRESS_FACTOR
354            || *self.analysis_factor != DEFAULT_STRESS_FACTOR
355    }
356
357    /// Turning transparent huge pages into HugePageSupport.
358    pub fn transparent_hugepages_as_huge_page_support(&self) -> HugePageSupport {
359        if *self.transparent_hugepages {
360            HugePageSupport::TransparentHugePages
361        } else {
362            HugePageSupport::No
363        }
364    }
365
366    /// The number of concurrent threads is set to 1/4 of the total GC threads with a minimal of 1 concurrent threads.
367    fn compute_default_concurrent_threads(gc_threads: usize) -> usize {
368        usize::max(gc_threads / 4, 1)
369    }
370
371    /// Some options may change based on other options. This function resolves those options based on the current values of other options.
372    pub(crate) fn resolve_connected_options(&mut self) {
373        if !self.concurrent_threads.was_set() {
374            self.concurrent_threads.value = Self::compute_default_concurrent_threads(*self.threads);
375        }
376    }
377}
378
379#[derive(Clone, Debug, PartialEq)]
380/// AffinityKind describes how to set the affinity of GC threads. Note that we currently assume
381/// that each GC thread is equivalent to an OS or hardware thread.
382pub enum AffinityKind {
383    /// Delegate thread affinity to the OS scheduler
384    OsDefault,
385    /// Assign thread affinities over a list of cores in a round robin fashion. Note that if number
386    /// of threads > number of cores specified, then multiple threads will be assigned the same
387    /// core.
388    // XXX: Maybe using a u128 bitvector with each bit representing a core is more performant?
389    RoundRobin(Vec<CoreId>),
390    /// Assign all the cores specified in the set to all the GC threads. This allows to have core
391    /// exclusivity for GC threads without us caring about which core it gets scheduled on.
392    AllInSet(Vec<CoreId>),
393}
394
395impl AffinityKind {
396    /// Returns an AffinityKind or String containing error. Expects the list of cores to be
397    /// formatted as numbers separated by commas, including ranges. There should be no spaces
398    /// between the cores in the list. Optionally can provide an affinity kind before the list
399    /// of cores.
400    ///
401    /// Performs de-duplication of specified cores. Note that the core list is sorted as a
402    /// side-effect whenever a new core is added to the set.
403    ///
404    /// For example:
405    ///  - "`0,5,8-11`" specifies that the cores 0,5,8,9,10,11 should be used for pinning threads.
406    ///  - "`AllInSet:0,5`" specifies that the cores 0,5 should be used for pinning threads using the
407    ///    [`AffinityKind::AllInSet`] method.
408    fn parse_cpulist(cpulist: &str) -> Result<AffinityKind, String> {
409        let mut cpuset = vec![];
410
411        if cpulist.is_empty() {
412            return Ok(AffinityKind::OsDefault);
413        }
414
415        // Trying to parse strings such as "RoundRobin:0,1-3"
416        // First split on ":" to check if an affinity kind has been specified.
417        // Check if it is one of the legal affinity kinds. If no affinity kind
418        // has been specified then use `RoundRobin`.
419        let mut all_in_set = false;
420        let kind_split: Vec<&str> = cpulist.splitn(2, ':').collect();
421        if kind_split.len() == 2 {
422            match kind_split[0] {
423                "RoundRobin" => {
424                    all_in_set = false;
425                }
426                "AllInSet" => {
427                    all_in_set = true;
428                }
429                _ => {
430                    return Err(format!("Unknown affinity kind: {}", kind_split[0]));
431                }
432            }
433        }
434
435        let cpulist = if kind_split.len() == 2 {
436            kind_split[1]
437        } else {
438            kind_split[0]
439        };
440
441        // Split on ',' first and then split on '-' if there is a range
442        for split in cpulist.split(',') {
443            if !split.contains('-') {
444                if !split.is_empty() {
445                    if let Ok(core) = split.parse::<u16>() {
446                        cpuset.push(core);
447                        cpuset.sort_unstable();
448                        cpuset.dedup();
449                        continue;
450                    }
451                }
452            } else {
453                // Contains a range
454                let range: Vec<&str> = split.split('-').collect();
455                if range.len() == 2 {
456                    if let Ok(start) = range[0].parse::<u16>() {
457                        if let Ok(end) = range[1].parse::<u16>() {
458                            if start >= end {
459                                return Err(
460                                    "Starting core id in range should be less than the end"
461                                        .to_string(),
462                                );
463                            }
464
465                            for cpu in start..=end {
466                                cpuset.push(cpu);
467                                cpuset.sort_unstable();
468                                cpuset.dedup();
469                            }
470
471                            continue;
472                        }
473                    }
474                }
475            }
476
477            return Err("Core ids have been incorrectly specified".to_string());
478        }
479
480        if all_in_set {
481            Ok(AffinityKind::AllInSet(cpuset))
482        } else {
483            Ok(AffinityKind::RoundRobin(cpuset))
484        }
485    }
486
487    /// Return true if the affinity is either OsDefault or the cores in the list do not exceed the
488    /// maximum number of cores allocated to the program. Assumes core ids on the system are
489    /// 0-indexed.
490    pub fn validate(&self) -> bool {
491        let num_cpu = OS::get_total_num_cpus();
492
493        if let AffinityKind::RoundRobin(cpuset) = self {
494            for cpu in cpuset {
495                if cpu >= &num_cpu {
496                    return false;
497                }
498            }
499        }
500
501        true
502    }
503}
504
505impl FromStr for AffinityKind {
506    type Err = String;
507
508    fn from_str(s: &str) -> Result<Self, Self::Err> {
509        AffinityKind::parse_cpulist(s)
510    }
511}
512
513#[derive(Copy, Clone, Debug)]
514/// An option that provides a min/max interface to MMTk and a Bounded/Fixed interface to the
515/// user/VM.
516pub enum NurserySize {
517    /// A Bounded nursery has different upper and lower bounds. The size only controls the upper
518    /// bound. Hence, it is considered to be a "variable size" nursery.
519    Bounded {
520        /// The lower bound of the nursery size in bytes. Default to [`DEFAULT_MIN_NURSERY`].
521        min: usize,
522        /// The upper bound of the nursery size in bytes. Default to [`DEFAULT_MAX_NURSERY`].
523        max: usize,
524    },
525    /// A bounded nursery that is proportional to the current heap size.
526    ProportionalBounded {
527        /// The lower bound of the nursery size as a proportion of the current heap size. Default to [`DEFAULT_PROPORTIONAL_MIN_NURSERY`].
528        min: f64,
529        /// The upper bound of the nursery size as a proportion of the current heap size. Default to [`DEFAULT_PROPORTIONAL_MAX_NURSERY`].
530        max: f64,
531    },
532    /// A Fixed nursery has the same upper and lower bounds. The size controls both the upper and
533    /// lower bounds. Note that this is considered less performant than a Bounded nursery since a
534    /// Fixed nursery size can be too restrictive and cause more GCs.
535    Fixed(usize),
536}
537
538impl NurserySize {
539    /// Return true if the values are valid.
540    fn validate(&self) -> bool {
541        match *self {
542            NurserySize::Bounded { min, max } => min <= max,
543            NurserySize::ProportionalBounded { min, max } => {
544                0.0f64 < min && min <= max && max <= 1.0f64
545            }
546            NurserySize::Fixed(_) => true,
547        }
548    }
549}
550
551impl FromStr for NurserySize {
552    type Err = String;
553
554    fn from_str(s: &str) -> Result<Self, Self::Err> {
555        let parts: Vec<&str> = s.split(':').collect();
556        if parts.len() != 2 {
557            return Err("Invalid format".to_string());
558        }
559
560        let variant = parts[0];
561        let values: Vec<&str> = parts[1].split(',').collect();
562
563        fn default_or_parse<T: FromStr>(val: &str, default_value: T) -> Result<T, String> {
564            if val == "_" {
565                Ok(default_value)
566            } else {
567                val.parse::<T>()
568                    .map_err(|_| format!("Failed to parse {:?}", std::any::type_name::<T>()))
569            }
570        }
571
572        match variant {
573            "Bounded" => {
574                if values.len() == 2 {
575                    let min = default_or_parse(values[0], DEFAULT_MIN_NURSERY)?;
576                    let max = default_or_parse(values[1], DEFAULT_MAX_NURSERY)?;
577                    Ok(NurserySize::Bounded { min, max })
578                } else {
579                    Err("Bounded requires two values".to_string())
580                }
581            }
582            "ProportionalBounded" => {
583                if values.len() == 2 {
584                    let min = default_or_parse(values[0], DEFAULT_PROPORTIONAL_MIN_NURSERY)?;
585                    let max = default_or_parse(values[1], DEFAULT_PROPORTIONAL_MAX_NURSERY)?;
586                    Ok(NurserySize::ProportionalBounded { min, max })
587                } else {
588                    Err("ProportionalBounded requires two values".to_string())
589                }
590            }
591            "Fixed" => {
592                if values.len() == 1 {
593                    let size = values[0]
594                        .parse::<usize>()
595                        .map_err(|_| "Invalid size value".to_string())?;
596                    Ok(NurserySize::Fixed(size))
597                } else {
598                    Err("Fixed requires one value".to_string())
599                }
600            }
601            _ => Err("Unknown variant".to_string()),
602        }
603    }
604}
605
606#[cfg(test)]
607mod nursery_size_parsing_tests {
608    use super::*;
609
610    #[test]
611    fn test_bounded() {
612        // Simple case
613        let result = "Bounded:1,2".parse::<NurserySize>().unwrap();
614        if let NurserySize::Bounded { min, max } = result {
615            assert_eq!(min, 1);
616            assert_eq!(max, 2);
617        } else {
618            panic!("Failed: {:?}", result);
619        }
620
621        // Default min
622        let result = "Bounded:_,2".parse::<NurserySize>().unwrap();
623        if let NurserySize::Bounded { min, max } = result {
624            assert_eq!(min, DEFAULT_MIN_NURSERY);
625            assert_eq!(max, 2);
626        } else {
627            panic!("Failed: {:?}", result);
628        }
629
630        // Default max
631        let result = "Bounded:1,_".parse::<NurserySize>().unwrap();
632        if let NurserySize::Bounded { min, max } = result {
633            assert_eq!(min, 1);
634            assert_eq!(max, DEFAULT_MAX_NURSERY);
635        } else {
636            panic!("Failed: {:?}", result);
637        }
638
639        // Default both
640        let result = "Bounded:_,_".parse::<NurserySize>().unwrap();
641        if let NurserySize::Bounded { min, max } = result {
642            assert_eq!(min, DEFAULT_MIN_NURSERY);
643            assert_eq!(max, DEFAULT_MAX_NURSERY);
644        } else {
645            panic!("Failed: {:?}", result);
646        }
647    }
648
649    #[test]
650    fn test_proportional() {
651        // Simple case
652        let result = "ProportionalBounded:0.1,0.8"
653            .parse::<NurserySize>()
654            .unwrap();
655        if let NurserySize::ProportionalBounded { min, max } = result {
656            assert_eq!(min, 0.1);
657            assert_eq!(max, 0.8);
658        } else {
659            panic!("Failed: {:?}", result);
660        }
661
662        // Default min
663        let result = "ProportionalBounded:_,0.8".parse::<NurserySize>().unwrap();
664        if let NurserySize::ProportionalBounded { min, max } = result {
665            assert_eq!(min, DEFAULT_PROPORTIONAL_MIN_NURSERY);
666            assert_eq!(max, 0.8);
667        } else {
668            panic!("Failed: {:?}", result);
669        }
670
671        // Default max
672        let result = "ProportionalBounded:0.1,_".parse::<NurserySize>().unwrap();
673        if let NurserySize::ProportionalBounded { min, max } = result {
674            assert_eq!(min, 0.1);
675            assert_eq!(max, DEFAULT_PROPORTIONAL_MAX_NURSERY);
676        } else {
677            panic!("Failed: {:?}", result);
678        }
679
680        // Default both
681        let result = "ProportionalBounded:_,_".parse::<NurserySize>().unwrap();
682        if let NurserySize::ProportionalBounded { min, max } = result {
683            assert_eq!(min, DEFAULT_PROPORTIONAL_MIN_NURSERY);
684            assert_eq!(max, DEFAULT_PROPORTIONAL_MAX_NURSERY);
685        } else {
686            panic!("Failed: {:?}", result);
687        }
688    }
689}
690
691/// Select a GC trigger for MMTk.
692#[derive(Copy, Clone, Debug, PartialEq, Eq)]
693pub enum GCTriggerSelector {
694    /// GC is triggered when a fixed-size heap is full. The value specifies the fixed heap size in bytes.
695    FixedHeapSize(usize),
696    /// GC is triggered by internal heuristics, and the heap size is varying between the two given values.
697    /// The two values are the lower and the upper bound of the heap size.
698    DynamicHeapSize(usize, usize),
699    /// Delegate the GC triggering to the binding.
700    Delegated,
701}
702
703impl GCTriggerSelector {
704    const K: u64 = 1024;
705    const M: u64 = 1024 * Self::K;
706    const G: u64 = 1024 * Self::M;
707    const T: u64 = 1024 * Self::G;
708
709    /// get max heap size
710    pub fn max_heap_size(&self) -> usize {
711        match self {
712            Self::FixedHeapSize(s) => *s,
713            Self::DynamicHeapSize(_, s) => *s,
714            _ => unreachable!("Cannot get max heap size"),
715        }
716    }
717
718    /// Parse a size representation, which could be a number to represents bytes,
719    /// or a number with the suffix K/k/M/m/G/g. Return the byte number if it can be
720    /// parsed properly, otherwise return an error string.
721    fn parse_size(s: &str) -> Result<usize, String> {
722        let s = s.to_lowercase();
723        if s.ends_with(char::is_alphabetic) {
724            let num = s[0..s.len() - 1]
725                .parse::<u64>()
726                .map_err(|e| e.to_string())?;
727            let size = if s.ends_with('k') {
728                num.checked_mul(Self::K)
729            } else if s.ends_with('m') {
730                num.checked_mul(Self::M)
731            } else if s.ends_with('g') {
732                num.checked_mul(Self::G)
733            } else if s.ends_with('t') {
734                num.checked_mul(Self::T)
735            } else {
736                return Err(format!(
737                    "Unknown size descriptor: {:?}",
738                    &s[(s.len() - 1)..]
739                ));
740            };
741
742            if let Some(size) = size {
743                size.try_into()
744                    .map_err(|_| format!("size overflow: {}", size))
745            } else {
746                Err(format!("size overflow: {}", s))
747            }
748        } else {
749            s.parse::<usize>().map_err(|e| e.to_string())
750        }
751    }
752
753    /// Return true if the GC trigger is valid
754    fn validate(&self) -> bool {
755        match self {
756            Self::FixedHeapSize(size) => *size > 0,
757            Self::DynamicHeapSize(min, max) => min <= max,
758            Self::Delegated => true,
759        }
760    }
761}
762
763impl FromStr for GCTriggerSelector {
764    type Err = String;
765
766    fn from_str(s: &str) -> Result<Self, Self::Err> {
767        use regex::Regex;
768        lazy_static! {
769            static ref FIXED_HEAP_REGEX: Regex =
770                Regex::new(r"^FixedHeapSize:(?P<size>\d+[kKmMgGtT]?)$").unwrap();
771            static ref DYNAMIC_HEAP_REGEX: Regex =
772                Regex::new(r"^DynamicHeapSize:(?P<min>\d+[kKmMgGtT]?),(?P<max>\d+[kKmMgGtT]?)$")
773                    .unwrap();
774        }
775
776        if s.is_empty() {
777            return Err("No GC trigger policy is supplied".to_string());
778        }
779
780        if let Some(captures) = FIXED_HEAP_REGEX.captures(s) {
781            return Self::parse_size(&captures["size"]).map(Self::FixedHeapSize);
782        } else if let Some(captures) = DYNAMIC_HEAP_REGEX.captures(s) {
783            let min = Self::parse_size(&captures["min"])?;
784            let max = Self::parse_size(&captures["max"])?;
785            return Ok(Self::DynamicHeapSize(min, max));
786        } else if s.starts_with("Delegated") {
787            return Ok(Self::Delegated);
788        }
789
790        Err(format!("Failed to parse the GC trigger option: {:?}", s))
791    }
792}
793
794#[cfg(test)]
795mod gc_trigger_tests {
796    use super::*;
797
798    #[test]
799    fn test_parse_size() {
800        // correct cases
801        assert_eq!(GCTriggerSelector::parse_size("0"), Ok(0));
802        assert_eq!(GCTriggerSelector::parse_size("1K"), Ok(1024));
803        assert_eq!(GCTriggerSelector::parse_size("1k"), Ok(1024));
804        assert_eq!(GCTriggerSelector::parse_size("2M"), Ok(2 * 1024 * 1024));
805        assert_eq!(GCTriggerSelector::parse_size("2m"), Ok(2 * 1024 * 1024));
806        assert_eq!(
807            GCTriggerSelector::parse_size("2G"),
808            Ok(2 * 1024 * 1024 * 1024)
809        );
810        assert_eq!(
811            GCTriggerSelector::parse_size("2g"),
812            Ok(2 * 1024 * 1024 * 1024)
813        );
814        #[cfg(target_pointer_width = "64")]
815        assert_eq!(
816            GCTriggerSelector::parse_size("2T"),
817            Ok(2 * 1024 * 1024 * 1024 * 1024)
818        );
819
820        // empty
821        assert_eq!(
822            GCTriggerSelector::parse_size(""),
823            Err("cannot parse integer from empty string".to_string())
824        );
825
826        // negative number - we dont care about actual error message
827        assert!(GCTriggerSelector::parse_size("-1").is_err());
828
829        // no number
830        assert!(GCTriggerSelector::parse_size("k").is_err());
831    }
832
833    #[test]
834    #[cfg(target_pointer_width = "32")]
835    fn test_parse_overflow_size() {
836        assert_eq!(
837            GCTriggerSelector::parse_size("4G"),
838            Err("size overflow: 4294967296".to_string())
839        );
840        assert_eq!(GCTriggerSelector::parse_size("4294967295"), Ok(4294967295));
841    }
842
843    #[test]
844    fn test_parse_fixed_heap() {
845        assert_eq!(
846            GCTriggerSelector::from_str("FixedHeapSize:1024"),
847            Ok(GCTriggerSelector::FixedHeapSize(1024))
848        );
849        assert_eq!(
850            GCTriggerSelector::from_str("FixedHeapSize:4m"),
851            Ok(GCTriggerSelector::FixedHeapSize(4 * 1024 * 1024))
852        );
853        #[cfg(target_pointer_width = "64")]
854        assert_eq!(
855            GCTriggerSelector::from_str("FixedHeapSize:4t"),
856            Ok(GCTriggerSelector::FixedHeapSize(
857                4 * 1024 * 1024 * 1024 * 1024
858            ))
859        );
860
861        // incorrect
862        assert!(GCTriggerSelector::from_str("FixedHeapSize").is_err());
863        assert!(GCTriggerSelector::from_str("FixedHeapSize:").is_err());
864        assert!(GCTriggerSelector::from_str("FixedHeapSize:-1").is_err());
865    }
866
867    #[test]
868    fn test_parse_dynamic_heap() {
869        assert_eq!(
870            GCTriggerSelector::from_str("DynamicHeapSize:1024,2048"),
871            Ok(GCTriggerSelector::DynamicHeapSize(1024, 2048))
872        );
873        assert_eq!(
874            GCTriggerSelector::from_str("DynamicHeapSize:1024,1024"),
875            Ok(GCTriggerSelector::DynamicHeapSize(1024, 1024))
876        );
877        assert_eq!(
878            GCTriggerSelector::from_str("DynamicHeapSize:1m,2m"),
879            Ok(GCTriggerSelector::DynamicHeapSize(
880                1024 * 1024,
881                2 * 1024 * 1024
882            ))
883        );
884
885        // incorrect
886        assert!(GCTriggerSelector::from_str("DynamicHeapSize:1024,1024,").is_err());
887    }
888
889    #[test]
890    fn test_validate() {
891        assert!(GCTriggerSelector::FixedHeapSize(1024).validate());
892        assert!(GCTriggerSelector::DynamicHeapSize(1024, 2048).validate());
893        assert!(GCTriggerSelector::DynamicHeapSize(1024, 1024).validate());
894
895        assert!(!GCTriggerSelector::FixedHeapSize(0).validate());
896        assert!(!GCTriggerSelector::DynamicHeapSize(2048, 1024).validate());
897    }
898}
899
900options! {
901    /// The GC plan to use.
902    plan:                   PlanSelector            [always_valid] = PlanSelector::GenImmix,
903    /// Number of GC worker threads.
904    threads:                usize                   [|v: &usize| *v > 0] = num_cpus::get(),
905    /// Maximum number of GC worker threads that may run concurrent GC work.
906    /// If this exceeds the total number of GC worker threads, all workers may participate.
907    concurrent_threads:     usize                   [|v: &usize| *v > 0] = Options::compute_default_concurrent_threads(num_cpus::get()),
908    /// Enable an optimization that only scans the part of the stack that has changed since the last GC (not supported)
909    use_short_stack_scans:  bool                    [always_valid] = false,
910    /// Enable a return barrier (not supported)
911    use_return_barrier:     bool                    [always_valid] = false,
912    /// Should we eagerly finish sweeping at the start of a collection? (not supported)
913    eager_complete_sweep:   bool                    [always_valid] = false,
914    /// Should we ignore GCs requested by the user (e.g. java.lang.System.gc)?
915    ignore_system_gc:       bool                    [always_valid] = false,
916    /// The nursery size for generational plans. It can be one of Bounded, ProportionalBounded or Fixed.
917    /// The nursery size can be set like 'Fixed:8192', for example,
918    /// to have a Fixed nursery size of 8192 bytes, or 'ProportionalBounded:0.2,1.0' to have a nursery size
919    /// between 20% and 100% of the heap size. You can omit lower bound and upper bound to use the default
920    /// value for bounded nursery by using '_'. For example, 'ProportionalBounded:0.1,_' sets the min nursery
921    /// to 10% of the heap size while using the default value for max nursery.
922    nursery:                NurserySize             [|v: &NurserySize| v.validate()]
923        = NurserySize::ProportionalBounded { min: DEFAULT_PROPORTIONAL_MIN_NURSERY, max: DEFAULT_PROPORTIONAL_MAX_NURSERY },
924    /// Should a major GC be performed when a system GC is required?
925    full_heap_system_gc:    bool                    [always_valid] = false,
926    /// Should finalization be disabled?
927    no_finalizer:           bool                    [always_valid] = false,
928    /// Should reference type processing be disabled?
929    /// If reference type processing is disabled, no weak reference processing work is scheduled,
930    /// and we expect a binding to treat weak references as strong references.
931    no_reference_types:     bool                    [always_valid] = false,
932    /// The zeroing approach to use for new object allocations. Affects each plan differently. (not supported)
933    nursery_zeroing:        NurseryZeroingOptions   [always_valid] = NurseryZeroingOptions::Temporal,
934    /// How frequent (every X bytes) should we do a stress GC?
935    stress_factor:          usize                   [always_valid] = DEFAULT_STRESS_FACTOR,
936    /// How frequent (every X bytes) should we run analysis (a STW event that collects data)
937    analysis_factor:        usize                   [always_valid] = DEFAULT_STRESS_FACTOR,
938    /// Precise stress test. Trigger stress GCs exactly at X bytes if this is true. This is usually used to test the GC correctness
939    /// and will significantly slow down the mutator performance. If this is false, stress GCs will only be triggered when an allocation reaches
940    /// the slow path. This means we may have allocated more than X bytes or fewer than X bytes when we actually trigger a stress GC.
941    /// But this should have no obvious mutator overhead, and can be used to test GC performance along with a larger stress
942    /// factor (e.g. tens of metabytes).
943    precise_stress:         bool                    [always_valid] = true,
944    /// The start of vmspace.
945    vm_space_start:         Address                 [always_valid] = Address::ZERO,
946    /// The size of vmspace.
947    vm_space_size:          usize                   [|v: &usize| *v > 0] = 0xdc0_0000,
948    /// The base address to reserve side metadata at startup.
949    /// If this is zero, MMTk will reserve side metadata at any available address.
950    /// If non-zero, MMTk will quarantine side metadata at this fixed address.
951    side_metadata_base_address: Address             [always_valid] = Address::ZERO,
952    /// Perf events to measure
953    /// Semicolons are used to separate events
954    /// Each event is in the format of event_name,pid,cpu (see man perf_event_open for what pid and cpu mean).
955    /// For example, PERF_COUNT_HW_CPU_CYCLES,0,-1 measures the CPU cycles for the current process on all the CPU cores.
956    /// Measuring perf events for work packets. NOTE that be VERY CAREFUL when using this option, as this may greatly slowdown GC performance.
957    // TODO: Ideally this option should only be included when the features 'perf_counter' and 'work_packet_stats' are enabled. The current macro does not allow us to do this.
958    work_perf_events:       PerfEventOptions        [|_| cfg!(all(feature = "perf_counter", feature = "work_packet_stats"))] = PerfEventOptions {events: vec![]},
959    /// Measuring perf events for GC and mutators
960    // TODO: Ideally this option should only be included when the features 'perf_counter' are enabled. The current macro does not allow us to do this.
961    phase_perf_events:      PerfEventOptions        [|_| cfg!(feature = "perf_counter")] = PerfEventOptions {events: vec![]},
962    /// Should we exclude perf events occurring in kernel space. By default we include the kernel.
963    /// Only set this option if you know the implications of excluding the kernel!
964    perf_exclude_kernel:    bool                    [|_| cfg!(feature = "perf_counter")] = false,
965    /// Set how to bind affinity to the GC Workers. Default thread affinity delegates to the OS
966    /// scheduler.
967    ///
968    /// There are two ways cores can be allocated to threads:
969    ///  1. round-robin, wherein each GC thread is allocated exactly one core to run
970    ///     on in a round-robin fashion; and
971    ///  2. "all in set", wherein each GC thread is allocated all the cores in the provided
972    ///     CPU set.
973    ///
974    /// The method can be selected by specifying "`RoundRobin:<core ids>`" or "`AllInSet:<core ids>`".
975    /// By default, if no kind is specified in the option, then it will use the round-robin
976    /// method.
977    ///
978    /// The core ids should match the ones reported by /proc/cpuinfo. Core IDs are separated by
979    /// commas and may include ranges. There should be no spaces in the core list. For example:
980    /// 0,5,8-11 specifies that cores 0,5,8,9,10,11 should be used for pinning threads.
981    ///
982    /// Note that in the case the program has only been allocated a certain number of cores using
983    /// `taskset`, the core IDs in the list should be specified by their perceived index as using
984    /// `taskset` will essentially re-label the core IDs. For example, running the program with
985    /// `MMTK_THREAD_AFFINITY="0-4" taskset -c 6-12 <program>` means that the cores 6,7,8,9,10 will
986    /// be used to pin threads even though we specified the core IDs "0,1,2,3,4".
987    /// `MMTK_THREAD_AFFINITY="12" taskset -c 6-12 <program>` will not work, on the other hand, as
988    /// there is no core with (perceived) ID 12.
989    // XXX: This option is currently only supported on Linux.
990    thread_affinity:        AffinityKind            [|v: &AffinityKind| v.validate()] = AffinityKind::OsDefault,
991    /// Set the GC trigger. This defines the heap size and how MMTk triggers a GC.
992    /// Default to a fixed heap size of 0.5x physical memory.
993    gc_trigger:             GCTriggerSelector       [|v: &GCTriggerSelector| v.validate()] = GCTriggerSelector::FixedHeapSize((OS::get_system_total_memory().unwrap_or(4 * 1024 * 1024 * 1024) as f64 * 0.5f64) as usize),
994    /// Enable transparent hugepage support for MMTk spaces via madvise (only Linux is supported)
995    /// This only affects the memory for MMTk spaces.
996    transparent_hugepages:  bool                    [|v: &bool| !v || cfg!(target_os = "linux")] = false,
997    /// Count live bytes for objects in each space during a GC.
998    count_live_bytes_in_gc: bool                    [always_valid] = false,
999    /// Make every GC a defragment GC. (for debugging)
1000    immix_always_defrag: bool                       [always_valid] = false,
1001    /// Mark every allocated block as defragmentation source before GC. (for debugging)
1002    /// Depending on the defrag headroom, Immix may not be able to defrag every block even if this option is set to true.
1003    immix_defrag_every_block: bool                  [always_valid] = false,
1004    /// Percentage of heap size reserved for defragmentation.
1005    /// According to [this paper](https://doi.org/10.1145/1375581.1375586), Immix works well with
1006    /// headroom between 1% to 3% of the heap size.
1007    immix_defrag_headroom_percent: usize            [|v: &usize| *v <= 50] = 2,
1008    /// Disable concurrent marking in ConcurrentImmix. Setting this to true will make ConcurrentImmix behave exactly like full heap Immix. This option is only intended for debugging.
1009    concurrent_immix_disable_concurrent_marking: bool              [always_valid] = false
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::DEFAULT_STRESS_FACTOR;
1015    use super::*;
1016    use crate::util::options::Options;
1017    use crate::util::test_util::{serial_test, with_cleanup};
1018
1019    #[test]
1020    fn no_env_var() {
1021        serial_test(|| {
1022            let mut options = Options::default();
1023            options.read_env_var_settings();
1024            assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1025        })
1026    }
1027
1028    #[test]
1029    fn with_valid_env_var() {
1030        serial_test(|| {
1031            with_cleanup(
1032                || {
1033                    std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1034
1035                    let mut options = Options::default();
1036                    options.read_env_var_settings();
1037                    assert_eq!(*options.stress_factor, 4096);
1038                },
1039                || {
1040                    std::env::remove_var("MMTK_STRESS_FACTOR");
1041                },
1042            )
1043        })
1044    }
1045
1046    #[test]
1047    fn with_multiple_valid_env_vars() {
1048        serial_test(|| {
1049            with_cleanup(
1050                || {
1051                    std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1052                    std::env::set_var("MMTK_NO_FINALIZER", "true");
1053
1054                    let mut options = Options::default();
1055                    options.read_env_var_settings();
1056                    assert_eq!(*options.stress_factor, 4096);
1057                    assert!(*options.no_finalizer);
1058                },
1059                || {
1060                    std::env::remove_var("MMTK_STRESS_FACTOR");
1061                    std::env::remove_var("MMTK_NO_FINALIZER");
1062                },
1063            )
1064        })
1065    }
1066
1067    #[test]
1068    fn with_invalid_env_var_value() {
1069        serial_test(|| {
1070            with_cleanup(
1071                || {
1072                    // invalid value, we cannot parse the value, so use the default value
1073                    std::env::set_var("MMTK_STRESS_FACTOR", "abc");
1074
1075                    let mut options = Options::default();
1076                    options.read_env_var_settings();
1077                    assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1078                },
1079                || {
1080                    std::env::remove_var("MMTK_STRESS_FACTOR");
1081                },
1082            )
1083        })
1084    }
1085
1086    #[test]
1087    fn with_invalid_env_var_key() {
1088        serial_test(|| {
1089            with_cleanup(
1090                || {
1091                    // invalid value, we cannot parse the value, so use the default value
1092                    std::env::set_var("MMTK_ABC", "42");
1093
1094                    let mut options = Options::default();
1095                    options.read_env_var_settings();
1096                    assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1097                },
1098                || {
1099                    std::env::remove_var("MMTK_ABC");
1100                },
1101            )
1102        })
1103    }
1104
1105    #[test]
1106    fn ignore_env_var() {
1107        serial_test(|| {
1108            with_cleanup(
1109                || {
1110                    std::env::set_var("MMTK_STRESS_FACTOR", "42");
1111
1112                    let options = Options::default();
1113                    // Not calling read_env_var_settings here.
1114                    assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1115                },
1116                || {
1117                    std::env::remove_var("MMTK_STRESS_FACTOR");
1118                },
1119            )
1120        })
1121    }
1122
1123    #[test]
1124    fn test_str_option_default() {
1125        serial_test(|| {
1126            let options = Options::default();
1127            assert_eq!(
1128                *options.work_perf_events,
1129                PerfEventOptions { events: vec![] }
1130            );
1131        })
1132    }
1133
1134    #[test]
1135    fn test_concurrent_threads_validation() {
1136        serial_test(|| {
1137            let mut options = Options::default();
1138            let concurrent_threads = *options.concurrent_threads;
1139            let success = options.concurrent_threads.set(0);
1140            assert!(!success);
1141            assert_eq!(*options.concurrent_threads, concurrent_threads);
1142        })
1143    }
1144
1145    #[test]
1146    fn test_compute_default_concurrent_threads() {
1147        assert_eq!(Options::compute_default_concurrent_threads(1), 1);
1148        assert_eq!(Options::compute_default_concurrent_threads(3), 1);
1149        assert_eq!(Options::compute_default_concurrent_threads(4), 1);
1150        assert_eq!(Options::compute_default_concurrent_threads(8), 2);
1151        assert_eq!(Options::compute_default_concurrent_threads(100), 25);
1152    }
1153
1154    #[test]
1155    fn test_concurrent_threads_default_tracks_threads_default() {
1156        serial_test(|| {
1157            // Rule 1: if `threads` is left at its default, `concurrent_threads` defaults to 1/4 of it.
1158            let options = Options::default();
1159            assert_eq!(
1160                *options.concurrent_threads,
1161                Options::compute_default_concurrent_threads(*options.threads)
1162            );
1163        })
1164    }
1165
1166    #[test]
1167    fn test_concurrent_threads_resolves_from_explicit_threads() {
1168        serial_test(|| {
1169            // Rule 2: if `threads` is explicitly set (and `concurrent_threads` is not),
1170            // resolving should derive `concurrent_threads` as 1/4 of the new `threads` value.
1171            let mut options = Options::default();
1172            assert!(options.threads.set(16));
1173            options.resolve_connected_options();
1174            assert_eq!(*options.concurrent_threads, 4);
1175        })
1176    }
1177
1178    #[test]
1179    fn test_concurrent_threads_resolves_from_threads_set_via_string() {
1180        serial_test(|| {
1181            let mut options = Options::default();
1182            assert!(options.set_from_string("threads", "12"));
1183            options.resolve_connected_options();
1184            assert_eq!(*options.concurrent_threads, 3);
1185        })
1186    }
1187
1188    #[test]
1189    fn test_concurrent_threads_explicit_value_is_not_overridden() {
1190        serial_test(|| {
1191            // Rule 3: if `concurrent_threads` is explicitly set, it is kept as-is even if
1192            // `threads` is changed afterwards.
1193            let mut options = Options::default();
1194            assert!(options.concurrent_threads.set(3));
1195            assert!(options.threads.set(16));
1196            options.resolve_connected_options();
1197            assert_eq!(*options.concurrent_threads, 3);
1198        })
1199    }
1200
1201    #[test]
1202    fn test_concurrent_threads_resolve_without_explicit_set_is_a_noop() {
1203        serial_test(|| {
1204            let mut options = Options::default();
1205            let concurrent_threads = *options.concurrent_threads;
1206            options.resolve_connected_options();
1207            assert_eq!(*options.concurrent_threads, concurrent_threads);
1208        })
1209    }
1210
1211    #[test]
1212    #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1213    fn test_work_perf_events_option_from_env_var() {
1214        serial_test(|| {
1215            with_cleanup(
1216                || {
1217                    std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1218
1219                    let mut options = Options::default();
1220                    options.read_env_var_settings();
1221                    assert_eq!(
1222                        *options.work_perf_events,
1223                        PerfEventOptions {
1224                            events: vec![("PERF_COUNT_HW_CPU_CYCLES".into(), 0, -1)]
1225                        }
1226                    );
1227                },
1228                || {
1229                    std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1230                },
1231            )
1232        })
1233    }
1234
1235    #[test]
1236    #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1237    fn test_invalid_work_perf_events_option_from_env_var() {
1238        serial_test(|| {
1239            with_cleanup(
1240                || {
1241                    // The option needs to start with "hello", otherwise it is invalid.
1242                    std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES");
1243
1244                    let mut options = Options::default();
1245                    options.read_env_var_settings();
1246                    // invalid value from env var, use default.
1247                    assert_eq!(
1248                        *options.work_perf_events,
1249                        PerfEventOptions { events: vec![] }
1250                    );
1251                },
1252                || {
1253                    std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1254                },
1255            )
1256        })
1257    }
1258
1259    #[test]
1260    #[cfg(not(feature = "perf_counter"))]
1261    fn test_phase_perf_events_option_without_feature() {
1262        serial_test(|| {
1263            with_cleanup(
1264                || {
1265                    // We did not enable the perf_counter feature. The option will be invalid anyway, and will be set to empty.
1266                    std::env::set_var("MMTK_PHASE_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1267
1268                    let mut options = Options::default();
1269                    options.read_env_var_settings();
1270                    // invalid value from env var, use default.
1271                    assert_eq!(
1272                        *options.work_perf_events,
1273                        PerfEventOptions { events: vec![] }
1274                    );
1275                },
1276                || {
1277                    std::env::remove_var("MMTK_PHASE_PERF_EVENTS");
1278                },
1279            )
1280        })
1281    }
1282
1283    #[test]
1284    fn test_thread_affinity_invalid_option() {
1285        serial_test(|| {
1286            with_cleanup(
1287                || {
1288                    std::env::set_var("MMTK_THREAD_AFFINITY", "0-");
1289
1290                    let mut options = Options::default();
1291                    options.read_env_var_settings();
1292                    // invalid value from env var, use default.
1293                    assert_eq!(*options.thread_affinity, AffinityKind::OsDefault);
1294                },
1295                || {
1296                    std::env::remove_var("MMTK_THREAD_AFFINITY");
1297                },
1298            )
1299        })
1300    }
1301
1302    #[cfg(target_os = "linux")]
1303    #[test]
1304    fn test_thread_affinity_single_core() {
1305        serial_test(|| {
1306            with_cleanup(
1307                || {
1308                    std::env::set_var("MMTK_THREAD_AFFINITY", "0");
1309
1310                    let mut options = Options::default();
1311                    options.read_env_var_settings();
1312                    assert_eq!(
1313                        *options.thread_affinity,
1314                        AffinityKind::RoundRobin(vec![0_u16])
1315                    );
1316                },
1317                || {
1318                    std::env::remove_var("MMTK_THREAD_AFFINITY");
1319                },
1320            )
1321        })
1322    }
1323
1324    #[cfg(target_os = "linux")]
1325    #[test]
1326    fn test_thread_affinity_generate_core_list() {
1327        serial_test(|| {
1328            with_cleanup(
1329                || {
1330                    let mut vec = vec![0_u16];
1331                    let mut cpu_list = String::new();
1332                    let num_cpus = OS::get_total_num_cpus();
1333
1334                    cpu_list.push('0');
1335                    for cpu in 1..num_cpus {
1336                        cpu_list.push_str(format!(",{}", cpu).as_str());
1337                        vec.push(cpu);
1338                    }
1339
1340                    std::env::set_var("MMTK_THREAD_AFFINITY", cpu_list);
1341                    let mut options = Options::default();
1342                    options.read_env_var_settings();
1343                    assert_eq!(*options.thread_affinity, AffinityKind::RoundRobin(vec));
1344                },
1345                || {
1346                    std::env::remove_var("MMTK_THREAD_AFFINITY");
1347                },
1348            )
1349        })
1350    }
1351
1352    #[test]
1353    fn test_thread_affinity_single_range() {
1354        serial_test(|| {
1355            let affinity = "0-1".parse::<AffinityKind>();
1356            assert_eq!(affinity, Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16])));
1357        })
1358    }
1359
1360    #[test]
1361    fn test_thread_affinity_complex_core_list() {
1362        serial_test(|| {
1363            let affinity = "0,1-2,4".parse::<AffinityKind>();
1364            assert_eq!(
1365                affinity,
1366                Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16, 2_u16, 4_u16]))
1367            );
1368        })
1369    }
1370
1371    #[test]
1372    fn test_thread_affinity_space_in_core_list() {
1373        serial_test(|| {
1374            let affinity = "0,1-2,4, 6".parse::<AffinityKind>();
1375            assert_eq!(
1376                affinity,
1377                Err("Core ids have been incorrectly specified".to_string())
1378            );
1379        })
1380    }
1381
1382    #[test]
1383    fn test_thread_affinity_bad_core_list() {
1384        serial_test(|| {
1385            let affinity = "0,1-2,4,".parse::<AffinityKind>();
1386            assert_eq!(
1387                affinity,
1388                Err("Core ids have been incorrectly specified".to_string())
1389            );
1390        })
1391    }
1392
1393    #[test]
1394    fn test_thread_affinity_range_start_greater_than_end() {
1395        serial_test(|| {
1396            let affinity = "1-0".parse::<AffinityKind>();
1397            assert_eq!(
1398                affinity,
1399                Err("Starting core id in range should be less than the end".to_string())
1400            );
1401        })
1402    }
1403
1404    #[test]
1405    fn test_thread_affinity_bad_range_option() {
1406        serial_test(|| {
1407            let affinity = "0-1-4".parse::<AffinityKind>();
1408            assert_eq!(
1409                affinity,
1410                Err("Core ids have been incorrectly specified".to_string())
1411            );
1412        })
1413    }
1414
1415    #[test]
1416    fn test_thread_affinity_allinset() {
1417        serial_test(|| {
1418            let affinity = "AllInSet:0,1".parse::<AffinityKind>();
1419            assert_eq!(affinity, Ok(AffinityKind::AllInSet(vec![0_u16, 1_u16])));
1420        })
1421    }
1422
1423    #[test]
1424    fn test_thread_affinity_bad_affinity_kind() {
1425        serial_test(|| {
1426            let affinity = "AllIn:0,1".parse::<AffinityKind>();
1427            assert_eq!(affinity, Err("Unknown affinity kind: AllIn".to_string()));
1428        })
1429    }
1430
1431    #[test]
1432    fn test_process_valid() {
1433        serial_test(|| {
1434            let mut options = Options::default();
1435            let success = options.set_from_string("no_finalizer", "true");
1436            assert!(success);
1437            assert!(*options.no_finalizer);
1438        })
1439    }
1440
1441    #[test]
1442    fn test_process_concurrent_threads_valid() {
1443        serial_test(|| {
1444            let mut options = Options::default();
1445            let success = options.set_from_string("concurrent_threads", "2");
1446            assert!(success);
1447            assert_eq!(*options.concurrent_threads, 2);
1448        })
1449    }
1450
1451    #[test]
1452    fn test_process_invalid() {
1453        serial_test(|| {
1454            let mut options = Options::default();
1455            let default_no_finalizer = *options.no_finalizer;
1456            let success = options.set_from_string("no_finalizer", "100");
1457            assert!(!success);
1458            assert_eq!(*options.no_finalizer, default_no_finalizer);
1459        })
1460    }
1461
1462    #[test]
1463    fn test_process_bulk_empty() {
1464        serial_test(|| {
1465            let mut options = Options::default();
1466            let success = options.set_bulk_from_string("");
1467            assert!(success);
1468        })
1469    }
1470
1471    #[test]
1472    fn test_process_bulk_valid() {
1473        serial_test(|| {
1474            let mut options = Options::default();
1475            let success = options.set_bulk_from_string("no_finalizer=true stress_factor=42");
1476            assert!(success);
1477            assert!(*options.no_finalizer);
1478            assert_eq!(*options.stress_factor, 42);
1479        })
1480    }
1481
1482    #[test]
1483    fn test_process_bulk_comma_separated_valid() {
1484        serial_test(|| {
1485            let mut options = Options::default();
1486            let success = options.set_bulk_from_string("no_finalizer=true,stress_factor=42");
1487            assert!(success);
1488            assert!(*options.no_finalizer);
1489            assert_eq!(*options.stress_factor, 42);
1490        })
1491    }
1492
1493    #[test]
1494    fn test_process_bulk_invalid() {
1495        serial_test(|| {
1496            let mut options = Options::default();
1497            let success = options.set_bulk_from_string("no_finalizer=true stress_factor=a");
1498            assert!(!success);
1499        })
1500    }
1501
1502    #[test]
1503    fn test_set_typed_option_valid() {
1504        serial_test(|| {
1505            let mut options = Options::default();
1506            let success = options.no_finalizer.set(true);
1507            assert!(success);
1508            assert!(*options.no_finalizer);
1509        })
1510    }
1511
1512    #[test]
1513    fn test_set_typed_option_invalid() {
1514        serial_test(|| {
1515            let mut options = Options::default();
1516            let threads = *options.threads;
1517            let success = options.threads.set(0);
1518            assert!(!success);
1519            assert_eq!(*options.threads, threads);
1520        })
1521    }
1522}