use std::hint::black_box;
use std::path::PathBuf;
use criterion::{criterion_group, criterion_main, Criterion};
use harn_hostlib::{ast::AstCapability, tools::permissions, BuiltinRegistry, HostlibCapability};
use harn_vm::VmValue;
fn ast_registry() -> BuiltinRegistry {
let mut registry = BuiltinRegistry::new();
AstCapability.register_builtins(&mut registry);
registry
}
fn dict(pairs: &[(&str, VmValue)]) -> VmValue {
let mut map: harn_vm::value::DictMap = Default::default();
for (k, v) in pairs {
map.insert((*k).into(), v.clone());
}
VmValue::dict(map)
}
fn fixture_path(rel: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ast")
.join(rel)
}
fn bench_parse_file_warm(c: &mut Criterion) {
let registry = ast_registry();
let path = fixture_path("rust/source.rs");
let payload = dict(&[(
"path",
VmValue::String(arcstr::ArcStr::from(path.to_string_lossy().as_ref())),
)]);
let entry = registry
.find("hostlib_ast_parse_file")
.expect("hostlib_ast_parse_file builtin registered");
let _ = (entry.handler)(std::slice::from_ref(&payload)).expect("warmup parse_file succeeds");
c.bench_function("hostlib_ast_parse_file/rust_source.rs (warm)", |b| {
b.iter(|| {
let result = (entry.handler)(std::slice::from_ref(black_box(&payload)))
.expect("parse_file succeeds");
black_box(result);
});
});
}
fn large_source(language: &str) -> (String, &'static str, &'static str) {
match language {
"rust" => {
let mut body = String::new();
for i in 0..700 {
body.push_str(&format!("fn f{i}() -> i32 {{ {i} }}\n"));
}
(
body,
"(function_item name: (identifier) @name (#eq? @name \"f0\") body: (block) @target)",
"{ 42 }",
)
}
"json" => {
let mut body = String::from("{\n \"first\": 1");
for i in 0..2000 {
body.push_str(&format!(",\n \"k{i}\": {i}"));
}
body.push_str("\n}\n");
(body, "(pair value: (number) @target)", "9")
}
"yaml" => {
let mut body = String::from("first: 1\n");
for i in 0..2000 {
body.push_str(&format!("k{i}: {i}\n"));
}
(
body,
"(block_mapping_pair value: (flow_node (plain_scalar) @target))",
"9",
)
}
"css" => {
let mut body = String::new();
for i in 0..1500 {
body.push_str(&format!(".c{i} {{ color: red; }}\n"));
}
(body, "(declaration (plain_value) @target)", "blue")
}
other => panic!("no large-source generator for {other}"),
}
}
fn bench_apply_node_large(c: &mut Criterion) {
use std::io::Write;
permissions::enable_for_test();
let registry = ast_registry();
let entry = registry
.find("hostlib_ast_apply_node")
.expect("hostlib_ast_apply_node builtin registered");
for language in ["rust", "json", "yaml", "css"] {
let (source, query, replacement) = large_source(language);
let mut file = tempfile::Builder::new()
.suffix(&format!(".bench-{language}"))
.tempfile()
.expect("temp file");
file.write_all(source.as_bytes()).expect("write source");
let path = file.path().to_string_lossy().to_string();
let payload = dict(&[
("path", VmValue::String(arcstr::ArcStr::from(path.as_str()))),
("language", VmValue::String(arcstr::ArcStr::from(language))),
("query", VmValue::String(arcstr::ArcStr::from(query))),
(
"replacement",
VmValue::String(arcstr::ArcStr::from(replacement)),
),
("select", VmValue::String(arcstr::ArcStr::from("first"))),
("dry_run", VmValue::Bool(true)),
]);
c.bench_function(
&format!("hostlib_ast_apply_node/{language}_5k_loc (dry_run)"),
|b| {
b.iter(|| {
let result = (entry.handler)(std::slice::from_ref(black_box(&payload)))
.expect("apply_node succeeds");
black_box(result);
});
},
);
}
}
criterion_group!(benches, bench_parse_file_warm, bench_apply_node_large);
criterion_main!(benches);