pub mod api;
mod asert_sim;
pub mod bench;
pub mod cpu;
#[cfg(any(feature = "metal", feature = "webgpu"))]
pub mod gpu;
pub mod job;
pub mod run;
pub mod target;
pub use job::Job;
pub trait Backend: Send + Sync {
fn name(&self) -> String;
fn search(&self, job: &Job, base: u128, count: u32) -> Result<Option<u32>, String>;
fn dispatch(&self) -> u32;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Choice {
Auto,
Cpu,
Metal,
WebGpu,
}
impl core::str::FromStr for Choice {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s {
"auto" => Ok(Choice::Auto),
"cpu" => Ok(Choice::Cpu),
"metal" => Ok(Choice::Metal),
"webgpu" => Ok(Choice::WebGpu),
_ => Err(format!("unknown backend `{s}` (auto, cpu, metal, webgpu)")),
}
}
}
pub fn metal() -> Result<Box<dyn Backend>, String> {
#[cfg(all(feature = "metal", target_os = "macos"))]
{
gpu::Metal::new().map(|m| Box::new(m) as Box<dyn Backend>)
}
#[cfg(not(all(feature = "metal", target_os = "macos")))]
{
Err("metal: not built (feature `metal`, macOS)".into())
}
}
pub fn webgpu() -> Result<Box<dyn Backend>, String> {
#[cfg(feature = "webgpu")]
{
gpu::WebGpu::new().map(|w| Box::new(w) as Box<dyn Backend>)
}
#[cfg(not(feature = "webgpu"))]
{
Err("webgpu: not built (feature `webgpu`)".into())
}
}
pub fn select(choice: Choice, threads: usize) -> Result<Box<dyn Backend>, String> {
match choice {
Choice::Cpu => Ok(Box::new(cpu::Cpu::new(threads))),
Choice::Metal => metal(),
Choice::WebGpu => webgpu(),
Choice::Auto => Ok(metal()
.or_else(|_| webgpu())
.unwrap_or_else(|_| Box::new(cpu::Cpu::new(threads)))),
}
}
pub fn all(threads: usize) -> Vec<Box<dyn Backend>> {
let mut v: Vec<Box<dyn Backend>> = vec![Box::new(cpu::Cpu::new(threads))];
for b in [metal(), webgpu()] {
match b {
Ok(b) => v.push(b),
Err(e) => eprintln!("{e}"),
}
}
v
}
#[cfg(test)]
mod tests {
use super::*;
use target::{pow2, verifies};
#[test]
fn every_backend_agrees_and_verifies() {
let backends = all(4);
let mut vector = [0u8; 168];
vector[0..4].copy_from_slice(&1u32.to_le_bytes());
vector[4..12].copy_from_slice(&1u64.to_le_bytes());
vector[12..20].copy_from_slice(&1_793_885_850u64.to_le_bytes());
vector[20..24].copy_from_slice(&0x1e01_0000u32.to_le_bytes());
vector[40..72].fill(0x11);
vector[72..104].fill(0x22);
vector[104..136].fill(0x33);
vector[136..168].fill(0x44);
let jobs = [
(
Job::new(&vector, pow2(232)),
934_000u128,
2_000u32,
Some(636u32),
),
(
Job::new(&[0x3cu8; 168], pow2(246)),
(5u128 << 64) + 100,
1 << 14,
None,
),
(
Job::new(&[0x3cu8; 168], pow2(246)),
((9u128 << 64) | (u64::MAX as u128)) - 20_000,
1 << 14,
None,
),
(
Job::new(&[0x99u8; 168], [1, 0, 0, 0]),
0,
4096,
Some(u32::MAX),
),
];
for (job, base, count, known) in jobs {
let want = backends[0].search(&job, base, count).expect("cpu");
if let Some(k) = known {
assert_eq!(want, (k != u32::MAX).then_some(k), "cpu vs known");
} else {
assert!(want.is_some(), "a nonce in the range");
}
if let Some(i) = want {
assert!(verifies(&job.header_with(base + i as u128), &job.target));
}
for b in &backends[1..] {
assert_eq!(
b.search(&job, base, count).expect("gpu"),
want,
"{} base {base:#x}",
b.name()
);
}
}
}
}