use once_cell::sync::OnceCell;
use crate::engine::{
self, Engine, GfElement, CANTOR_BASIS, GF_BITS, GF_MODULUS, GF_ORDER, GF_POLYNOMIAL,
};
pub type Exp = [GfElement; GF_ORDER];
pub type Log = [GfElement; GF_ORDER];
pub type LogWalsh = [GfElement; GF_ORDER];
pub type Mul16 = [[[GfElement; 16]; 4]; GF_ORDER];
pub type Skew = [GfElement; GF_MODULUS as usize];
struct ExpLog {
exp: Box<Exp>,
log: Box<Log>,
}
static EXP_LOG: OnceCell<ExpLog> = OnceCell::new();
static LOG_WALSH: OnceCell<Box<LogWalsh>> = OnceCell::new();
static MUL16: OnceCell<Box<Mul16>> = OnceCell::new();
static SKEW: OnceCell<Box<Skew>> = OnceCell::new();
#[inline(always)]
pub fn mul(x: GfElement, log_m: GfElement, exp: &Exp, log: &Log) -> GfElement {
if x == 0 {
0
} else {
exp[engine::add_mod(log[x as usize], log_m) as usize]
}
}
#[allow(clippy::needless_range_loop)]
pub fn initialize_exp_log() -> (&'static Exp, &'static Log) {
let exp_log = EXP_LOG.get_or_init(|| {
let mut exp = Box::new([0; GF_ORDER]);
let mut log = Box::new([0; GF_ORDER]);
let mut state = 1;
for i in 0..GF_MODULUS {
exp[state] = i;
state <<= 1;
if state >= GF_ORDER {
state ^= GF_POLYNOMIAL;
}
}
exp[0] = GF_MODULUS;
log[0] = 0;
for i in 0..GF_BITS {
let width = 1usize << i;
for j in 0..width {
log[j + width] = log[j] ^ CANTOR_BASIS[i];
}
}
for i in 0..GF_ORDER {
log[i] = exp[log[i] as usize];
}
for i in 0..GF_ORDER {
exp[log[i] as usize] = i as GfElement;
}
exp[GF_MODULUS as usize] = exp[0];
ExpLog { exp, log }
});
(&exp_log.exp, &exp_log.log)
}
pub fn initialize_log_walsh<E: Engine>() -> &'static LogWalsh {
LOG_WALSH.get_or_init(|| {
let (_, log) = initialize_exp_log();
let mut log_walsh: Box<LogWalsh> = Box::new([0; GF_ORDER]);
log_walsh.copy_from_slice(log.as_ref());
log_walsh[0] = 0;
E::fwht(log_walsh.as_mut(), GF_ORDER);
log_walsh
})
}
pub fn initialize_mul16() -> &'static Mul16 {
MUL16.get_or_init(|| {
let (exp, log) = initialize_exp_log();
let mut mul16 = vec![[[0; 16]; 4]; GF_ORDER];
for log_m in 0..=GF_MODULUS {
let lut = &mut mul16[log_m as usize];
for i in 0..16 {
lut[0][i] = mul(i as GfElement, log_m, exp, log);
lut[1][i] = mul((i << 4) as GfElement, log_m, exp, log);
lut[2][i] = mul((i << 8) as GfElement, log_m, exp, log);
lut[3][i] = mul((i << 12) as GfElement, log_m, exp, log);
}
}
mul16.into_boxed_slice().try_into().unwrap()
})
}
#[allow(clippy::needless_range_loop)]
pub fn initialize_skew() -> &'static Skew {
SKEW.get_or_init(|| {
let (exp, log) = initialize_exp_log();
let mut skew = Box::new([0; GF_MODULUS as usize]);
let mut temp = [0; GF_BITS - 1];
for i in 1..GF_BITS {
temp[i - 1] = 1 << i;
}
for m in 0..GF_BITS - 1 {
let step: usize = 1 << (m + 1);
skew[(1 << m) - 1] = 0;
for i in m..GF_BITS - 1 {
let s: usize = 1 << (i + 1);
let mut j = (1 << m) - 1;
while j < s {
skew[j + s] = skew[j] ^ temp[i];
j += step;
}
}
temp[m] =
GF_MODULUS - log[mul(temp[m], log[(temp[m] ^ 1) as usize], exp, log) as usize];
for i in m + 1..GF_BITS - 1 {
let sum = engine::add_mod(log[(temp[i] ^ 1) as usize], temp[m]);
temp[i] = mul(temp[i], sum, exp, log);
}
}
for i in 0..GF_MODULUS as usize {
skew[i] = log[skew[i] as usize];
}
skew
})
}