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 lxr_inc_buffer_limit: usize [always_valid] = usize::MAX,
1019 lxr_max_survival_mb: usize [|v: &usize| *v > 0] = 128,
1023 lxr_max_stw_sweep_nursery_blocks: usize [always_valid] = usize::MAX,
1027 lxr_min_packet_split_size: usize [always_valid] = usize::MAX,
1031 lxr_min_packet_split_depth: usize [always_valid] = 16
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040 use super::DEFAULT_STRESS_FACTOR;
1041 use super::*;
1042 use crate::util::options::Options;
1043 use crate::util::test_util::{serial_test, with_cleanup};
1044
1045 #[test]
1046 fn no_env_var() {
1047 serial_test(|| {
1048 let mut options = Options::default();
1049 options.read_env_var_settings();
1050 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1051 })
1052 }
1053
1054 #[test]
1055 fn with_valid_env_var() {
1056 serial_test(|| {
1057 with_cleanup(
1058 || {
1059 std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1060
1061 let mut options = Options::default();
1062 options.read_env_var_settings();
1063 assert_eq!(*options.stress_factor, 4096);
1064 },
1065 || {
1066 std::env::remove_var("MMTK_STRESS_FACTOR");
1067 },
1068 )
1069 })
1070 }
1071
1072 #[test]
1073 fn with_multiple_valid_env_vars() {
1074 serial_test(|| {
1075 with_cleanup(
1076 || {
1077 std::env::set_var("MMTK_STRESS_FACTOR", "4096");
1078 std::env::set_var("MMTK_NO_FINALIZER", "true");
1079
1080 let mut options = Options::default();
1081 options.read_env_var_settings();
1082 assert_eq!(*options.stress_factor, 4096);
1083 assert!(*options.no_finalizer);
1084 },
1085 || {
1086 std::env::remove_var("MMTK_STRESS_FACTOR");
1087 std::env::remove_var("MMTK_NO_FINALIZER");
1088 },
1089 )
1090 })
1091 }
1092
1093 #[test]
1094 fn with_invalid_env_var_value() {
1095 serial_test(|| {
1096 with_cleanup(
1097 || {
1098 std::env::set_var("MMTK_STRESS_FACTOR", "abc");
1100
1101 let mut options = Options::default();
1102 options.read_env_var_settings();
1103 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1104 },
1105 || {
1106 std::env::remove_var("MMTK_STRESS_FACTOR");
1107 },
1108 )
1109 })
1110 }
1111
1112 #[test]
1113 fn with_invalid_env_var_key() {
1114 serial_test(|| {
1115 with_cleanup(
1116 || {
1117 std::env::set_var("MMTK_ABC", "42");
1119
1120 let mut options = Options::default();
1121 options.read_env_var_settings();
1122 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1123 },
1124 || {
1125 std::env::remove_var("MMTK_ABC");
1126 },
1127 )
1128 })
1129 }
1130
1131 #[test]
1132 fn ignore_env_var() {
1133 serial_test(|| {
1134 with_cleanup(
1135 || {
1136 std::env::set_var("MMTK_STRESS_FACTOR", "42");
1137
1138 let options = Options::default();
1139 assert_eq!(*options.stress_factor, DEFAULT_STRESS_FACTOR);
1141 },
1142 || {
1143 std::env::remove_var("MMTK_STRESS_FACTOR");
1144 },
1145 )
1146 })
1147 }
1148
1149 #[test]
1150 fn test_str_option_default() {
1151 serial_test(|| {
1152 let options = Options::default();
1153 assert_eq!(
1154 *options.work_perf_events,
1155 PerfEventOptions { events: vec![] }
1156 );
1157 })
1158 }
1159
1160 #[test]
1161 fn test_concurrent_threads_validation() {
1162 serial_test(|| {
1163 let mut options = Options::default();
1164 let concurrent_threads = *options.concurrent_threads;
1165 let success = options.concurrent_threads.set(0);
1166 assert!(!success);
1167 assert_eq!(*options.concurrent_threads, concurrent_threads);
1168 })
1169 }
1170
1171 #[test]
1172 fn test_compute_default_concurrent_threads() {
1173 assert_eq!(Options::compute_default_concurrent_threads(1), 1);
1174 assert_eq!(Options::compute_default_concurrent_threads(3), 1);
1175 assert_eq!(Options::compute_default_concurrent_threads(4), 1);
1176 assert_eq!(Options::compute_default_concurrent_threads(8), 2);
1177 assert_eq!(Options::compute_default_concurrent_threads(100), 25);
1178 }
1179
1180 #[test]
1181 fn test_concurrent_threads_default_tracks_threads_default() {
1182 serial_test(|| {
1183 let options = Options::default();
1185 assert_eq!(
1186 *options.concurrent_threads,
1187 Options::compute_default_concurrent_threads(*options.threads)
1188 );
1189 })
1190 }
1191
1192 #[test]
1193 fn test_concurrent_threads_resolves_from_explicit_threads() {
1194 serial_test(|| {
1195 let mut options = Options::default();
1198 assert!(options.threads.set(16));
1199 options.resolve_connected_options();
1200 assert_eq!(*options.concurrent_threads, 4);
1201 })
1202 }
1203
1204 #[test]
1205 fn test_concurrent_threads_resolves_from_threads_set_via_string() {
1206 serial_test(|| {
1207 let mut options = Options::default();
1208 assert!(options.set_from_string("threads", "12"));
1209 options.resolve_connected_options();
1210 assert_eq!(*options.concurrent_threads, 3);
1211 })
1212 }
1213
1214 #[test]
1215 fn test_concurrent_threads_explicit_value_is_not_overridden() {
1216 serial_test(|| {
1217 let mut options = Options::default();
1220 assert!(options.concurrent_threads.set(3));
1221 assert!(options.threads.set(16));
1222 options.resolve_connected_options();
1223 assert_eq!(*options.concurrent_threads, 3);
1224 })
1225 }
1226
1227 #[test]
1228 fn test_concurrent_threads_resolve_without_explicit_set_is_a_noop() {
1229 serial_test(|| {
1230 let mut options = Options::default();
1231 let concurrent_threads = *options.concurrent_threads;
1232 options.resolve_connected_options();
1233 assert_eq!(*options.concurrent_threads, concurrent_threads);
1234 })
1235 }
1236
1237 #[test]
1238 #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1239 fn test_work_perf_events_option_from_env_var() {
1240 serial_test(|| {
1241 with_cleanup(
1242 || {
1243 std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1244
1245 let mut options = Options::default();
1246 options.read_env_var_settings();
1247 assert_eq!(
1248 *options.work_perf_events,
1249 PerfEventOptions {
1250 events: vec![("PERF_COUNT_HW_CPU_CYCLES".into(), 0, -1)]
1251 }
1252 );
1253 },
1254 || {
1255 std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1256 },
1257 )
1258 })
1259 }
1260
1261 #[test]
1262 #[cfg(all(feature = "perf_counter", feature = "work_packet_stats"))]
1263 fn test_invalid_work_perf_events_option_from_env_var() {
1264 serial_test(|| {
1265 with_cleanup(
1266 || {
1267 std::env::set_var("MMTK_WORK_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES");
1269
1270 let mut options = Options::default();
1271 options.read_env_var_settings();
1272 assert_eq!(
1274 *options.work_perf_events,
1275 PerfEventOptions { events: vec![] }
1276 );
1277 },
1278 || {
1279 std::env::remove_var("MMTK_WORK_PERF_EVENTS");
1280 },
1281 )
1282 })
1283 }
1284
1285 #[test]
1286 #[cfg(not(feature = "perf_counter"))]
1287 fn test_phase_perf_events_option_without_feature() {
1288 serial_test(|| {
1289 with_cleanup(
1290 || {
1291 std::env::set_var("MMTK_PHASE_PERF_EVENTS", "PERF_COUNT_HW_CPU_CYCLES,0,-1");
1293
1294 let mut options = Options::default();
1295 options.read_env_var_settings();
1296 assert_eq!(
1298 *options.work_perf_events,
1299 PerfEventOptions { events: vec![] }
1300 );
1301 },
1302 || {
1303 std::env::remove_var("MMTK_PHASE_PERF_EVENTS");
1304 },
1305 )
1306 })
1307 }
1308
1309 #[test]
1310 fn test_thread_affinity_invalid_option() {
1311 serial_test(|| {
1312 with_cleanup(
1313 || {
1314 std::env::set_var("MMTK_THREAD_AFFINITY", "0-");
1315
1316 let mut options = Options::default();
1317 options.read_env_var_settings();
1318 assert_eq!(*options.thread_affinity, AffinityKind::OsDefault);
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_single_core() {
1331 serial_test(|| {
1332 with_cleanup(
1333 || {
1334 std::env::set_var("MMTK_THREAD_AFFINITY", "0");
1335
1336 let mut options = Options::default();
1337 options.read_env_var_settings();
1338 assert_eq!(
1339 *options.thread_affinity,
1340 AffinityKind::RoundRobin(vec![0_u16])
1341 );
1342 },
1343 || {
1344 std::env::remove_var("MMTK_THREAD_AFFINITY");
1345 },
1346 )
1347 })
1348 }
1349
1350 #[cfg(target_os = "linux")]
1351 #[test]
1352 fn test_thread_affinity_generate_core_list() {
1353 serial_test(|| {
1354 with_cleanup(
1355 || {
1356 let mut vec = vec![0_u16];
1357 let mut cpu_list = String::new();
1358 let num_cpus = OS::get_total_num_cpus();
1359
1360 cpu_list.push('0');
1361 for cpu in 1..num_cpus {
1362 cpu_list.push_str(format!(",{}", cpu).as_str());
1363 vec.push(cpu);
1364 }
1365
1366 std::env::set_var("MMTK_THREAD_AFFINITY", cpu_list);
1367 let mut options = Options::default();
1368 options.read_env_var_settings();
1369 assert_eq!(*options.thread_affinity, AffinityKind::RoundRobin(vec));
1370 },
1371 || {
1372 std::env::remove_var("MMTK_THREAD_AFFINITY");
1373 },
1374 )
1375 })
1376 }
1377
1378 #[test]
1379 fn test_thread_affinity_single_range() {
1380 serial_test(|| {
1381 let affinity = "0-1".parse::<AffinityKind>();
1382 assert_eq!(affinity, Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16])));
1383 })
1384 }
1385
1386 #[test]
1387 fn test_thread_affinity_complex_core_list() {
1388 serial_test(|| {
1389 let affinity = "0,1-2,4".parse::<AffinityKind>();
1390 assert_eq!(
1391 affinity,
1392 Ok(AffinityKind::RoundRobin(vec![0_u16, 1_u16, 2_u16, 4_u16]))
1393 );
1394 })
1395 }
1396
1397 #[test]
1398 fn test_thread_affinity_space_in_core_list() {
1399 serial_test(|| {
1400 let affinity = "0,1-2,4, 6".parse::<AffinityKind>();
1401 assert_eq!(
1402 affinity,
1403 Err("Core ids have been incorrectly specified".to_string())
1404 );
1405 })
1406 }
1407
1408 #[test]
1409 fn test_thread_affinity_bad_core_list() {
1410 serial_test(|| {
1411 let affinity = "0,1-2,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_range_start_greater_than_end() {
1421 serial_test(|| {
1422 let affinity = "1-0".parse::<AffinityKind>();
1423 assert_eq!(
1424 affinity,
1425 Err("Starting core id in range should be less than the end".to_string())
1426 );
1427 })
1428 }
1429
1430 #[test]
1431 fn test_thread_affinity_bad_range_option() {
1432 serial_test(|| {
1433 let affinity = "0-1-4".parse::<AffinityKind>();
1434 assert_eq!(
1435 affinity,
1436 Err("Core ids have been incorrectly specified".to_string())
1437 );
1438 })
1439 }
1440
1441 #[test]
1442 fn test_thread_affinity_allinset() {
1443 serial_test(|| {
1444 let affinity = "AllInSet:0,1".parse::<AffinityKind>();
1445 assert_eq!(affinity, Ok(AffinityKind::AllInSet(vec![0_u16, 1_u16])));
1446 })
1447 }
1448
1449 #[test]
1450 fn test_thread_affinity_bad_affinity_kind() {
1451 serial_test(|| {
1452 let affinity = "AllIn:0,1".parse::<AffinityKind>();
1453 assert_eq!(affinity, Err("Unknown affinity kind: AllIn".to_string()));
1454 })
1455 }
1456
1457 #[test]
1458 fn test_process_valid() {
1459 serial_test(|| {
1460 let mut options = Options::default();
1461 let success = options.set_from_string("no_finalizer", "true");
1462 assert!(success);
1463 assert!(*options.no_finalizer);
1464 })
1465 }
1466
1467 #[test]
1468 fn test_process_concurrent_threads_valid() {
1469 serial_test(|| {
1470 let mut options = Options::default();
1471 let success = options.set_from_string("concurrent_threads", "2");
1472 assert!(success);
1473 assert_eq!(*options.concurrent_threads, 2);
1474 })
1475 }
1476
1477 #[test]
1478 fn test_process_invalid() {
1479 serial_test(|| {
1480 let mut options = Options::default();
1481 let default_no_finalizer = *options.no_finalizer;
1482 let success = options.set_from_string("no_finalizer", "100");
1483 assert!(!success);
1484 assert_eq!(*options.no_finalizer, default_no_finalizer);
1485 })
1486 }
1487
1488 #[test]
1489 fn test_process_bulk_empty() {
1490 serial_test(|| {
1491 let mut options = Options::default();
1492 let success = options.set_bulk_from_string("");
1493 assert!(success);
1494 })
1495 }
1496
1497 #[test]
1498 fn test_process_bulk_valid() {
1499 serial_test(|| {
1500 let mut options = Options::default();
1501 let success = options.set_bulk_from_string("no_finalizer=true stress_factor=42");
1502 assert!(success);
1503 assert!(*options.no_finalizer);
1504 assert_eq!(*options.stress_factor, 42);
1505 })
1506 }
1507
1508 #[test]
1509 fn test_process_bulk_comma_separated_valid() {
1510 serial_test(|| {
1511 let mut options = Options::default();
1512 let success = options.set_bulk_from_string("no_finalizer=true,stress_factor=42");
1513 assert!(success);
1514 assert!(*options.no_finalizer);
1515 assert_eq!(*options.stress_factor, 42);
1516 })
1517 }
1518
1519 #[test]
1520 fn test_process_bulk_invalid() {
1521 serial_test(|| {
1522 let mut options = Options::default();
1523 let success = options.set_bulk_from_string("no_finalizer=true stress_factor=a");
1524 assert!(!success);
1525 })
1526 }
1527
1528 #[test]
1529 fn test_set_typed_option_valid() {
1530 serial_test(|| {
1531 let mut options = Options::default();
1532 let success = options.no_finalizer.set(true);
1533 assert!(success);
1534 assert!(*options.no_finalizer);
1535 })
1536 }
1537
1538 #[test]
1539 fn test_set_typed_option_invalid() {
1540 serial_test(|| {
1541 let mut options = Options::default();
1542 let threads = *options.threads;
1543 let success = options.threads.set(0);
1544 assert!(!success);
1545 assert_eq!(*options.threads, threads);
1546 })
1547 }
1548}