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