use crate::generate::generators;
use crate::OpSpec;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use super::util::*;
static GOLDEN_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
#[inline]
pub fn freeze_goldens(specs: &[OpSpec], out_dir: &Path) -> io::Result<usize> {
let mut frozen = 0;
for spec in specs {
let seed = fnv1a_u64(spec.id.as_bytes());
let inputs = generate_50_inputs(spec, seed);
let op_dir = out_dir
.join(sanitize(spec.id))
.join(format!("v{}", spec.version));
fs::create_dir_all(&op_dir)?;
for input in inputs {
let hash = sha256_hex(&input);
let path = op_dir.join(format!("{hash}.json"));
if path.exists() {
continue;
}
let output = (spec.cpu_fn)(&input);
let golden = Golden {
op_id: spec.id.to_string(),
spec_version: spec.version,
input,
output,
};
let json = serde_json::to_string_pretty(&golden).map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, format!("JSON serialize: {e}"))
})?;
atomic_write_new(&path, json.as_bytes())?;
frozen += 1;
}
}
Ok(frozen)
}
fn atomic_write_new(path: &Path, bytes: &[u8]) -> io::Result<()> {
let tmp = temp_path(path);
let mut file = fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp)?;
if let Err(err) = file
.write_all(bytes)
.and_then(|()| file.sync_all())
.and_then(|()| fs::hard_link(&tmp, path))
.and_then(|()| fs::remove_file(&tmp))
{
let _ = fs::remove_file(&tmp);
if err.kind() == io::ErrorKind::AlreadyExists {
return Ok(());
}
return Err(err);
}
Ok(())
}
fn temp_path(path: &Path) -> PathBuf {
let pid = std::process::id();
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_nanos());
let counter = GOLDEN_TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let file_name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("golden");
path.with_file_name(format!("{file_name}.tmp.{pid}.{nanos}.{counter}"))
}