mmtk/util/
options.rs

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