diff --git a/feature-set/src/lib.rs b/feature-set/src/lib.rs index e01c1adf572924..1019c4249131bf 100644 --- a/feature-set/src/lib.rs +++ b/feature-set/src/lib.rs @@ -156,6 +156,8 @@ impl FeatureSet { reenable_zk_elgamal_proof_program: self .is_active(&reenable_zk_elgamal_proof_program::id()), raise_cpi_nesting_limit_to_8: self.is_active(&raise_cpi_nesting_limit_to_8::id()), + provide_instruction_data_offset_in_vm_r2: self + .is_active(&provide_instruction_data_offset_in_vm_r2::id()), } } } @@ -1126,6 +1128,10 @@ pub mod enforce_fixed_fec_set { solana_pubkey::declare_id!("fixfecLZYMfkGzwq6NJA11Yw6KYztzXiK9QcL3K78in"); } +pub mod provide_instruction_data_offset_in_vm_r2 { + solana_pubkey::declare_id!("5xXZc66h4UdB6Yq7FzdBxBiRAFMMScMLwHxk2QZDaNZL"); +} + pub static FEATURE_NAMES: LazyLock> = LazyLock::new(|| { [ (secp256k1_program_enabled::id(), "secp256k1 program"), @@ -2035,6 +2041,10 @@ pub static FEATURE_NAMES: LazyLock> = LazyLock::n enforce_fixed_fec_set::id(), "SIMD-0317: Enforce 32 data + 32 coding shreds", ), + ( + provide_instruction_data_offset_in_vm_r2::id(), + "SIMD-0321: Provide instruction data offset in VM r2", + ), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index ec012cfab6d390..5a6bf773bb9cc2 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -505,6 +505,10 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { let interpreted = matches.value_of("mode").unwrap() != "jit"; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); + let provide_instruction_data_offset_in_vm_r2 = invoke_context + .get_feature_set() + .provide_instruction_data_offset_in_vm_r2; + // Adding `DELAY_VISIBILITY_SLOT_OFFSET` to slots to accommodate for delay visibility of the program let mut program_cache_for_tx_batch = bank.new_program_cache_for_tx_batch_for_slot(bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET); @@ -527,16 +531,17 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { ) .unwrap(); invoke_context.push().unwrap(); - let (_parameter_bytes, regions, account_lengths) = serialize_parameters( - &invoke_context - .transaction_context - .get_current_instruction_context() - .unwrap(), - false, // stricter_abi_and_runtime_constraints - false, // account_data_direct_mapping - true, // for mask_out_rent_epoch_in_vm_serialization - ) - .unwrap(); + let (_parameter_bytes, regions, account_lengths, instruction_data_offset) = + serialize_parameters( + &invoke_context + .transaction_context + .get_current_instruction_context() + .unwrap(), + false, // stricter_abi_and_runtime_constraints + false, // account_data_direct_mapping + true, // for mask_out_rent_epoch_in_vm_serialization + ) + .unwrap(); let program = matches.value_of("PROGRAM").unwrap(); let verified_executable = load_program(Path::new(program), program_id, &invoke_context); @@ -554,6 +559,11 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { vm.debug_port = Some(matches.value_of("port").unwrap().parse::().unwrap()); } vm.registers[1] = MM_INPUT_START; + + // SIMD-0321: Provide offset to instruction data in VM register 2. + if provide_instruction_data_offset_in_vm_r2 { + vm.registers[2] = instruction_data_offset as u64; + } let (instruction_count, result) = vm.execute_program(&verified_executable, interpreted); let duration = Instant::now() - start_time; if matches.occurrences_of("trace") > 0 { diff --git a/program-runtime/src/serialization.rs b/program-runtime/src/serialization.rs index 0f540186dd61c4..a535680c5aa28e 100644 --- a/program-runtime/src/serialization.rs +++ b/program-runtime/src/serialization.rs @@ -229,6 +229,7 @@ pub fn serialize_parameters( AlignedMemory, Vec, Vec, + usize, ), InstructionError, > { @@ -323,6 +324,7 @@ fn serialize_parameters_unaligned( AlignedMemory, Vec, Vec, + usize, ), InstructionError, > { @@ -395,11 +397,16 @@ fn serialize_parameters_unaligned( }; } s.write::((instruction_data.len() as u64).to_le()); - s.write_all(instruction_data); + let instruction_data_offset = s.write_all(instruction_data); s.write_all(program_id.as_ref()); let (mem, regions) = s.finish(); - Ok((mem, regions, accounts_metadata)) + Ok(( + mem, + regions, + accounts_metadata, + instruction_data_offset as usize, + )) } fn deserialize_parameters_unaligned>( @@ -476,6 +483,7 @@ fn serialize_parameters_aligned( AlignedMemory, Vec, Vec, + usize, ), InstructionError, > { @@ -557,11 +565,16 @@ fn serialize_parameters_aligned( }; } s.write::((instruction_data.len() as u64).to_le()); - s.write_all(instruction_data); + let instruction_data_offset = s.write_all(instruction_data); s.write_all(program_id.as_ref()); let (mem, regions) = s.finish(); - Ok((mem, regions, accounts_metadata)) + Ok(( + mem, + regions, + accounts_metadata, + instruction_data_offset as usize, + )) } fn deserialize_parameters_aligned>( @@ -812,7 +825,8 @@ mod tests { continue; } - let (mut serialized, regions, _account_lengths) = serialization_result.unwrap(); + let (mut serialized, regions, _account_lengths, _instruction_data_offset) = + serialization_result.unwrap(); let mut serialized_regions = concat_regions(®ions); let (de_program_id, de_accounts, de_instruction_data) = unsafe { deserialize( @@ -957,13 +971,14 @@ mod tests { .unwrap(); // check serialize_parameters_aligned - let (mut serialized, regions, accounts_metadata) = serialize_parameters( - &instruction_context, - stricter_abi_and_runtime_constraints, - false, // account_data_direct_mapping - true, // mask_out_rent_epoch_in_vm_serialization - ) - .unwrap(); + let (mut serialized, regions, accounts_metadata, _instruction_data_offset) = + serialize_parameters( + &instruction_context, + stricter_abi_and_runtime_constraints, + false, // account_data_direct_mapping + true, // mask_out_rent_epoch_in_vm_serialization + ) + .unwrap(); let mut serialized_regions = concat_regions(®ions); if !stricter_abi_and_runtime_constraints { @@ -1051,13 +1066,14 @@ mod tests { .get_current_instruction_context() .unwrap(); - let (mut serialized, regions, account_lengths) = serialize_parameters( - &instruction_context, - stricter_abi_and_runtime_constraints, - false, // account_data_direct_mapping - true, // mask_out_rent_epoch_in_vm_serialization - ) - .unwrap(); + let (mut serialized, regions, account_lengths, _instruction_data_offset) = + serialize_parameters( + &instruction_context, + stricter_abi_and_runtime_constraints, + false, // account_data_direct_mapping + true, // mask_out_rent_epoch_in_vm_serialization + ) + .unwrap(); let mut serialized_regions = concat_regions(®ions); let (de_program_id, de_accounts, de_instruction_data) = unsafe { @@ -1218,13 +1234,14 @@ mod tests { .unwrap(); // check serialize_parameters_aligned - let (_serialized, regions, _accounts_metadata) = serialize_parameters( - &instruction_context, - true, - false, // account_data_direct_mapping - mask_out_rent_epoch_in_vm_serialization, - ) - .unwrap(); + let (_serialized, regions, _accounts_metadata, _instruction_data_offset) = + serialize_parameters( + &instruction_context, + true, + false, // account_data_direct_mapping + mask_out_rent_epoch_in_vm_serialization, + ) + .unwrap(); let mut serialized_regions = concat_regions(®ions); let (_de_program_id, de_accounts, _de_instruction_data) = unsafe { @@ -1250,13 +1267,14 @@ mod tests { .get_current_instruction_context() .unwrap(); - let (_serialized, regions, _account_lengths) = serialize_parameters( - &instruction_context, - true, - false, // account_data_direct_mapping - mask_out_rent_epoch_in_vm_serialization, - ) - .unwrap(); + let (_serialized, regions, _account_lengths, _instruction_data_offset) = + serialize_parameters( + &instruction_context, + true, + false, // account_data_direct_mapping + mask_out_rent_epoch_in_vm_serialization, + ) + .unwrap(); let mut serialized_regions = concat_regions(®ions); let (_de_program_id, de_accounts, _de_instruction_data) = unsafe { diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index f5be6aeef061f1..baddd95b9fc5d3 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -131,12 +131,13 @@ pub fn invoke_builtin_function( let mask_out_rent_epoch_in_vm_serialization = invoke_context .get_feature_set() .mask_out_rent_epoch_in_vm_serialization; - let (mut parameter_bytes, _regions, _account_lengths) = serialize_parameters( - &instruction_context, - false, // There is no VM so stricter_abi_and_runtime_constraints can not be implemented here - false, // There is no VM so account_data_direct_mapping can not be implemented here - mask_out_rent_epoch_in_vm_serialization, - )?; + let (mut parameter_bytes, _regions, _account_lengths, _instruction_data_offset) = + serialize_parameters( + &instruction_context, + false, // There is no VM so stricter_abi_and_runtime_constraints can not be implemented here + false, // There is no VM so account_data_direct_mapping can not be implemented here + mask_out_rent_epoch_in_vm_serialization, + )?; // Deserialize data back into instruction params let (program_id, account_infos, input) = diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 2571d14b6ad7be..32dd8cdfb4c01d 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -1466,14 +1466,18 @@ fn execute<'a, 'b: 'a>( let mask_out_rent_epoch_in_vm_serialization = invoke_context .get_feature_set() .mask_out_rent_epoch_in_vm_serialization; + let provide_instruction_data_offset_in_vm_r2 = invoke_context + .get_feature_set() + .provide_instruction_data_offset_in_vm_r2; let mut serialize_time = Measure::start("serialize"); - let (parameter_bytes, regions, accounts_metadata) = serialization::serialize_parameters( - &instruction_context, - stricter_abi_and_runtime_constraints, - invoke_context.account_data_direct_mapping, - mask_out_rent_epoch_in_vm_serialization, - )?; + let (parameter_bytes, regions, accounts_metadata, instruction_data_offset) = + serialization::serialize_parameters( + &instruction_context, + stricter_abi_and_runtime_constraints, + invoke_context.account_data_direct_mapping, + mask_out_rent_epoch_in_vm_serialization, + )?; serialize_time.stop(); // save the account addresses so in case we hit an AccessViolation error we @@ -1508,6 +1512,11 @@ fn execute<'a, 'b: 'a>( vm.context_object_pointer.execute_time = Some(Measure::start("execute")); vm.registers[1] = ebpf::MM_INPUT_START; + + // SIMD-0321: Provide offset to instruction data in VM register 2. + if provide_instruction_data_offset_in_vm_r2 { + vm.registers[2] = instruction_data_offset as u64; + } let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit); MEMORY_POOL.with_borrow_mut(|memory_pool| { memory_pool.put_stack(stack); diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 45d725e2c6648f..859db9c3789fd4 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -8281,6 +8281,7 @@ dependencies = [ "solana-transaction-status", "solana-vote", "solana-vote-program", + "test-case", ] [[package]] @@ -8715,6 +8716,14 @@ dependencies = [ "solana-program-entrypoint", ] +[[package]] +name = "solana-sbf-rust-r2-instruction-data-pointer" +version = "3.1.0" +dependencies = [ + "solana-cpi", + "solana-program-entrypoint", +] + [[package]] name = "solana-sbf-rust-rand" version = "3.1.0" @@ -10659,6 +10668,39 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "test-case-core", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/programs/sbf/Cargo.toml b/programs/sbf/Cargo.toml index 8974aece9e6d92..567051ad96bd77 100644 --- a/programs/sbf/Cargo.toml +++ b/programs/sbf/Cargo.toml @@ -52,6 +52,7 @@ members = [ "rust/param_passing", "rust/param_passing_dep", "rust/poseidon", + "rust/r2_instruction_data_pointer", "rust/rand", "rust/realloc", "rust/realloc_invoke", @@ -124,6 +125,7 @@ solana-cli-output = { path = "../../cli-output", version = "=3.1.0" } solana-clock = { version = "=3.0.0", features = ["serde", "sysvar"] } solana-compute-budget = { path = "../../compute-budget", version = "=3.1.0" } solana-compute-budget-instruction = { path = "../../compute-budget-instruction", version = "=3.1.0" } +solana-cpi = "=3.0.0" solana-curve25519 = { path = "../../curves/curve25519", version = "=3.1.0" } solana-define-syscall = "=3.0.0" solana-fee = { path = "../../fee", version = "=3.1.0" } @@ -150,6 +152,7 @@ solana-sbf-rust-invoked-dep = { path = "rust/invoked_dep", version = "=3.1.0" } solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=3.1.0" } solana-sbf-rust-mem-dep = { path = "rust/mem_dep", version = "=3.1.0" } solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=3.1.0" } +solana-sbf-rust-r2-instruction-data-pointer = { path = "rust/r2_instruction_data_pointer", version = "=3.1.0" } solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=3.1.0" } solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=3.1.0" } solana-sbpf = "=0.12.2" @@ -170,6 +173,7 @@ solana-transaction-context = { path = "../../transaction-context", version = "=3 solana-transaction-status = { path = "../../transaction-status", version = "=3.1.0" } solana-vote = { path = "../../vote", version = "=3.1.0" } solana-vote-program = { path = "../../programs/vote", version = "=3.1.0" } +test-case = "3.3.1" thiserror = "1.0" [features] @@ -251,6 +255,7 @@ solana-transaction-error = "3.0.0" solana-transaction-status = { workspace = true } solana-vote = { workspace = true } solana-vote-program = { workspace = true } +test-case = { workspace = true } [profile.release] # The test programs are build in release mode diff --git a/programs/sbf/benches/bpf_loader.rs b/programs/sbf/benches/bpf_loader.rs index 4559dee837f8d3..eaa55491597df7 100644 --- a/programs/sbf/benches/bpf_loader.rs +++ b/programs/sbf/benches/bpf_loader.rs @@ -248,7 +248,7 @@ fn bench_create_vm(bencher: &mut Bencher) { executable.verify::().unwrap(); // Serialize account data - let (_serialized, regions, account_lengths) = serialize_parameters( + let (_serialized, regions, account_lengths, _instruction_data_offset) = serialize_parameters( &invoke_context .transaction_context .get_current_instruction_context() @@ -283,7 +283,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) { .stricter_abi_and_runtime_constraints; // Serialize account data - let (_serialized, regions, account_lengths) = serialize_parameters( + let (_serialized, regions, account_lengths, _instruction_data_offset) = serialize_parameters( &invoke_context .transaction_context .get_current_instruction_context() diff --git a/programs/sbf/rust/r2_instruction_data_pointer/Cargo.toml b/programs/sbf/rust/r2_instruction_data_pointer/Cargo.toml new file mode 100644 index 00000000000000..ecc7184fc64ba8 --- /dev/null +++ b/programs/sbf/rust/r2_instruction_data_pointer/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-sbf-rust-r2-instruction-data-pointer" +version = { workspace = true } +description = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[lib] +crate-type = ["cdylib"] + +[dependencies] +solana-cpi = { workspace = true } +solana-program-entrypoint = { workspace = true } + +[lints] +workspace = true diff --git a/programs/sbf/rust/r2_instruction_data_pointer/src/lib.rs b/programs/sbf/rust/r2_instruction_data_pointer/src/lib.rs new file mode 100644 index 00000000000000..1597d282c79263 --- /dev/null +++ b/programs/sbf/rust/r2_instruction_data_pointer/src/lib.rs @@ -0,0 +1,18 @@ +//! Test program that reads instruction data using the r2 register pointer. + +#![allow(clippy::arithmetic_side_effects)] +#![allow(clippy::missing_safety_doc)] + +#[no_mangle] +pub unsafe extern "C" fn entrypoint(_input: *mut u8, instruction_data_addr: *const u8) -> u64 { + let instruction_data_len = *((instruction_data_addr as u64 - 8) as *const u64); + let instruction_data = + core::slice::from_raw_parts(instruction_data_addr, instruction_data_len as usize); + + solana_cpi::set_return_data(instruction_data); + + solana_program_entrypoint::SUCCESS +} + +solana_program_entrypoint::custom_heap_default!(); +solana_program_entrypoint::custom_panic_default!(); diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 33646aa2710590..7ef4dea59e59b2 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -69,6 +69,7 @@ use { sync::{Arc, RwLock}, time::Duration, }, + test_case::test_matrix, }; #[cfg(feature = "sbf_rust")] @@ -1578,6 +1579,71 @@ fn test_program_sbf_instruction_introspection() { assert!(bank.get_account(&sysvar::instructions::id()).is_none()); } +#[test_matrix( + [0, 1, 2, 5, 10, 15, 20], + [1, 10, 50, 100, 255, 500, 1000, 1024] // MAX_RETURN_DATA = 1024 +)] +#[allow(clippy::arithmetic_side_effects)] +#[cfg(feature = "sbf_rust")] +fn test_program_sbf_r2_instruction_data_pointer(num_accounts: usize, input_data_len: usize) { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + + let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); + let mut bank_client = BankClient::new_shared(bank.clone()); + let authority_keypair = Keypair::new(); + + let (bank, program_id) = load_program_of_loader_v4( + &mut bank_client, + &bank_forks, + &mint_keypair, + &authority_keypair, + "solana_sbf_rust_r2_instruction_data_pointer", + ); + + let mut account_metas = Vec::new(); + + for i in 0..num_accounts { + let pubkey = Pubkey::new_unique(); + + // Mixed account sizes. + bank.store_account( + &pubkey, + &AccountSharedData::new(0, 100 + (i * 50), &program_id), + ); + + // Mixed account roles. + if i % 2 == 0 { + account_metas.push(AccountMeta::new(pubkey, false)); + } else { + account_metas.push(AccountMeta::new_readonly(pubkey, false)); + } + } + + bank.freeze(); + + // The provided instruction data will be set to the return data. + let input_data: Vec = (0..input_data_len).map(|i| (i % 256) as u8).collect(); + + let instruction = Instruction::new_with_bytes(program_id, &input_data, account_metas); + + let blockhash = bank.last_blockhash(); + let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); + let transaction = Transaction::new(&[&mint_keypair], message, blockhash); + let sanitized_tx = RuntimeTransaction::from_transaction_for_tests(transaction); + + let result = bank.simulate_transaction(&sanitized_tx, false); + assert!(result.result.is_ok()); + + let return_data = result.return_data.unwrap().data; + assert_eq!(input_data, return_data); +} + fn get_stable_genesis_config() -> GenesisConfigInfo { let validator_pubkey = Pubkey::from_str("GLh546CXmtZdvpEzL8sxzqhhUf7KPvmGaRpFHB5W1sjV").unwrap(); diff --git a/svm-feature-set/src/lib.rs b/svm-feature-set/src/lib.rs index dd8e6863419dd2..a1c148c56d5e30 100644 --- a/svm-feature-set/src/lib.rs +++ b/svm-feature-set/src/lib.rs @@ -36,6 +36,7 @@ pub struct SVMFeatureSet { pub disable_zk_elgamal_proof_program: bool, pub reenable_zk_elgamal_proof_program: bool, pub raise_cpi_nesting_limit_to_8: bool, + pub provide_instruction_data_offset_in_vm_r2: bool, } impl SVMFeatureSet { @@ -77,6 +78,7 @@ impl SVMFeatureSet { disable_zk_elgamal_proof_program: true, reenable_zk_elgamal_proof_program: true, raise_cpi_nesting_limit_to_8: true, + provide_instruction_data_offset_in_vm_r2: true, } } }