diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 11080e5cc..50075014f 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1755,6 +1755,7 @@ dependencies = [ "elf", "solana-bpf-loader-program", "solana-logger", + "solana-measure", "solana-runtime", "solana-sdk", "solana_rbpf", diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index db0d03641..359983db3 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -24,6 +24,7 @@ byteorder = "1.3.2" elf = "0.0.10" solana-bpf-loader-program = { path = "../bpf_loader", version = "1.4.0" } solana-logger = { path = "../../logger", version = "1.4.0" } +solana-measure = { path = "../../measure", version = "1.4.0" } solana-runtime = { path = "../../runtime", version = "1.4.0" } solana-sdk = { path = "../../sdk", version = "1.4.0" } solana_rbpf = "=0.1.31" diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index 214887cca..cc08db8f2 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -6,7 +6,9 @@ extern crate test; extern crate solana_bpf_loader_program; use byteorder::{ByteOrder, LittleEndian, WriteBytesExt}; -use solana_rbpf::vm::EbpfVm; +use solana_bpf_loader_program::syscalls::SyscallError; +use solana_measure::measure::Measure; +use solana_rbpf::vm::{EbpfVm, InstructionMeter}; use solana_runtime::{ bank::Bank, bank_client::BankClient, @@ -186,11 +188,48 @@ fn bench_program_execute_noop(bencher: &mut Bencher) { }); } +#[bench] +fn bench_instruction_count_tuner(_bencher: &mut Bencher) { + const BUDGET: u64 = 200_000; + let loader_id = bpf_loader::id(); + let mut invoke_context = MockInvokeContext::default(); + invoke_context.compute_meter.borrow_mut().remaining = BUDGET; + let compute_meter = invoke_context.get_compute_meter(); + + let elf = load_elf("tuner").unwrap(); + let executable = + EbpfVm::::create_executable_from_elf(&elf, None) + .unwrap(); + let (mut vm, _) = solana_bpf_loader_program::create_vm( + &loader_id, + executable.as_ref(), + &[], + &mut invoke_context, + ) + .unwrap(); + let instruction_meter = MockInstructionMeter { compute_meter }; + + let mut measure = Measure::start("tune"); + let _ = vm.execute_program_metered(&mut [0], &[], &[], instruction_meter.clone()); + measure.stop(); + assert_eq!( + 0, + instruction_meter.get_remaining(), + "Tuner must consume the whole budget" + ); + println!( + "{:?} Consumed compute budget took {:?} us ({:?} instructions)", + BUDGET - instruction_meter.get_remaining(), + measure.as_us(), + vm.get_total_instruction_count(), + ); +} + #[derive(Debug, Default)] pub struct MockInvokeContext { key: Pubkey, - mock_logger: MockLogger, - mock_compute_meter: MockComputeMeter, + logger: MockLogger, + compute_meter: Rc>, } impl InvokeContext for MockInvokeContext { fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> { @@ -212,7 +251,7 @@ impl InvokeContext for MockInvokeContext { &[] } fn get_logger(&self) -> Rc> { - Rc::new(RefCell::new(self.mock_logger.clone())) + Rc::new(RefCell::new(self.logger.clone())) } fn is_cross_program_supported(&self) -> bool { true @@ -221,7 +260,7 @@ impl InvokeContext for MockInvokeContext { ComputeBudget::default() } fn get_compute_meter(&self) -> Rc> { - Rc::new(RefCell::new(self.mock_compute_meter.clone())) + self.compute_meter.clone() } fn add_executor(&mut self, _pubkey: &Pubkey, _executor: Arc) {} fn get_executor(&mut self, _pubkey: &Pubkey) -> Option> { @@ -257,3 +296,19 @@ impl ComputeMeter for MockComputeMeter { self.remaining } } + +/// Passed to the VM to enforce the compute budget +#[derive(Clone)] +struct MockInstructionMeter { + compute_meter: Rc>, +} +impl InstructionMeter for MockInstructionMeter { + fn consume(&mut self, amount: u64) { + // 1 to 1 instruction to compute unit mapping + // ignore error, Ebpf will bail if exceeded + let _ = self.compute_meter.borrow_mut().consume(amount); + } + fn get_remaining(&self) -> u64 { + self.compute_meter.borrow().get_remaining() + } +} diff --git a/programs/bpf/c/src/sanity++/sanity++.cc b/programs/bpf/c/src/sanity++/sanity++.cc new file mode 100644 index 000000000..8b70030ea --- /dev/null +++ b/programs/bpf/c/src/sanity++/sanity++.cc @@ -0,0 +1,27 @@ +/** + * @brief Example C++-based BPF program that prints out the parameters + * passed to it + */ +#include + +/** + * Custom error for when input serialization fails + */ +#define INVALID_INPUT 1 + +extern uint64_t entrypoint(const uint8_t *input) { + SolAccountInfo ka[1]; + SolParameters params = (SolParameters) { .ka = ka }; + + sol_log(__FILE__); + + if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { + return ERROR_INVALID_ARGUMENT; + } + + // Log the provided input parameters. In the case of the no-op + // program, no account keys or input data are expected but real + // programs will have specific requirements so they can do their work. + sol_log_params(¶ms); + return SUCCESS; +} diff --git a/programs/bpf/c/src/tuner/tuner.c b/programs/bpf/c/src/tuner/tuner.c new file mode 100644 index 000000000..db76dd4e3 --- /dev/null +++ b/programs/bpf/c/src/tuner/tuner.c @@ -0,0 +1,23 @@ +/** + * @brief Compute budget tuner program. Spins in a loop consuming the entire + * budget, used by the tuner bench test to tune the compute budget costs. + * + * Care should be taken because the compiler might optimize out the mechanism + * you are trying to tune. + */ + +#include + +extern uint64_t entrypoint(const uint8_t *input) { + uint8_t *val = (uint8_t *)input; + for (uint64_t i = 0; i < UINT64_MAX; i++) { + + // Uncomment for raw compute + { + if (*val != 0) { + *val = *val + 1; + } + } + } + return *val; +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 71173fd63..7a1ce52e3 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -134,6 +134,8 @@ fn test_program_bpf_sanity() { ("noop++", true), ("panic", false), ("relative_call", true), + ("sanity", true), + ("sanity++", true), ("struct_pass", true), ("struct_ret", true), ]); @@ -150,6 +152,7 @@ fn test_program_bpf_sanity() { ("solana_bpf_rust_noop", true), ("solana_bpf_rust_panic", false), ("solana_bpf_rust_param_passing", true), + ("solana_bpf_rust_sanity", true), ("solana_bpf_rust_sysval", true), ]); }