pub fn trisha_available() -> bool {
std::process::Command::new("trisha")
.arg("--help")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
}
#[allow(dead_code)]
pub struct TrishaResult {
pub output: Vec<u64>,
pub cycle_count: u64,
pub elapsed_ms: f64,
}
pub struct Harness {
pub tasm: String,
pub n_funcs: usize,
pub read_io_count: usize,
pub divine_count: usize,
pub merkle_count: usize,
}
pub fn generate_test_harness(tasm: &str) -> Harness {
let clean_lines: Vec<&str> = tasm
.lines()
.filter(|l| {
let t = l.trim();
!t.starts_with("//") && !t.starts_with("call @")
})
.collect();
let clean = clean_lines.join("\n");
let mut func_labels: Vec<&str> = Vec::new();
let lines: Vec<&str> = clean.lines().collect();
let mut i = 0;
while i < lines.len() {
let t = lines[i].trim();
if t.ends_with(':') && !t.is_empty() {
func_labels.push(t.trim_end_matches(':'));
}
i += 1;
}
let mut divine_count: usize = 0;
let mut merkle_count: usize = 0;
for line in clean.lines() {
let t = line.trim();
if let Some(rest) = t.strip_prefix("divine ") {
if let Ok(n) = rest.trim().parse::<usize>() {
divine_count += n;
} else {
divine_count += 1;
}
} else if t == "divine" {
divine_count += 1;
} else if t == "merkle_step" {
merkle_count += 1;
}
}
let mut body = String::with_capacity(clean.len());
for line in clean.lines() {
let t = line.trim();
if t == "assert" {
body.push_str(" pop 1\n");
} else if t == "assert_vector" {
body.push_str(" pop 5\n");
} else if t == "merkle_step" || t == "merkle_step_mem" {
body.push_str(" nop\n");
} else if t == "recurse" {
body.push_str(" return\n");
} else if let Some(rest) = t.strip_prefix("read_io ") {
let n: usize = rest.trim().parse().unwrap_or(1);
for _ in 0..n {
body.push_str(" push 0\n");
}
} else if t == "read_io" {
body.push_str(" push 0\n");
} else {
body.push_str(line);
body.push('\n');
}
}
let n_funcs = func_labels.len();
let mut harness = String::with_capacity(body.len() + n_funcs * 200);
for label in &func_labels {
for _ in 0..16 {
harness.push_str(" push 0\n");
}
harness.push_str(&format!(" call {}\n", label));
}
harness.push_str(" halt\n");
harness.push_str(&body);
Harness {
tasm: harness,
n_funcs,
read_io_count: 0, divine_count,
merkle_count,
}
}
pub fn run_trisha(args: &[&str]) -> Result<TrishaResult, String> {
let start = std::time::Instant::now();
let result = std::process::Command::new("trisha")
.args(args)
.output()
.map_err(|e| format!("failed to run trisha: {}", e))?;
let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
let err_msg: String = stderr
.lines()
.filter(|l| !l.starts_with("GPU:") && !l.starts_with("Backend:"))
.collect::<Vec<_>>()
.join("\n");
return Err(err_msg.trim().to_string());
}
let stdout = String::from_utf8_lossy(&result.stdout);
let output: Vec<u64> = stdout
.lines()
.filter_map(|l| l.trim().parse().ok())
.collect();
let stderr = String::from_utf8_lossy(&result.stderr);
let cycle_count = stderr
.lines()
.find_map(|l| {
if l.contains("cycles") {
l.split_whitespace().find_map(|w| w.parse::<u64>().ok())
} else {
None
}
})
.unwrap_or(0);
Ok(TrishaResult {
output,
cycle_count,
elapsed_ms,
})
}
pub fn trisha_args_with_inputs(base_args: &[&str], harness: &Harness) -> Vec<String> {
let mut args: Vec<String> = base_args.iter().map(|s| s.to_string()).collect();
let multiplier = (harness.n_funcs * harness.n_funcs).max(1);
if harness.read_io_count > 0 {
args.push("--input-values".into());
let n = harness.read_io_count * multiplier;
let vals: Vec<String> = vec!["0".into(); n];
args.push(vals.join(","));
}
if harness.divine_count > 0 {
args.push("--secret".into());
let n = harness.divine_count * multiplier;
let vals: Vec<String> = vec!["0".into(); n];
args.push(vals.join(","));
}
if harness.merkle_count > 0 {
args.push("--digests".into());
let n = harness.merkle_count * multiplier * 5;
let vals: Vec<String> = vec!["0".into(); n];
args.push(vals.join(","));
}
args
}
pub fn generate_program_harness(
tasm: &str,
input_values: &[u64],
divine_values: &[u64],
) -> Harness {
let mut result = String::with_capacity(tasm.len());
let mut input_idx: usize = 0;
let mut divine_idx: usize = 0;
let mut divine_count: usize = 0;
let mut merkle_count: usize = 0;
let inline_divine = !divine_values.is_empty();
for line in tasm.lines() {
let t = line.trim();
if t.starts_with("//") {
continue;
}
if t == "assert" {
result.push_str(" pop 1\n");
} else if t == "assert_vector" {
result.push_str(" pop 5\n");
} else if t == "merkle_step" || t == "merkle_step_mem" {
result.push_str(" nop\n");
merkle_count += 1;
} else if t == "divine" {
if inline_divine {
let val = if divine_idx < divine_values.len() {
divine_values[divine_idx]
} else {
0
};
result.push_str(&format!(" push {}\n", val));
divine_idx += 1;
} else {
result.push_str(" divine 1\n");
divine_count += 1;
}
} else if let Some(rest) = t.strip_prefix("divine ") {
let n: usize = rest.trim().parse().unwrap_or(1);
if inline_divine {
for _ in 0..n {
let val = if divine_idx < divine_values.len() {
divine_values[divine_idx]
} else {
0
};
result.push_str(&format!(" push {}\n", val));
divine_idx += 1;
}
} else {
result.push_str(line);
result.push('\n');
divine_count += n;
}
} else if let Some(rest) = t.strip_prefix("read_io ") {
let n: usize = rest.trim().parse().unwrap_or(1);
for _ in 0..n {
let val = if input_idx < input_values.len() {
input_values[input_idx]
} else {
0
};
result.push_str(&format!(" push {}\n", val));
input_idx += 1;
}
} else if t == "read_io" {
let val = if input_idx < input_values.len() {
input_values[input_idx]
} else {
0
};
result.push_str(&format!(" push {}\n", val));
input_idx += 1;
} else {
result.push_str(line);
result.push('\n');
}
}
Harness {
tasm: result,
n_funcs: 1,
read_io_count: 0,
divine_count: if inline_divine { 0 } else { divine_count * 4096 },
merkle_count,
}
}
pub fn run_trisha_with_inputs(
base_args: &[&str],
harness: &Harness,
) -> Result<TrishaResult, String> {
let args = trisha_args_with_inputs(base_args, harness);
let str_args: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
run_trisha(&str_args)
}