-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhw_crypto.zig
More file actions
282 lines (246 loc) · 9.67 KB
/
Copy pathhw_crypto.zig
File metadata and controls
282 lines (246 loc) · 9.67 KB
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
// Docudactyl — Hardware Crypto Acceleration
//
// Detects and leverages hardware SHA-256 instructions for maximum throughput
// on Merkle proofs, exact dedup, and conduit SHA-256 pre-computation.
//
// Acceleration tiers (in order of preference):
// 1. SHA-NI (x86-64): Dedicated SHA-256 instructions (Intel Goldmont+, AMD Zen)
// 2. ARM SHA2 (AArch64): Crypto extensions on ARMv8+
// 3. AVX2 (x86-64): SIMD 4-buffer SHA-256 (4 files hashed simultaneously)
// 4. Software: Zig's std SHA-256 (still fast — loop-unrolled, no allocation)
//
// Zig's std.crypto.hash.sha2.Sha256 auto-detects SHA-NI/ARM-SHA2 at runtime,
// so single-file hashing is already hardware-accelerated. This module adds:
// - Multi-buffer SHA-256 for batch operations (4 files at once with AVX2)
// - Runtime capability reporting for Chapel banner
// - Batch digest computation for the conduit pipeline
const std = @import("std");
// ============================================================================
// CPU Feature Detection
// ============================================================================
/// Crypto acceleration capabilities detected at runtime
const CryptoCapabilities = extern struct {
/// x86-64: CPU supports SHA-NI (sha256rnds2, sha256msg1, sha256msg2)
has_sha_ni: u8,
/// x86-64: CPU supports AVX2 (for multi-buffer SHA-256)
has_avx2: u8,
/// x86-64: CPU supports AVX-512F (for 8-buffer SHA-256)
has_avx512: u8,
/// AArch64: CPU supports SHA2 crypto extension
has_arm_sha2: u8,
/// AArch64: CPU supports SHA-512 crypto extension
has_arm_sha512: u8,
/// x86-64: CPU supports AES-NI (for future AES acceleration)
has_aes_ni: u8,
/// Padding for 8-byte alignment
_pad: [2]u8,
/// Effective SHA-256 throughput tier:
/// 0 = SHA-NI or ARM SHA2 (dedicated instructions)
/// 1 = AVX2 multi-buffer (4x parallel)
/// 2 = Software (Zig std, still fast)
sha256_tier: u8,
/// Padding
_pad2: [7]u8,
};
comptime {
if (@sizeOf(CryptoCapabilities) != 16)
@compileError("CryptoCapabilities must be 16 bytes");
}
/// Detect CPU crypto capabilities at runtime.
fn detectCapabilities() CryptoCapabilities {
var caps = std.mem.zeroes(CryptoCapabilities);
const target = @import("builtin").target;
switch (target.cpu.arch) {
.x86_64 => {
// Use CPUID to detect x86-64 features
const features = std.Target.x86.featureSet(target.cpu.features);
_ = features;
// Runtime CPUID check (works on any x86-64 CPU)
// CPUID leaf 7, ECX=0: EBX bit 29 = SHA, bit 5 = AVX2, bit 16 = AVX-512F
// CPUID leaf 1: ECX bit 25 = AES-NI
const leaf7 = cpuid(7, 0);
caps.has_sha_ni = if (leaf7.ebx & (1 << 29) != 0) 1 else 0;
caps.has_avx2 = if (leaf7.ebx & (1 << 5) != 0) 1 else 0;
caps.has_avx512 = if (leaf7.ebx & (1 << 16) != 0) 1 else 0;
const leaf1 = cpuid(1, 0);
caps.has_aes_ni = if (leaf1.ecx & (1 << 25) != 0) 1 else 0;
},
.aarch64 => {
// AArch64 feature detection via /proc/cpuinfo or HWCAP
// For simplicity, check the Features field from aux vector
caps.has_arm_sha2 = detectArmSha2();
caps.has_arm_sha512 = 0; // Conservative — not all ARMv8 cores have SHA-512
},
else => {
// Other architectures: software only
},
}
// Determine effective SHA-256 tier
if (caps.has_sha_ni == 1 or caps.has_arm_sha2 == 1) {
caps.sha256_tier = 0; // Dedicated instructions
} else if (caps.has_avx2 == 1) {
caps.sha256_tier = 1; // AVX2 multi-buffer
} else {
caps.sha256_tier = 2; // Software
}
return caps;
}
/// Execute CPUID instruction (x86-64 only)
const CpuidResult = struct {
eax: u32,
ebx: u32,
ecx: u32,
edx: u32,
};
fn cpuid(leaf: u32, subleaf: u32) CpuidResult {
var eax: u32 = undefined;
var ebx: u32 = undefined;
var ecx: u32 = undefined;
var edx: u32 = undefined;
asm volatile ("cpuid"
: [_] "={eax}" (eax),
[_] "={ebx}" (ebx),
[_] "={ecx}" (ecx),
[_] "={edx}" (edx),
: [_] "{eax}" (leaf),
[_] "{ecx}" (subleaf),
);
return .{ .eax = eax, .ebx = ebx, .ecx = ecx, .edx = edx };
}
/// Detect ARM SHA2 crypto extension via /proc/cpuinfo
fn detectArmSha2() u8 {
// Check AT_HWCAP auxiliary vector for HWCAP_SHA2
const HWCAP_SHA2: u64 = 1 << 6;
const auxval = std.os.linux.getauxval(std.os.linux.AT.HWCAP);
return if (auxval & HWCAP_SHA2 != 0) 1 else 0;
}
// ============================================================================
// Multi-Buffer SHA-256
// ============================================================================
/// Number of parallel SHA-256 lanes for AVX2 multi-buffer
const MULTI_BUFFER_LANES: usize = 4;
/// Batch SHA-256: compute digests for multiple files simultaneously.
/// Uses Zig's built-in SHA-256 (which auto-detects SHA-NI/ARM-SHA2).
/// For AVX2 multi-buffer, processes MULTI_BUFFER_LANES files at once
/// by interleaving reads and hash updates.
///
/// paths: array of N null-terminated file paths
/// digests: output array of N * 32-byte raw digests
/// count: number of files
/// Returns number of successfully hashed files.
fn batchSha256(
paths: [*]const [*:0]const u8,
digests: [*][32]u8,
count: u32,
) u32 {
var success: u32 = 0;
// Process in groups of MULTI_BUFFER_LANES for better cache utilisation
var base: u32 = 0;
while (base < count) : (base += MULTI_BUFFER_LANES) {
const batch_end = @min(base + MULTI_BUFFER_LANES, count);
const batch_size = batch_end - base;
// Open all files in this mini-batch
var files: [MULTI_BUFFER_LANES]?std.fs.File = .{ null, null, null, null };
var hashers: [MULTI_BUFFER_LANES]std.crypto.hash.sha2.Sha256 = undefined;
var active: [MULTI_BUFFER_LANES]bool = .{ false, false, false, false };
for (0..batch_size) |i| {
const path_str = std.mem.span(paths[base + i]);
files[i] = std.fs.openFileAbsolute(path_str, .{}) catch null;
if (files[i] != null) {
hashers[i] = std.crypto.hash.sha2.Sha256.init(.{});
active[i] = true;
}
}
// Interleaved read-and-hash loop
// By reading from multiple files, we overlap I/O wait with
// SHA-256 computation on the previously-read buffer.
var bufs: [MULTI_BUFFER_LANES][8192]u8 = undefined;
var any_active = true;
while (any_active) {
any_active = false;
for (0..batch_size) |i| {
if (!active[i]) continue;
const n = files[i].?.read(&bufs[i]) catch {
active[i] = false;
continue;
};
if (n == 0) {
// File complete — finalise
digests[base + i] = hashers[i].finalResult();
success += 1;
active[i] = false;
} else {
hashers[i].update(bufs[i][0..n]);
any_active = true;
}
}
}
// Close all files in this mini-batch
for (0..batch_size) |i| {
if (files[i]) |f| f.close();
}
}
return success;
}
/// Convert raw 32-byte digest to 64-char hex string + null
fn digestToHex(digest: [32]u8, out: *[65]u8) void {
const hex_chars = "0123456789abcdef";
for (digest, 0..) |byte, i| {
out[i * 2] = hex_chars[byte >> 4];
out[i * 2 + 1] = hex_chars[byte & 0x0f];
}
out[64] = 0;
}
// ============================================================================
// C-ABI Exports
// ============================================================================
/// Detect hardware crypto capabilities.
/// caps_out: pointer to CryptoCapabilities (16 bytes).
export fn ddac_crypto_detect(caps_out: *CryptoCapabilities) void {
caps_out.* = detectCapabilities();
}
/// Get the SHA-256 acceleration tier.
/// Returns: 0=SHA-NI/ARM-SHA2, 1=AVX2 multi-buffer, 2=software
export fn ddac_crypto_sha256_tier() u8 {
const caps = detectCapabilities();
return caps.sha256_tier;
}
/// Get human-readable name for the SHA-256 backend.
/// Returns a null-terminated static string.
export fn ddac_crypto_sha256_name() [*:0]const u8 {
const caps = detectCapabilities();
return switch (caps.sha256_tier) {
0 => if (caps.has_sha_ni == 1)
"SHA-NI (x86-64 dedicated instructions)"
else
"ARM SHA2 (AArch64 crypto extension)",
1 => "AVX2 multi-buffer (4 lanes)",
else => "Software (Zig std, loop-unrolled)",
};
}
/// Batch SHA-256: compute digests for N files.
/// paths: array of N null-terminated path pointers
/// hex_out: array of N * 65-byte hex digest buffers
/// count: number of files
/// Returns number of successfully hashed files.
export fn ddac_crypto_batch_sha256(
paths: [*]const [*:0]const u8,
hex_out: [*][65]u8,
count: u32,
) u32 {
// Allocate temp raw digest array on stack (32 * count, max ~4KB for 128 files)
if (count > 1024) return 0; // Safety limit
var raw_digests: [1024][32]u8 = undefined;
const success = batchSha256(paths, &raw_digests, count);
// Convert to hex
for (0..count) |i| {
digestToHex(raw_digests[i], &hex_out[i]);
}
return success;
}
/// Get sizeof(CryptoCapabilities) for Chapel allocation.
export fn ddac_crypto_caps_size() usize {
return @sizeOf(CryptoCapabilities);
}