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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
use std::process::{Command, Stdio};
use thiserror::Error;
use crate::AsmService;
/// Enum representing various errors that can occur during the execution of the assembly runner, including semaphore errors, thread pool errors, child process errors, and unexpected conditions.
#[derive(Debug, Error)]
pub enum AsmRunError {
/// Errors related to semaphore creation and synchronization.
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[error("Failed to create semaphore '{0}': {1}")]
SemaphoreError(String, #[source] named_sem::Error),
/// Errors related to thread pool creation for parallel execution.
#[error("Thread pool creation failed")]
ThreadPoolError(#[from] rayon::ThreadPoolBuildError),
/// Errors related to waiting on a semaphore.
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[error("Semaphore wait failed: {0}")]
SemaphoreWaitError(#[from] std::io::Error),
/// Errors related to child process execution, including non-zero exit codes.
#[error("Child process exited with code: {0}")]
ExitCode(u32),
/// Errors related to joining the thread that runs the child process.
#[error("Thread join failed")]
JoinPanic,
/// Errors returned by the child service process, encapsulated as `anyhow::Error` for context.
#[error("Child service returned error: {0}")]
ServiceError(#[source] anyhow::Error),
/// Errors related to unexpected conditions, such as unwrapping an `Arc` that has been dropped.
#[error("Arc unwrap failed")]
ArcUnwrap,
}
/// Enum representing the level of tracing to be performed during assembly execution, with options for no tracing, basic tracing, and extended tracing.
#[derive(Debug, Clone)]
pub enum AsmRunnerTraceLevel {
/// No tracing will be performed.
None,
/// Basic tracing will be performed, capturing essential execution information.
Trace,
/// Extended tracing will be performed, capturing detailed execution information for in-depth analysis.
ExtendedTrace,
}
/// This struct represents the assembly runner options, allowing configuration of logging, metrics, verbosity, trace level, and other execution parameters. It provides a builder pattern for easy configuration and a method to apply these options to a command-line `Command` that will execute the assembly code.
#[derive(Debug, Clone)]
pub struct AsmRunnerOptions {
/// Enables or disables logging output from the assembly runner.
pub log_output: bool,
/// Enables or disables metrics collection during assembly execution.
pub metrics: bool,
/// Enables or disables verbose output for debugging purposes.
pub verbose: bool,
/// Specifies the level of tracing to be performed during assembly execution.
pub trace_level: AsmRunnerTraceLevel,
/// Enables or disables Keccak-specific tracing, which may provide additional insights for certain workloads.
pub keccak_trace: bool,
/// The local rank of the process, used for distinguishing between multiple instances in a distributed setup.
pub local_rank: i32,
/// Enables or disables unlocking of mapped memory after use, which can be important for certain performance optimizations or resource management strategies.
pub unlock_mapped_memory: bool,
/// Enables or disables redirecting assembly output to a file, which can be useful for debugging or record-keeping.
pub asm_out_file: bool,
}
impl Default for AsmRunnerOptions {
fn default() -> Self {
Self::new()
}
}
impl AsmRunnerOptions {
/// Creates a new builder with default values.
pub fn new() -> Self {
Self {
log_output: false,
metrics: false,
verbose: false,
trace_level: AsmRunnerTraceLevel::None,
keccak_trace: false,
local_rank: 0,
unlock_mapped_memory: false,
asm_out_file: false,
}
}
/// Enables or disables logging output.
pub fn with_log_output(mut self, value: bool) -> Self {
self.log_output = value;
self
}
/// Enables or disables metrics collection.
pub fn with_metrics(mut self, value: bool) -> Self {
self.metrics = value;
self
}
/// Enables or disables verbose output.
pub fn with_verbose(mut self, value: bool) -> Self {
self.verbose = value;
self
}
/// Sets the desired trace level.
pub fn with_trace_level(mut self, level: AsmRunnerTraceLevel) -> Self {
self.trace_level = level;
self
}
/// Enables or disables Keccak-specific tracing.
pub fn keccak_trace(mut self, value: bool) -> Self {
self.keccak_trace = value;
self
}
/// Sets the local rank of the process.
pub fn with_local_rank(mut self, rank: i32) -> Self {
self.local_rank = rank;
self
}
/// Enables or disables unlocking of mapped memory after use.
pub fn with_unlock_mapped_memory(mut self, value: bool) -> Self {
self.unlock_mapped_memory = value;
self
}
/// Enables or disables redirecting assembly output to a file.
pub fn with_asm_out_file(mut self, value: bool) -> Self {
self.asm_out_file = value;
self
}
/// Applies the configuration flags to a command-line `Command`.
///
/// # Arguments
/// * `command` - A mutable reference to the `Command` to be modified.
pub fn apply_to_command(
&self,
command: &mut Command,
asm_service: &AsmService,
shm_prefix: &str,
sem_prefix: &str,
) {
// Execute in server mode
command.arg("-s");
command.arg(format!("--gen={}", asm_service.gen_index()));
command.arg("--stdio");
command.arg("--open_all_shm");
command.arg("--share_input_shm");
if self.unlock_mapped_memory {
command.arg("-u");
}
if self.asm_out_file {
command.arg("--redirect-output-to-file");
}
command.arg("--shm_prefix").arg(shm_prefix);
command.arg("--sem_prefix").arg(sem_prefix);
if self.log_output {
command.arg("-o");
}
if self.metrics {
command.arg("-m");
}
if self.verbose {
command.arg("-v");
}
command.stderr(if self.verbose { Stdio::inherit() } else { Stdio::null() });
match self.trace_level {
AsmRunnerTraceLevel::None => {}
AsmRunnerTraceLevel::Trace => {
command.arg("-t");
}
AsmRunnerTraceLevel::ExtendedTrace => {
command.arg("-tt");
}
}
if self.keccak_trace {
command.arg("-k");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AsmService;
#[test]
fn options_default_to_all_off() {
let o = AsmRunnerOptions::new();
assert!(!o.log_output);
assert!(!o.metrics);
assert!(!o.verbose);
assert!(!o.keccak_trace);
assert!(!o.unlock_mapped_memory);
assert!(!o.asm_out_file);
assert_eq!(o.local_rank, 0);
assert!(matches!(o.trace_level, AsmRunnerTraceLevel::None));
// Default must equal `new()`.
let d = AsmRunnerOptions::default();
assert_eq!(d.verbose, o.verbose);
assert_eq!(d.local_rank, o.local_rank);
}
#[test]
fn builder_sets_each_field() {
let o = AsmRunnerOptions::new()
.with_verbose(true)
.with_metrics(true)
.with_log_output(true)
.with_local_rank(3)
.with_unlock_mapped_memory(true)
.with_asm_out_file(true)
.keccak_trace(true)
.with_trace_level(AsmRunnerTraceLevel::ExtendedTrace);
assert!(o.verbose && o.metrics && o.log_output);
assert!(o.unlock_mapped_memory && o.asm_out_file && o.keccak_trace);
assert_eq!(o.local_rank, 3);
assert!(matches!(o.trace_level, AsmRunnerTraceLevel::ExtendedTrace));
}
fn applied_args(o: &AsmRunnerOptions, svc: AsmService) -> Vec<String> {
let mut cmd = Command::new("ziskemuasm");
o.apply_to_command(&mut cmd, &svc, "ZISK_1_0", "ZISK_1_h_0");
cmd.get_args().map(|a| a.to_string_lossy().into_owned()).collect()
}
#[test]
fn apply_to_command_emits_the_mandatory_flags() {
let args = applied_args(&AsmRunnerOptions::new(), AsmService::MO);
for expected in ["-s", "--gen=7", "--stdio", "--open_all_shm", "--share_input_shm"] {
assert!(args.iter().any(|a| a == expected), "missing {expected} in {args:?}");
}
// prefixes are passed as flag + value pairs
let i = args.iter().position(|a| a == "--shm_prefix").expect("--shm_prefix");
assert_eq!(args[i + 1], "ZISK_1_0");
let j = args.iter().position(|a| a == "--sem_prefix").expect("--sem_prefix");
assert_eq!(args[j + 1], "ZISK_1_h_0");
// gen index is per-service
assert!(
applied_args(&AsmRunnerOptions::new(), AsmService::MT).contains(&"--gen=1".to_string())
);
assert!(
applied_args(&AsmRunnerOptions::new(), AsmService::RH).contains(&"--gen=2".to_string())
);
}
#[test]
fn apply_to_command_reflects_optional_flags() {
let off = applied_args(&AsmRunnerOptions::new(), AsmService::MO);
assert!(!off.iter().any(|a| a == "-v"
|| a == "-m"
|| a == "-o"
|| a == "-t"
|| a == "-tt"
|| a == "-k"));
let on = applied_args(
&AsmRunnerOptions::new()
.with_verbose(true)
.with_metrics(true)
.with_log_output(true)
.keccak_trace(true)
.with_trace_level(AsmRunnerTraceLevel::ExtendedTrace),
AsmService::MO,
);
for expected in ["-v", "-m", "-o", "-tt", "-k"] {
assert!(on.iter().any(|a| a == expected), "missing {expected} in {on:?}");
}
// Trace (not ExtendedTrace) emits "-t", not "-tt".
let t = applied_args(
&AsmRunnerOptions::new().with_trace_level(AsmRunnerTraceLevel::Trace),
AsmService::MO,
);
assert!(t.contains(&"-t".to_string()) && !t.contains(&"-tt".to_string()));
}
}