use crate::core::{compile::*, instruction::ArcisInstruction, ir::IntermediateRepresentation};
use arcis_interface::path_with_hash::PathWithHash;
use serde::{Deserialize, Serialize};
use std::io::Write;
#[derive(Debug)]
pub struct ArcisCOptions {
pub out_dir: Option<String>,
}
fn compile(ir: IntermediateRepresentation) -> IntermediateRepresentation {
Compiler::optimize_into_circuitable(ir)
}
pub fn write_ir(
ir: IntermediateRepresentation,
circuit_name: &str,
arcisc_options: &ArcisCOptions,
) -> PathWithHash {
match ir.write_bytes(circuit_name, arcisc_options.out_dir.clone()) {
Err(e) => {
panic!("Failed to write IR for '{}': {}", circuit_name, e);
}
Ok(s) => s,
}
}
pub(crate) fn compile_and_write(
path: &str,
ir: IntermediateRepresentation,
) -> Result<CompilationOutput, std::io::Error> {
let compiled_ir = compile(ir);
let (temp_circuit, metadata, profile) = Compiler::ir_to_async_mpc_circuit(&compiled_ir);
let compiled_circuit = Compiler::improve_async_mpc_circuit(temp_circuit);
let circuit = ArcisInstruction {
circuit: compiled_circuit,
metadata,
};
let profile_bytes = serde_json::to_string(&profile).expect("Serialization failed.");
let profile_file = PathWithHash::new(
profile_bytes.as_bytes(),
CompilationOutput::profile_path(path),
)?;
let circuit_bytes = circuit.to_bytes();
let arcis_file = PathWithHash::new(&circuit_bytes, CompilationOutput::arcis_path(path))?;
let hash = arcis_file.hash;
let hash_json = serde_json::to_string(&hash).expect("Failed to serialize circuit hash");
let hash_file = PathWithHash::new(hash_json.as_bytes(), CompilationOutput::hash_path(path))?;
let profile_info = circuit.profile_info();
let weight_json = profile_info.to_json();
let weight_file =
PathWithHash::new(weight_json.as_bytes(), CompilationOutput::weight_path(path))?;
let weight = profile_info.weight();
println!(
"Built encrypted instruction weighing {:>12} ACUs named \"{}\".",
weight,
std::path::Path::new(path)
.file_stem()
.unwrap_or(std::ffi::OsStr::new("no-name"))
.display(),
);
Ok(CompilationOutput {
arcis_hash: arcis_file.hash,
profile_hash: profile_file.hash,
hash_hash: hash_file.hash,
weight_hash: weight_file.hash,
})
}
type Hash = [u8; 32];
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct CompilationOutput {
arcis_hash: Hash,
profile_hash: Hash,
hash_hash: Hash,
weight_hash: Hash,
}
impl CompilationOutput {
fn arcis_path(prefix: &str) -> String {
format!("{}.arcis", prefix)
}
fn profile_path(prefix: &str) -> String {
format!("{}.profile.json", prefix)
}
fn hash_path(prefix: &str) -> String {
format!("{}.hash", prefix)
}
fn weight_path(prefix: &str) -> String {
format!("{}.weight", prefix)
}
fn check_up_to_date(&self, prefix: &str) -> bool {
fn check(path: String, hash: Hash) -> bool {
PathWithHash { path, hash }.check()
}
check(Self::arcis_path(prefix), self.arcis_hash)
&& check(Self::profile_path(prefix), self.profile_hash)
&& check(Self::weight_path(prefix), self.weight_hash)
&& check(Self::hash_path(prefix), self.hash_hash)
}
}
#[derive(Debug, Serialize, Deserialize)]
struct CompilationState {
ir_hash: Hash,
output: CompilationOutput,
}
impl CompilationState {
fn path(prefix: &str) -> String {
format!("{}.lock", prefix)
}
fn up_to_date(ir_hash: Hash, prefix: &str) -> bool {
let state_file_path = Self::path(prefix);
let Ok(state_file) = std::fs::read(&state_file_path) else {
return false;
};
let Ok(state) = bincode::deserialize::<CompilationState>(&state_file) else {
return false;
};
ir_hash == state.ir_hash && state.output.check_up_to_date(prefix)
}
fn save(&self, prefix: &str) -> std::io::Result<()> {
let state_bytes = bincode::serialize(&self).expect("Serialization failed");
let state_file_path = Self::path(prefix);
let mut file = std::fs::File::create(&state_file_path)?;
file.write_all(&state_bytes)?;
Ok(())
}
}
pub const TOUCH_FILE_EXTENSION: &str = ".touch";
pub fn compile_and_write_all(
ir_paths: &[(&str, Hash)],
other_paths: &[(&str, Hash)],
) -> Result<(), Box<dyn std::error::Error>> {
for &(path, hash) in other_paths {
PathWithHash {
path: path.to_string(),
hash,
}
.checked_read()
.map_err(|e| format!("Failed reading file at path \"{}\": {}", path, e))?;
}
for &(path, hash) in ir_paths {
let ir_path = PathWithHash {
path: path.to_string(),
hash,
};
let prefix = path.strip_suffix(".arcis.ir").ok_or_else(|| {
format!(
"Internal Error. IR file path should end in \".arcis.ir\", is \"{}\" instead.",
path
)
})?;
std::fs::write(format!("{}{}", prefix, TOUCH_FILE_EXTENSION), [])?;
if CompilationState::up_to_date(hash, prefix) {
println!(
"Encrypted instruction {} is already up-to-date and compiled.",
prefix,
);
continue;
}
let ir_bytes = ir_path
.checked_read()
.map_err(|e| format!("Failed reading IR at path \"{}\": {}", path, e))?;
let ir = IntermediateRepresentation::from_bytes(&ir_bytes)?;
let output = compile_and_write(prefix, ir)?;
let state = CompilationState {
ir_hash: hash,
output,
};
state.save(prefix)?;
}
Ok(())
}
pub fn read_ir(path: &str) -> Result<IntermediateRepresentation, std::io::Error> {
let circ = std::fs::read(path)?;
IntermediateRepresentation::from_bytes(&circ)
}