#[allow(dead_code)]
#[path = "../build.rs"]
mod build_script;
use build_script::{HostBindingKind, classify_host_binding};
use syn::parse_quote;
use vm::{HostFunctionRegistry, JitConfig, JitTraceTerminal, Value, Vm, VmStatus, compile_source};
fn native_jit_supported() -> bool {
(cfg!(target_arch = "x86_64")
&& (cfg!(target_os = "windows") || (cfg!(unix) && !cfg!(target_os = "macos"))))
|| (cfg!(target_arch = "aarch64")
&& (cfg!(target_os = "linux") || cfg!(target_os = "macos")))
}
#[test]
fn classifies_best_effort_host_bindings_from_signatures() {
for function in [
parse_quote!(
fn host(vm: &mut Vm, value: i64) -> i64 {}
),
parse_quote!(
fn host(value: i64, vm: &mut (crate::vm::Vm)) -> VmResult<i64> {}
),
] {
assert_eq!(
classify_host_binding(&function),
HostBindingKind::StaticStack
);
}
for function in [
parse_quote!(
fn host() -> CallOutcome {}
),
parse_quote!(
fn host() -> VmResult<CallOutcome> {}
),
parse_quote!(
fn host() -> HostResult<CallOutcome> {}
),
parse_quote!(
fn host() -> (VmResult<&CallOutcome>) {}
),
] {
assert_eq!(
classify_host_binding(&function),
HostBindingKind::StaticArgs
);
}
for function in [
parse_quote!(
fn host() {}
),
parse_quote!(
fn host() -> () {}
),
parse_quote!(
fn host() -> Option<i64> {}
),
parse_quote!(
fn host() -> bool {}
),
parse_quote!(
fn host() -> VmResult<bool> {}
),
parse_quote!(
fn host() -> HostResult<String> {}
),
parse_quote!(
fn host() -> Value {}
),
parse_quote!(
fn host() -> Vec<Value> {}
),
parse_quote!(
fn host() -> Vec<(Value, Value)> {}
),
parse_quote!(
fn host() -> SharedArray {}
),
parse_quote!(
fn host() -> NumberValue {}
),
] {
assert_eq!(
classify_host_binding(&function),
HostBindingKind::StaticNonYieldingArgs
);
}
for unsupported in [
parse_quote!(
fn host() -> impl IntoVmValue {}
),
parse_quote!(
fn host() -> VmResult {}
),
parse_quote!(
fn host() -> Result<bool, HostError> {}
),
parse_quote!(
fn host() -> CustomReturn {}
),
parse_quote!(
fn host() -> Vec<bool> {}
),
parse_quote!(
fn host() -> VmResult<Result<bool, HostError>> {}
),
parse_quote!(
fn host() -> Option<bool, i64> {}
),
] {
assert_eq!(
classify_host_binding(&unsupported),
HostBindingKind::StaticArgs
);
}
}
fn assert_runtime_sleep_loop_uses_native_host_call(bind_cached_registry: bool) {
let compiled = compile_source(
r#"
use runtime;
let mut i = 0;
while i < 100 {
let _ = runtime::sleep(0);
i = i + 1;
}
i;
"#,
)
.expect("runtime::sleep loop should compile");
let mut vm = Vm::new(compiled.program);
vm.set_jit_config(JitConfig {
enabled: native_jit_supported(),
hot_loop_threshold: 1,
max_trace_len: 512,
});
if bind_cached_registry {
HostFunctionRegistry::new()
.bind_vm_cached(&mut vm)
.expect("cached registry should bind runtime::sleep");
}
let status = vm.run();
assert!(
status.is_ok(),
"runtime::sleep loop should run: {status:?}\n{}",
vm.dump_jit_info()
);
assert_eq!(status.unwrap(), VmStatus::Halted);
assert_eq!(vm.stack(), &[Value::Int(100)]);
if native_jit_supported() {
let snapshot = vm.jit_snapshot();
assert!(
snapshot.traces.iter().any(|trace| {
trace.terminal == JitTraceTerminal::LoopBack
&& trace.op_names().iter().any(|op| op == "host_call")
&& trace.ssa_text().contains("host_call")
}),
"runtime::sleep should remain in a loop-back trace, cached={bind_cached_registry}, dump:\n{}",
vm.dump_jit_info()
);
assert!(
vm.jit_native_exec_count() > 0,
"runtime::sleep loop should execute natively, cached={bind_cached_registry}, dump:\n{}",
vm.dump_jit_info()
);
}
}
#[test]
fn runtime_sleep_default_bindings_remain_inside_jit_loop_traces() {
assert_runtime_sleep_loop_uses_native_host_call(false);
assert_runtime_sleep_loop_uses_native_host_call(true);
}
#[test]
fn runtime_exit_still_halts_for_direct_and_cached_default_bindings() {
for bind_cached_registry in [false, true] {
let compiled = compile_source(
r#"
use runtime;
runtime::exit();
99;
"#,
)
.expect("runtime::exit program should compile");
let mut vm = Vm::new(compiled.program);
if bind_cached_registry {
HostFunctionRegistry::new()
.bind_vm_cached(&mut vm)
.expect("cached registry should bind runtime::exit");
}
assert_eq!(
vm.run().expect("runtime::exit should run"),
VmStatus::Halted
);
assert!(vm.stack().is_empty());
}
}