1use crate::platform::{Platform, PlatformSpec};
5use std::ops::{BitAnd, BitOr};
6use target_spec::TargetSpec;
7
8#[derive(Copy, Clone, Debug)]
12pub enum PlatformStatus<'g> {
13 Never,
15 Always,
17 PlatformDependent {
19 eval: PlatformEval<'g>,
21 },
22}
23
24assert_covariant!(PlatformStatus);
25
26impl<'g> PlatformStatus<'g> {
27 pub(crate) fn new(specs: &'g PlatformStatusImpl) -> Self {
28 match specs {
29 PlatformStatusImpl::Always => PlatformStatus::Always,
30 PlatformStatusImpl::Specs(specs) => {
31 if specs.is_empty() {
32 PlatformStatus::Never
33 } else {
34 PlatformStatus::PlatformDependent {
35 eval: PlatformEval { specs },
36 }
37 }
38 }
39 }
40 }
41
42 pub fn is_always(&self) -> bool {
44 match self {
45 PlatformStatus::Always => true,
46 PlatformStatus::PlatformDependent { .. } | PlatformStatus::Never => false,
47 }
48 }
49
50 pub fn is_never(&self) -> bool {
52 match self {
53 PlatformStatus::Never => true,
54 PlatformStatus::PlatformDependent { .. } | PlatformStatus::Always => false,
55 }
56 }
57
58 pub fn is_present(&self) -> bool {
60 !self.is_never()
61 }
62
63 pub fn enabled_on(&self, platform_spec: &PlatformSpec) -> EnabledTernary {
69 match platform_spec {
70 PlatformSpec::Any => match self {
71 PlatformStatus::Always | PlatformStatus::PlatformDependent { .. } => {
72 EnabledTernary::Enabled
73 }
74 PlatformStatus::Never => EnabledTernary::Disabled,
75 },
76 PlatformSpec::Always => match self {
77 PlatformStatus::Always => EnabledTernary::Enabled,
78 PlatformStatus::Never | PlatformStatus::PlatformDependent { .. } => {
79 EnabledTernary::Disabled
80 }
81 },
82 PlatformSpec::Platforms(platforms) => EnabledTernary::or_all(
83 platforms
84 .iter()
85 .map(|platform| self.enabled_on_platform(platform)),
86 ),
87 }
88 }
89
90 fn enabled_on_platform(&self, platform: &Platform) -> EnabledTernary {
91 match self {
92 PlatformStatus::Always => EnabledTernary::Enabled,
93 PlatformStatus::Never => EnabledTernary::Disabled,
94 PlatformStatus::PlatformDependent { eval } => eval.eval(platform),
95 }
96 }
97}
98
99#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
111pub enum EnabledTernary {
112 Disabled,
114 Unknown,
121 Enabled,
123}
124
125impl EnabledTernary {
126 fn new(x: Option<bool>) -> Self {
127 match x {
128 Some(false) => EnabledTernary::Disabled,
129 None => EnabledTernary::Unknown,
130 Some(true) => EnabledTernary::Enabled,
131 }
132 }
133
134 fn or_all(iter: impl IntoIterator<Item = EnabledTernary>) -> Self {
136 let mut res = EnabledTernary::Disabled;
137 for item in iter {
138 if item == EnabledTernary::Enabled {
140 return EnabledTernary::Enabled;
141 }
142 res = res | item;
143 }
144 res
145 }
146
147 pub fn is_known(self) -> bool {
149 match self {
150 EnabledTernary::Disabled | EnabledTernary::Enabled => true,
151 EnabledTernary::Unknown => false,
152 }
153 }
154}
155
156impl BitAnd for EnabledTernary {
158 type Output = Self;
159
160 fn bitand(self, rhs: Self) -> Self::Output {
161 self.min(rhs)
162 }
163}
164
165impl BitOr for EnabledTernary {
167 type Output = Self;
168
169 fn bitor(self, rhs: Self) -> Self {
170 self.max(rhs)
171 }
172}
173
174#[derive(Copy, Clone, Debug)]
178pub struct PlatformEval<'g> {
179 specs: &'g [TargetSpec],
180}
181
182assert_covariant!(PlatformEval);
183
184impl<'g> PlatformEval<'g> {
185 pub fn eval(&self, platform: &Platform) -> EnabledTernary {
187 EnabledTernary::or_all(
188 self.specs
189 .iter()
190 .map(|spec| EnabledTernary::new(spec.eval(platform))),
191 )
192 }
193
194 pub fn target_specs(&self) -> &'g [TargetSpec] {
199 self.specs
200 }
201}
202
203#[derive(Clone, Debug)]
204pub(crate) enum PlatformStatusImpl {
205 Always,
206 Specs(Vec<TargetSpec>),
208}
209
210impl PlatformStatusImpl {
211 pub(crate) fn is_never(&self) -> bool {
213 match self {
214 PlatformStatusImpl::Always => false,
215 PlatformStatusImpl::Specs(specs) => specs.is_empty(),
216 }
217 }
218
219 pub(crate) fn extend(&mut self, other: &PlatformStatusImpl) {
220 match (&mut *self, other) {
222 (PlatformStatusImpl::Always, _) => {
223 }
225 (PlatformStatusImpl::Specs(_), PlatformStatusImpl::Always) => {
226 *self = PlatformStatusImpl::Always;
228 }
229 (PlatformStatusImpl::Specs(specs), PlatformStatusImpl::Specs(other)) => {
230 specs.extend_from_slice(other.as_slice());
231 }
232 }
233 }
234
235 pub(crate) fn add_spec(&mut self, spec: Option<&TargetSpec>) {
236 match (&mut *self, spec) {
238 (PlatformStatusImpl::Always, _) => {
239 }
241 (PlatformStatusImpl::Specs(_), None) => {
242 *self = PlatformStatusImpl::Always;
244 }
245 (PlatformStatusImpl::Specs(specs), Some(spec)) => {
246 specs.push(spec.clone());
247 }
248 }
249 }
250}
251
252impl Default for PlatformStatusImpl {
253 fn default() -> Self {
254 PlatformStatusImpl::Specs(vec![])
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::platform::TargetFeatures;
263 use std::sync::Arc;
264
265 fn platform(triple: &'static str, target_features: TargetFeatures) -> Arc<Platform> {
266 Arc::new(Platform::new(triple, target_features).expect("triple is known"))
267 }
268
269 fn specs(specs: &[&'static str]) -> PlatformStatusImpl {
270 PlatformStatusImpl::Specs(
271 specs
272 .iter()
273 .map(|spec| TargetSpec::new(*spec).expect("spec is valid"))
274 .collect(),
275 )
276 }
277
278 fn linux() -> Arc<Platform> {
279 platform("x86_64-unknown-linux-gnu", TargetFeatures::Unknown)
280 }
281
282 fn windows() -> Arc<Platform> {
283 platform("x86_64-pc-windows-msvc", TargetFeatures::Unknown)
284 }
285
286 fn windows_sse() -> Arc<Platform> {
287 platform(
288 "x86_64-pc-windows-msvc",
289 TargetFeatures::features(["sse"].iter().copied()),
290 )
291 }
292
293 #[test]
294 fn platforms_is_a_union() {
295 let status_impl = specs(&["cfg(windows)"]);
296 let status = PlatformStatus::new(&status_impl);
297
298 assert_eq!(
299 status.enabled_on(&PlatformSpec::Platforms(vec![linux(), windows()])),
300 EnabledTernary::Enabled,
301 "enabled on the union of linux and windows",
302 );
303 assert_eq!(
304 status.enabled_on(&PlatformSpec::Platforms(vec![linux()])),
305 EnabledTernary::Disabled,
306 "disabled on the union of just linux",
307 );
308 assert_eq!(
309 status.enabled_on(&PlatformSpec::Platforms(vec![windows()])),
310 EnabledTernary::Enabled,
311 "enabled on the union of just windows",
312 );
313 }
314
315 #[test]
316 fn platforms_empty_is_disabled() {
317 let status_impl = specs(&["cfg(windows)"]);
318 let status = PlatformStatus::new(&status_impl);
319 assert_eq!(
320 status.enabled_on(&PlatformSpec::Platforms(vec![])),
321 EnabledTernary::Disabled,
322 "the empty union is vacuously disabled",
323 );
324
325 let always = PlatformStatus::new(&PlatformStatusImpl::Always);
328 assert_eq!(
329 always.enabled_on(&PlatformSpec::Platforms(vec![])),
330 EnabledTernary::Disabled,
331 "an always-enabled status is disabled on the empty union",
332 );
333 assert_eq!(
334 always.enabled_on(&PlatformSpec::Platforms(vec![linux()])),
335 EnabledTernary::Enabled,
336 "an always-enabled status is enabled on a non-empty union",
337 );
338
339 let never_impl = specs(&[]);
340 let never = PlatformStatus::new(&never_impl);
341 assert_eq!(
342 never.enabled_on(&PlatformSpec::Platforms(vec![linux(), windows()])),
343 EnabledTernary::Disabled,
344 "a never-enabled status stays disabled",
345 );
346 }
347
348 #[test]
349 fn platforms_unknown_follows_k3_or() {
350 let status_impl = specs(&["cfg(all(windows, target_feature = \"sse\"))"]);
351 let status = PlatformStatus::new(&status_impl);
352
353 assert_eq!(
354 status.enabled_on(&PlatformSpec::from(windows())),
355 EnabledTernary::Unknown,
356 "unknown target features make the status unknown",
357 );
358 assert_eq!(
359 status.enabled_on(&PlatformSpec::Platforms(vec![linux(), windows()])),
360 EnabledTernary::Unknown,
361 "disabled | unknown is unknown",
362 );
363 assert_eq!(
364 status.enabled_on(&PlatformSpec::Platforms(vec![windows(), windows_sse()])),
365 EnabledTernary::Enabled,
366 "unknown | enabled is enabled",
367 );
368 }
369
370 #[test]
371 fn k3_truth_tables() {
372 use EnabledTernary::*;
373
374 let and_table = [
377 (Disabled, Disabled, Disabled),
378 (Disabled, Unknown, Disabled),
379 (Disabled, Enabled, Disabled),
380 (Unknown, Disabled, Disabled),
381 (Unknown, Unknown, Unknown),
382 (Unknown, Enabled, Unknown),
383 (Enabled, Disabled, Disabled),
384 (Enabled, Unknown, Unknown),
385 (Enabled, Enabled, Enabled),
386 ];
387 for (a, b, expected) in and_table {
388 assert_eq!(a & b, expected, "{a:?} & {b:?}");
389 }
390
391 let or_table = [
392 (Disabled, Disabled, Disabled),
393 (Disabled, Unknown, Unknown),
394 (Disabled, Enabled, Enabled),
395 (Unknown, Disabled, Unknown),
396 (Unknown, Unknown, Unknown),
397 (Unknown, Enabled, Enabled),
398 (Enabled, Disabled, Enabled),
399 (Enabled, Unknown, Enabled),
400 (Enabled, Enabled, Enabled),
401 ];
402 for (a, b, expected) in or_table {
403 assert_eq!(a | b, expected, "{a:?} | {b:?}");
404 }
405
406 assert_eq!(EnabledTernary::or_all([]), Disabled, "empty OR is Disabled");
407 assert_eq!(
408 EnabledTernary::or_all([Disabled, Unknown, Disabled]),
409 Unknown,
410 "OR of Disabled and Unknown is Unknown",
411 );
412 assert_eq!(
413 EnabledTernary::or_all([Unknown, Enabled, Disabled]),
414 Enabled,
415 "OR with any Enabled is Enabled",
416 );
417 }
418}
419
420#[cfg(all(test, feature = "proptest1"))]
421mod proptests {
422 use super::*;
423 use crate::platform::TargetFeatures;
424 use proptest::{
425 collection::{SizeRange, vec},
426 prelude::*,
427 sample::select,
428 };
429 use std::sync::Arc;
430
431 fn target_spec_strategy() -> impl Strategy<Value = TargetSpec> {
434 static SPECS: &[&str] = &[
435 "cfg(windows)",
436 "cfg(unix)",
437 "cfg(not(windows))",
438 "cfg(target_feature = \"sse\")",
439 "cfg(all(unix, target_feature = \"sse\"))",
440 "cfg(any(target_os = \"macos\", target_feature = \"avx\"))",
441 "x86_64-unknown-linux-gnu",
442 ];
443 select(SPECS).prop_map(|spec| TargetSpec::new(spec).expect("spec is valid"))
444 }
445
446 fn status_impl_strategy() -> impl Strategy<Value = PlatformStatusImpl> {
447 prop_oneof![
448 1 => Just(PlatformStatusImpl::Always),
449 4 => vec(target_spec_strategy(), 0..4).prop_map(PlatformStatusImpl::Specs),
451 ]
452 }
453
454 fn platforms_strategy(size: impl Into<SizeRange>) -> impl Strategy<Value = Vec<Arc<Platform>>> {
455 vec(
456 Platform::strategy(any::<TargetFeatures>()).prop_map(Arc::new),
457 size,
458 )
459 }
460
461 fn enabled_on(
462 status_impl: &PlatformStatusImpl,
463 platforms: Vec<Arc<Platform>>,
464 ) -> EnabledTernary {
465 PlatformStatus::new(status_impl).enabled_on(&PlatformSpec::Platforms(platforms))
466 }
467
468 proptest! {
469 #[test]
470 fn platforms_is_the_k3_or_of_its_elements(
471 status_impl in status_impl_strategy(),
472 platforms in platforms_strategy(0..4),
473 ) {
474 let expected = EnabledTernary::or_all(
475 platforms
476 .iter()
477 .map(|platform| enabled_on(&status_impl, vec![platform.clone()])),
478 );
479 prop_assert_eq!(enabled_on(&status_impl, platforms), expected);
480 }
481
482 #[test]
483 fn platforms_append_is_or(
484 status_impl in status_impl_strategy(),
485 a in platforms_strategy(0..3),
486 b in platforms_strategy(0..3),
487 ) {
488 let combined: Vec<_> = a.iter().chain(&b).cloned().collect();
489 let expected = enabled_on(&status_impl, a) | enabled_on(&status_impl, b);
490 prop_assert_eq!(enabled_on(&status_impl, combined), expected);
491 }
492
493 #[test]
494 fn platforms_order_and_duplicates_do_not_matter(
495 status_impl in status_impl_strategy(),
496 (platforms, shuffled) in platforms_strategy(0..4).prop_flat_map(|platforms| {
497 (Just(platforms.clone()), Just(platforms).prop_shuffle())
498 }),
499 ) {
500 let expected = enabled_on(&status_impl, platforms.clone());
501 prop_assert_eq!(enabled_on(&status_impl, shuffled), expected, "order");
502 let doubled: Vec<_> = platforms.iter().chain(&platforms).cloned().collect();
503 prop_assert_eq!(enabled_on(&status_impl, doubled), expected, "duplicates");
504 }
505
506 #[test]
507 fn single_platform_matches_direct_eval(
508 status_impl in status_impl_strategy(),
509 platform in Platform::strategy(any::<TargetFeatures>()),
510 ) {
511 let status = PlatformStatus::new(&status_impl);
512 let expected = match status {
513 PlatformStatus::Always => EnabledTernary::Enabled,
514 PlatformStatus::Never => EnabledTernary::Disabled,
515 PlatformStatus::PlatformDependent { eval } => eval.eval(&platform),
516 };
517 prop_assert_eq!(status.enabled_on(&PlatformSpec::from(platform)), expected);
518 }
519
520 #[test]
521 fn specs_are_ordered(
522 status_impl in status_impl_strategy(),
523 platforms in platforms_strategy(1..4),
524 ) {
525 let status = PlatformStatus::new(&status_impl);
526 let empty = status.enabled_on(&PlatformSpec::Platforms(vec![]));
527 let always = status.enabled_on(&PlatformSpec::Always);
528 let some = status.enabled_on(&PlatformSpec::Platforms(platforms));
529 let any = status.enabled_on(&PlatformSpec::Any);
530 prop_assert!(empty <= always, "Platforms([]) <= Always: {:?} <= {:?}", empty, always);
531 prop_assert!(always <= some, "Always <= Platforms(non-empty): {:?} <= {:?}", always, some);
532 prop_assert!(some <= any, "Platforms(non-empty) <= Any: {:?} <= {:?}", some, any);
533 }
534 }
535}