Skip to main content

binius_core/constraint_system/
value_index.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
4use bytes::{Buf, BufMut};
5
6/// The section of the [`ValueVec`](super::ValueVec) a [`ValueIndex`] names.
7///
8/// The sections partition every word a circuit allocates. The first three hold the values a
9/// [`ConstraintSystem`](super::ConstraintSystem) may reference; [`Self::Scratch`] holds the
10/// uncommitted temporaries that only exist while a circuit is evaluated.
11///
12/// The discriminants are the two-bit tag [`ValueIndex`] packs, and their order is the order the
13/// sections occupy in the value vector.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(u8)]
16pub enum ValueSegment {
17	/// The constants the circuit declares, known to both prover and verifier.
18	Constant = 0,
19	/// The input/output values, which are public but chosen per instance.
20	InOut = 1,
21	/// The values only the prover knows: the declared witness and the values the gates create.
22	Private = 2,
23	/// The uncommitted temporaries, live only while a circuit is evaluated.
24	///
25	/// These words are not committed and no constraint may reference them, so a
26	/// [`ValueIndex`] in this segment is meaningful only in the circuit's wire mapping and its
27	/// evaluation form. [`ConstraintSystem::validate`](super::ConstraintSystem::validate) rejects
28	/// any operand term that names it.
29	Scratch = 3,
30}
31
32impl ValueSegment {
33	/// The four segments, in value-vector order.
34	pub const ALL: [ValueSegment; 4] = [
35		ValueSegment::Constant,
36		ValueSegment::InOut,
37		ValueSegment::Private,
38		ValueSegment::Scratch,
39	];
40
41	/// Whether a [`ConstraintSystem`](super::ConstraintSystem) operand may reference this segment.
42	pub const fn is_referenceable(self) -> bool {
43		!matches!(self, ValueSegment::Scratch)
44	}
45
46	/// The segment a two-bit tag encodes.
47	const fn from_tag(tag: u32) -> Self {
48		match tag {
49			0 => ValueSegment::Constant,
50			1 => ValueSegment::InOut,
51			2 => ValueSegment::Private,
52			3 => ValueSegment::Scratch,
53			_ => panic!("tag is masked to two bits"),
54		}
55	}
56}
57
58/// A type safe reference to one word of the [`ValueVec`](super::ValueVec), as a segment and an
59/// index within it.
60///
61/// # Representation
62///
63/// The pair is packed into a single `u32`: the [`ValueSegment`] in the top two bits and the
64/// index in the bottom [`Self::INDEX_BITS`]. Constraint systems hold millions of these, so the
65/// packing keeps a [`ShiftedValueIndex`](super::ShiftedValueIndex) at eight bytes rather than
66/// twelve.
67///
68/// The packing also makes the derived [`Ord`] order the words by segment and then by index, which
69/// is the order they occupy in the value vector.
70#[repr(transparent)]
71#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72pub struct ValueIndex(u32);
73
74impl ValueIndex {
75	/// The number of bits the index occupies, the segment tag taking the remaining two.
76	pub const INDEX_BITS: u32 = 30;
77
78	/// The number of values one segment can hold.
79	pub const SEGMENT_CAPACITY: u32 = 1 << Self::INDEX_BITS;
80
81	/// The bits of the packed word holding the index.
82	const INDEX_MASK: u32 = Self::SEGMENT_CAPACITY - 1;
83
84	/// Creates an index naming the given word of the given segment.
85	///
86	/// # Panics
87	///
88	/// Panics if the index is not below [`Self::SEGMENT_CAPACITY`].
89	pub const fn new(segment: ValueSegment, index: u32) -> Self {
90		assert!(index < Self::SEGMENT_CAPACITY, "value index out of range");
91		Self(((segment as u32) << Self::INDEX_BITS) | index)
92	}
93
94	/// Creates an index naming a constant.
95	pub const fn constant(index: u32) -> Self {
96		Self::new(ValueSegment::Constant, index)
97	}
98
99	/// Creates an index naming an inout value.
100	pub const fn inout(index: u32) -> Self {
101		Self::new(ValueSegment::InOut, index)
102	}
103
104	/// Creates an index naming a private value.
105	pub const fn private(index: u32) -> Self {
106		Self::new(ValueSegment::Private, index)
107	}
108
109	/// Creates an index naming a scratch word.
110	pub const fn scratch(index: u32) -> Self {
111		Self::new(ValueSegment::Scratch, index)
112	}
113
114	/// The segment this index names.
115	pub const fn segment(self) -> ValueSegment {
116		ValueSegment::from_tag(self.0 >> Self::INDEX_BITS)
117	}
118
119	/// The index within [`Self::segment`].
120	pub const fn index(self) -> u32 {
121		self.0 & Self::INDEX_MASK
122	}
123}
124
125/// Prints the segment and index rather than the packed word, which reads as a nonsense integer.
126impl std::fmt::Debug for ValueIndex {
127	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128		write!(f, "ValueIndex({:?}, {})", self.segment(), self.index())
129	}
130}
131
132impl SerializeBytes for ValueIndex {
133	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
134		self.0.serialize(write_buf)
135	}
136}
137
138impl DeserializeBytes for ValueIndex {
139	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
140	where
141		Self: Sized,
142	{
143		Ok(ValueIndex(u32::deserialize(read_buf)?))
144	}
145}
146
147#[cfg(test)]
148mod tests {
149	use super::*;
150
151	#[test]
152	fn round_trips_every_segment_through_the_packing() {
153		for segment in ValueSegment::ALL {
154			for index in [0, 1, 12345, ValueIndex::SEGMENT_CAPACITY - 1] {
155				let value_index = ValueIndex::new(segment, index);
156				assert_eq!(value_index.segment(), segment);
157				assert_eq!(value_index.index(), index);
158			}
159		}
160	}
161
162	#[test]
163	fn orders_words_by_segment_then_index() {
164		// The value vector holds the segments in this order, so the packed order must match.
165		let ascending = [
166			ValueIndex::constant(0),
167			ValueIndex::constant(1),
168			ValueIndex::inout(0),
169			ValueIndex::private(0),
170			ValueIndex::private(7),
171			ValueIndex::scratch(0),
172		];
173		assert!(ascending.is_sorted());
174	}
175
176	#[test]
177	#[should_panic(expected = "value index out of range")]
178	fn rejects_an_index_past_the_segment_capacity() {
179		ValueIndex::scratch(ValueIndex::SEGMENT_CAPACITY);
180	}
181
182	#[test]
183	fn only_scratch_is_unreferenceable() {
184		for segment in ValueSegment::ALL {
185			assert_eq!(segment.is_referenceable(), segment != ValueSegment::Scratch);
186		}
187	}
188
189	#[test]
190	fn test_value_index_serialization_round_trip() {
191		let value_index = ValueIndex::private(12345);
192
193		let mut buf = Vec::new();
194		value_index.serialize(&mut buf).unwrap();
195
196		let deserialized = ValueIndex::deserialize(&mut buf.as_slice()).unwrap();
197
198		assert_eq!(value_index, deserialized);
199	}
200}