use crate::node::{CompiledU64Op, GkNode, NodeMeta, Port, Slot, Value};
const BANKS_PER_WIDTH: usize = 8;
const FEEDBACK_BANKS: [u64; 61 * BANKS_PER_WIDTH] = include!("metashift_banks.inc");
fn feedback_for_width_and_bank(width: u32, bank: usize) -> u64 {
assert!((4..=64).contains(&width), "LFSR width must be 4..64, got {width}");
let base = (width as usize - 4) * BANKS_PER_WIDTH;
FEEDBACK_BANKS[base + (bank % BANKS_PER_WIDTH)]
}
#[allow(dead_code)]
fn feedback_for_width(width: u32) -> u64 {
feedback_for_width_and_bank(width, 0)
}
fn width_for_period(period: u64) -> u32 {
assert!(period > 0, "period must be positive");
let bits = 64 - period.leading_zeros();
bits.max(4) }
#[inline]
fn lfsr_step(register: u64, feedback: u64) -> u64 {
let lsb = register & 1;
let shifted = register >> 1;
shifted ^ (lsb.wrapping_neg() & feedback)
}
struct ShuffleConfig {
feedback: u64,
size: u64,
min: u64,
}
pub struct Shuffle {
meta: NodeMeta,
config: ShuffleConfig,
}
impl Shuffle {
pub fn new(min: u64, size: u64) -> Self {
Self::with_bank(min, size, 0)
}
pub fn with_bank(min: u64, size: u64, bank: usize) -> Self {
assert!(size > 0, "shuffle size must be positive");
let width = width_for_period(size);
let feedback = feedback_for_width_and_bank(width, bank);
Self {
meta: NodeMeta {
name: "shuffle".into(),
outs: vec![Port::u64("output")],
ins: vec![
Slot::Wire(Port::u64("input")),
Slot::const_u64("feedback", feedback),
Slot::const_u64("size", size),
Slot::const_u64("min", min),
],
},
config: ShuffleConfig { feedback, size, min },
}
}
pub fn zero_based(size: u64) -> Self {
Self::new(0, size)
}
pub fn zero_based_with_bank(size: u64, bank: usize) -> Self {
Self::with_bank(0, size, bank)
}
#[inline]
fn apply(&self, input: u64) -> u64 {
let mut register = (input % self.config.size) + 1;
loop {
register = lfsr_step(register, self.config.feedback);
if register <= self.config.size {
break;
}
}
(register - 1) + self.config.min
}
}
impl GkNode for Shuffle {
fn meta(&self) -> &NodeMeta {
&self.meta
}
fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
outputs[0] = Value::U64(self.apply(inputs[0].as_u64()));
}
fn compiled_u64(&self) -> Option<CompiledU64Op> {
let feedback = self.config.feedback;
let size = self.config.size;
let min = self.config.min;
Some(Box::new(move |inputs, outputs| {
let mut register = (inputs[0] % size) + 1;
loop {
register = lfsr_step(register, feedback);
if register <= size {
break;
}
}
outputs[0] = (register - 1) + min;
}))
}
fn jit_constants(&self) -> Vec<u64> {
vec![self.config.feedback, self.config.size, self.config.min]
}
}
pub struct LfsrStep {
meta: NodeMeta,
feedback: u64,
}
impl LfsrStep {
pub fn new(width: u32) -> Self {
Self::with_bank(width, 0)
}
pub fn with_bank(width: u32, bank: usize) -> Self {
Self {
meta: NodeMeta {
name: "lfsr_step".into(),
outs: vec![Port::u64("output")],
ins: vec![Slot::Wire(Port::u64("input"))],
},
feedback: feedback_for_width_and_bank(width, bank),
}
}
}
impl GkNode for LfsrStep {
fn meta(&self) -> &NodeMeta {
&self.meta
}
fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
outputs[0] = Value::U64(lfsr_step(inputs[0].as_u64(), self.feedback));
}
fn compiled_u64(&self) -> Option<CompiledU64Op> {
let feedback = self.feedback;
Some(Box::new(move |inputs, outputs| {
outputs[0] = lfsr_step(inputs[0], feedback);
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lfsr_step_nonzero() {
let feedback = feedback_for_width(8);
let mut reg = 1u64;
for _ in 0..255 {
reg = lfsr_step(reg, feedback);
assert_ne!(reg, 0, "LFSR must never produce 0");
}
}
#[test]
fn lfsr_full_cycle() {
let feedback = feedback_for_width(8);
let mut seen = vec![false; 256];
let mut reg = 1u64;
for _ in 0..255 {
reg = lfsr_step(reg, feedback);
assert!(!seen[reg as usize], "duplicate value {reg}");
seen[reg as usize] = true;
}
for i in 1..=255u64 {
assert!(seen[i as usize], "value {i} not visited");
}
}
#[test]
fn lfsr_period_returns_to_start() {
let feedback = feedback_for_width(8);
let start = 42u64;
let mut reg = start;
for _ in 0..255 {
reg = lfsr_step(reg, feedback);
}
assert_eq!(reg, start, "LFSR should return to start after 2^N-1 steps");
}
#[test]
fn shuffle_bijective_small() {
let shuf = Shuffle::zero_based(31);
let mut seen = vec![false; 31];
for i in 0..31u64 {
let out = shuf.apply(i);
assert!(out < 31, "out of range: {out}");
assert!(!seen[out as usize], "duplicate at input {i}: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_bijective_non_power_of_two() {
let shuf = Shuffle::zero_based(50);
let mut seen = vec![false; 50];
for i in 0..50u64 {
let out = shuf.apply(i);
assert!(out < 50, "out of range: {out}");
assert!(!seen[out as usize], "duplicate at input {i}: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_with_min_offset() {
let shuf = Shuffle::new(100, 20);
let mut seen = vec![false; 20];
for i in 0..20u64 {
let out = shuf.apply(i);
assert!((100..120).contains(&out), "out of range: {out}");
seen[(out - 100) as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn shuffle_deterministic() {
let shuf = Shuffle::zero_based(100);
let a = shuf.apply(42);
let b = shuf.apply(42);
assert_eq!(a, b);
}
#[test]
fn shuffle_not_identity() {
let shuf = Shuffle::zero_based(100);
let mut identity_count = 0;
for i in 0..100u64 {
if shuf.apply(i) == i {
identity_count += 1;
}
}
assert!(identity_count < 50, "shuffle should reorder most values");
}
#[test]
fn shuffle_gk_node() {
let node = Shuffle::zero_based(100);
let mut out = [Value::None];
node.eval(&[Value::U64(7)], &mut out);
assert!(out[0].as_u64() < 100);
}
#[test]
fn shuffle_compiled() {
let node = Shuffle::zero_based(100);
let op = node.compiled_u64().expect("should compile");
let mut out = [0u64];
op(&[7], &mut out);
assert!(out[0] < 100);
let mut eval_out = [Value::None];
node.eval(&[Value::U64(7)], &mut eval_out);
assert_eq!(out[0], eval_out[0].as_u64());
}
#[test]
fn lfsr_step_node() {
let node = LfsrStep::new(8);
let mut out = [Value::None];
node.eval(&[Value::U64(1)], &mut out);
let v = out[0].as_u64();
assert_ne!(v, 0);
assert_ne!(v, 1);
}
#[test]
fn shuffle_large_range() {
let shuf = Shuffle::zero_based(1000);
let mut seen = vec![false; 1000];
for i in 0..1000u64 {
let out = shuf.apply(i);
assert!(out < 1000, "out of range: {out}");
seen[out as usize] = true;
}
assert!(seen.iter().all(|&s| s), "not all values produced");
}
#[test]
fn different_banks_different_orderings() {
let shuf0 = Shuffle::zero_based_with_bank(100, 0);
let shuf1 = Shuffle::zero_based_with_bank(100, 1);
let mut seen0 = vec![false; 100];
let mut seen1 = vec![false; 100];
let mut differ = false;
for i in 0..100u64 {
let a = shuf0.apply(i);
let b = shuf1.apply(i);
assert!(a < 100);
assert!(b < 100);
seen0[a as usize] = true;
seen1[b as usize] = true;
if a != b {
differ = true;
}
}
assert!(seen0.iter().all(|&s| s), "bank 0 not bijective");
assert!(seen1.iter().all(|&s| s), "bank 1 not bijective");
assert!(differ, "different banks should produce different orderings");
}
#[test]
fn width_for_period_table() {
assert_eq!(width_for_period(1), 4); assert_eq!(width_for_period(15), 4); assert_eq!(width_for_period(16), 5); assert_eq!(width_for_period(31), 5);
assert_eq!(width_for_period(32), 6);
assert_eq!(width_for_period(255), 8);
assert_eq!(width_for_period(256), 9);
assert_eq!(width_for_period(1000), 10);
}
}