1use std::path::Path;
2
3use crate::diagnostic::Diagnostic;
4use crate::span::Span;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum Arch {
9 Stack,
11 Register,
13 Tree,
15}
16
17#[derive(Clone, Debug)]
19pub struct EmulatedField {
20 pub name: String,
22 pub bits: u32,
24 pub limbs: u32,
26}
27
28#[derive(Clone, Debug)]
34pub struct WarriorConfig {
35 pub name: String,
37 pub crate_name: String,
39 pub runner: bool,
41 pub prover: bool,
43}
44
45#[derive(Clone, Debug)]
50pub struct TerrainConfig {
51 pub name: String,
53 pub display_name: String,
55 pub architecture: Arch,
57 pub field_prime: String,
59 pub field_bits: u32,
61 pub field_limbs: u32,
63 pub emulated_fields: Vec<EmulatedField>,
65 pub stack_depth: u32,
67 pub spill_ram_base: u64,
69 pub digest_width: u32,
71 pub xfield_width: u32,
73 pub hash_rate: u32,
75 pub output_extension: String,
77 pub cost_tables: Vec<String>,
79 pub warrior: Option<WarriorConfig>,
81}
82
83impl TerrainConfig {
84 pub fn triton() -> Self {
86 Self {
87 name: "triton".to_string(),
88 display_name: "Triton VM".to_string(),
89 architecture: Arch::Stack,
90 field_prime: "2^64 - 2^32 + 1".to_string(),
91 field_bits: 64,
92 field_limbs: 2,
93 emulated_fields: Vec::new(),
94 stack_depth: 16,
95 spill_ram_base: 1 << 30,
96 digest_width: 5,
97 xfield_width: 3,
98 hash_rate: 10,
99 output_extension: ".tasm".to_string(),
100 cost_tables: vec![
101 "processor".to_string(),
102 "hash".to_string(),
103 "u32".to_string(),
104 "op_stack".to_string(),
105 "ram".to_string(),
106 "jump_stack".to_string(),
107 ],
108 warrior: Some(WarriorConfig {
109 name: "trisha".to_string(),
110 crate_name: "trident-trisha".to_string(),
111 runner: true,
112 prover: true,
113 }),
114 }
115 }
116
117 pub fn load(path: &Path) -> Result<Self, Diagnostic> {
119 let content = std::fs::read_to_string(path).map_err(|e| {
120 Diagnostic::error(
121 format!("cannot read target config '{}': {}", path.display(), e),
122 Span::dummy(),
123 )
124 })?;
125 Self::parse_toml(&content, path)
126 }
127
128 pub fn resolve(name: &str) -> Result<Self, Diagnostic> {
131 if name.contains('/') || name.contains('\\') || name.contains("..") || name.starts_with('.')
133 {
134 return Err(Diagnostic::error(
135 format!("invalid target name '{}'", name),
136 Span::dummy(),
137 ));
138 }
139
140 if name == "triton" {
142 return Ok(Self::triton());
143 }
144
145 let primary = format!("vm/{}/target.toml", name);
147 let fallback = format!("vm/{}.toml", name);
148
149 if let Ok(exe) = std::env::current_exe() {
151 if let Some(dir) = exe.parent() {
152 for ancestor in &[
153 Some(dir.to_path_buf()),
154 dir.parent().map(|p| p.to_path_buf()),
155 dir.parent()
156 .and_then(|p| p.parent())
157 .map(|p| p.to_path_buf()),
158 ] {
159 if let Some(base) = ancestor {
160 let path = base.join(&primary);
161 if path.exists() {
162 return Self::load(&path);
163 }
164 let path = base.join(&fallback);
165 if path.exists() {
166 return Self::load(&path);
167 }
168 }
169 }
170 }
171 }
172
173 let cwd_path = std::path::PathBuf::from(&primary);
175 if cwd_path.exists() {
176 return Self::load(&cwd_path);
177 }
178 let cwd_path = std::path::PathBuf::from(&fallback);
179 if cwd_path.exists() {
180 return Self::load(&cwd_path);
181 }
182
183 Err(Diagnostic::error(
184 format!("unknown target '{}' (looked for '{}')", name, primary),
185 Span::dummy(),
186 )
187 .with_help("available targets: triton, miden, openvm, sp1, cairo, nock".to_string()))
188 }
189
190 fn parse_toml(content: &str, path: &Path) -> Result<Self, Diagnostic> {
191 let err =
192 |msg: String| Diagnostic::error(format!("{}: {}", path.display(), msg), Span::dummy());
193
194 let mut name = String::new();
195 let mut display_name = String::new();
196 let mut architecture = String::new();
197 let mut output_extension = String::new();
198 let mut field_prime = String::new();
199 let mut field_bits: u32 = 0;
200 let mut field_limbs: u32 = 0;
201 let mut emulated_fields: Vec<EmulatedField> = Vec::new();
202 let mut stack_depth: u32 = 0;
203 let mut spill_ram_base: u64 = 0;
204 let mut digest_width: u32 = 0;
205 let mut hash_rate: u32 = 0;
206 let mut xfield_degree: u32 = 0;
207 let mut cost_tables: Vec<String> = Vec::new();
208 let mut warrior_name = String::new();
209 let mut warrior_crate = String::new();
210 let mut warrior_runner = false;
211 let mut warrior_prover = false;
212
213 let mut section = String::new();
214
215 for line in content.lines() {
216 let trimmed = line.trim();
217 if trimmed.is_empty() || trimmed.starts_with('#') {
218 continue;
219 }
220 if trimmed.starts_with('[') && trimmed.ends_with(']') {
221 section = trimmed[1..trimmed.len() - 1].trim().to_string();
222 continue;
223 }
224 if let Some((key, value)) = trimmed.split_once('=') {
225 let key = key.trim();
226 let value = value.trim();
227 let unquoted = value.trim_matches('"');
228
229 match (section.as_str(), key) {
230 ("target", "name") => name = unquoted.to_string(),
231 ("target", "display_name") => display_name = unquoted.to_string(),
232 ("target", "architecture") => architecture = unquoted.to_string(),
233 ("target", "output_extension") => output_extension = unquoted.to_string(),
234 ("field", "prime") => field_prime = unquoted.to_string(),
235 ("field", "bits") => {
236 field_bits = value
237 .parse()
238 .map_err(|_| err(format!("invalid field.bits: {}", value)))?;
239 }
240 ("field", "limbs") => {
241 field_limbs = value
242 .parse()
243 .map_err(|_| err(format!("invalid field.limbs: {}", value)))?;
244 }
245 ("stack", "depth") => {
246 stack_depth = value
247 .parse()
248 .map_err(|_| err(format!("invalid stack.depth: {}", value)))?;
249 }
250 ("stack", "spill_ram_base") => {
251 spill_ram_base = value
252 .parse()
253 .map_err(|_| err(format!("invalid stack.spill_ram_base: {}", value)))?;
254 }
255 ("hash", "digest_width") => {
256 digest_width = value
257 .parse()
258 .map_err(|_| err(format!("invalid hash.digest_width: {}", value)))?;
259 }
260 ("hash", "rate") => {
261 hash_rate = value
262 .parse()
263 .map_err(|_| err(format!("invalid hash.rate: {}", value)))?;
264 }
265 ("extension_field", "degree") => {
266 xfield_degree = value.parse().map_err(|_| {
267 err(format!("invalid extension_field.degree: {}", value))
268 })?;
269 }
270 ("cost", "tables") => {
271 cost_tables = parse_string_array(value);
272 }
273 ("warrior", "name") => warrior_name = unquoted.to_string(),
274 ("warrior", "crate") => warrior_crate = unquoted.to_string(),
275 ("warrior", "runner") => warrior_runner = value == "true",
276 ("warrior", "prover") => warrior_prover = value == "true",
277 _ => {
278 if section.starts_with("emulated_field.") {
280 let ef_name = section
281 .strip_prefix("emulated_field.")
282 .expect("guarded by starts_with check");
283 let entry = emulated_fields.iter_mut().find(|ef| ef.name == ef_name);
285 let entry = if let Some(e) = entry {
286 e
287 } else {
288 emulated_fields.push(EmulatedField {
289 name: ef_name.to_string(),
290 bits: 0,
291 limbs: 0,
292 });
293 emulated_fields.last_mut().expect("just pushed")
294 };
295 match key {
296 "bits" => {
297 entry.bits = value.parse().map_err(|_| {
298 err(format!(
299 "invalid emulated_field.{}.bits: {}",
300 ef_name, value
301 ))
302 })?;
303 }
304 "limbs" => {
305 entry.limbs = value.parse().map_err(|_| {
306 err(format!(
307 "invalid emulated_field.{}.limbs: {}",
308 ef_name, value
309 ))
310 })?;
311 }
312 _ => {}
313 }
314 }
315 }
316 }
317 }
318 }
319
320 if name.is_empty() {
321 return Err(err("missing target.name".to_string()));
322 }
323 if stack_depth == 0 {
324 return Err(err("stack.depth must be > 0".to_string()));
325 }
326 if digest_width == 0 {
327 return Err(err("hash.digest_width must be > 0".to_string()));
328 }
329 if hash_rate == 0 {
330 return Err(err("hash.rate must be > 0".to_string()));
331 }
332 if field_bits == 0 {
333 return Err(err("field.bits must be > 0".to_string()));
334 }
335 if field_limbs == 0 {
336 return Err(err("field.limbs must be > 0".to_string()));
337 }
338
339 let arch = match architecture.as_str() {
340 "stack" => Arch::Stack,
341 "register" => Arch::Register,
342 "tree" => Arch::Tree,
343 other => {
344 return Err(err(format!(
345 "unknown architecture '{}' (expected 'stack', 'register', or 'tree')",
346 other
347 )))
348 }
349 };
350
351 Ok(Self {
352 name,
353 display_name,
354 architecture: arch,
355 field_prime,
356 field_bits,
357 field_limbs,
358 emulated_fields,
359 stack_depth,
360 spill_ram_base,
361 digest_width,
362 xfield_width: xfield_degree,
363 hash_rate,
364 output_extension,
365 cost_tables,
366 warrior: if warrior_name.is_empty() {
367 None
368 } else {
369 let default_crate = format!("trident-{}", warrior_name);
370 Some(WarriorConfig {
371 name: warrior_name,
372 crate_name: if warrior_crate.is_empty() {
373 default_crate
374 } else {
375 warrior_crate
376 },
377 runner: warrior_runner,
378 prover: warrior_prover,
379 })
380 },
381 })
382 }
383}
384
385mod state;
386pub use state::*;
387
388mod os;
389pub use os::*;
390
391#[cfg(test)]
392mod tests;