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
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 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)]
356pub enum AffinityKind {
359 OsDefault,
361 RoundRobin(Vec<CoreId>),
366 AllInSet(Vec<CoreId>),
369}
370
371impl AffinityKind {
372 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 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 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 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 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)]
490pub enum NurserySize {
493 Bounded {
496 min: usize,
498 max: usize,
500 },
501 ProportionalBounded {
503 min: f64,
505 max: f64,
507 },
508 Fixed(usize),
512}
513
514impl NurserySize {
515 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 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 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 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 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 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 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 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 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
669pub enum GCTriggerSelector {
670 FixedHeapSize(usize),
672 DynamicHeapSize(usize, usize),
675 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 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 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 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 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 assert_eq!(
798 GCTriggerSelector::parse_size(""),
799 Err("cannot parse integer from empty string".to_string())
800 );
801
802 assert!(GCTriggerSelector::parse_size("-1").is_err());
804
805 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 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 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 plan: PlanSelector [always_valid] = PlanSelector::GenImmix,
879 threads: usize [|v: &usize| *v > 0] = num_cpus::get(),
881 use_short_stack_scans: bool [always_valid] = false,
883 use_return_barrier: bool [always_valid] = false,
885 eager_complete_sweep: bool [always_valid] = false,
887 ignore_system_gc: bool [always_valid] = false,
889 nursery: NurserySize [|v: &NurserySize| v.validate()]
896 = NurserySize::ProportionalBounded { min: DEFAULT_PROPORTIONAL_MIN_NURSERY, max: DEFAULT_PROPORTIONAL_MAX_NURSERY },
897 full_heap_system_gc: bool [always_valid] = false,
899 no_finalizer: bool [always_valid] = false,
901 no_reference_types: bool [always_valid] = false,
905 nursery_zeroing: NurseryZeroingOptions [always_valid] = NurseryZeroingOptions::Temporal,
907 stress_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
909 analysis_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
911 precise_stress: bool [always_valid] = true,
917 vm_space_start: Address [always_valid] = Address::ZERO,
919 vm_space_size: usize [|v: &usize| *v > 0] = 0xdc0_0000,
921 side_metadata_base_address: Address [always_valid] = Address::ZERO,
925 work_perf_events: PerfEventOptions [|_| cfg!(all(feature = "perf_counter", feature = "work_packet_stats"))] = PerfEventOptions {events: vec![]},
932 phase_perf_events: PerfEventOptions [|_| cfg!(feature = "perf_counter")] = PerfEventOptions {events: vec![]},
935 perf_exclude_kernel: bool [|_| cfg!(feature = "perf_counter")] = false,
938 thread_affinity: AffinityKind [|v: &AffinityKind| v.validate()] = AffinityKind::OsDefault,
964 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 transparent_hugepages: bool [|v: &bool| !v || cfg!(target_os = "linux")] = false,
970 count_live_bytes_in_gc: bool [always_valid] = false,
972 immix_always_defrag: bool [always_valid] = false,
974 immix_defrag_every_block: bool [always_valid] = false,
977 immix_defrag_headroom_percent: usize [|v: &usize| *v <= 50] = 2,
981 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 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 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 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 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 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 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 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 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}