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
9pub const DEFAULT_STRESS_FACTOR: usize = usize::MAX;
12
13#[derive(Copy, Clone, EnumString, Debug)]
16pub enum NurseryZeroingOptions {
17 Temporal,
19 Nontemporal,
21 Concurrent,
23 Adaptive,
25}
26
27#[derive(Copy, Clone, EnumString, Debug, PartialEq, Eq)]
29pub enum PlanSelector {
30 NoGC,
33 SemiSpace,
36 GenCopy,
38 GenImmix,
40 MarkSweep,
42 PageProtect,
45 Immix,
47 MarkCompact,
49 Compressor,
51 StickyImmix,
53 ConcurrentImmix,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct PerfEventOptions {
66 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#[cfg(target_pointer_width = "64")]
105pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
106#[cfg(target_pointer_width = "64")]
109pub const DEFAULT_MAX_NURSERY: usize = (1 << 20) << LOG_BYTES_IN_MBYTE;
110
111#[cfg(target_pointer_width = "32")]
114pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
115pub const DEFAULT_MAX_NURSERY_32: usize = 32 << LOG_BYTES_IN_MBYTE;
117#[cfg(target_pointer_width = "32")]
120pub const DEFAULT_MAX_NURSERY: usize = DEFAULT_MAX_NURSERY_32;
121
122pub const DEFAULT_PROPORTIONAL_MIN_NURSERY: f64 = 0.25;
124pub const DEFAULT_PROPORTIONAL_MAX_NURSERY: f64 = 1.0;
126
127fn always_valid<T>(_: &T) -> bool {
128 true
129}
130
131enum SetOptionByStringError {
133 InvalidKey,
135 ValueParseError,
137 ValueValidationError,
139}
140
141#[derive(Clone)]
145pub struct MMTKOption<T: Debug + Clone + FromStr> {
146 value: T,
148 validator: fn(&T) -> bool,
150}
151
152impl<T: Debug + Clone + FromStr> MMTKOption<T> {
153 pub fn new(value: T, validator: fn(&T) -> bool) -> Self {
155 MMTKOption { value, validator }
168 }
169
170 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
180impl<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 #[derive(Clone)]
203 pub struct Options {
204 $($(#[$outer])*pub $name: MMTKOption<$type>),*
205 }
206
207 impl Options {
208 fn set_from_string_inner(&mut self, s: &str, val: &str) -> Result<(), SetOptionByStringError> {
210 match s {
211 $(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 fn new() -> Self {
229 Options {
230 $($name: MMTKOption::new($default, $validator)),*
231 }
232 }
233 }
234 ]
235}
236
237impl Default for Options {
238 fn default() -> Self {
240 Self::new()
241 }
242}
243
244impl Options {
245 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 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 pub fn read_env_var_settings(&mut self) {
316 const PREFIX: &str = "MMTK_";
317 for (key, val) in std::env::vars() {
318 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 }
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 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)]
347pub enum AffinityKind {
350 OsDefault,
352 RoundRobin(Vec<CoreId>),
357 AllInSet(Vec<CoreId>),
360}
361
362impl AffinityKind {
363 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 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 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 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 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)]
481pub enum NurserySize {
484 Bounded {
487 min: usize,
489 max: usize,
491 },
492 ProportionalBounded {
494 min: f64,
496 max: f64,
498 },
499 Fixed(usize),
503}
504
505impl NurserySize {
506 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 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 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 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 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 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 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 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 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
660pub enum GCTriggerSelector {
661 FixedHeapSize(usize),
663 DynamicHeapSize(usize, usize),
666 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 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 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 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 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 assert_eq!(
789 GCTriggerSelector::parse_size(""),
790 Err("cannot parse integer from empty string".to_string())
791 );
792
793 assert!(GCTriggerSelector::parse_size("-1").is_err());
795
796 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 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 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 plan: PlanSelector [always_valid] = PlanSelector::GenImmix,
870 threads: usize [|v: &usize| *v > 0] = num_cpus::get(),
872 use_short_stack_scans: bool [always_valid] = false,
874 use_return_barrier: bool [always_valid] = false,
876 eager_complete_sweep: bool [always_valid] = false,
878 ignore_system_gc: bool [always_valid] = false,
880 nursery: NurserySize [|v: &NurserySize| v.validate()]
887 = NurserySize::ProportionalBounded { min: DEFAULT_PROPORTIONAL_MIN_NURSERY, max: DEFAULT_PROPORTIONAL_MAX_NURSERY },
888 full_heap_system_gc: bool [always_valid] = false,
890 no_finalizer: bool [always_valid] = false,
892 no_reference_types: bool [always_valid] = false,
896 nursery_zeroing: NurseryZeroingOptions [always_valid] = NurseryZeroingOptions::Temporal,
898 stress_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
900 analysis_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
902 precise_stress: bool [always_valid] = true,
908 vm_space_start: Address [always_valid] = Address::ZERO,
910 vm_space_size: usize [|v: &usize| *v > 0] = 0xdc0_0000,
912 work_perf_events: PerfEventOptions [|_| cfg!(all(feature = "perf_counter", feature = "work_packet_stats"))] = PerfEventOptions {events: vec![]},
919 phase_perf_events: PerfEventOptions [|_| cfg!(feature = "perf_counter")] = PerfEventOptions {events: vec![]},
922 perf_exclude_kernel: bool [|_| cfg!(feature = "perf_counter")] = false,
925 thread_affinity: AffinityKind [|v: &AffinityKind| v.validate()] = AffinityKind::OsDefault,
951 gc_trigger: GCTriggerSelector [|v: &GCTriggerSelector| v.validate()] = GCTriggerSelector::FixedHeapSize((crate::util::memory::get_system_total_memory() as f64 * 0.5f64) as usize),
954 transparent_hugepages: bool [|v: &bool| !v || cfg!(target_os = "linux")] = false,
957 count_live_bytes_in_gc: bool [always_valid] = false,
959 immix_always_defrag: bool [always_valid] = false,
961 immix_defrag_every_block: bool [always_valid] = false,
964 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 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 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 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 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 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 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 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 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}