1use serde::{Deserialize, Serialize};
11use xpile_contracts::ContractId;
12use xpile_meta_hir::Module;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
16pub enum Target {
17 Rust,
19 Ruchy,
21 Ptx,
23 Wgsl,
25 Spirv,
27 Lean,
31 Shell,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum Profile {
43 RustOut,
45 RuchyOut,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub enum HwProfile {
54 Ptx {
55 compute_capability: String,
57 },
58 Wgsl {
59 features: Vec<String>,
61 },
62 Spirv {
63 version: (u32, u32),
64 },
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub struct BackendConfig {
70 pub target: Target,
71 pub profile: Profile,
72 pub hardware: Option<HwProfile>,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct Artifact {
85 pub primary: String,
88 pub sidecars: Vec<(String, Vec<u8>)>,
90 pub citations: Vec<ContractId>,
93 #[serde(default = "default_quorum_status")]
102 pub quorum_status: QuorumStatus,
103}
104
105fn default_quorum_status() -> QuorumStatus {
106 QuorumStatus::Single {
107 emitter: "unknown".to_string(),
108 }
109}
110
111#[derive(Debug, thiserror::Error)]
112pub enum BackendError {
113 #[error("unsupported target: {0:?}")]
114 UnsupportedTarget(Target),
115 #[error("missing hardware profile for target {0:?}")]
116 MissingHardware(Target),
117 #[error("lowering error: {0}")]
118 Lower(String),
119 #[error("compile-contract citation missing for emitted construct: {0}")]
120 MissingCompileContractCitation(String),
121}
122
123pub trait Backend: Send + Sync {
125 fn name(&self) -> &'static str;
127
128 fn targets(&self) -> &[Target];
131
132 fn lower(&self, module: &Module, config: &BackendConfig) -> Result<Artifact, BackendError>;
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
154#[serde(rename_all = "snake_case")]
155pub enum EmitterRole {
156 General,
162 Specialist,
167}
168
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173#[serde(rename_all = "snake_case", tag = "kind")]
174pub enum QuorumPolicy {
175 PreferSpecialist,
178 DiffExec {
184 tolerance: f64,
187 },
188 Strict,
193}
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198#[serde(tag = "kind", rename_all = "snake_case")]
199pub enum QuorumStatus {
200 Single { emitter: String },
204 Multi {
207 emitters: Vec<String>,
208 diff_exec: Option<DiffExecResult>,
209 },
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
216#[serde(tag = "kind", rename_all = "snake_case")]
217pub enum DiffExecResult {
218 Match { max_abs_diff: f64 },
221 Divergent { max_abs_diff: f64, tolerance: f64 },
224 NotRun { reason: String },
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
237pub struct ViaEntry {
238 pub emitter: String,
240 pub role: EmitterRole,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub crate_name: Option<String>,
245 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub cross_repo: Option<String>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub shape_filter: Option<String>,
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct EmittedText {
270 pub primary: String,
271 #[serde(default, skip_serializing_if = "Vec::is_empty")]
272 pub citations: Vec<ContractId>,
273}
274
275pub trait TargetEmitter: Send + Sync {
280 fn name(&self) -> &str;
282
283 fn try_emit(
288 &self,
289 module: &Module,
290 config: &BackendConfig,
291 ) -> Option<Result<EmittedText, BackendError>>;
292}
293
294pub trait DiffExecEngine: Send + Sync {
306 fn execute_and_compare(
311 &self,
312 general_text: &str,
313 specialist_text: &str,
314 module: &Module,
315 config: &BackendConfig,
316 tolerance: f64,
317 ) -> Result<DiffExecResult, String>;
318}
319
320pub struct MultiEmitterBackend {
325 pub target: Target,
328 pub general: Box<dyn TargetEmitter>,
331 pub specialist: Option<Box<dyn TargetEmitter>>,
334 pub quorum_policy: QuorumPolicy,
336 pub diff_exec_engine: Option<std::sync::Arc<dyn DiffExecEngine>>,
340}
341
342impl MultiEmitterBackend {
343 pub fn new_single(target: Target, general: Box<dyn TargetEmitter>) -> Self {
344 Self {
345 target,
346 general,
347 specialist: None,
348 quorum_policy: QuorumPolicy::PreferSpecialist,
349 diff_exec_engine: None,
350 }
351 }
352
353 pub fn new_with_specialist(
354 target: Target,
355 general: Box<dyn TargetEmitter>,
356 specialist: Box<dyn TargetEmitter>,
357 quorum_policy: QuorumPolicy,
358 ) -> Self {
359 Self {
360 target,
361 general,
362 specialist: Some(specialist),
363 quorum_policy,
364 diff_exec_engine: None,
365 }
366 }
367
368 pub fn with_diff_exec_engine(mut self, engine: std::sync::Arc<dyn DiffExecEngine>) -> Self {
372 self.diff_exec_engine = Some(engine);
373 self
374 }
375}
376
377impl Backend for MultiEmitterBackend {
378 fn name(&self) -> &'static str {
379 "multi-emitter"
382 }
383
384 fn targets(&self) -> &[Target] {
385 std::slice::from_ref(&self.target)
386 }
387
388 fn lower(&self, module: &Module, config: &BackendConfig) -> Result<Artifact, BackendError> {
389 let general_result = self.general.try_emit(module, config).ok_or_else(|| {
390 BackendError::Lower(format!(
391 "general emitter {} must always match contract-conforming input",
392 self.general.name()
393 ))
394 })??;
395
396 let specialist_result = self.specialist.as_ref().and_then(|s| {
397 s.try_emit(module, config)
398 .map(|r| (s.name().to_string(), r))
399 });
400
401 match specialist_result {
402 None => {
403 Ok(Artifact {
405 primary: general_result.primary,
406 sidecars: Vec::new(),
407 citations: general_result.citations,
408 quorum_status: QuorumStatus::Single {
409 emitter: self.general.name().to_string(),
410 },
411 })
412 }
413 Some((specialist_name, specialist_emit)) => {
414 let specialist_text = specialist_emit?;
415 match &self.quorum_policy {
416 QuorumPolicy::PreferSpecialist => {
417 Ok(Artifact {
420 primary: specialist_text.primary,
421 sidecars: Vec::new(),
422 citations: specialist_text.citations,
423 quorum_status: QuorumStatus::Single {
424 emitter: specialist_name,
425 },
426 })
427 }
428 QuorumPolicy::Strict => {
429 let diff_exec = if general_result.primary == specialist_text.primary {
431 Some(DiffExecResult::Match { max_abs_diff: 0.0 })
432 } else {
433 Some(DiffExecResult::Divergent {
434 max_abs_diff: f64::INFINITY,
435 tolerance: 0.0,
436 })
437 };
438 Ok(Artifact {
439 primary: general_result.primary.clone(),
440 sidecars: vec![(
441 "specialist_emission".to_string(),
442 specialist_text.primary.into_bytes(),
443 )],
444 citations: general_result.citations,
445 quorum_status: QuorumStatus::Multi {
446 emitters: vec![self.general.name().to_string(), specialist_name],
447 diff_exec,
448 },
449 })
450 }
451 QuorumPolicy::DiffExec { tolerance } => {
452 let diff_exec = match &self.diff_exec_engine {
458 Some(engine) => engine
459 .execute_and_compare(
460 &general_result.primary,
461 &specialist_text.primary,
462 module,
463 config,
464 *tolerance,
465 )
466 .map_err(|e| {
467 BackendError::Lower(format!(
468 "DiffExec engine for {:?} failed: {e}",
469 self.target
470 ))
471 })?,
472 None => DiffExecResult::NotRun {
473 reason: format!(
474 "no DiffExec engine installed (tolerance was {tolerance})"
475 ),
476 },
477 };
478 Ok(Artifact {
479 primary: general_result.primary.clone(),
480 sidecars: vec![(
481 "specialist_emission".to_string(),
482 specialist_text.primary.into_bytes(),
483 )],
484 citations: general_result.citations,
485 quorum_status: QuorumStatus::Multi {
486 emitters: vec![self.general.name().to_string(), specialist_name],
487 diff_exec: Some(diff_exec),
488 },
489 })
490 }
491 }
492 }
493 }
494 }
495}
496
497#[cfg(test)]
498mod quorum_scaffolding_tests {
499 use super::*;
500
501 #[test]
502 fn emitter_role_serde_round_trip() {
503 let general = EmitterRole::General;
504 let s = serde_json::to_string(&general).unwrap();
505 assert_eq!(s, "\"general\"");
506 let back: EmitterRole = serde_json::from_str(&s).unwrap();
507 assert_eq!(back, general);
508
509 let specialist = EmitterRole::Specialist;
510 let s = serde_json::to_string(&specialist).unwrap();
511 assert_eq!(s, "\"specialist\"");
512 }
513
514 #[test]
515 fn quorum_policy_diff_exec_carries_tolerance() {
516 let policy = QuorumPolicy::DiffExec { tolerance: 1.0e-3 };
517 let s = serde_json::to_string(&policy).unwrap();
518 assert!(s.contains("diff_exec"));
519 assert!(s.contains("0.001"));
520 let back: QuorumPolicy = serde_json::from_str(&s).unwrap();
521 assert_eq!(back, policy);
522 }
523
524 #[test]
525 fn quorum_status_multi_records_emitters_and_diff() {
526 let status = QuorumStatus::Multi {
527 emitters: vec!["rustc_codegen_nvvm".into(), "aprender-gpu".into()],
528 diff_exec: Some(DiffExecResult::Match {
529 max_abs_diff: 1.3e-4,
530 }),
531 };
532 let s = serde_json::to_string(&status).unwrap();
533 assert!(s.contains("multi"));
534 assert!(s.contains("rustc_codegen_nvvm"));
535 assert!(s.contains("aprender-gpu"));
536 }
537
538 #[test]
539 fn diff_exec_divergent_carries_both_diff_and_tolerance() {
540 let r = DiffExecResult::Divergent {
541 max_abs_diff: 0.5,
542 tolerance: 0.001,
543 };
544 let s = serde_json::to_string(&r).unwrap();
545 let back: DiffExecResult = serde_json::from_str(&s).unwrap();
546 assert_eq!(back, r);
547 }
548
549 #[test]
550 fn via_entry_general_has_no_specialist_fields() {
551 let v = ViaEntry {
552 emitter: "rustc_codegen_nvvm".into(),
553 role: EmitterRole::General,
554 crate_name: Some("xpile-ptx-codegen".into()),
555 cross_repo: None,
556 shape_filter: None,
557 };
558 let s = serde_json::to_string(&v).unwrap();
559 assert!(!s.contains("cross_repo"));
561 assert!(!s.contains("shape_filter"));
562 assert!(s.contains("general"));
563 }
564
565 #[test]
566 fn via_entry_specialist_carries_cross_repo_and_shape_filter() {
567 let v = ViaEntry {
568 emitter: "aprender-gpu".into(),
569 role: EmitterRole::Specialist,
570 crate_name: None,
571 cross_repo: Some("aprender".into()),
572 shape_filter: Some("gemm_fp16_mma_64x128".into()),
573 };
574 let s = serde_json::to_string(&v).unwrap();
575 assert!(s.contains("specialist"));
576 assert!(s.contains("aprender"));
577 assert!(s.contains("gemm_fp16_mma_64x128"));
578 }
579
580 #[test]
584 fn artifact_quorum_status_defaults_for_older_payloads() {
585 let legacy_json = r#"{"primary":"// test","sidecars":[],"citations":[]}"#;
586 let a: Artifact = serde_json::from_str(legacy_json).unwrap();
587 assert_eq!(
588 a.quorum_status,
589 QuorumStatus::Single {
590 emitter: "unknown".to_string()
591 }
592 );
593 }
594
595 #[test]
598 fn artifact_quorum_status_single_round_trips() {
599 let a = Artifact {
600 primary: "// test".into(),
601 sidecars: Vec::new(),
602 citations: Vec::new(),
603 quorum_status: QuorumStatus::Single {
604 emitter: "xpile-rust-codegen".to_string(),
605 },
606 };
607 let s = serde_json::to_string(&a).unwrap();
608 let back: Artifact = serde_json::from_str(&s).unwrap();
609 assert_eq!(back, a);
610 }
611
612 struct MockGeneral {
618 name: &'static str,
619 body: String,
620 }
621 impl TargetEmitter for MockGeneral {
622 fn name(&self) -> &str {
623 self.name
624 }
625 fn try_emit(
626 &self,
627 _module: &Module,
628 _config: &BackendConfig,
629 ) -> Option<Result<EmittedText, BackendError>> {
630 Some(Ok(EmittedText {
631 primary: self.body.clone(),
632 citations: Vec::new(),
633 }))
634 }
635 }
636
637 struct MockSpecialist {
640 name: &'static str,
641 matches: bool,
642 body: String,
643 }
644 impl TargetEmitter for MockSpecialist {
645 fn name(&self) -> &str {
646 self.name
647 }
648 fn try_emit(
649 &self,
650 _module: &Module,
651 _config: &BackendConfig,
652 ) -> Option<Result<EmittedText, BackendError>> {
653 if self.matches {
654 Some(Ok(EmittedText {
655 primary: self.body.clone(),
656 citations: Vec::new(),
657 }))
658 } else {
659 None
660 }
661 }
662 }
663
664 fn dummy_module() -> Module {
665 Module {
666 name: "test".into(),
667 source_lang: xpile_meta_hir::SourceLang::Rust,
668 items: Vec::new(),
669 ffi_boundaries: Vec::new(),
670 }
671 }
672
673 fn dummy_config() -> BackendConfig {
674 BackendConfig {
675 target: Target::Ptx,
676 profile: Profile::RustOut,
677 hardware: None,
678 }
679 }
680
681 #[test]
682 fn multi_emitter_specialist_missing_falls_back_to_general() {
683 let backend = MultiEmitterBackend::new_single(
684 Target::Ptx,
685 Box::new(MockGeneral {
686 name: "general",
687 body: "general output".into(),
688 }),
689 );
690 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
691 assert_eq!(artifact.primary, "general output");
692 assert_eq!(
693 artifact.quorum_status,
694 QuorumStatus::Single {
695 emitter: "general".to_string()
696 }
697 );
698 }
699
700 #[test]
701 fn multi_emitter_specialist_unmatched_falls_back_to_general() {
702 let backend = MultiEmitterBackend::new_with_specialist(
703 Target::Ptx,
704 Box::new(MockGeneral {
705 name: "general",
706 body: "general output".into(),
707 }),
708 Box::new(MockSpecialist {
709 name: "specialist",
710 matches: false,
711 body: "specialist output".into(),
712 }),
713 QuorumPolicy::DiffExec { tolerance: 1e-3 },
714 );
715 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
716 assert_eq!(artifact.primary, "general output");
717 assert_eq!(
719 artifact.quorum_status,
720 QuorumStatus::Single {
721 emitter: "general".to_string()
722 }
723 );
724 }
725
726 #[test]
727 fn multi_emitter_prefer_specialist_uses_specialist_output() {
728 let backend = MultiEmitterBackend::new_with_specialist(
729 Target::Ptx,
730 Box::new(MockGeneral {
731 name: "general",
732 body: "general".into(),
733 }),
734 Box::new(MockSpecialist {
735 name: "specialist",
736 matches: true,
737 body: "specialist tuned".into(),
738 }),
739 QuorumPolicy::PreferSpecialist,
740 );
741 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
742 assert_eq!(artifact.primary, "specialist tuned");
743 assert_eq!(
744 artifact.quorum_status,
745 QuorumStatus::Single {
746 emitter: "specialist".to_string()
747 }
748 );
749 }
750
751 #[test]
752 fn multi_emitter_strict_match_records_zero_diff() {
753 let backend = MultiEmitterBackend::new_with_specialist(
754 Target::Ptx,
755 Box::new(MockGeneral {
756 name: "general",
757 body: "same output".into(),
758 }),
759 Box::new(MockSpecialist {
760 name: "specialist",
761 matches: true,
762 body: "same output".into(),
763 }),
764 QuorumPolicy::Strict,
765 );
766 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
767 match artifact.quorum_status {
768 QuorumStatus::Multi {
769 emitters,
770 diff_exec,
771 } => {
772 assert_eq!(emitters, vec!["general", "specialist"]);
773 assert_eq!(diff_exec, Some(DiffExecResult::Match { max_abs_diff: 0.0 }));
774 }
775 _ => panic!("expected Multi quorum status"),
776 }
777 assert_eq!(artifact.sidecars.len(), 1);
779 assert_eq!(artifact.sidecars[0].0, "specialist_emission");
780 }
781
782 #[test]
783 fn multi_emitter_strict_divergence_records_infinity() {
784 let backend = MultiEmitterBackend::new_with_specialist(
785 Target::Ptx,
786 Box::new(MockGeneral {
787 name: "general",
788 body: "general output".into(),
789 }),
790 Box::new(MockSpecialist {
791 name: "specialist",
792 matches: true,
793 body: "different output".into(),
794 }),
795 QuorumPolicy::Strict,
796 );
797 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
798 match artifact.quorum_status {
799 QuorumStatus::Multi { diff_exec, .. } => {
800 assert!(matches!(diff_exec, Some(DiffExecResult::Divergent { .. })));
801 }
802 _ => panic!("expected Multi quorum status"),
803 }
804 }
805
806 #[test]
807 fn multi_emitter_diff_exec_records_not_run_until_engine_plugged_in() {
808 let backend = MultiEmitterBackend::new_with_specialist(
809 Target::Ptx,
810 Box::new(MockGeneral {
811 name: "rustc_codegen_nvvm",
812 body: "ptx general".into(),
813 }),
814 Box::new(MockSpecialist {
815 name: "aprender-gpu",
816 matches: true,
817 body: "ptx specialist".into(),
818 }),
819 QuorumPolicy::DiffExec { tolerance: 1e-3 },
820 );
821 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
822 match artifact.quorum_status {
823 QuorumStatus::Multi {
824 emitters,
825 diff_exec,
826 } => {
827 assert_eq!(emitters, vec!["rustc_codegen_nvvm", "aprender-gpu"]);
828 assert!(matches!(diff_exec, Some(DiffExecResult::NotRun { .. })));
830 }
831 _ => panic!("expected Multi quorum status"),
832 }
833 }
834
835 struct MockGeneralWithCitations {
846 body: String,
847 citations: Vec<ContractId>,
848 }
849 impl TargetEmitter for MockGeneralWithCitations {
850 fn name(&self) -> &str {
851 "general-with-cites"
852 }
853 fn try_emit(
854 &self,
855 _module: &Module,
856 _config: &BackendConfig,
857 ) -> Option<Result<EmittedText, BackendError>> {
858 Some(Ok(EmittedText {
859 primary: self.body.clone(),
860 citations: self.citations.clone(),
861 }))
862 }
863 }
864
865 struct MockSpecialistWithCitations {
868 body: String,
869 citations: Vec<ContractId>,
870 }
871 impl TargetEmitter for MockSpecialistWithCitations {
872 fn name(&self) -> &str {
873 "specialist-with-cites"
874 }
875 fn try_emit(
876 &self,
877 _module: &Module,
878 _config: &BackendConfig,
879 ) -> Option<Result<EmittedText, BackendError>> {
880 Some(Ok(EmittedText {
881 primary: self.body.clone(),
882 citations: self.citations.clone(),
883 }))
884 }
885 }
886
887 struct MockFailingEmitter {
890 name: &'static str,
891 err: String,
892 }
893 impl TargetEmitter for MockFailingEmitter {
894 fn name(&self) -> &str {
895 self.name
896 }
897 fn try_emit(
898 &self,
899 _module: &Module,
900 _config: &BackendConfig,
901 ) -> Option<Result<EmittedText, BackendError>> {
902 Some(Err(BackendError::Lower(self.err.clone())))
903 }
904 }
905
906 struct MockNoneEmitter;
910 impl TargetEmitter for MockNoneEmitter {
911 fn name(&self) -> &str {
912 "always-none"
913 }
914 fn try_emit(
915 &self,
916 _module: &Module,
917 _config: &BackendConfig,
918 ) -> Option<Result<EmittedText, BackendError>> {
919 None
920 }
921 }
922
923 #[test]
924 fn strict_divergence_preserves_general_citations_not_specialist() {
925 let backend = MultiEmitterBackend::new_with_specialist(
931 Target::Ptx,
932 Box::new(MockGeneralWithCitations {
933 body: "general output".into(),
934 citations: vec![ContractId::new("C-GENERAL-CITED")],
935 }),
936 Box::new(MockSpecialistWithCitations {
937 body: "different output".into(),
938 citations: vec![ContractId::new("C-SPECIALIST-CITED")],
939 }),
940 QuorumPolicy::Strict,
941 );
942 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
943 assert_eq!(artifact.citations.len(), 1);
944 assert_eq!(artifact.citations[0].as_str(), "C-GENERAL-CITED");
945 assert_eq!(artifact.sidecars.len(), 1);
948 assert_eq!(
949 artifact.sidecars[0].1,
950 b"different output".to_vec(),
951 "specialist body should be preserved in sidecar"
952 );
953 }
954
955 #[test]
956 fn prefer_specialist_hides_divergence_by_design() {
957 let backend = MultiEmitterBackend::new_with_specialist(
964 Target::Ptx,
965 Box::new(MockGeneralWithCitations {
966 body: "general thinks the answer is 42".into(),
967 citations: vec![ContractId::new("C-GENERAL")],
968 }),
969 Box::new(MockSpecialistWithCitations {
970 body: "specialist thinks the answer is 99".into(),
971 citations: vec![ContractId::new("C-SPECIALIST")],
972 }),
973 QuorumPolicy::PreferSpecialist,
974 );
975 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
976 assert!(artifact.primary.contains("99"));
979 assert_eq!(artifact.citations[0].as_str(), "C-SPECIALIST");
980 match artifact.quorum_status {
981 QuorumStatus::Single { emitter } => {
982 assert_eq!(emitter, "specialist-with-cites");
983 }
984 other => panic!("expected Single quorum status, got {other:?}"),
985 }
986 assert!(artifact.sidecars.is_empty());
988 }
989
990 #[test]
991 fn general_emitter_failure_propagates() {
992 let backend = MultiEmitterBackend::new_with_specialist(
997 Target::Ptx,
998 Box::new(MockFailingEmitter {
999 name: "general-broken",
1000 err: "general blew up".into(),
1001 }),
1002 Box::new(MockSpecialist {
1003 name: "specialist",
1004 matches: true,
1005 body: "specialist output".into(),
1006 }),
1007 QuorumPolicy::PreferSpecialist,
1008 );
1009 let err = backend.lower(&dummy_module(), &dummy_config()).unwrap_err();
1010 match err {
1011 BackendError::Lower(msg) => assert!(msg.contains("general blew up")),
1012 other => panic!("expected Lower error, got {other:?}"),
1013 }
1014 }
1015
1016 #[test]
1017 fn specialist_emitter_failure_propagates_when_matched() {
1018 let backend = MultiEmitterBackend::new_with_specialist(
1022 Target::Ptx,
1023 Box::new(MockGeneral {
1024 name: "general",
1025 body: "general output".into(),
1026 }),
1027 Box::new(MockFailingEmitter {
1028 name: "specialist-broken",
1029 err: "specialist blew up after matching".into(),
1030 }),
1031 QuorumPolicy::Strict,
1032 );
1033 let err = backend.lower(&dummy_module(), &dummy_config()).unwrap_err();
1034 match err {
1035 BackendError::Lower(msg) => {
1036 assert!(msg.contains("specialist blew up after matching"))
1037 }
1038 other => panic!("expected Lower error, got {other:?}"),
1039 }
1040 }
1041
1042 #[test]
1043 fn general_returning_none_is_a_hard_contract_violation() {
1044 let backend = MultiEmitterBackend::new_single(Target::Ptx, Box::new(MockNoneEmitter));
1048 let err = backend.lower(&dummy_module(), &dummy_config()).unwrap_err();
1049 match err {
1050 BackendError::Lower(msg) => {
1051 assert!(
1052 msg.contains("always-none"),
1053 "error should name the offending emitter; got: {msg}"
1054 );
1055 assert!(msg.contains("must always match"));
1056 }
1057 other => panic!("expected Lower error, got {other:?}"),
1058 }
1059 }
1060
1061 #[test]
1062 fn diff_exec_not_run_reason_records_tolerance_for_observability() {
1063 let backend = MultiEmitterBackend::new_with_specialist(
1067 Target::Ptx,
1068 Box::new(MockGeneral {
1069 name: "general",
1070 body: "g".into(),
1071 }),
1072 Box::new(MockSpecialist {
1073 name: "specialist",
1074 matches: true,
1075 body: "s".into(),
1076 }),
1077 QuorumPolicy::DiffExec { tolerance: 2.5e-4 },
1078 );
1079 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
1080 match artifact.quorum_status {
1081 QuorumStatus::Multi {
1082 diff_exec: Some(DiffExecResult::NotRun { reason }),
1083 ..
1084 } => {
1085 assert!(
1086 reason.contains("0.00025")
1087 || reason.contains("2.5e-4")
1088 || reason.contains("0.000250"),
1089 "tolerance should appear in NotRun reason; got: {reason}"
1090 );
1091 }
1092 other => panic!("expected Multi NotRun status, got {other:?}"),
1093 }
1094 }
1095
1096 #[test]
1097 fn diff_exec_does_not_short_circuit_on_text_equality() {
1098 let backend = MultiEmitterBackend::new_with_specialist(
1107 Target::Ptx,
1108 Box::new(MockGeneral {
1109 name: "general",
1110 body: "byte identical".into(),
1111 }),
1112 Box::new(MockSpecialist {
1113 name: "specialist",
1114 matches: true,
1115 body: "byte identical".into(),
1116 }),
1117 QuorumPolicy::DiffExec { tolerance: 1e-6 },
1118 );
1119 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
1120 match artifact.quorum_status {
1121 QuorumStatus::Multi { diff_exec, .. } => {
1122 assert!(
1123 matches!(diff_exec, Some(DiffExecResult::NotRun { .. })),
1124 "DiffExec policy must NOT short-circuit on text equality \
1125 — engine compares runtime values, not source text"
1126 );
1127 }
1128 other => panic!("expected Multi quorum status, got {other:?}"),
1129 }
1130 }
1131
1132 struct StubEngine {
1136 result: Result<DiffExecResult, String>,
1137 }
1138 impl DiffExecEngine for StubEngine {
1139 fn execute_and_compare(
1140 &self,
1141 _g: &str,
1142 _s: &str,
1143 _m: &Module,
1144 _c: &BackendConfig,
1145 _tol: f64,
1146 ) -> Result<DiffExecResult, String> {
1147 self.result.clone()
1148 }
1149 }
1150
1151 fn diff_exec_backend() -> MultiEmitterBackend {
1152 MultiEmitterBackend::new_with_specialist(
1153 Target::Ptx,
1154 Box::new(MockGeneral {
1155 name: "general",
1156 body: "g".into(),
1157 }),
1158 Box::new(MockSpecialist {
1159 name: "specialist",
1160 matches: true,
1161 body: "s".into(),
1162 }),
1163 QuorumPolicy::DiffExec { tolerance: 1e-6 },
1164 )
1165 }
1166
1167 #[test]
1170 fn diff_exec_engine_records_match() {
1171 let backend = diff_exec_backend().with_diff_exec_engine(std::sync::Arc::new(StubEngine {
1172 result: Ok(DiffExecResult::Match { max_abs_diff: 0.0 }),
1173 }));
1174 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
1175 match artifact.quorum_status {
1176 QuorumStatus::Multi {
1177 diff_exec: Some(DiffExecResult::Match { .. }),
1178 ..
1179 } => {}
1180 other => panic!("expected Multi Match, got {other:?}"),
1181 }
1182 }
1183
1184 #[test]
1187 fn diff_exec_engine_error_is_a_hard_failure() {
1188 let backend = diff_exec_backend().with_diff_exec_engine(std::sync::Arc::new(StubEngine {
1189 result: Err("driver fault: CUDA_ERROR_LAUNCH_FAILED".into()),
1190 }));
1191 let err = backend
1192 .lower(&dummy_module(), &dummy_config())
1193 .expect_err("engine error must surface as a hard BackendError");
1194 assert!(matches!(err, BackendError::Lower(_)));
1195 }
1196
1197 #[test]
1200 fn diff_exec_no_engine_records_not_run() {
1201 let backend = diff_exec_backend();
1202 let artifact = backend.lower(&dummy_module(), &dummy_config()).unwrap();
1203 match artifact.quorum_status {
1204 QuorumStatus::Multi {
1205 diff_exec: Some(DiffExecResult::NotRun { reason }),
1206 ..
1207 } => assert!(reason.contains("no DiffExec engine"), "got: {reason}"),
1208 other => panic!("expected Multi NotRun, got {other:?}"),
1209 }
1210 }
1211}