1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Find Equi-X solutions.
//!
//! This is a particular instance of Equihash, a tree-structured search for
//! partial hash collisions using temporary memory. Equi-X modifies it to use
//! sums instead of XOR, and chooses specific parameters.

use crate::bucket_array::{
    hash::Insert, hash::KeyValueBucketArray, mem::BucketArrayMemory, mem::Uninit,
};
use crate::collision::{self, PackedCollision};
use crate::solution::{self, HashValue, Solution, SolutionArray, SolutionItem, EQUIHASH_N};
use arrayvec::ArrayVec;
use hashx::HashX;

// The hash table bucket counts here are mostly constrained by the shape of
// the Equihash tree, but the bucket capacities are somewhat arbitrary. Larger
// buckets use more memory, smaller buckets may discard solutions if there are
// sufficient collisions during the earlier layers. This uses the same bucket
// counts as the original Equi-X implementation, in order to exactly match
// its solution discarding behavior and thus its output.

/// First layer hash table, needs room for the full set of [`HashValue`]s and
/// [`SolutionItem`]s. Key remainder is `(EQUIHASH_N - 8 == 52)` bits here.
type Layer0<'k, 'v> =
    KeyValueBucketArray<'k, 'v, 256, 336, u16, HashValue, HashValue, SolutionItem>;
/// Key backing storage for [`Layer0`]
type Layer0KeyMem = BucketArrayMemory<256, 336, HashValue>;
/// Value backing storage for [`Layer0`]
type Layer0ValueMem = BucketArrayMemory<256, 336, SolutionItem>;
/// Packed collision type for [`Layer0`]
type Layer0Collision = PackedCollision<u32, 8, 9>;

/// Next layer maps residual hash (3/4 full width == 45 bits) to packed
/// [`Layer0Collision`]. Key remainder is 37 bits after this.
type Layer1<'k, 'v> = KeyValueBucketArray<'k, 'v, 256, 336, u16, u64, u64, u32>;
/// Key backing storage for [`Layer1`]
type Layer1KeyMem = BucketArrayMemory<256, 336, u64>;
/// Value backing storage for [`Layer1`]
type Layer1ValueMem = BucketArrayMemory<256, 336, u32>;
/// Packed collision type for [`Layer1`]
type Layer1Collision = PackedCollision<u32, 8, 9>;

/// Final layer maps the (N/2 == 30 bits) residual hash sum to packed
/// [`Layer1Collision`]. Key remainder is 22 bits.
type Layer2<'k, 'v> = KeyValueBucketArray<'k, 'v, 256, 336, u16, u32, u32, u32>;
/// Key backing storage for [`Layer2`]
type Layer2KeyMem = BucketArrayMemory<256, 336, u32>;
/// Value backing storage for [`Layer2`]
type Layer2ValueMem = BucketArrayMemory<256, 336, u32>;

/// Temporary value hash for resolving collisions between buckets.
/// This removes 7 bits of key between each of the other layers.
/// Value type may hold a [`SolutionItem`] or a temporary item-in-bucket index.
type TempMem = BucketArrayMemory<128, 12, u16>;

/// Internal solver memory, inside the heap allocation
///
/// Contains exclusively [`std::mem::MaybeUninit`] members. Other than the
/// temporary memory used between each pair of tree layers, the memory regions
/// are organized by which layer of the solution tree they form starting
/// from leaf `(hash, index)` items and moving toward the root of the tree.
#[derive(Copy, Clone)]
struct SolverMemoryInner {
    /// Temporary memory for each [`collision::search()`]
    temp: TempMem,
    /// Memory overlay union, access controlled with mutable references
    overlay: Overlay,
    /// Temporary value memory for [`Layer0`]
    layer0_values: Layer0ValueMem,
    /// Temporary value memory for [`Layer1`]
    layer1_values: Layer1ValueMem,
    /// Temporary key storage memory for [`Layer1`]
    layer1_keys: Layer1KeyMem,
}

/// SAFETY: We are proimising that [`SolverMemoryInner`] is
///         made only from [`Uninit`] types like [`BucketArrayMemory`].
unsafe impl Uninit for SolverMemoryInner {}

/// Union of overlay layouts used in solver memory
///
/// As a memory optimization, some of the allocations in [`SolverMemoryInner`]
/// are overlapped. We only use one of these at a time, checked statically
/// using a mutable borrow.
#[derive(Copy, Clone)]
union Overlay {
    /// Initial memory overlay layout
    first: OverlayFirst,
    /// Layout which replaces the first once we can drop `layer0_keys`
    second: OverlaySecond,
}

impl Overlay {
    /// Access the first overlay layout, via a mutable reference
    fn first(&mut self) -> &mut OverlayFirst {
        // SAFETY: This union and its members implement [`Uninit`], promising
        //         that we can soundly create an instance out of uninitialized
        //         or reused memory. Initialized state is controlled via a
        //         mutable reference, and by reborrowing a union field we ensure
        //         exclusive access using the borrow checker.
        unsafe { &mut self.first }
    }

    /// Access the second overlay layout, via a mutable reference
    fn second(&mut self) -> &mut OverlaySecond {
        // SAFETY: As above, we implement [`Uninit`] and use &mut to control access
        unsafe { &mut self.second }
    }
}

/// First memory overlay, contains the key portion of [`Layer0`]
#[derive(Copy, Clone)]
struct OverlayFirst {
    /// Key remainder table for [`Layer0`], which we can drop
    /// after running [`collision::search()`] on that layer.
    layer0_keys: Layer0KeyMem,
}

/// Second overlay, with both parts of Layer2
#[derive(Copy, Clone)]
struct OverlaySecond {
    /// Temporary key storage memory for [`Layer2`]
    layer2_keys: Layer2KeyMem,
    /// Temporary value memory for [`Layer2`]
    layer2_values: Layer2ValueMem,
}

/// Search for solutions, iterating the entire [`SolutionItem`] space and using
/// temporary memory to locate partial sum collisions at each tree layer.
pub(crate) fn find_solutions(func: &HashX, mem: &mut SolverMemory, results: &mut SolutionArray) {
    // Use the first memory overlay layout.
    let overlay = mem.heap.overlay.first();

    // Enumerate all hash values into the first layer
    let mut layer0 = Layer0::new(&mut overlay.layer0_keys, &mut mem.heap.layer0_values);
    for item in SolutionItem::MIN..=SolutionItem::MAX {
        let hash = solution::item_hash(func, item);
        let _ = layer0.insert(hash, item);
    }

    // Now form the first layer of the Equihash tree,
    // with collisions in the low N/4 (15) bits
    let layer1_n = EQUIHASH_N / 4;
    let mut layer1 = Layer1::new(&mut mem.heap.layer1_keys, &mut mem.heap.layer1_values);
    collision::search(&layer0, &mut mem.heap.temp, layer1_n, |sum, loc| {
        let _ = layer1.insert(sum, Layer0Collision::pack(&loc).into_inner());
    });

    // Once we finish searching a layer for collisions,
    // we can drop the key data and make the rest immutable.
    // This drops the last mutable reference into the first overlay.
    let layer0 = layer0.drop_key_storage();

    // Now switch to the second memory overlay layout.
    let overlay = mem.heap.overlay.second();

    // Next Equihash layer, collisions in the low N/2 (30) bits
    let layer2_n = EQUIHASH_N / 2;
    let mut layer2 = Layer2::new(&mut overlay.layer2_keys, &mut overlay.layer2_values);
    collision::search(
        &layer1,
        &mut mem.heap.temp,
        layer2_n - layer1_n,
        |sum, loc| {
            let _ = layer2.insert(sum as u32, Layer1Collision::pack(&loc).into_inner());
        },
    );

    // Final layer, match the entire N bits and assemble complete solutions
    let layer3_n = EQUIHASH_N;
    collision::search(
        &layer2,
        &mut mem.heap.temp,
        layer3_n - layer2_n,
        |_sum, loc| {
            let mut items = ArrayVec::<SolutionItem, { Solution::NUM_ITEMS }>::new();

            // Walk the binary tree of collision locations, in order.
            // The leaf layer will have our SolutionItems.
            loc.pair(&layer2).map(|loc| {
                Layer1Collision::new(loc).unpack().pair(&layer1).map(|loc| {
                    Layer0Collision::new(loc)
                        .unpack()
                        .pair(&layer0)
                        .map(|item| items.push(item));
                });
            });

            // Apply the ordering constraints and check for duplicates
            let solution = Solution::sort_from_array(
                items
                    .into_inner()
                    .expect("always collected a full SolutionItem tree"),
            );
            if results.last() != Some(&solution) {
                let _ = results.try_push(solution);
            }
        },
    );
}

/// Temporary memory used by the Equi-X solver
///
/// This space is needed temporarily during a solver run. It will be
/// allocated on the heap by [`SolverMemory::new()`], and the solver
/// provides a [`crate::EquiX::solve_with_memory()`] interface for reusing
/// this memory between runs.
pub struct SolverMemory {
    /// Inner heap allocation which holds the actual solver memory
    heap: Box<SolverMemoryInner>,
}

impl SolverMemory {
    /// Size of the solver memory region, in bytes
    pub const SIZE: usize = std::mem::size_of::<SolverMemoryInner>();

    /// New uninitialized memory, usable as solver temporary space.
    pub fn new() -> Self {
        Self {
            heap: SolverMemoryInner::alloc(),
        }
    }
}

impl Default for SolverMemory {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn solver_memory_size() {
        // Regression test for memory usage. Our actual memory size is very
        // similar to the original C implementation, the only difference that
        // our bucket counters are stored outside this structure.
        let size = super::SolverMemory::SIZE;
        assert_eq!(size, 1_895_424);
    }
}