use std::collections::HashMap;
use std::sync::Arc;
use crate::chunk::Op;
use crate::{Chunk, CompiledFunction, Vm, VmClosure, VmEnv, VmValue};
pub const VMENV_CAPTURE_COUNTS: [usize; 4] = [0, 5, 25, 100];
pub const INLINE_CACHE_LOOKUP_COUNTS: [usize; 4] = [8, 32, 128, 512];
pub struct InlineCacheSlotLookupFixture {
chunk: Chunk,
offsets: Vec<usize>,
}
impl InlineCacheSlotLookupFixture {
pub fn new(op_count: usize) -> Self {
let mut chunk = Chunk::new();
let mut offsets = Vec::with_capacity(op_count);
for _ in 0..op_count {
offsets.push(chunk.code.len());
chunk.emit(Op::Add, 1);
}
Self { chunk, offsets }
}
pub fn op_count(&self) -> usize {
self.offsets.len()
}
pub fn invoke(&self) -> usize {
let mut acc = 0usize;
for &offset in &self.offsets {
if let Some(slot) = self.chunk.inline_cache_slot(offset) {
acc = acc.wrapping_add(slot);
}
}
acc
}
pub fn invoke_btreemap_control(&self) -> usize {
let mut acc = 0usize;
for &offset in &self.offsets {
if let Some(slot) = self.chunk.inline_cache_slot_via_btreemap_for_bench(offset) {
acc = acc.wrapping_add(slot);
}
}
acc
}
}
struct VmInlineCacheReadFixture {
vm: Vm,
cache_set: usize,
cache_id: u64,
hash_control: HashMap<u64, Vec<crate::chunk::InlineCacheEntry>>,
slots: Vec<usize>,
}
impl VmInlineCacheReadFixture {
fn new(
chunk: &Chunk,
slots: Vec<usize>,
mut entry_for_slot: impl FnMut(usize) -> crate::chunk::InlineCacheEntry,
) -> Self {
let slot_count = chunk.inline_cache_slot_count();
let cache_id = chunk.cache_id();
let mut vm = Vm::new();
let cache_set = vm.inline_cache_set_index_for_chunk(chunk);
let mut hash_entries = vec![crate::chunk::InlineCacheEntry::Empty; slot_count];
for &slot in &slots {
let entry = entry_for_slot(slot);
vm.set_inline_cache_entry_by_index(cache_set, slot_count, slot, entry.clone());
hash_entries[slot] = entry;
}
Self {
vm,
cache_set,
cache_id,
hash_control: HashMap::from([(cache_id, hash_entries)]),
slots,
}
}
fn op_count(&self) -> usize {
self.slots.len()
}
#[inline]
fn hash_entry(&self, slot: usize) -> Option<crate::chunk::InlineCacheEntry> {
self.hash_control
.get(&self.cache_id)
.and_then(|entries| entries.get(slot))
.cloned()
}
}
pub const ADAPTIVE_BINARY_CACHE_READ_COUNTS: [usize; 4] = [8, 32, 128, 512];
pub struct AdaptiveBinaryCacheReadFixture {
cache: VmInlineCacheReadFixture,
}
impl AdaptiveBinaryCacheReadFixture {
pub fn new(op_count: usize) -> Self {
use crate::chunk::{AdaptiveBinaryOp, AdaptiveBinaryState, BinaryShape, InlineCacheEntry};
let mut chunk = Chunk::new();
let mut offsets = Vec::with_capacity(op_count);
for _ in 0..op_count {
offsets.push(chunk.code.len());
chunk.emit(Op::Add, 1);
}
let mut slots = Vec::with_capacity(op_count);
for &offset in &offsets {
let slot = chunk
.inline_cache_slot(offset)
.expect("Op::Add registers an inline-cache slot at emit time");
slots.push(slot);
}
let cache = VmInlineCacheReadFixture::new(&chunk, slots, |_slot| {
InlineCacheEntry::AdaptiveBinary {
op: AdaptiveBinaryOp::Add,
state: AdaptiveBinaryState::Specialized {
shape: BinaryShape::Int,
hits: 1_000,
misses: 0,
},
}
});
Self { cache }
}
pub fn op_count(&self) -> usize {
self.cache.op_count()
}
pub fn invoke_peek(&self) -> u64 {
use crate::chunk::AdaptiveBinaryState;
let mut acc = 0u64;
for &slot in &self.cache.slots {
if let Some((_op, state)) = self
.cache
.vm
.peek_adaptive_binary_cache_by_index(self.cache.cache_set, slot)
{
let hits = match state {
AdaptiveBinaryState::Specialized { hits, .. } => hits,
AdaptiveBinaryState::Warmup { hits, .. } => hits as u64,
};
acc = acc.wrapping_add(hits);
}
}
acc
}
pub fn invoke_clone_control(&self) -> u64 {
use crate::chunk::{AdaptiveBinaryState, InlineCacheEntry};
let mut acc = 0u64;
for &slot in &self.cache.slots {
let entry = self.cache.hash_entry(slot);
if let Some(InlineCacheEntry::AdaptiveBinary { state, .. }) = entry {
let hits = match state {
AdaptiveBinaryState::Specialized { hits, .. } => hits,
AdaptiveBinaryState::Warmup { hits, .. } => hits as u64,
};
acc = acc.wrapping_add(hits);
}
}
acc
}
}
pub const METHOD_CACHE_READ_COUNTS: [usize; 4] = [8, 32, 128, 512];
pub struct MethodCacheReadFixture {
cache: VmInlineCacheReadFixture,
}
impl MethodCacheReadFixture {
pub fn new(op_count: usize) -> Self {
use crate::chunk::{InlineCacheEntry, MethodCacheTarget};
let mut chunk = Chunk::new();
let mut offsets = Vec::with_capacity(op_count);
for _ in 0..op_count {
offsets.push(chunk.code.len());
chunk.emit_method_call(0, 1, 1);
}
let mut slots = Vec::with_capacity(op_count);
for &offset in &offsets {
let slot = chunk
.inline_cache_slot(offset)
.expect("Op::MethodCall registers an inline-cache slot at emit time");
slots.push(slot);
}
let cache = VmInlineCacheReadFixture::new(&chunk, slots, |_slot| {
InlineCacheEntry::Method {
name_idx: 0,
argc: 1,
target: MethodCacheTarget::ListContains,
}
});
Self { cache }
}
pub fn op_count(&self) -> usize {
self.cache.op_count()
}
pub fn invoke_peek(&self) -> usize {
let mut acc = 0usize;
for &slot in &self.cache.slots {
if let Some((_name_idx, argc, _target)) = self
.cache
.vm
.peek_method_cache_by_index(self.cache.cache_set, slot)
{
acc = acc.wrapping_add(argc);
}
}
acc
}
pub fn invoke_clone_control(&self) -> usize {
use crate::chunk::InlineCacheEntry;
let mut acc = 0usize;
for &slot in &self.cache.slots {
let entry = self.cache.hash_entry(slot);
if let Some(InlineCacheEntry::Method { argc, .. }) = entry {
acc = acc.wrapping_add(argc);
}
}
acc
}
}
pub const PROPERTY_CACHE_READ_COUNTS: [usize; 4] = [8, 32, 128, 512];
pub struct PropertyCacheReadFixture {
cache: VmInlineCacheReadFixture,
}
impl PropertyCacheReadFixture {
pub fn new(op_count: usize) -> Self {
use crate::chunk::{InlineCacheEntry, PropertyCacheTarget};
let mut chunk = Chunk::new();
let mut offsets = Vec::with_capacity(op_count);
for _ in 0..op_count {
offsets.push(chunk.code.len());
chunk.emit_u16(Op::GetProperty, 0, 1);
}
let mut slots = Vec::with_capacity(op_count);
for &offset in &offsets {
let slot = chunk
.inline_cache_slot(offset)
.expect("Op::GetProperty registers an inline-cache slot at emit time");
slots.push(slot);
}
let cache =
VmInlineCacheReadFixture::new(&chunk, slots, |_slot| InlineCacheEntry::Property {
name_idx: 7,
target: PropertyCacheTarget::ListCount,
});
Self { cache }
}
pub fn op_count(&self) -> usize {
self.cache.op_count()
}
pub fn invoke_peek(&self) -> usize {
let mut acc = 0usize;
for &slot in &self.cache.slots {
if let Some((name_idx, _target)) = self
.cache
.vm
.peek_property_cache_by_index(self.cache.cache_set, slot)
{
acc = acc.wrapping_add(name_idx as usize);
}
}
acc
}
pub fn invoke_clone_control(&self) -> usize {
use crate::chunk::InlineCacheEntry;
let mut acc = 0usize;
for &slot in &self.cache.slots {
let entry = self.cache.hash_entry(slot);
if let Some(InlineCacheEntry::Property { name_idx, .. }) = entry {
acc = acc.wrapping_add(name_idx as usize);
}
}
acc
}
}
pub const DIRECT_CALL_STATE_READ_COUNTS: [usize; 4] = [8, 32, 128, 512];
pub struct DirectCallStateReadFixture {
cache: VmInlineCacheReadFixture,
}
impl DirectCallStateReadFixture {
pub fn new(op_count: usize) -> Self {
use crate::chunk::{DirectCallState, DirectCallTarget, InlineCacheEntry};
let target_closure = synthetic_direct_call_closure();
let mut chunk = Chunk::new();
let mut offsets = Vec::with_capacity(op_count);
for _ in 0..op_count {
offsets.push(chunk.code.len());
chunk.emit_u8(Op::Call, 1, 1);
}
let mut slots = Vec::with_capacity(op_count);
for &offset in &offsets {
let slot = chunk
.inline_cache_slot(offset)
.expect("Op::Call registers an inline-cache slot at emit time");
slots.push(slot);
}
let cache =
VmInlineCacheReadFixture::new(&chunk, slots, |_slot| InlineCacheEntry::DirectCall {
state: DirectCallState::Specialized {
argc: 1,
target: DirectCallTarget::Closure(Arc::clone(&target_closure)),
hits: 1_000,
misses: 0,
},
});
Self { cache }
}
pub fn op_count(&self) -> usize {
self.cache.op_count()
}
pub fn invoke_peek(&self) -> usize {
use crate::chunk::DirectCallState;
let mut acc = 0usize;
for &slot in &self.cache.slots {
if let Some(DirectCallState::Specialized { argc, .. }) = self
.cache
.vm
.peek_direct_call_state_by_index(self.cache.cache_set, slot)
{
acc = acc.wrapping_add(argc);
}
}
acc
}
pub fn invoke_clone_control(&self) -> usize {
use crate::chunk::{DirectCallState, InlineCacheEntry};
let mut acc = 0usize;
for &slot in &self.cache.slots {
let entry = self.cache.hash_entry(slot);
if let Some(InlineCacheEntry::DirectCall {
state: DirectCallState::Specialized { argc, .. },
}) = entry
{
acc = acc.wrapping_add(argc);
}
}
acc
}
}
fn synthetic_direct_call_closure() -> Arc<VmClosure> {
let func = CompiledFunction {
name: "synthetic_direct_call_target".to_string(),
type_params: Vec::new(),
nominal_type_names: Vec::new(),
params: Vec::new(),
default_start: None,
chunk: Arc::new(Chunk::new()),
is_generator: false,
is_stream: false,
has_rest_param: false,
has_runtime_type_checks: false,
};
Arc::new(VmClosure {
func: Arc::new(func),
env: VmEnv::new(),
source_dir: None,
module_functions: None,
module_state: None,
})
}
pub struct NonModuleClosureCallFixture {
capture_count: usize,
last_capture_name: Option<String>,
caller_env: VmEnv,
closure: VmClosure,
}
impl NonModuleClosureCallFixture {
pub fn new(capture_count: usize) -> Self {
let nested_inner = synthetic_closure("nested_inner", VmEnv::new());
let mut caller_env = VmEnv::new();
caller_env
.define(
"nested_inner",
VmValue::Closure(Arc::new(nested_inner)),
false,
)
.expect("synthetic caller closure binding should be valid");
let mut closure_env = VmEnv::new();
for index in 0..capture_count {
closure_env
.define(
&format!("captured_{index:03}"),
VmValue::Int(index as i64),
false,
)
.expect("synthetic captured binding should be valid");
}
let closure = synthetic_closure(&format!("capture_{capture_count:03}"), closure_env);
Self {
capture_count,
last_capture_name: capture_count
.checked_sub(1)
.map(|index| format!("captured_{index:03}")),
caller_env,
closure,
}
}
pub fn capture_count(&self) -> usize {
self.capture_count
}
pub fn invoke(&self) -> usize {
let env = Vm::closure_call_env(&self.caller_env, &self.closure);
let mut score = env.scope_depth();
if let Some(name) = self.last_capture_name.as_deref() {
if let Some(VmValue::Int(value)) = env.get(name) {
score += value as usize;
}
}
if matches!(env.get("nested_inner"), Some(VmValue::Closure(_))) {
score += 1;
}
score
}
}
fn synthetic_closure(name: &str, env: VmEnv) -> VmClosure {
let func = CompiledFunction {
name: name.to_string(),
type_params: Vec::new(),
nominal_type_names: Vec::new(),
params: Vec::new(),
default_start: None,
chunk: Arc::new(Chunk::new()),
is_generator: false,
is_stream: false,
has_rest_param: false,
has_runtime_type_checks: false,
};
VmClosure {
func: Arc::new(func),
env,
source_dir: None,
module_functions: None,
module_state: None,
}
}
pub mod harn_entry_crossing {
use crate::value::{VmError, VmValue};
use crate::vm::AsyncBuiltinCtx;
use crate::Vm;
pub const IMPORT_PATH: &str = "std/semver";
pub const STDLIB_MODULE: &str = "semver";
pub const EXPORT_NAME: &str = "parse";
#[derive(Debug, serde::Deserialize)]
pub struct ParsedVersion {
pub major: i64,
pub minor: i64,
pub patch: i64,
}
pub fn stdlib_module_is_cached(vm: &Vm, module: &str) -> bool {
let synthetic = std::path::PathBuf::from(format!("<stdlib>/{module}.harn"));
vm.module_cache.contains_key(&synthetic)
}
pub async fn warm_parent_module_cache(vm: &mut Vm, import_path: &str) -> Result<(), VmError> {
vm.load_module_exports_from_import(import_path)
.await
.map(|_| ())
}
pub async fn call_export_by_name(
ctx: &AsyncBuiltinCtx,
args: &[VmValue],
) -> Result<VmValue, VmError> {
crate::stdlib::harn_entry::call_harn_export_by_name(
ctx,
IMPORT_PATH,
EXPORT_NAME,
"bench_harn_entry_crossing_by_name",
args,
)
.await
}
pub async fn call_export_typed(
ctx: &AsyncBuiltinCtx,
payload: serde_json::Value,
) -> Result<ParsedVersion, VmError> {
crate::stdlib::harn_entry::call_harn_export_typed(
ctx,
IMPORT_PATH,
EXPORT_NAME,
"bench_harn_entry_crossing_typed",
payload,
)
.await
}
}
pub mod transcript_projection {
use crate::value::{VmDictExt, VmError, VmValue};
use crate::vm::AsyncBuiltinCtx;
pub fn transcript_value_from_messages(messages: &[serde_json::Value]) -> VmValue {
let mut dict = crate::value::DictMap::new();
dict.put_str("_type", "transcript");
dict.insert(
crate::value::intern_key("messages"),
VmValue::List(std::sync::Arc::new(
messages
.iter()
.map(crate::schema::json_to_vm_value)
.collect(),
)),
);
VmValue::dict(dict)
}
pub async fn project_for_bench(
ctx: &AsyncBuiltinCtx,
transcript: &VmValue,
options: &serde_json::Value,
) -> Result<VmValue, VmError> {
let dict = transcript.as_dict().ok_or_else(|| {
VmError::Runtime("bench transcript fixture must be a dict".to_string())
})?;
let options_vm = crate::schema::json_to_vm_value(options);
let policy = crate::stdlib::transcript_project::parse_projection_options(&options_vm)?;
let result =
crate::stdlib::transcript_project::project_transcript(Some(ctx), dict, &policy).await?;
Ok(crate::stdlib::transcript_project::result_to_vm(
&result, &policy,
))
}
}