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