mod bits;
mod compiled;
mod control;
mod ops;
mod wide;
use std::cell::RefCell;
use std::fmt::Debug;
use std::marker::PhantomData;
use crate::circuit::Circuit;
use crate::wire::Wire;
use wide::Limbs;
pub use bits::{Shift, shl, shr};
pub use compiled::{
Attrs, Compiled, EncodedRecords, HwType, Inputs, Params, Report, Rng, VerifyError, declare_scalar, iteration_limit,
sample_variant,
};
pub use control::{
Branch, CastTo, Cond, Conv, Elem, IfElse, Lift, Mux, Placeholder, StoreElem, Unify, cast, if_else, index,
placeholder, store, unsupported,
};
pub use handwired_rs_macros::HwType;
pub use ops::{Cmp, Logic, RunningSum, and, compare_native, eq, ge, gt, le, lt, ne, or, running_sums};
thread_local! {
static CONTEXT: RefCell<Option<Circuit>> = const { RefCell::new(None) };
}
pub(crate) fn with_circuit<R>(f: impl FnOnce(&mut Circuit) -> R) -> R {
CONTEXT.with(|context| {
let mut context = context.borrow_mut();
let circuit =
context.as_mut().expect("handwired values can only be used while a #[handwired] circuit is built");
f(circuit)
})
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Kind {
Bool,
Int,
Float,
}
pub trait Scalar: Copy + PartialEq + Debug + 'static {
const KIND: Kind;
const NAME: &'static str;
const MIN: f64;
const MAX: f64;
fn to_f64(self) -> f64;
fn from_f64(value: f64) -> Self;
fn default_value() -> Self {
Self::from_f64(0.0)
}
fn to_i128(self) -> i128 {
self.to_f64() as i128
}
}
pub trait IntScalar: Scalar {}
pub trait Num: Scalar {}
pub(crate) fn width<T: Scalar>() -> usize {
let magnitude = (T::MAX + 1.0).log2() as usize;
if T::MIN < 0.0 { magnitude + 1 } else { magnitude }
}
pub(crate) fn is_wide<T: Scalar>() -> bool {
T::KIND == Kind::Int && T::MAX - T::MIN > 16_777_216.0
}
impl Scalar for bool {
const KIND: Kind = Kind::Bool;
const NAME: &'static str = "bool";
const MIN: f64 = 0.0;
const MAX: f64 = 1.0;
fn to_f64(self) -> f64 {
if self { 1.0 } else { 0.0 }
}
fn from_f64(value: f64) -> bool {
assert!(value == 0.0 || value == 1.0, "a bool wire holds {value}, not 0 or 1");
value == 1.0
}
}
impl Scalar for f32 {
const KIND: Kind = Kind::Float;
const NAME: &'static str = "f32";
const MIN: f64 = f32::MIN as f64;
const MAX: f64 = f32::MAX as f64;
fn to_f64(self) -> f64 {
self as f64
}
fn from_f64(value: f64) -> f32 {
value as f32
}
}
impl Num for f32 {}
macro_rules! integer_scalars {
($($int:ident),*) => {$(
impl Scalar for $int {
const KIND: Kind = Kind::Int;
const NAME: &'static str = stringify!($int);
const MIN: f64 = $int::MIN as f64;
const MAX: f64 = $int::MAX as f64;
fn to_f64(self) -> f64 {
self as f64
}
fn from_f64(value: f64) -> $int {
value as $int
}
fn to_i128(self) -> i128 {
self as i128
}
}
impl IntScalar for $int {}
impl Num for $int {}
)*};
}
integer_scalars!(u8, u16, u32, u64, usize, i8, i16, i32, i64);
pub struct Val<T> {
narrow: Wire,
pub(crate) wide: Option<Limbs>,
marker: PhantomData<T>,
}
impl<T> Clone for Val<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Val<T> {}
impl<T> Debug for Val<T> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.wide {
None => write!(formatter, "Val({})", self.narrow.index()),
Some(limbs) => {
let indices: Vec<usize> = limbs.wires().iter().map(|wire| wire.index()).collect();
write!(formatter, "Val(bytes {indices:?})")
}
}
}
}
impl<T> Val<T> {
#[track_caller]
pub fn wire(&self) -> Wire {
if self.wide.is_some() {
panic!(
"{}: this value may leave ±2^24, so it is held as bytes, and this operation needs it as one wire; \
narrow it first, e.g. with `as u8` or a mask",
std::panic::Location::caller()
);
}
self.narrow
}
pub fn is_wide(&self) -> bool {
self.wide.is_some()
}
}
impl<T: Scalar> Val<T> {
pub fn from_wire(wire: Wire) -> Val<T> {
Val { narrow: wire, wide: None, marker: PhantomData }
}
pub(crate) fn from_limbs(limbs: Limbs) -> Val<T> {
Val { narrow: limbs.wires()[0], wide: Some(limbs), marker: PhantomData }
}
pub fn constant(value: T) -> Val<T> {
if T::KIND == Kind::Int && value.to_i128().unsigned_abs() >= 1 << 24 {
return Val::from_limbs(wide::constant_limbs::<T>(value.to_i128()));
}
with_circuit(|circuit| {
let wire = circuit.constant(value.to_f64());
if T::KIND != Kind::Float {
circuit.assume_grid_allowing_readers(wire, 1.0);
}
Val::from_wire(wire)
})
}
}
pub(crate) fn known_bit(bit: Val<bool>) -> Option<bool> {
with_circuit(|circuit| circuit.constant_value(bit.wire())).map(|value| value as f32 != 0.0)
}
pub(crate) fn settle<T: Scalar>(circuit: &mut Circuit, wire: Wire, min: f64, max: f64) -> Val<T> {
let range = circuit.range(wire);
let (min, max) = (range.min.max(min), range.max.min(max));
if min <= max {
circuit.assume_range_allowing_readers(wire, min, max);
}
if T::KIND != Kind::Float && wire.index() >= circuit.source_size() {
circuit.assume_grid_allowing_readers(wire, 1.0);
}
Val::from_wire(wire)
}