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