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 Lisp2,
49 OVC,
51 StickyImmix,
53 LXR,
55 ConcurrentImmix,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct PerfEventOptions {
68 pub events: Vec<(String, i32, i32)>,
70}
71
72impl PerfEventOptions {
73 fn parse_perf_events(events: &str) -> Result<Vec<(String, i32, i32)>, String> {
74 events
75 .split(';')
76 .filter(|e| !e.is_empty())
77 .map(|e| {
78 let e: Vec<&str> = e.split(',').collect();
79 if e.len() != 3 {
80 Err("Please supply (event name, pid, cpu)".into())
81 } else {
82 let event_name = e[0].into();
83 let pid = e[1]
84 .parse()
85 .map_err(|_| String::from("Failed to parse cpu"))?;
86 let cpu = e[2]
87 .parse()
88 .map_err(|_| String::from("Failed to parse cpu"))?;
89 Ok((event_name, pid, cpu))
90 }
91 })
92 .collect()
93 }
94}
95
96impl FromStr for PerfEventOptions {
97 type Err = String;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 PerfEventOptions::parse_perf_events(s).map(|events| PerfEventOptions { events })
101 }
102}
103
104#[cfg(target_pointer_width = "64")]
107pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
108#[cfg(target_pointer_width = "64")]
111pub const DEFAULT_MAX_NURSERY: usize = (1 << 20) << LOG_BYTES_IN_MBYTE;
112
113#[cfg(target_pointer_width = "32")]
116pub const DEFAULT_MIN_NURSERY: usize = 2 << LOG_BYTES_IN_MBYTE;
117pub const DEFAULT_MAX_NURSERY_32: usize = 32 << LOG_BYTES_IN_MBYTE;
119#[cfg(target_pointer_width = "32")]
122pub const DEFAULT_MAX_NURSERY: usize = DEFAULT_MAX_NURSERY_32;
123
124pub const DEFAULT_PROPORTIONAL_MIN_NURSERY: f64 = 0.25;
126pub const DEFAULT_PROPORTIONAL_MAX_NURSERY: f64 = 1.0;
128
129fn always_valid<T>(_: &T) -> bool {
130 true
131}
132
133enum SetOptionByStringError {
135 InvalidKey,
137 ValueParseError,
139 ValueValidationError,
141}
142
143#[derive(Clone)]
147pub struct MMTKOption<T: Debug + Clone + FromStr> {
148 value: T,
150 validator: fn(&T) -> bool,
152 was_set: bool,
154}
155
156impl<T: Debug + Clone + FromStr> MMTKOption<T> {
157 pub fn new(value: T, validator: fn(&T) -> bool) -> Self {
159 MMTKOption {
172 value,
173 validator,
174 was_set: false,
175 }
176 }
177
178 pub fn set(&mut self, value: T) -> bool {
180 if (self.validator)(&value) {
181 self.value = value;
182 self.was_set = true;
183 return true;
184 }
185 false
186 }
187
188 pub fn was_set(&self) -> bool {
190 self.was_set
191 }
192}
193
194impl<T: Debug + Clone + FromStr> std::ops::Deref for MMTKOption<T> {
196 type Target = T;
197
198 fn deref(&self) -> &Self::Target {
199 &self.value
200 }
201}
202
203macro_rules! options {
204 ($($(#[$outer:meta])*$name:ident: $type:ty [$validator:expr] = $default:expr),*,) => [
205 options!($(#[$outer])*$($name: $type [$validator] = $default),*);
206 ];
207 ($($(#[$outer:meta])*$name:ident: $type:ty [$validator:expr] = $default:expr),*) => [
208 #[derive(Clone)]
217 pub struct Options {
218 $($(#[$outer])*pub $name: MMTKOption<$type>),*
219 }
220
221 impl Options {
222 fn set_from_string_inner(&mut self, s: &str, val: &str) -> Result<(), SetOptionByStringError> {
224 match s {
225 $(stringify!($name) => {
227 let Ok(typed_val) = val.parse::<$type>() else {
228 return Err(SetOptionByStringError::ValueParseError);
229 };
230
231 if !self.$name.set(typed_val) {
232 return Err(SetOptionByStringError::ValueValidationError);
233 }
234
235 Ok(())
236 })*
237 _ => Err(SetOptionByStringError::InvalidKey)
238 }
239 }
240
241 fn new() -> Self {
243 Options {
244 $($name: MMTKOption::new($default, $validator)),*
245 }
246 }
247 }
248 ]
249}
250
251impl Default for Options {
252 fn default() -> Self {
254 Self::new()
255 }
256}
257
258impl Options {
259 pub fn set_from_string(&mut self, s: &str, val: &str) -> bool {
282 self.set_from_string_inner(s, val).is_ok()
283 }
284
285 pub fn set_bulk_from_string(&mut self, options: &str) -> bool {
298 for opt in options.replace(',', " ").split_ascii_whitespace() {
299 let kv_pair: Vec<&str> = opt.split('=').collect();
300 if kv_pair.len() != 2 {
301 return false;
302 }
303
304 let key = kv_pair[0];
305 let val = kv_pair[1];
306 if let Err(e) = self.set_from_string_inner(key, val) {
307 match e {
308 SetOptionByStringError::InvalidKey => {
309 panic!("Invalid Options key: {}", key);
310 }
311 SetOptionByStringError::ValueParseError => {
312 eprintln!("Warn: unable to set {}={:?}. Can't parse value. Default value will be used.", key, val);
313 }
314 SetOptionByStringError::ValueValidationError => {
315 eprintln!("Warn: unable to set {}={:?}. Invalid value. Default value will be used.", key, val);
316 }
317 }
318 return false;
319 }
320 }
321
322 true
323 }
324
325 pub fn read_env_var_settings(&mut self) {
330 const PREFIX: &str = "MMTK_";
331 for (key, val) in std::env::vars() {
332 if let Some(rest_of_key) = key.strip_prefix(PREFIX) {
334 let lowercase: &str = &rest_of_key.to_lowercase();
335 if let Err(e) = self.set_from_string_inner(lowercase, &val) {
336 match e {
337 SetOptionByStringError::InvalidKey => {
338 }
340 SetOptionByStringError::ValueParseError => {
341 eprintln!("Warn: unable to set {}={:?}. Can't parse value. Default value will be used.", key, val);
342 }
343 SetOptionByStringError::ValueValidationError => {
344 eprintln!("Warn: unable to set {}={:?}. Invalid value. Default value will be used.", key, val);
345 }
346 }
347 }
348 }
349 }
350 }
351
352 pub fn is_stress_test_gc_enabled(&self) -> bool {
355 *self.stress_factor != DEFAULT_STRESS_FACTOR
356 || *self.analysis_factor != DEFAULT_STRESS_FACTOR
357 }
358
359 pub fn transparent_hugepages_as_huge_page_support(&self) -> HugePageSupport {
361 if *self.transparent_hugepages {
362 HugePageSupport::TransparentHugePages
363 } else {
364 HugePageSupport::No
365 }
366 }
367
368 fn compute_default_concurrent_threads(gc_threads: usize) -> usize {
370 usize::max(gc_threads / 4, 1)
371 }
372
373 pub(crate) fn resolve_connected_options(&mut self) {
375 if !self.concurrent_threads.was_set() {
376 self.concurrent_threads.value = Self::compute_default_concurrent_threads(*self.threads);
377 }
378 }
379}
380
381#[derive(Clone, Debug, PartialEq)]
382pub enum AffinityKind {
385 OsDefault,
387 RoundRobin(Vec<CoreId>),
392 AllInSet(Vec<CoreId>),
395}
396
397impl AffinityKind {
398 fn parse_cpulist(cpulist: &str) -> Result<AffinityKind, String> {
411 let mut cpuset = vec![];
412
413 if cpulist.is_empty() {
414 return Ok(AffinityKind::OsDefault);
415 }
416
417 let mut all_in_set = false;
422 let kind_split: Vec<&str> = cpulist.splitn(2, ':').collect();
423 if kind_split.len() == 2 {
424 match kind_split[0] {
425 "RoundRobin" => {
426 all_in_set = false;
427 }
428 "AllInSet" => {
429 all_in_set = true;
430 }
431 _ => {
432 return Err(format!("Unknown affinity kind: {}", kind_split[0]));
433 }
434 }
435 }
436
437 let cpulist = if kind_split.len() == 2 {
438 kind_split[1]
439 } else {
440 kind_split[0]
441 };
442
443 for split in cpulist.split(',') {
445 if !split.contains('-') {
446 if !split.is_empty() {
447 if let Ok(core) = split.parse::<u16>() {
448 cpuset.push(core);
449 cpuset.sort_unstable();
450 cpuset.dedup();
451 continue;
452 }
453 }
454 } else {
455 let range: Vec<&str> = split.split('-').collect();
457 if range.len() == 2 {
458 if let Ok(start) = range[0].parse::<u16>() {
459 if let Ok(end) = range[1].parse::<u16>() {
460 if start >= end {
461 return Err(
462 "Starting core id in range should be less than the end"
463 .to_string(),
464 );
465 }
466
467 for cpu in start..=end {
468 cpuset.push(cpu);
469 cpuset.sort_unstable();
470 cpuset.dedup();
471 }
472
473 continue;
474 }
475 }
476 }
477 }
478
479 return Err("Core ids have been incorrectly specified".to_string());
480 }
481
482 if all_in_set {
483 Ok(AffinityKind::AllInSet(cpuset))
484 } else {
485 Ok(AffinityKind::RoundRobin(cpuset))
486 }
487 }
488
489 pub fn validate(&self) -> bool {
493 let num_cpu = OS::get_total_num_cpus();
494
495 if let AffinityKind::RoundRobin(cpuset) = self {
496 for cpu in cpuset {
497 if cpu >= &num_cpu {
498 return false;
499 }
500 }
501 }
502
503 true
504 }
505}
506
507impl FromStr for AffinityKind {
508 type Err = String;
509
510 fn from_str(s: &str) -> Result<Self, Self::Err> {
511 AffinityKind::parse_cpulist(s)
512 }
513}
514
515#[derive(Copy, Clone, Debug)]
516pub enum NurserySize {
519 Bounded {
522 min: usize,
524 max: usize,
526 },
527 ProportionalBounded {
529 min: f64,
531 max: f64,
533 },
534 Fixed(usize),
538}
539
540impl NurserySize {
541 fn validate(&self) -> bool {
543 match *self {
544 NurserySize::Bounded { min, max } => min <= max,
545 NurserySize::ProportionalBounded { min, max } => {
546 0.0f64 < min && min <= max && max <= 1.0f64
547 }
548 NurserySize::Fixed(_) => true,
549 }
550 }
551}
552
553impl FromStr for NurserySize {
554 type Err = String;
555
556 fn from_str(s: &str) -> Result<Self, Self::Err> {
557 let parts: Vec<&str> = s.split(':').collect();
558 if parts.len() != 2 {
559 return Err("Invalid format".to_string());
560 }
561
562 let variant = parts[0];
563 let values: Vec<&str> = parts[1].split(',').collect();
564
565 fn default_or_parse<T: FromStr>(val: &str, default_value: T) -> Result<T, String> {
566 if val == "_" {
567 Ok(default_value)
568 } else {
569 val.parse::<T>()
570 .map_err(|_| format!("Failed to parse {:?}", std::any::type_name::<T>()))
571 }
572 }
573
574 match variant {
575 "Bounded" => {
576 if values.len() == 2 {
577 let min = default_or_parse(values[0], DEFAULT_MIN_NURSERY)?;
578 let max = default_or_parse(values[1], DEFAULT_MAX_NURSERY)?;
579 Ok(NurserySize::Bounded { min, max })
580 } else {
581 Err("Bounded requires two values".to_string())
582 }
583 }
584 "ProportionalBounded" => {
585 if values.len() == 2 {
586 let min = default_or_parse(values[0], DEFAULT_PROPORTIONAL_MIN_NURSERY)?;
587 let max = default_or_parse(values[1], DEFAULT_PROPORTIONAL_MAX_NURSERY)?;
588 Ok(NurserySize::ProportionalBounded { min, max })
589 } else {
590 Err("ProportionalBounded requires two values".to_string())
591 }
592 }
593 "Fixed" => {
594 if values.len() == 1 {
595 let size = values[0]
596 .parse::<usize>()
597 .map_err(|_| "Invalid size value".to_string())?;
598 Ok(NurserySize::Fixed(size))
599 } else {
600 Err("Fixed requires one value".to_string())
601 }
602 }
603 _ => Err("Unknown variant".to_string()),
604 }
605 }
606}
607
608#[cfg(test)]
609mod nursery_size_parsing_tests {
610 use super::*;
611
612 #[test]
613 fn test_bounded() {
614 let result = "Bounded:1,2".parse::<NurserySize>().unwrap();
616 if let NurserySize::Bounded { min, max } = result {
617 assert_eq!(min, 1);
618 assert_eq!(max, 2);
619 } else {
620 panic!("Failed: {:?}", result);
621 }
622
623 let result = "Bounded:_,2".parse::<NurserySize>().unwrap();
625 if let NurserySize::Bounded { min, max } = result {
626 assert_eq!(min, DEFAULT_MIN_NURSERY);
627 assert_eq!(max, 2);
628 } else {
629 panic!("Failed: {:?}", result);
630 }
631
632 let result = "Bounded:1,_".parse::<NurserySize>().unwrap();
634 if let NurserySize::Bounded { min, max } = result {
635 assert_eq!(min, 1);
636 assert_eq!(max, DEFAULT_MAX_NURSERY);
637 } else {
638 panic!("Failed: {:?}", result);
639 }
640
641 let result = "Bounded:_,_".parse::<NurserySize>().unwrap();
643 if let NurserySize::Bounded { min, max } = result {
644 assert_eq!(min, DEFAULT_MIN_NURSERY);
645 assert_eq!(max, DEFAULT_MAX_NURSERY);
646 } else {
647 panic!("Failed: {:?}", result);
648 }
649 }
650
651 #[test]
652 fn test_proportional() {
653 let result = "ProportionalBounded:0.1,0.8"
655 .parse::<NurserySize>()
656 .unwrap();
657 if let NurserySize::ProportionalBounded { min, max } = result {
658 assert_eq!(min, 0.1);
659 assert_eq!(max, 0.8);
660 } else {
661 panic!("Failed: {:?}", result);
662 }
663
664 let result = "ProportionalBounded:_,0.8".parse::<NurserySize>().unwrap();
666 if let NurserySize::ProportionalBounded { min, max } = result {
667 assert_eq!(min, DEFAULT_PROPORTIONAL_MIN_NURSERY);
668 assert_eq!(max, 0.8);
669 } else {
670 panic!("Failed: {:?}", result);
671 }
672
673 let result = "ProportionalBounded:0.1,_".parse::<NurserySize>().unwrap();
675 if let NurserySize::ProportionalBounded { min, max } = result {
676 assert_eq!(min, 0.1);
677 assert_eq!(max, DEFAULT_PROPORTIONAL_MAX_NURSERY);
678 } else {
679 panic!("Failed: {:?}", result);
680 }
681
682 let result = "ProportionalBounded:_,_".parse::<NurserySize>().unwrap();
684 if let NurserySize::ProportionalBounded { min, max } = result {
685 assert_eq!(min, DEFAULT_PROPORTIONAL_MIN_NURSERY);
686 assert_eq!(max, DEFAULT_PROPORTIONAL_MAX_NURSERY);
687 } else {
688 panic!("Failed: {:?}", result);
689 }
690 }
691}
692
693#[derive(Copy, Clone, Debug, PartialEq, Eq)]
695pub enum GCTriggerSelector {
696 FixedHeapSize(usize),
698 DynamicHeapSize(usize, usize),
701 Delegated,
703}
704
705impl GCTriggerSelector {
706 const K: u64 = 1024;
707 const M: u64 = 1024 * Self::K;
708 const G: u64 = 1024 * Self::M;
709 const T: u64 = 1024 * Self::G;
710
711 pub fn max_heap_size(&self) -> usize {
713 match self {
714 Self::FixedHeapSize(s) => *s,
715 Self::DynamicHeapSize(_, s) => *s,
716 _ => unreachable!("Cannot get max heap size"),
717 }
718 }
719
720 fn parse_size(s: &str) -> Result<usize, String> {
724 let s = s.to_lowercase();
725 if s.ends_with(char::is_alphabetic) {
726 let num = s[0..s.len() - 1]
727 .parse::<u64>()
728 .map_err(|e| e.to_string())?;
729 let size = if s.ends_with('k') {
730 num.checked_mul(Self::K)
731 } else if s.ends_with('m') {
732 num.checked_mul(Self::M)
733 } else if s.ends_with('g') {
734 num.checked_mul(Self::G)
735 } else if s.ends_with('t') {
736 num.checked_mul(Self::T)
737 } else {
738 return Err(format!(
739 "Unknown size descriptor: {:?}",
740 &s[(s.len() - 1)..]
741 ));
742 };
743
744 if let Some(size) = size {
745 size.try_into()
746 .map_err(|_| format!("size overflow: {}", size))
747 } else {
748 Err(format!("size overflow: {}", s))
749 }
750 } else {
751 s.parse::<usize>().map_err(|e| e.to_string())
752 }
753 }
754
755 fn validate(&self) -> bool {
757 match self {
758 Self::FixedHeapSize(size) => *size > 0,
759 Self::DynamicHeapSize(min, max) => min <= max,
760 Self::Delegated => true,
761 }
762 }
763}
764
765impl FromStr for GCTriggerSelector {
766 type Err = String;
767
768 fn from_str(s: &str) -> Result<Self, Self::Err> {
769 use regex::Regex;
770 lazy_static! {
771 static ref FIXED_HEAP_REGEX: Regex =
772 Regex::new(r"^FixedHeapSize:(?P<size>\d+[kKmMgGtT]?)$").unwrap();
773 static ref DYNAMIC_HEAP_REGEX: Regex =
774 Regex::new(r"^DynamicHeapSize:(?P<min>\d+[kKmMgGtT]?),(?P<max>\d+[kKmMgGtT]?)$")
775 .unwrap();
776 }
777
778 if s.is_empty() {
779 return Err("No GC trigger policy is supplied".to_string());
780 }
781
782 if let Some(captures) = FIXED_HEAP_REGEX.captures(s) {
783 return Self::parse_size(&captures["size"]).map(Self::FixedHeapSize);
784 } else if let Some(captures) = DYNAMIC_HEAP_REGEX.captures(s) {
785 let min = Self::parse_size(&captures["min"])?;
786 let max = Self::parse_size(&captures["max"])?;
787 return Ok(Self::DynamicHeapSize(min, max));
788 } else if s.starts_with("Delegated") {
789 return Ok(Self::Delegated);
790 }
791
792 Err(format!("Failed to parse the GC trigger option: {:?}", s))
793 }
794}
795
796#[cfg(test)]
797mod gc_trigger_tests {
798 use super::*;
799
800 #[test]
801 fn test_parse_size() {
802 assert_eq!(GCTriggerSelector::parse_size("0"), Ok(0));
804 assert_eq!(GCTriggerSelector::parse_size("1K"), Ok(1024));
805 assert_eq!(GCTriggerSelector::parse_size("1k"), Ok(1024));
806 assert_eq!(GCTriggerSelector::parse_size("2M"), Ok(2 * 1024 * 1024));
807 assert_eq!(GCTriggerSelector::parse_size("2m"), Ok(2 * 1024 * 1024));
808 assert_eq!(
809 GCTriggerSelector::parse_size("2G"),
810 Ok(2 * 1024 * 1024 * 1024)
811 );
812 assert_eq!(
813 GCTriggerSelector::parse_size("2g"),
814 Ok(2 * 1024 * 1024 * 1024)
815 );
816 #[cfg(target_pointer_width = "64")]
817 assert_eq!(
818 GCTriggerSelector::parse_size("2T"),
819 Ok(2 * 1024 * 1024 * 1024 * 1024)
820 );
821
822 assert_eq!(
824 GCTriggerSelector::parse_size(""),
825 Err("cannot parse integer from empty string".to_string())
826 );
827
828 assert!(GCTriggerSelector::parse_size("-1").is_err());
830
831 assert!(GCTriggerSelector::parse_size("k").is_err());
833 }
834
835 #[test]
836 #[cfg(target_pointer_width = "32")]
837 fn test_parse_overflow_size() {
838 assert_eq!(
839 GCTriggerSelector::parse_size("4G"),
840 Err("size overflow: 4294967296".to_string())
841 );
842 assert_eq!(GCTriggerSelector::parse_size("4294967295"), Ok(4294967295));
843 }
844
845 #[test]
846 fn test_parse_fixed_heap() {
847 assert_eq!(
848 GCTriggerSelector::from_str("FixedHeapSize:1024"),
849 Ok(GCTriggerSelector::FixedHeapSize(1024))
850 );
851 assert_eq!(
852 GCTriggerSelector::from_str("FixedHeapSize:4m"),
853 Ok(GCTriggerSelector::FixedHeapSize(4 * 1024 * 1024))
854 );
855 #[cfg(target_pointer_width = "64")]
856 assert_eq!(
857 GCTriggerSelector::from_str("FixedHeapSize:4t"),
858 Ok(GCTriggerSelector::FixedHeapSize(
859 4 * 1024 * 1024 * 1024 * 1024
860 ))
861 );
862
863 assert!(GCTriggerSelector::from_str("FixedHeapSize").is_err());
865 assert!(GCTriggerSelector::from_str("FixedHeapSize:").is_err());
866 assert!(GCTriggerSelector::from_str("FixedHeapSize:-1").is_err());
867 }
868
869 #[test]
870 fn test_parse_dynamic_heap() {
871 assert_eq!(
872 GCTriggerSelector::from_str("DynamicHeapSize:1024,2048"),
873 Ok(GCTriggerSelector::DynamicHeapSize(1024, 2048))
874 );
875 assert_eq!(
876 GCTriggerSelector::from_str("DynamicHeapSize:1024,1024"),
877 Ok(GCTriggerSelector::DynamicHeapSize(1024, 1024))
878 );
879 assert_eq!(
880 GCTriggerSelector::from_str("DynamicHeapSize:1m,2m"),
881 Ok(GCTriggerSelector::DynamicHeapSize(
882 1024 * 1024,
883 2 * 1024 * 1024
884 ))
885 );
886
887 assert!(GCTriggerSelector::from_str("DynamicHeapSize:1024,1024,").is_err());
889 }
890
891 #[test]
892 fn test_validate() {
893 assert!(GCTriggerSelector::FixedHeapSize(1024).validate());
894 assert!(GCTriggerSelector::DynamicHeapSize(1024, 2048).validate());
895 assert!(GCTriggerSelector::DynamicHeapSize(1024, 1024).validate());
896
897 assert!(!GCTriggerSelector::FixedHeapSize(0).validate());
898 assert!(!GCTriggerSelector::DynamicHeapSize(2048, 1024).validate());
899 }
900}
901
902options! {
903 plan: PlanSelector [always_valid] = PlanSelector::GenImmix,
905 threads: usize [|v: &usize| *v > 0] = num_cpus::get(),
907 concurrent_threads: usize [|v: &usize| *v > 0] = Options::compute_default_concurrent_threads(num_cpus::get()),
910 use_short_stack_scans: bool [always_valid] = false,
912 use_return_barrier: bool [always_valid] = false,
914 eager_complete_sweep: bool [always_valid] = false,
916 ignore_system_gc: bool [always_valid] = false,
918 nursery: NurserySize [|v: &NurserySize| v.validate()]
925 = NurserySize::ProportionalBounded { min: DEFAULT_PROPORTIONAL_MIN_NURSERY, max: DEFAULT_PROPORTIONAL_MAX_NURSERY },
926 full_heap_system_gc: bool [always_valid] = false,
928 no_finalizer: bool [always_valid] = false,
930 no_reference_types: bool [always_valid] = false,
934 nursery_zeroing: NurseryZeroingOptions [always_valid] = NurseryZeroingOptions::Temporal,
936 stress_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
938 analysis_factor: usize [always_valid] = DEFAULT_STRESS_FACTOR,
940 precise_stress: bool [always_valid] = true,
946 vm_space_start: Address [always_valid] = Address::ZERO,
948 vm_space_size: usize [|v: &usize| *v > 0] = 0xdc0_0000,
950 side_metadata_base_address: Address [always_valid] = Address::ZERO,
954 work_perf_events: PerfEventOptions [|_| cfg!(all(feature = "perf_counter", feature = "work_packet_stats"))] = PerfEventOptions {events: vec![]},
961 phase_perf_events: PerfEventOptions [|_| cfg!(feature = "perf_counter")] = PerfEventOptions {events: vec![]},
964 perf_exclude_kernel: bool [|_| cfg!(feature = "perf_counter")] = false,
967 thread_affinity: AffinityKind [|v: &AffinityKind| v.validate()] = AffinityKind::OsDefault,
993 verbose: usize [|v: &usize| *v <= 10] = 0,
995 gc_trigger: GCTriggerSelector [|v: &GCTriggerSelector| v.validate()] = GCTriggerSelector::FixedHeapSize((OS::get_system_total_memory().unwrap_or(4 * 1024 * 1024 * 1024) as f64 * 0.5f64) as usize),
998 transparent_hugepages: bool [|v: &bool| !v || cfg!(target_os = "linux")] = false,
1001 count_live_bytes_in_gc: bool [always_valid] = false,
1003 immix_always_defrag: bool [always_valid] = false,
1005 immix_defrag_every_block: bool [always_valid] = false,
1008 immix_defrag_headroom_percent: usize [|v: &usize| *v <= 50] = 5,
1012 concurrent_immix_disable_concurrent_marking: bool [always_valid] = false
1014}
1015
1016#[cfg(test)]
1017mod tests {
1018 use super::DEFAULT_STRESS_FACTOR;
1019 use super::*;
1020 use crate::util::options::Options;
1021 use crate::util::test_util::{serial_test, with_cleanup};
1022
1023 #[test]
1024 fn no_env_var() {
1025 serial_test(|| {
1026 let mut options = Options::default();
1027 options.read_env_var_settings();
1028 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1029 })
1030 }
1031
1032 #[test]
1033 fn with_valid_env_var() {
1034 serial_test(|| {
1035 with_cleanup(
1036 || {
1037 std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1038
1039 let mut options = Options::default();
1040 options.read_env_var_settings();
1041 assert_eq!(*options.stress_factor, 4096);
1042 },
1043 || {
1044 std::env::remove_var("MMTK_STRESS_FACTOR");
1045 },
1046 )
1047 })
1048 }
1049
1050 #[test]
1051 fn with_multiple_valid_env_vars() {
1052 serial_test(|| {
1053 with_cleanup(
1054 || {
1055 std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1056 std::env::set_var("MMTK_NO_FINALIZER", "true");
1057
1058 let mut options = Options::default();
1059 options.read_env_var_settings();
1060 assert_eq!(*options.stress_factor, 4096);
1061 assert!(*options.no_finalizer);
1062 },
1063 || {
1064 std::env::remove_var("MMTK_STRESS_FACTOR");
1065 std::env::remove_var("MMTK_NO_FINALIZER");
1066 },
1067 )
1068 })
1069 }
1070
1071 #[test]
1072 fn with_invalid_env_var_value() {
1073 serial_test(|| {
1074 with_cleanup(
1075 || {
1076 std::env::set_var("MMTK_STRESS_FACTOR", "abc");
1078
1079 let mut options = Options::default();
1080 options.read_env_var_settings();
1081 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1082 },
1083 || {
1084 std::env::remove_var("MMTK_STRESS_FACTOR");
1085 },
1086 )
1087 })
1088 }
1089
1090 #[test]
1091 fn with_invalid_env_var_key() {
1092 serial_test(|| {
1093 with_cleanup(
1094 || {
1095 std::env::set_var("MMTK_ABC", "42");
1097
1098 let mut options = Options::default();
1099 options.read_env_var_settings();
1100 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1101 },
1102 || {
1103 std::env::remove_var("MMTK_ABC");
1104 },
1105 )
1106 })
1107 }
1108
1109 #[test]
1110 fn ignore_env_var() {
1111 serial_test(|| {
1112 with_cleanup(
1113 || {
1114 std::env::set_var("MMTK_STRESS_FACTOR", "42");
1115
1116 let options = Options::default();
1117 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1119 },
1120 || {
1121 std::env::remove_var("MMTK_STRESS_FACTOR");
1122 },
1123 )
1124 })
1125 }
1126
1127 #[test]
1128 fn test_str_option_default() {
1129 serial_test(|| {
1130 let options = Options::default();
1131 assert_eq!(
1132 *options.work_perf_events,
1133 PerfEventOptions { events: vec![] }
1134 );
1135 })
1136 }
1137
1138 #[test]
1139 fn test_concurrent_threads_validation() {
1140 serial_test(|| {
1141 let mut options = Options::default();
1142 let concurrent_threads = *options.concurrent_threads;
1143 let success = options.concurrent_threads.set(0);
1144 assert!(!success);
1145 assert_eq!(*options.concurrent_threads, concurrent_threads);
1146 })
1147 }
1148
1149 #[test]
1150 fn test_compute_default_concurrent_threads() {
1151 assert_eq!(Options::compute_default_concurrent_threads(1), 1);
1152 assert_eq!(Options::compute_default_concurrent_threads(3), 1);
1153 assert_eq!(Options::compute_default_concurrent_threads(4), 1);
1154 assert_eq!(Options::compute_default_concurrent_threads(8), 2);
1155 assert_eq!(Options::compute_default_concurrent_threads(100), 25);
1156 }
1157
1158 #[test]
1159 fn test_concurrent_threads_default_tracks_threads_default() {
1160 serial_test(|| {
1161 let options = Options::default();
1163 assert_eq!(
1164 *options.concurrent_threads,
1165 Options::compute_default_concurrent_threads(*options.threads)
1166 );
1167 })
1168 }
1169
1170 #[test]
1171 fn test_concurrent_threads_resolves_from_explicit_threads() {
1172 serial_test(|| {
1173 let mut options = Options::default();
1176 assert!(options.threads.set(16));
1177 options.resolve_connected_options();
1178 assert_eq!(*options.concurrent_threads, 4);
1179 })
1180 }
1181
1182 #[test]
1183 fn test_concurrent_threads_resolves_from_threads_set_via_string() {
1184 serial_test(|| {
1185 let mut options = Options::default();
1186 assert!(options.set_from_string("threads", "12"));
1187 options.resolve_connected_options();
1188 assert_eq!(*options.concurrent_threads, 3);
1189 })
1190 }
1191
1192 #[test]
1193 fn test_concurrent_threads_explicit_value_is_not_overridden() {
1194 serial_test(|| {
1195 let mut options = Options::default();
1198 assert!(options.concurrent_threads.set(3));
1199 assert!(options.threads.set(16));
1200 options.resolve_connected_options();
1201 assert_eq!(*options.concurrent_threads, 3);
1202 })
1203 }
1204
1205 #[test]
1206 fn test_concurrent_threads_resolve_without_explicit_set_is_a_noop() {
1207 serial_test(|| {
1208 let mut options = Options::default();
1209 let concurrent_threads = *options.concurrent_threads;
1210 options.resolve_connected_options();
1211 assert_eq!(*options.concurrent_threads, concurrent_threads);
1212 })
1213 }
1214
1215 #[test]
1216 #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1217 fn test_work_perf_events_option_from_env_var() {
1218 serial_test(|| {
1219 with_cleanup(
1220 || {
1221 std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1222
1223 let mut options = Options::default();
1224 options.read_env_var_settings();
1225 assert_eq!(
1226 *options.work_perf_events,
1227 PerfEventOptions {
1228 events: vec![("PERF_COUNT_HW_CPU_CYCLES".into(), 0, -1)]
1229 }
1230 );
1231 },
1232 || {
1233 std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1234 },
1235 )
1236 })
1237 }
1238
1239 #[test]
1240 #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1241 fn test_invalid_work_perf_events_option_from_env_var() {
1242 serial_test(|| {
1243 with_cleanup(
1244 || {
1245 std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES");
1247
1248 let mut options = Options::default();
1249 options.read_env_var_settings();
1250 assert_eq!(
1252 *options.work_perf_events,
1253 PerfEventOptions { events: vec![] }
1254 );
1255 },
1256 || {
1257 std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1258 },
1259 )
1260 })
1261 }
1262
1263 #[test]
1264 #[cfg(not(feature = "perf_counter"))]
1265 fn test_phase_perf_events_option_without_feature() {
1266 serial_test(|| {
1267 with_cleanup(
1268 || {
1269 std::env::set_var("MMTK_PHASE_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1271
1272 let mut options = Options::default();
1273 options.read_env_var_settings();
1274 assert_eq!(
1276 *options.work_perf_events,
1277 PerfEventOptions { events: vec![] }
1278 );
1279 },
1280 || {
1281 std::env::remove_var("MMTK_PHASE_PERF_EVENTS");
1282 },
1283 )
1284 })
1285 }
1286
1287 #[test]
1288 fn test_thread_affinity_invalid_option() {
1289 serial_test(|| {
1290 with_cleanup(
1291 || {
1292 std::env::set_var("MMTK_THREAD_AFFINITY", "0-");
1293
1294 let mut options = Options::default();
1295 options.read_env_var_settings();
1296 assert_eq!(*options.thread_affinity, AffinityKind::OsDefault);
1298 },
1299 || {
1300 std::env::remove_var("MMTK_THREAD_AFFINITY");
1301 },
1302 )
1303 })
1304 }
1305
1306 #[cfg(target_os = "linux")]
1307 #[test]
1308 fn test_thread_affinity_single_core() {
1309 serial_test(|| {
1310 with_cleanup(
1311 || {
1312 std::env::set_var("MMTK_THREAD_AFFINITY", "0");
1313
1314 let mut options = Options::default();
1315 options.read_env_var_settings();
1316 assert_eq!(
1317 *options.thread_affinity,
1318 AffinityKind::RoundRobin(vec![0_u16])
1319 );
1320 },
1321 || {
1322 std::env::remove_var("MMTK_THREAD_AFFINITY");
1323 },
1324 )
1325 })
1326 }
1327
1328 #[cfg(target_os = "linux")]
1329 #[test]
1330 fn test_thread_affinity_generate_core_list() {
1331 serial_test(|| {
1332 with_cleanup(
1333 || {
1334 let mut vec = vec![0_u16];
1335 let mut cpu_list = String::new();
1336 let num_cpus = OS::get_total_num_cpus();
1337
1338 cpu_list.push('0');
1339 for cpu in 1..num_cpus {
1340 cpu_list.push_str(format!(",{}", cpu).as_str());
1341 vec.push(cpu);
1342 }
1343
1344 std::env::set_var("MMTK_THREAD_AFFINITY", cpu_list);
1345 let mut options = Options::default();
1346 options.read_env_var_settings();
1347 assert_eq!(*options.thread_affinity, AffinityKind::RoundRobin(vec));
1348 },
1349 || {
1350 std::env::remove_var("MMTK_THREAD_AFFINITY");
1351 },
1352 )
1353 })
1354 }
1355
1356 #[test]
1357 fn test_thread_affinity_single_range() {
1358 serial_test(|| {
1359 let affinity = "0-1".parse::<AffinityKind>();
1360 assert_eq!(affinity, Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16])));
1361 })
1362 }
1363
1364 #[test]
1365 fn test_thread_affinity_complex_core_list() {
1366 serial_test(|| {
1367 let affinity = "0,1-2,4".parse::<AffinityKind>();
1368 assert_eq!(
1369 affinity,
1370 Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16, 2_u16, 4_u16]))
1371 );
1372 })
1373 }
1374
1375 #[test]
1376 fn test_thread_affinity_space_in_core_list() {
1377 serial_test(|| {
1378 let affinity = "0,1-2,4, 6".parse::<AffinityKind>();
1379 assert_eq!(
1380 affinity,
1381 Err("Core ids have been incorrectly specified".to_string())
1382 );
1383 })
1384 }
1385
1386 #[test]
1387 fn test_thread_affinity_bad_core_list() {
1388 serial_test(|| {
1389 let affinity = "0,1-2,4,".parse::<AffinityKind>();
1390 assert_eq!(
1391 affinity,
1392 Err("Core ids have been incorrectly specified".to_string())
1393 );
1394 })
1395 }
1396
1397 #[test]
1398 fn test_thread_affinity_range_start_greater_than_end() {
1399 serial_test(|| {
1400 let affinity = "1-0".parse::<AffinityKind>();
1401 assert_eq!(
1402 affinity,
1403 Err("Starting core id in range should be less than the end".to_string())
1404 );
1405 })
1406 }
1407
1408 #[test]
1409 fn test_thread_affinity_bad_range_option() {
1410 serial_test(|| {
1411 let affinity = "0-1-4".parse::<AffinityKind>();
1412 assert_eq!(
1413 affinity,
1414 Err("Core ids have been incorrectly specified".to_string())
1415 );
1416 })
1417 }
1418
1419 #[test]
1420 fn test_thread_affinity_allinset() {
1421 serial_test(|| {
1422 let affinity = "AllInSet:0,1".parse::<AffinityKind>();
1423 assert_eq!(affinity, Ok(AffinityKind::AllInSet(vec![0_u16, 1_u16])));
1424 })
1425 }
1426
1427 #[test]
1428 fn test_thread_affinity_bad_affinity_kind() {
1429 serial_test(|| {
1430 let affinity = "AllIn:0,1".parse::<AffinityKind>();
1431 assert_eq!(affinity, Err("Unknown affinity kind: AllIn".to_string()));
1432 })
1433 }
1434
1435 #[test]
1436 fn test_process_valid() {
1437 serial_test(|| {
1438 let mut options = Options::default();
1439 let success = options.set_from_string("no_finalizer", "true");
1440 assert!(success);
1441 assert!(*options.no_finalizer);
1442 })
1443 }
1444
1445 #[test]
1446 fn test_process_concurrent_threads_valid() {
1447 serial_test(|| {
1448 let mut options = Options::default();
1449 let success = options.set_from_string("concurrent_threads", "2");
1450 assert!(success);
1451 assert_eq!(*options.concurrent_threads, 2);
1452 })
1453 }
1454
1455 #[test]
1456 fn test_process_invalid() {
1457 serial_test(|| {
1458 let mut options = Options::default();
1459 let default_no_finalizer = *options.no_finalizer;
1460 let success = options.set_from_string("no_finalizer", "100");
1461 assert!(!success);
1462 assert_eq!(*options.no_finalizer, default_no_finalizer);
1463 })
1464 }
1465
1466 #[test]
1467 fn test_process_bulk_empty() {
1468 serial_test(|| {
1469 let mut options = Options::default();
1470 let success = options.set_bulk_from_string("");
1471 assert!(success);
1472 })
1473 }
1474
1475 #[test]
1476 fn test_process_bulk_valid() {
1477 serial_test(|| {
1478 let mut options = Options::default();
1479 let success = options.set_bulk_from_string("no_finalizer=true stress_factor=42");
1480 assert!(success);
1481 assert!(*options.no_finalizer);
1482 assert_eq!(*options.stress_factor, 42);
1483 })
1484 }
1485
1486 #[test]
1487 fn test_process_bulk_comma_separated_valid() {
1488 serial_test(|| {
1489 let mut options = Options::default();
1490 let success = options.set_bulk_from_string("no_finalizer=true,stress_factor=42");
1491 assert!(success);
1492 assert!(*options.no_finalizer);
1493 assert_eq!(*options.stress_factor, 42);
1494 })
1495 }
1496
1497 #[test]
1498 fn test_process_bulk_invalid() {
1499 serial_test(|| {
1500 let mut options = Options::default();
1501 let success = options.set_bulk_from_string("no_finalizer=true stress_factor=a");
1502 assert!(!success);
1503 })
1504 }
1505
1506 #[test]
1507 fn test_set_typed_option_valid() {
1508 serial_test(|| {
1509 let mut options = Options::default();
1510 let success = options.no_finalizer.set(true);
1511 assert!(success);
1512 assert!(*options.no_finalizer);
1513 })
1514 }
1515
1516 #[test]
1517 fn test_set_typed_option_invalid() {
1518 serial_test(|| {
1519 let mut options = Options::default();
1520 let threads = *options.threads;
1521 let success = options.threads.set(0);
1522 assert!(!success);
1523 assert_eq!(*options.threads, threads);
1524 })
1525 }
1526}