215 lines
6.1 KiB
Rust
215 lines
6.1 KiB
Rust
use std::hint::black_box;
|
|
use std::time::{Duration, Instant};
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
struct Fault {
|
|
message: String,
|
|
locations: Vec<&'static str>,
|
|
causes: Vec<String>,
|
|
}
|
|
|
|
impl Fault {
|
|
fn overflow(location: &'static str) -> Self {
|
|
Self {
|
|
message: "Int overflow".into(),
|
|
locations: vec![location],
|
|
causes: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn at(mut self, location: &'static str) -> Self {
|
|
self.locations.push(location);
|
|
self
|
|
}
|
|
}
|
|
|
|
struct Record {
|
|
first: i64,
|
|
second: i64,
|
|
third: i64,
|
|
}
|
|
|
|
struct OwnedRecord {
|
|
name: String,
|
|
tags: Vec<String>,
|
|
count: i64,
|
|
}
|
|
|
|
fn inspect_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
|
let partial = row
|
|
.count
|
|
.checked_add(row.name.chars().count() as i64)
|
|
.ok_or_else(|| Fault::overflow("inspect_owned"))?;
|
|
partial
|
|
.checked_add(row.tags.len() as i64)
|
|
.ok_or_else(|| Fault::overflow("inspect_owned"))
|
|
}
|
|
|
|
fn forward_owned(row: &OwnedRecord) -> Result<i64, Fault> {
|
|
inspect_owned(row).map_err(|error| error.at("forward_owned"))
|
|
}
|
|
|
|
fn owned_sum(rows: &[OwnedRecord]) -> Result<i64, Fault> {
|
|
let mut total = 0_i64;
|
|
for row in rows {
|
|
let projected = forward_owned(row).map_err(|error| error.at("call_read"))?;
|
|
total = total
|
|
.checked_add(projected)
|
|
.ok_or_else(|| Fault::overflow("call_read"))?;
|
|
}
|
|
Ok(total)
|
|
}
|
|
|
|
fn add(total: i64, value: i64) -> Result<i64, Fault> {
|
|
total
|
|
.checked_add(value)
|
|
.ok_or_else(|| Fault::overflow("add"))
|
|
}
|
|
|
|
fn record_value(row: &Record) -> Result<i64, Fault> {
|
|
let partial = row
|
|
.first
|
|
.checked_add(row.second)
|
|
.ok_or_else(|| Fault::overflow("record_value"))?;
|
|
partial
|
|
.checked_add(row.third)
|
|
.ok_or_else(|| Fault::overflow("record_value"))
|
|
}
|
|
|
|
fn record_sum(values: &[Record]) -> Result<i64, Fault> {
|
|
let mut total = 0i64;
|
|
for row in values {
|
|
let projected = record_value(row).map_err(|error| error.at("record_sum"))?;
|
|
total = total
|
|
.checked_add(projected)
|
|
.ok_or_else(|| Fault::overflow("record_sum"))?;
|
|
}
|
|
Ok(total)
|
|
}
|
|
|
|
fn measure(operation: &str, ticks: &str) -> Result<(i64, Duration), Fault> {
|
|
let source = black_box(ticks.as_bytes());
|
|
match operation {
|
|
"call_read" => {
|
|
let rows = source
|
|
.iter()
|
|
.map(|value| {
|
|
let name = format!("row-value-{value}");
|
|
OwnedRecord {
|
|
count: i64::from(*value) - name.chars().count() as i64 - 1,
|
|
tags: vec![name.clone()],
|
|
name,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let borrowed = black_box(&rows);
|
|
let begin = Instant::now();
|
|
let total = owned_sum(borrowed)?;
|
|
black_box(total);
|
|
let elapsed = begin.elapsed();
|
|
black_box(&rows);
|
|
Ok((total, elapsed))
|
|
}
|
|
"integer" => {
|
|
let begin = Instant::now();
|
|
let mut total = 0;
|
|
for value in source {
|
|
total = add(total, i64::from(*value)).map_err(|error| error.at("integer"))?;
|
|
}
|
|
Ok((black_box(total), begin.elapsed()))
|
|
}
|
|
"list_record_read" => {
|
|
let mut values = Vec::new();
|
|
for value in source {
|
|
values.push(Record {
|
|
first: i64::from(*value),
|
|
second: 0,
|
|
third: 0,
|
|
});
|
|
}
|
|
let borrowed = black_box(&values);
|
|
let begin = Instant::now();
|
|
let total = record_sum(borrowed)?;
|
|
black_box(total);
|
|
drop(values);
|
|
Ok((total, begin.elapsed()))
|
|
}
|
|
_ => panic!("unknown checked kernel"),
|
|
}
|
|
}
|
|
|
|
fn verify_errors() {
|
|
let fault = owned_sum(&[OwnedRecord {
|
|
name: "overflow".into(),
|
|
tags: vec!["preserved".into()],
|
|
count: i64::MAX,
|
|
}])
|
|
.unwrap_err();
|
|
assert_eq!(fault.message, "Int overflow");
|
|
assert_eq!(
|
|
fault.locations,
|
|
["inspect_owned", "forward_owned", "call_read"]
|
|
);
|
|
assert!(fault.causes.is_empty());
|
|
assert_eq!(add(i64::MAX, 0).unwrap(), i64::MAX);
|
|
assert_eq!(add(i64::MIN, 0).unwrap(), i64::MIN);
|
|
for (left, right) in [(i64::MAX, 1), (i64::MIN, -1)] {
|
|
let fault = add(left, right).unwrap_err().at("integer");
|
|
assert_eq!(fault.message, "Int overflow");
|
|
assert_eq!(fault.locations, ["add", "integer"]);
|
|
assert!(fault.causes.is_empty());
|
|
}
|
|
let fault = record_sum(&[Record {
|
|
first: i64::MAX,
|
|
second: 1,
|
|
third: 0,
|
|
}])
|
|
.unwrap_err();
|
|
assert_eq!(fault.locations, ["record_value", "record_sum"]);
|
|
let fault = record_sum(&[
|
|
Record {
|
|
first: i64::MAX,
|
|
second: 0,
|
|
third: 0,
|
|
},
|
|
Record {
|
|
first: 1,
|
|
second: 0,
|
|
third: 0,
|
|
},
|
|
])
|
|
.unwrap_err();
|
|
assert_eq!(fault.locations, ["record_sum"]);
|
|
}
|
|
|
|
fn main() {
|
|
let arguments = std::env::args().collect::<Vec<_>>();
|
|
if arguments[1] == "--verify-errors" {
|
|
verify_errors();
|
|
println!("checked overflow and error frames verified");
|
|
return;
|
|
}
|
|
let input: serde_json::Value = serde_json::from_str(&arguments[2]).expect("input object");
|
|
let ticks = input["ticks"].as_str().expect("ticks string");
|
|
let (operation, count) = arguments[1]
|
|
.strip_prefix("continuous_")
|
|
.map_or((arguments[1].as_str(), 1), |operation| (operation, 10));
|
|
let mut samples = Vec::new();
|
|
for _ in 0..count {
|
|
match measure(operation, ticks) {
|
|
Ok((checksum, elapsed)) => samples
|
|
.push(serde_json::json!({"checksum": checksum, "elapsed_ns": elapsed.as_nanos()})),
|
|
Err(error) => {
|
|
eprintln!("{error:?}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
let output = if count == 1 {
|
|
samples.pop().unwrap()
|
|
} else {
|
|
serde_json::Value::Array(samples)
|
|
};
|
|
println!("{output}");
|
|
}
|