use super::program::{
AdmissionProgram, InputDecl, InputSlot, Node, NodeId, NodeOp, Outputs, ProgramError, Width,
};
use super::schedule_circuit::{schedule_membrane_bit, ScheduleShape};
#[derive(Debug, Default)]
pub struct CircuitBuilder {
inputs: Vec<InputDecl>,
nodes: Vec<Node>,
}
impl CircuitBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn push(&mut self, op: NodeOp, operands: Vec<NodeId>, width: Width) -> NodeId {
let id = NodeId(u32::try_from(self.nodes.len()).unwrap_or(u32::MAX));
self.nodes.push(Node {
op,
operands,
width,
});
id
}
pub fn input(&mut self, width: Width) -> NodeId {
let slot = u16::try_from(self.inputs.len()).unwrap_or(u16::MAX);
self.inputs.push(InputDecl { width });
self.push(
NodeOp::Input {
slot: InputSlot(slot),
},
vec![],
width,
)
}
pub fn constant(&mut self, bytes: Vec<u8>, width: Width) -> NodeId {
self.push(NodeOp::Constant { bytes }, vec![], width)
}
pub fn compare_ule(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(
NodeOp::Compare {
rel: super::program::CompareRel::Ule,
},
vec![a, b],
Width::one(),
)
}
pub fn compare_ult(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(
NodeOp::Compare {
rel: super::program::CompareRel::Ult,
},
vec![a, b],
Width::one(),
)
}
pub fn and(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(NodeOp::And, vec![a, b], Width::one())
}
pub fn or(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(NodeOp::Or, vec![a, b], Width::one())
}
pub fn not(&mut self, a: NodeId) -> NodeId {
self.push(NodeOp::Not, vec![a], Width::one())
}
pub fn select(&mut self, cond: NodeId, a: NodeId, b: NodeId, width: Width) -> NodeId {
self.push(NodeOp::Select, vec![cond, a, b], width)
}
pub fn bitset_subset(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(NodeOp::BitsetSubset, vec![a, b], Width::one())
}
pub fn bitset_intersection(&mut self, a: NodeId, b: NodeId, width: Width) -> NodeId {
self.push(NodeOp::BitsetIntersection, vec![a, b], width)
}
pub fn equal(&mut self, a: NodeId, b: NodeId) -> NodeId {
self.push(NodeOp::Eq, vec![a, b], Width::one())
}
pub fn and_reduce(&mut self, items: &[NodeId]) -> NodeId {
match items {
[] => self.constant(vec![1], Width::one()),
[only] => *only,
_ => {
let mid = items.len() / 2;
let (left, right) = items.split_at(mid);
let l = self.and_reduce(left);
let r = self.and_reduce(right);
self.and(l, r)
}
}
}
pub fn or_reduce(&mut self, items: &[NodeId]) -> NodeId {
match items {
[] => self.constant(vec![0], Width::one()),
[only] => *only,
_ => {
let mid = items.len() / 2;
let (left, right) = items.split_at(mid);
let l = self.or_reduce(left);
let r = self.or_reduce(right);
self.or(l, r)
}
}
}
pub fn finish(self, outputs: Outputs) -> Result<AdmissionProgram, ProgramError> {
AdmissionProgram::new(self.inputs, self.nodes, outputs)
}
}
fn refusal_width() -> Width {
Width::new(8).expect("8 is within 1..=MAX_WIDTH")
}
const MEDIATED_CODE: u8 = 1;
fn finish_single_membrane(
mut builder: CircuitBuilder,
admit: NodeId,
) -> Result<AdmissionProgram, ProgramError> {
let rw = refusal_width();
let admitted = builder.constant(vec![0], rw);
let refused = builder.constant(vec![1], rw);
let refusal_code = builder.select(admit, admitted, refused, rw);
builder.finish(Outputs {
admit,
refusal_code,
membranes: vec![admit],
})
}
#[must_use]
pub fn compose_membranes(builder: &mut CircuitBuilder, membranes: &[NodeId]) -> (NodeId, NodeId) {
let admit = builder.and_reduce(membranes);
let refusal = priority_encode(builder, membranes);
(admit, refusal)
}
fn priority_encode(builder: &mut CircuitBuilder, membranes: &[NodeId]) -> NodeId {
let rw = refusal_width();
let mut code = builder.constant(vec![0], rw);
for (i, membrane) in membranes.iter().enumerate().rev() {
let index = u8::try_from(i + 1).unwrap_or(u8::MAX);
let fail_code = builder.constant(vec![index], rw);
code = builder.select(*membrane, code, fail_code, rw);
}
code
}
fn enforcement_width() -> Width {
Width::new(2).expect("2 is within 1..=MAX_WIDTH")
}
struct BudgetLanes<'a> {
limit: &'a [NodeId],
available: &'a [NodeId],
derived_min: &'a [NodeId],
guarantee_req: &'a [NodeId],
guarantee_avail: &'a [NodeId],
evidence_req: &'a [NodeId],
evidence_avail: &'a [NodeId],
}
fn dim_checks(builder: &mut CircuitBuilder, lanes: &BudgetLanes, d: usize) -> [NodeId; 4] {
[
builder.compare_ule(lanes.derived_min[d], lanes.limit[d]),
builder.compare_ule(lanes.limit[d], lanes.available[d]),
builder.compare_ule(lanes.guarantee_req[d], lanes.guarantee_avail[d]),
builder.bitset_subset(lanes.evidence_req[d], lanes.evidence_avail[d]),
]
}
fn budget_check(builder: &mut CircuitBuilder, lanes: &BudgetLanes) -> NodeId {
let per_dim: Vec<NodeId> = (0..lanes.limit.len())
.map(|d| {
let checks = dim_checks(builder, lanes, d);
builder.and_reduce(&checks)
})
.collect();
builder.and_reduce(&per_dim)
}
fn evidence_check(
builder: &mut CircuitBuilder,
required: &[NodeId],
available: &[NodeId],
) -> NodeId {
let checks: Vec<NodeId> = required
.iter()
.zip(available)
.map(|(req, avail)| builder.bitset_subset(*req, *avail))
.collect();
builder.and_reduce(&checks)
}
pub(crate) fn support_check(
builder: &mut CircuitBuilder,
enforcements: &[NodeId],
enf_width: Width,
) -> NodeId {
let checks: Vec<NodeId> = enforcements
.iter()
.map(|enf| {
let threshold = builder.constant(vec![MEDIATED_CODE], enf_width);
builder.compare_ule(threshold, *enf)
})
.collect();
builder.and_reduce(&checks)
}
fn conflict_check(
builder: &mut CircuitBuilder,
present: &[NodeId],
forbidden: &[NodeId],
width: Width,
) -> NodeId {
let zero = builder.constant(vec![0u8; usize::from(width.get()).div_ceil(8)], width);
let checks: Vec<NodeId> = present
.iter()
.zip(forbidden)
.map(|(p, f)| {
let intersection = builder.bitset_intersection(*p, *f, width);
builder.equal(intersection, zero)
})
.collect();
builder.and_reduce(&checks)
}
fn profile_drift_check(builder: &mut CircuitBuilder, planned: NodeId, live: NodeId) -> NodeId {
builder.equal(planned, live)
}
pub fn compile_budget_membrane(
dims: usize,
budget_width: Width,
evidence_width: Width,
) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let enf_width = enforcement_width();
let limit: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let available: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let derived_min: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let guarantee_req: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let guarantee_avail: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let evidence_req: Vec<NodeId> = (0..dims).map(|_| builder.input(evidence_width)).collect();
let evidence_avail: Vec<NodeId> = (0..dims).map(|_| builder.input(evidence_width)).collect();
let admit = budget_check(
&mut builder,
&BudgetLanes {
limit: &limit,
available: &available,
derived_min: &derived_min,
guarantee_req: &guarantee_req,
guarantee_avail: &guarantee_avail,
evidence_req: &evidence_req,
evidence_avail: &evidence_avail,
},
);
finish_single_membrane(builder, admit)
}
fn pack_detail(dim: usize, reason: u8) -> u8 {
(u8::try_from(dim).unwrap_or(0) << 3) | reason
}
pub fn compile_budget_detail(
dims: usize,
budget_width: Width,
evidence_width: Width,
) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let enf_width = enforcement_width();
let limit: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let available: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let derived_min: Vec<NodeId> = (0..dims).map(|_| builder.input(budget_width)).collect();
let guarantee_req: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let guarantee_avail: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let evidence_req: Vec<NodeId> = (0..dims).map(|_| builder.input(evidence_width)).collect();
let evidence_avail: Vec<NodeId> = (0..dims).map(|_| builder.input(evidence_width)).collect();
let lanes = BudgetLanes {
limit: &limit,
available: &available,
derived_min: &derived_min,
guarantee_req: &guarantee_req,
guarantee_avail: &guarantee_avail,
evidence_req: &evidence_req,
evidence_avail: &evidence_avail,
};
let cw = refusal_width();
let zero = builder.constant(vec![0], cw);
let entries: Vec<(NodeId, NodeId)> = (0..dims)
.map(|d| {
let checks = dim_checks(&mut builder, &lanes, d);
let mut code = zero;
for (offset, &pass) in checks.iter().enumerate().rev() {
let reason = u8::try_from(offset + 1).unwrap_or(0);
let packed = builder.constant(vec![pack_detail(d + 1, reason)], cw);
code = builder.select(pass, code, packed, cw);
}
let dim_pass = builder.and_reduce(&checks);
(dim_pass, code)
})
.collect();
let mut detail = zero;
for (dim_pass, code) in entries.iter().rev() {
detail = builder.select(*dim_pass, detail, *code, cw);
}
let admit = builder.equal(detail, zero);
builder.finish(Outputs {
admit,
refusal_code: detail,
membranes: vec![admit],
})
}
pub fn compile_evidence_membrane(
reqs: usize,
evidence_width: Width,
) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let required: Vec<NodeId> = (0..reqs).map(|_| builder.input(evidence_width)).collect();
let available: Vec<NodeId> = (0..reqs).map(|_| builder.input(evidence_width)).collect();
let admit = evidence_check(&mut builder, &required, &available);
finish_single_membrane(builder, admit)
}
pub fn compile_support_membrane(reqs: usize) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let enf_width = enforcement_width();
let enforcements: Vec<NodeId> = (0..reqs).map(|_| builder.input(enf_width)).collect();
let admit = support_check(&mut builder, &enforcements, enf_width);
finish_single_membrane(builder, admit)
}
pub fn compile_conflict_membrane(
reqs: usize,
width: Width,
) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let present: Vec<NodeId> = (0..reqs).map(|_| builder.input(width)).collect();
let forbidden: Vec<NodeId> = (0..reqs).map(|_| builder.input(width)).collect();
let admit = conflict_check(&mut builder, &present, &forbidden, width);
finish_single_membrane(builder, admit)
}
pub fn compile_profile_drift_membrane(hash_width: Width) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let planned = builder.input(hash_width);
let live = builder.input(hash_width);
let same = profile_drift_check(&mut builder, planned, live);
finish_single_membrane(builder, same)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AdmissionShape {
pub requirements: usize,
pub budget_dims: usize,
pub budget_width: Width,
pub evidence_width: Width,
pub conflict_width: Width,
pub hash_width: Width,
pub schedule: ScheduleShape,
}
pub fn compile_admission(shape: &AdmissionShape) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let reqs = shape.requirements;
let dims = shape.budget_dims;
let planned = builder.input(shape.hash_width);
let live = builder.input(shape.hash_width);
let drift = profile_drift_check(&mut builder, planned, live);
let enf_width = enforcement_width();
let enforcements: Vec<NodeId> = (0..reqs).map(|_| builder.input(enf_width)).collect();
let support = support_check(&mut builder, &enforcements, enf_width);
let required: Vec<NodeId> = (0..reqs)
.map(|_| builder.input(shape.evidence_width))
.collect();
let available: Vec<NodeId> = (0..reqs)
.map(|_| builder.input(shape.evidence_width))
.collect();
let evidence = evidence_check(&mut builder, &required, &available);
let budget_limit: Vec<NodeId> = (0..dims)
.map(|_| builder.input(shape.budget_width))
.collect();
let budget_avail: Vec<NodeId> = (0..dims)
.map(|_| builder.input(shape.budget_width))
.collect();
let budget_derived: Vec<NodeId> = (0..dims)
.map(|_| builder.input(shape.budget_width))
.collect();
let budget_g_req: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let budget_g_avail: Vec<NodeId> = (0..dims).map(|_| builder.input(enf_width)).collect();
let budget_e_req: Vec<NodeId> = (0..dims)
.map(|_| builder.input(shape.evidence_width))
.collect();
let budget_e_avail: Vec<NodeId> = (0..dims)
.map(|_| builder.input(shape.evidence_width))
.collect();
let budget = budget_check(
&mut builder,
&BudgetLanes {
limit: &budget_limit,
available: &budget_avail,
derived_min: &budget_derived,
guarantee_req: &budget_g_req,
guarantee_avail: &budget_g_avail,
evidence_req: &budget_e_req,
evidence_avail: &budget_e_avail,
},
);
let present: Vec<NodeId> = (0..reqs)
.map(|_| builder.input(shape.conflict_width))
.collect();
let forbidden: Vec<NodeId> = (0..reqs)
.map(|_| builder.input(shape.conflict_width))
.collect();
let conflict = conflict_check(&mut builder, &present, &forbidden, shape.conflict_width);
let schedule = schedule_membrane_bit(&mut builder, &shape.schedule);
let membranes = [drift, support, evidence, budget, conflict, schedule];
let (admit, refusal_code) = compose_membranes(&mut builder, &membranes);
builder.finish(Outputs {
admit,
refusal_code,
membranes: membranes.to_vec(),
})
}
#[cfg(test)]
#[path = "compile_tests.rs"]
mod compile_tests;