Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup - test_interpreter_and_jit!() macros #13

Merged
merged 11 commits into from
Jan 3, 2025
205 changes: 205 additions & 0 deletions test_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,208 @@ macro_rules! assert_error {
assert!(format!("{:?}", $result).contains(&format!($($error),+)));
}
}

#[macro_export]
macro_rules! test_interpreter_and_jit {
(override_budget => $override_budget:expr, $executable:expr, $mem:tt, $context_object:expr $(,)?) => {{
let expected_instruction_count = $context_object.get_remaining();
#[allow(unused_mut)]
let mut context_object = $context_object;
if $override_budget {
const INSTRUCTION_METER_BUDGET: u64 = 1024;
context_object.remaining = INSTRUCTION_METER_BUDGET;
}
$executable.verify::<RequisiteVerifier>().unwrap();
let (instruction_count_interpreter, result_interpreter, interpreter_final_pc, _tracer_interpreter) = {
let mut mem = $mem;
let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
let mut context_object = context_object.clone();
create_vm!(
vm,
&$executable,
&mut context_object,
stack,
heap,
vec![mem_region],
None
);
let (instruction_count_interpreter, result_interpreter) = vm.execute_program(&$executable, true);
(
instruction_count_interpreter,
result_interpreter,
vm.registers[11],
vm.context_object_pointer.clone(),
)
};
#[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
{
#[allow(unused_mut)]
let compilation_result = $executable.jit_compile();
let mut mem = $mem;
let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
create_vm!(
vm,
&$executable,
&mut context_object,
stack,
heap,
vec![mem_region],
None
);
match compilation_result {
Err(_) => panic!("{:?}", compilation_result),
Ok(()) => {
let (instruction_count_jit, result_jit) = vm.execute_program(&$executable, false);
let tracer_jit = &vm.context_object_pointer;
let mut diverged = false;
if format!("{:?}", result_interpreter) != format!("{:?}", result_jit) {
println!(
"Result of interpreter ({:?}) and JIT ({:?}) diverged",
result_interpreter, result_jit,
);
diverged = true;
}
if instruction_count_interpreter != instruction_count_jit {
println!(
"Instruction meter of interpreter ({:?}) and JIT ({:?}) diverged",
instruction_count_interpreter, instruction_count_jit,
);
diverged = true;
}
if interpreter_final_pc != vm.registers[11] {
println!(
"Final PC of interpreter ({:?}) and JIT ({:?}) result diverged",
interpreter_final_pc, vm.registers[11],
);
diverged = true;
}
if !TestContextObject::compare_trace_log(&_tracer_interpreter, tracer_jit) {
let analysis = Analysis::from_executable(&$executable).unwrap();
let stdout = std::io::stdout();
analysis
.disassemble_trace_log(
&mut stdout.lock(),
&_tracer_interpreter.trace_log,
)
.unwrap();
analysis
.disassemble_trace_log(&mut stdout.lock(), &tracer_jit.trace_log)
.unwrap();
diverged = true;
}
assert!(!diverged);
}
}
}
if $executable.get_config().enable_instruction_meter {
assert_eq!(
instruction_count_interpreter, expected_instruction_count,
"Instruction meter did not consume expected amount"
);
}
result_interpreter
}};
($executable:expr, $mem:tt, $context_object:expr, $expected_result:expr $(,)?) => {
let expected_result = $expected_result;
let result = test_interpreter_and_jit!(
override_budget => false,
$executable,
$mem,
$context_object,
);
assert_eq!(
format!("{:?}", result), format!("{:?}", expected_result),
"Unexpected result",
);
if !matches!(expected_result, ProgramResult::Err(solana_sbpf::error::EbpfError::ExceededMaxInstructions)) {
test_interpreter_and_jit!(
override_budget => true,
$executable,
$mem,
$context_object,
);
}
};
}

#[macro_export]
macro_rules! test_interpreter_and_jit_asm {
($source:expr, $config:expr, $mem:tt, $context_object:expr, $expected_result:expr $(,)?) => {
#[allow(unused_mut)]
{
let mut config = $config;
config.enable_instruction_tracing = true;
let mut function_registry =
FunctionRegistry::<BuiltinFunction<TestContextObject>>::default();
let loader = Arc::new(BuiltinProgram::new_loader(config, function_registry));
let mut executable = assemble($source, loader).unwrap();
test_interpreter_and_jit!(executable, $mem, $context_object, $expected_result);
}
};
($source:expr, $mem:tt, $context_object:expr, $expected_result:expr $(,)?) => {
#[allow(unused_mut)]
{
test_interpreter_and_jit_asm!(
$source,
Config::default(),
$mem,
$context_object,
$expected_result
);
}
};
}

#[macro_export]
macro_rules! test_interpreter_and_jit_elf {
(register, $function_registry:expr, $location:expr => $syscall_function:expr) => {
$function_registry
.register_function_hashed($location.as_bytes(), $syscall_function)
.unwrap();
};
($source:tt, $config:tt, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => {
let mut file = File::open($source).unwrap();
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
#[allow(unused_mut)]
{
let mut function_registry = FunctionRegistry::<BuiltinFunction<TestContextObject>>::default();
$(test_interpreter_and_jit_elf!(register, function_registry, $location => $syscall_function);)*
let loader = Arc::new(BuiltinProgram::new_loader($config, function_registry));
let mut executable = Executable::<TestContextObject>::from_elf(&elf, loader).unwrap();
test_interpreter_and_jit!(executable, $mem, $context_object, $expected_result);
}
};
($source:tt, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => {
let config = Config {
enable_instruction_tracing: true,
..Config::default()
};
test_interpreter_and_jit_elf!($source, config, $mem, ($($location => $syscall_function),*), $context_object, $expected_result);
};
}

#[macro_export]
macro_rules! test_syscall_asm {
(register, $loader:expr, $syscall_number:literal => $syscall_name:expr => $syscall_function:expr) => {
let _ = $loader.register_function($syscall_name, $syscall_number, $syscall_function).unwrap();
};
($source:tt, $mem:tt, ($($syscall_number:literal => $syscall_name:expr => $syscall_function:expr),*$(,)?), $context_object:expr, $expected_result:expr $(,)?) => {
let mut config = Config {
enable_instruction_tracing: true,
..Config::default()
};
for sbpf_version in [SBPFVersion::V0, SBPFVersion::V3] {
config.enabled_sbpf_versions = sbpf_version..=sbpf_version;
let src = if sbpf_version == SBPFVersion::V0 {
format!($source, $($syscall_name, )*)
} else {
format!($source, $($syscall_number, )*)
};
let mut loader = BuiltinProgram::new_loader_with_dense_registration(config.clone());
$(test_syscall_asm!(register, loader, $syscall_number => $syscall_name => $syscall_function);)*
let mut executable = assemble(src.as_str(), Arc::new(loader)).unwrap();
test_interpreter_and_jit!(executable, $mem, $context_object, $expected_result);
}
};
}
Loading
Loading