1use std::sync::{Arc, OnceLock};
6
7use num_bigint::BigInt;
8
9use crate::batch::RnsBatch;
10use crate::cpu::CpuBackend;
11use crate::gpu::GpuBackend;
12
13pub trait ArithmeticBackend: Send + Sync {
15 fn batch_add(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
17
18 fn batch_sub(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
20
21 fn batch_mul(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch;
23
24 fn batch_crt(&self, batch: &RnsBatch) -> Vec<BigInt>;
26
27 fn name(&self) -> &'static str;
29}
30
31pub struct Executor {
33 cpu: Arc<CpuBackend>,
34 gpu: Option<Arc<GpuBackend>>,
35 pub gpu_threshold: usize,
39}
40
41impl Executor {
42 pub fn init() -> Self {
44 let cpu = Arc::new(CpuBackend::new());
45 let gpu = GpuBackend::try_init().ok().map(Arc::new);
46 match &gpu {
47 Some(g) => log::info!("adele-ring: GPU backend active ({})", g.adapter_name()),
48 None => log::info!("adele-ring: no GPU found, using CPU backend"),
49 }
50 Self {
51 cpu,
52 gpu,
53 gpu_threshold: 128,
54 }
55 }
56
57 pub fn has_gpu(&self) -> bool {
59 self.gpu.is_some()
60 }
61
62 pub fn cpu(&self) -> &CpuBackend {
64 &self.cpu
65 }
66
67 pub fn gpu(&self) -> Option<&GpuBackend> {
69 self.gpu.as_deref()
70 }
71
72 fn select(&self, batch_size: usize) -> &dyn ArithmeticBackend {
74 match &self.gpu {
75 Some(g) if batch_size >= self.gpu_threshold => g.as_ref(),
76 _ => self.cpu.as_ref(),
77 }
78 }
79
80 pub fn add(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
82 self.select(a.batch_size).batch_add(a, b)
83 }
84
85 pub fn sub(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
87 self.select(a.batch_size).batch_sub(a, b)
88 }
89
90 pub fn mul(&self, a: &RnsBatch, b: &RnsBatch) -> RnsBatch {
92 self.select(a.batch_size).batch_mul(a, b)
93 }
94
95 pub fn crt(&self, batch: &RnsBatch) -> Vec<BigInt> {
97 self.cpu.batch_crt(batch)
98 }
99}
100
101impl Default for Executor {
102 fn default() -> Self {
103 Self::init()
104 }
105}
106
107static EXECUTOR: OnceLock<Executor> = OnceLock::new();
108
109pub fn executor() -> &'static Executor {
111 EXECUTOR.get_or_init(Executor::init)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::basis::Basis;
118 use crate::rns::RnsInt;
119
120 #[test]
121 fn executor_adds_batches() {
122 let b = Basis::standard();
123 let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(7, b.clone()); 200]);
124 let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(35, b.clone()); 200]);
125 let sum = executor().add(&x, &y);
126 for item in sum.to_rns_ints() {
127 assert_eq!(item.to_bigint(), BigInt::from(42));
128 }
129 }
130
131 #[test]
132 fn executor_subtracts_batches() {
133 let b = Basis::standard();
134 let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(35, b.clone()); 200]);
135 let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(42, b.clone()); 200]);
136 let diff = executor().sub(&x, &y);
137 for item in diff.to_rns_ints() {
138 assert_eq!(item.to_bigint(), BigInt::from(-7));
139 }
140 }
141
142 #[test]
143 fn cpu_gpu_identical() {
144 let exec = Executor::init();
145 if !exec.has_gpu() {
146 return;
147 }
148 let b = Basis::standard();
149 let x = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(123, b.clone()); 256]);
150 let y = RnsBatch::from_rns_ints(&vec![RnsInt::from_i64(456, b.clone()); 256]);
151 let cpu = exec.cpu().batch_add(&x, &y);
152 let gpu = exec.gpu().unwrap().batch_add(&x, &y);
153 assert_eq!(cpu.data, gpu.data);
154 }
155}