diff --git a/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_output_user_transaction_with_entry_function_payload.json b/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_output_user_transaction_with_entry_function_payload.json index 7c63230eee672..f70d271981207 100644 --- a/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_output_user_transaction_with_entry_function_payload.json +++ b/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_output_user_transaction_with_entry_function_payload.json @@ -114,7 +114,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -280,7 +280,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], diff --git a/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_returns_last_page_when_start_version_is_not_specified.json b/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_returns_last_page_when_start_version_is_not_specified.json index f530527c9ee58..3640e24a65ac8 100644 --- a/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_returns_last_page_when_start_version_is_not_specified.json +++ b/api/goldens/aptos_api__tests__transactions_test__test_get_transactions_returns_last_page_when_start_version_is_not_specified.json @@ -119,7 +119,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -285,7 +285,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -412,7 +412,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -578,7 +578,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -705,7 +705,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -871,7 +871,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -998,7 +998,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -1164,7 +1164,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -1291,7 +1291,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -1457,7 +1457,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -1584,7 +1584,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -1750,7 +1750,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -1877,7 +1877,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -2043,7 +2043,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], @@ -2170,7 +2170,7 @@ "state_change_hash": "", "event_root_hash": "", "state_checkpoint_hash": null, - "gas_used": "4", + "gas_used": "5", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "", @@ -2336,7 +2336,7 @@ "io_gas_units": "1", "storage_fee_octas": "0", "storage_fee_refund_octas": "0", - "total_charge_gas_units": "4" + "total_charge_gas_units": "5" } } ], diff --git a/aptos-move/aptos-gas-calibration/samples_ir/vector/vec-generic.mvir b/aptos-move/aptos-gas-calibration/samples_ir/vector/vec-generic.mvir new file mode 100644 index 0000000000000..2af4918258c20 --- /dev/null +++ b/aptos-move/aptos-gas-calibration/samples_ir/vector/vec-generic.mvir @@ -0,0 +1,230 @@ +module 0xcafe.VecGeneric { + struct D has copy, drop { x: u64 } + struct C has copy, drop { x: T, y: u64 } + struct E has copy, drop { x: T, y: u64 } + struct F has copy, drop { x: T, y: u64 } + struct G has copy, drop { x: T, y: u64 } + + public make(): Self.C { + let d: Self.D; + label b0: + d = D { x: 0 }; + return C { x: move(d), y: 0 }; + } + + public calibrate_vec_len_generic_impl(n: u64) { + let i: u64; + let c: Self.C; + let v: vector>; + label entry: + i = 0; + c = Self.make(); + v = vec_pack_1>(move(c)); + label loop_start: + jump_if_false (copy(i) < copy(n)) loop_end; + i = move(i) + 1; + Self.vec_len(&v); + jump loop_start; + label loop_end: + return; + } + + public calibrate_vec_len_generic_inlined_impl(n: u64) { + let i: u64; + let c: Self.C; + let v: vector>; + label entry: + i = 0; + c = Self.make(); + v = vec_pack_1>(move(c)); + label loop_start: + jump_if_false (copy(i) < copy(n)) loop_end; + i = move(i) + 1; + _ = vec_len>(&v); + jump loop_start; + label loop_end: + return; + } + + public vec_len(v: &vector>) { + label entry: + _ = vec_len>(move(v)); + return; + } + + public entry calibrate_vec_len_generic_1_x100() { + label b0: + Self.calibrate_vec_len_generic_impl(10); + return; + } + + public entry calibrate_vec_len_generic_1_x500() { + label b0: + Self.calibrate_vec_len_generic_impl(50); + return; + } + + public entry calibrate_vec_len_generic_1_x1000() { + label b0: + Self.calibrate_vec_len_generic_impl(100); + return; + } + + public entry calibrate_vec_len_generic_inlined_1_x100() { + label b0: + Self.calibrate_vec_len_generic_inlined_impl(10); + return; + } + + public entry calibrate_vec_len_generic_inlined_1_x500() { + label b0: + Self.calibrate_vec_len_generic_inlined_impl(50); + return; + } + + public entry calibrate_vec_len_generic_inlined_1_x1000() { + label b0: + Self.calibrate_vec_len_generic_inlined_impl(100); + return; + } + + public make_gen(v: T): Self.C { + label b0: + return C { x: move(v), y: 0 }; + } + + public calibrate_vec_len_generic_2_impl(n: u64, val: T) { + let i: u64; + let c: Self.C; + let v: vector>; + label entry: + i = 0; + c = Self.make_gen(move(val)); + v = vec_pack_1>(move(c)); + label loop_start: + jump_if_false (copy(i) < copy(n)) loop_end; + i = move(i) + 1; + Self.vec_len_gen(&v); + jump loop_start; + label loop_end: + return; + } + + public calibrate_vec_len_generic_inlined_2_impl(n: u64, val: T) { + let i: u64; + let c: Self.C; + let v: vector>; + label entry: + i = 0; + c = Self.make_gen(move(val)); + v = vec_pack_1>(move(c)); + label loop_start: + jump_if_false (copy(i) < copy(n)) loop_end; + i = move(i) + 1; + _ = vec_len>(&v); + jump loop_start; + label loop_end: + return; + } + + public vec_len_gen(v: &vector>) { + label entry: + _ = vec_len>(move(v)); + return; + } + + public entry calibrate_vec_len_generic_2_x100() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_2_impl(10, move(d)); + return; + } + + public entry calibrate_vec_len_generic_2_x500() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_2_impl(50, move(d)); + return; + } + + public entry calibrate_vec_len_generic_2_x1000() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_2_impl(100, move(d)); + return; + } + + public entry calibrate_vec_len_generic_inlined_2_x100() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_2_impl(10, move(d)); + return; + } + + public entry calibrate_vec_len_generic_inlined_2_x500() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_2_impl(50, move(d)); + return; + } + + public entry calibrate_vec_len_generic_inlined_2_x1000() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_2_impl(100, move(d)); + return; + } + + public calibrate_vec_len_generic_inlined_exterme_impl(n: u64, val: T) { + let i: u64; + let v: vector>>>>; + let g: Self.G; + let f: Self.F>; + let e: Self.E>>; + let c: Self.C>>>; + label entry: + i = 0; + g = G { x: move(val), y: 0}; + f = F> { x: move(g), y: 0 }; + e = E>> { x: move(f), y: 0 }; + c = C>>> { x: move(e), y: 0 }; + v = vec_pack_1>>>>(move(c)); + label loop_start: + jump_if_false (copy(i) < copy(n)) loop_end; + i = move(i) + 1; + _ = vec_len>>>>(&v); + jump loop_start; + label loop_end: + return; + } + + public entry calibrate_vec_len_generic_inlined_exterme_x100() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_exterme_impl(10, move(d)); + return; + } + + public entry calibrate_vec_len_generic_inlined_exterme_x500() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_exterme_impl(50, move(d)); + return; + } + + public entry calibrate_vec_len_generic_inlined_exterme_x1000() { + let d: Self.D; + label b0: + d = D { x: 0 }; + Self.calibrate_vec_len_generic_inlined_exterme_impl(100, move(d)); + return; + } +} \ No newline at end of file diff --git a/aptos-move/aptos-gas-meter/src/meter.rs b/aptos-move/aptos-gas-meter/src/meter.rs index 4dbf4f25d0856..b9fe99f8e001f 100644 --- a/aptos-move/aptos-gas-meter/src/meter.rs +++ b/aptos-move/aptos-gas-meter/src/meter.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::traits::{AptosGasMeter, GasAlgebra}; -use aptos_gas_algebra::{Fee, FeePerGasUnit}; +use aptos_gas_algebra::{Fee, FeePerGasUnit, NumTypeNodes}; use aptos_gas_schedule::gas_params::{instr::*, txn::*}; use aptos_types::{state_store::state_key::StateKey, write_set::WriteOpSize}; use move_binary_format::{ @@ -458,6 +458,17 @@ where ) -> PartialVMResult<()> { Ok(()) } + + #[inline] + fn charge_create_ty(&mut self, num_nodes: NumTypeNodes) -> PartialVMResult<()> { + if self.feature_version() < 13 { + return Ok(()); + } + + let cost = SUBST_TY_PER_NODE * num_nodes; + + self.algebra.charge_execution(cost) + } } impl AptosGasMeter for StandardGasMeter diff --git a/aptos-move/aptos-gas-profiling/src/aggregate.rs b/aptos-move/aptos-gas-profiling/src/aggregate.rs index b57856f00774f..c1c903cbd86a3 100644 --- a/aptos-move/aptos-gas-profiling/src/aggregate.rs +++ b/aptos-move/aptos-gas-profiling/src/aggregate.rs @@ -100,6 +100,7 @@ impl ExecutionAndIOCosts { ty, cost, } => insert_or_add(&mut storage_reads, format!("{}", ty), *cost), + CreateTy { cost } => insert_or_add(&mut ops, "create_ty".to_string(), *cost), } } diff --git a/aptos-move/aptos-gas-profiling/src/erased.rs b/aptos-move/aptos-gas-profiling/src/erased.rs index 048c864a0c046..b9a107e3a23f1 100644 --- a/aptos-move/aptos-gas-profiling/src/erased.rs +++ b/aptos-move/aptos-gas-profiling/src/erased.rs @@ -151,6 +151,7 @@ impl ExecutionGasEvent { LoadResource { addr, ty, cost } => { Node::new(format!("load<{}::{}>", Render(addr), ty), *cost) }, + CreateTy { cost } => Node::new("create_ty", *cost), } } } diff --git a/aptos-move/aptos-gas-profiling/src/flamegraph.rs b/aptos-move/aptos-gas-profiling/src/flamegraph.rs index 4271d35f22bd4..76753a402ab4d 100644 --- a/aptos-move/aptos-gas-profiling/src/flamegraph.rs +++ b/aptos-move/aptos-gas-profiling/src/flamegraph.rs @@ -119,7 +119,7 @@ impl ExecutionAndIOCosts { match event { Loc(_) => (), - Bytecode { cost, .. } => frame_cost += *cost, + Bytecode { cost, .. } | CreateTy { cost } => frame_cost += *cost, Call(inner_frame) => self.visit(inner_frame), CallNative { module_id: module, diff --git a/aptos-move/aptos-gas-profiling/src/log.rs b/aptos-move/aptos-gas-profiling/src/log.rs index 3d1ee8c3a27a9..2bde37a2fe300 100644 --- a/aptos-move/aptos-gas-profiling/src/log.rs +++ b/aptos-move/aptos-gas-profiling/src/log.rs @@ -35,6 +35,9 @@ pub enum ExecutionGasEvent { ty: TypeTag, cost: InternalGas, }, + CreateTy { + cost: InternalGas, + }, } /// An enum representing the name of a call frame. @@ -210,7 +213,7 @@ impl ExecutionAndIOCosts { } pub(crate) fn assert_consistency(&self) { - use ExecutionGasEvent::{Bytecode, Call, CallNative, LoadResource, Loc}; + use ExecutionGasEvent::{Bytecode, Call, CallNative, CreateTy, LoadResource, Loc}; let mut total = InternalGas::zero(); @@ -219,9 +222,10 @@ impl ExecutionAndIOCosts { for op in self.gas_events() { match op { Loc(..) | Call(..) => (), - Bytecode { cost, .. } | CallNative { cost, .. } | LoadResource { cost, .. } => { - total += *cost - }, + Bytecode { cost, .. } + | CallNative { cost, .. } + | LoadResource { cost, .. } + | CreateTy { cost, .. } => total += *cost, } } diff --git a/aptos-move/aptos-gas-profiling/src/profiler.rs b/aptos-move/aptos-gas-profiling/src/profiler.rs index ed503c439f797..7b833d922aec9 100644 --- a/aptos-move/aptos-gas-profiling/src/profiler.rs +++ b/aptos-move/aptos-gas-profiling/src/profiler.rs @@ -5,8 +5,8 @@ use crate::log::{ CallFrame, EventStorage, ExecutionAndIOCosts, ExecutionGasEvent, FrameName, StorageFees, TransactionGasLog, WriteOpType, WriteStorage, WriteTransient, }; -use aptos_gas_algebra::{Fee, FeePerGasUnit, InternalGas, NumArgs, NumBytes}; -use aptos_gas_meter::AptosGasMeter; +use aptos_gas_algebra::{Fee, FeePerGasUnit, InternalGas, NumArgs, NumBytes, NumTypeNodes}; +use aptos_gas_meter::{AptosGasMeter, GasAlgebra}; use aptos_types::{state_store::state_key::StateKey, write_set::WriteOpSize}; use aptos_vm_types::{ change_set::VMChangeSet, resolver::ExecutorView, storage::space_pricing::ChargeAndRefund, @@ -32,7 +32,6 @@ pub struct GasProfiler { base: G, intrinsic_cost: Option, - total_exec_io: InternalGas, frames: Vec, write_set_transient: Vec, storage_fees: Option, @@ -86,7 +85,6 @@ impl GasProfiler { base, intrinsic_cost: None, - total_exec_io: 0.into(), frames: vec![CallFrame::new_script()], write_set_transient: vec![], storage_fees: None, @@ -103,7 +101,6 @@ impl GasProfiler { base, intrinsic_cost: None, - total_exec_io: 0.into(), frames: vec![CallFrame::new_function(module_id, func_name, ty_args)], write_set_transient: vec![], storage_fees: None, @@ -120,16 +117,6 @@ where } fn record_gas_event(&mut self, event: ExecutionGasEvent) { - use ExecutionGasEvent::*; - - match &event { - Loc(..) => (), - Call(..) => unreachable!("call frames are handled separately"), - Bytecode { cost, .. } | CallNative { cost, .. } | LoadResource { cost, .. } => { - self.total_exec_io += *cost; - }, - } - self.active_event_stream().push(event); } @@ -321,6 +308,14 @@ where let (cost, res) = self.delegate_charge(|base| base.charge_native_function(amount, ret_vals)); + // Whenever a function gets called, the VM will notify the gas profiler + // via `charge_call/charge_call_generic`. + // + // At this point of time, the gas profiler does not yet have an efficient way to determine + // whether the function is a native or not, so it will blindly create a new frame. + // + // Later when it realizes the function is native, it will transform the original frame + // into a native-specific event that does not contain recursive structures. let cur = self.frames.pop().expect("frame must exist"); let (module_id, name, ty_args) = match cur.name { FrameName::Function { @@ -330,6 +325,11 @@ where } => (module_id, name, ty_args), FrameName::Script => unreachable!(), }; + // The following line of code is needed for correctness. + // + // This is because additional gas events may be produced after the frame has been + // created and these events need to be preserved. + self.active_event_stream().extend(cur.events); self.record_gas_event(ExecutionGasEvent::CallNative { module_id, @@ -458,6 +458,14 @@ where res } + + fn charge_create_ty(&mut self, num_nodes: NumTypeNodes) -> PartialVMResult<()> { + let (cost, res) = self.delegate_charge(|base| base.charge_create_ty(num_nodes)); + + self.record_gas_event(ExecutionGasEvent::CreateTy { cost }); + + res + } } fn write_op_type(op: &WriteOpSize) -> WriteOpType { @@ -494,7 +502,6 @@ where fn charge_io_gas_for_write(&mut self, key: &StateKey, op: &WriteOpSize) -> VMResult<()> { let (cost, res) = self.delegate_charge(|base| base.charge_io_gas_for_write(key, op)); - self.total_exec_io += cost; self.write_set_transient.push(WriteTransient { key: key.clone(), cost, @@ -588,7 +595,6 @@ where self.delegate_charge(|base| base.charge_intrinsic_gas_for_transaction(txn_size)); self.intrinsic_cost = Some(cost); - self.total_exec_io += cost; res } @@ -607,7 +613,7 @@ where let exec_io = ExecutionAndIOCosts { gas_scaling_factor: self.base.gas_unit_scaling_factor(), - total: self.total_exec_io, + total: self.algebra().execution_gas_used() + self.algebra().io_gas_used(), intrinsic_cost: self.intrinsic_cost.unwrap_or_else(|| 0.into()), call_graph: self.frames.pop().expect("frame must exist"), write_set_transient: self.write_set_transient, diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs index d64d87e167d13..8f5b6ea6bd19d 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/instr.rs @@ -6,6 +6,7 @@ use crate::gas_schedule::VMGasParameters; use aptos_gas_algebra::{ InternalGas, InternalGasPerAbstractValueUnit, InternalGasPerArg, InternalGasPerByte, + InternalGasPerTypeNode, }; crate::gas_schedule::macros::define_gas_parameters!( @@ -125,5 +126,6 @@ crate::gas_schedule::macros::define_gas_parameters!( [vec_pack_per_elem: InternalGasPerArg, "vec_pack.per_elem", 147], [vec_unpack_base: InternalGas, "vec_unpack.base", 1838], [vec_unpack_per_expected_elem: InternalGasPerArg, "vec_unpack.per_expected_elem", 147], + [subst_ty_per_node: InternalGasPerTypeNode, { 13.. => "subst_ty_per_node" }, 400], ] ); diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index c94a1877bde2f..cdc53b4a356cc 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -199,7 +199,7 @@ crate::gas_schedule::macros::define_gas_parameters!( max_storage_fee: Fee, { 7.. => "max_storage_fee" }, 2_0000_0000, // 2 APT - ] + ], ] ); diff --git a/aptos-move/aptos-memory-usage-tracker/src/lib.rs b/aptos-move/aptos-memory-usage-tracker/src/lib.rs index 3a4ac1895532c..8b4a4b7e35394 100644 --- a/aptos-move/aptos-memory-usage-tracker/src/lib.rs +++ b/aptos-move/aptos-memory-usage-tracker/src/lib.rs @@ -1,7 +1,9 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use aptos_gas_algebra::{AbstractValueSize, Fee, FeePerGasUnit, InternalGas, NumArgs, NumBytes}; +use aptos_gas_algebra::{ + AbstractValueSize, Fee, FeePerGasUnit, InternalGas, NumArgs, NumBytes, NumTypeNodes, +}; use aptos_gas_meter::AptosGasMeter; use aptos_types::{ account_config::CORE_CODE_ADDRESS, state_store::state_key::StateKey, write_set::WriteOpSize, @@ -156,6 +158,8 @@ where ) -> PartialVMResult<()>; fn charge_vec_swap(&mut self, ty: impl TypeView) -> PartialVMResult<()>; + + fn charge_create_ty(&mut self, num_nodes: NumTypeNodes) -> PartialVMResult<()>; } #[inline] diff --git a/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_16.mvir b/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_16.mvir new file mode 100644 index 0000000000000..03bb43d6d731b --- /dev/null +++ b/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_16.mvir @@ -0,0 +1,30 @@ +module 0x1.M { + struct Foo has drop { x: u64 } + + f() { + label entry: + return; + } + + run_impl() { + let i: u64; + + label entry: + i = 0; + label loop_start: + jump_if_false (copy(i) < 1000) loop_end; + + Self.f>(); + + i = move(i) + 1; + jump loop_start; + label loop_end: + return; + } + + run() { + label entry: + Self.run_impl(); + return; + } +} \ No newline at end of file diff --git a/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_8.mvir b/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_8.mvir new file mode 100644 index 0000000000000..33748386cc77e --- /dev/null +++ b/aptos-move/aptos-vm-profiling/move/subst_ty/node_size_8.mvir @@ -0,0 +1,30 @@ +module 0x1.M { + struct Foo has drop { x: u64 } + + f() { + label entry: + return; + } + + run_impl() { + let i: u64; + + label entry: + i = 0; + label loop_start: + jump_if_false (copy(i) < 1000) loop_end; + + Self.f>(); + + i = move(i) + 1; + jump loop_start; + label loop_end: + return; + } + + run() { + label entry: + Self.run_impl(); + return; + } +} \ No newline at end of file diff --git a/aptos-move/e2e-move-tests/src/tests/smart_data_structures.rs b/aptos-move/e2e-move-tests/src/tests/smart_data_structures.rs index edf46d75f210e..4f6df2e67d168 100644 --- a/aptos-move/e2e-move-tests/src/tests/smart_data_structures.rs +++ b/aptos-move/e2e-move-tests/src/tests/smart_data_structures.rs @@ -12,6 +12,8 @@ use aptos_types::account_address::AccountAddress; #[test] fn test_smart_data_structures_gas() { let mut h = MoveHarness::new(); + // This test uses a lot of execution gas so the upper bound need to be bumped to accommodate it. + h.modify_gas_schedule(|params| params.vm.txn.max_execution_gas = 40_000_000_000.into()); // Load the code let acc = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap()); assert_success!(h.publish_package(&acc, &common::test_dir_path("smart_data_structures.data"))); diff --git a/aptos-move/e2e-move-tests/src/tests/storage_refund.rs b/aptos-move/e2e-move-tests/src/tests/storage_refund.rs index f0ea30d96da20..c0925645e88ea 100644 --- a/aptos-move/e2e-move-tests/src/tests/storage_refund.rs +++ b/aptos-move/e2e-move-tests/src/tests/storage_refund.rs @@ -23,8 +23,10 @@ fn test_refunds() { ], vec![], ); + // Note: This test uses a lot of execution gas so we need to bump the limit in order for it + // to pass. h.modify_gas_schedule(|params| { - params.vm.txn.max_execution_gas = 10_000_000_000.into(); + params.vm.txn.max_execution_gas = 40_000_000_000.into(); params.vm.txn.storage_fee_per_state_byte = 0.into(); // tested in DiskSpacePricing. }); let mod_addr = AccountAddress::from_hex_literal("0xcafe").unwrap(); diff --git a/aptos-move/e2e-tests/goldens/language_e2e_testsuite__tests__create_account__create_account.exp b/aptos-move/e2e-tests/goldens/language_e2e_testsuite__tests__create_account__create_account.exp index a8a3c51458f67..a0dc53ff7e01d 100644 --- a/aptos-move/e2e-tests/goldens/language_e2e_testsuite__tests__create_account__create_account.exp +++ b/aptos-move/e2e-tests/goldens/language_e2e_testsuite__tests__create_account__create_account.exp @@ -23,9 +23,9 @@ Ok( ), events: [ ContractEvent { key: EventKey { creation_number: 0, account_address: f5b9d6f01a99e74c790e2f330c092fa05455a8193f1dfc1b113ecc54d067afe1 }, index: 0, type: Struct(StructTag { address: 0000000000000000000000000000000000000000000000000000000000000001, module: Identifier("account"), name: Identifier("CoinRegisterEvent"), type_params: [] }), event_data: "00000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e" }, - ModuleEvent { type: Struct(StructTag { address: 0000000000000000000000000000000000000000000000000000000000000001, module: Identifier("transaction_fee"), name: Identifier("FeeStatement"), type_params: [] }), event_data: "04000000000000000400000000000000010000000000000000000000000000000000000000000000" }, + ModuleEvent { type: Struct(StructTag { address: 0000000000000000000000000000000000000000000000000000000000000001, module: Identifier("transaction_fee"), name: Identifier("FeeStatement"), type_params: [] }), event_data: "05000000000000000400000000000000010000000000000000000000000000000000000000000000" }, ], - gas_used: 4, + gas_used: 5, status: Keep( Success, ), diff --git a/testsuite/single_node_performance.py b/testsuite/single_node_performance.py index 8267b906e4c8b..338519a71a14c 100755 --- a/testsuite/single_node_performance.py +++ b/testsuite/single_node_performance.py @@ -108,7 +108,7 @@ class RunGroupConfig: RunGroupConfig(expected_tps=1000, key=RunGroupKey("vector-picture40", module_working_set_size=20), included_in=Flow(0), waived=True), RunGroupConfig(expected_tps=165, key=RunGroupKey("vector-picture30k"), included_in=LAND_BLOCKING_AND_C), RunGroupConfig(expected_tps=995, key=RunGroupKey("vector-picture30k", module_working_set_size=20), included_in=Flow.CONTINUOUS), - RunGroupConfig(expected_tps=17, key=RunGroupKey("smart-table-picture30-k-with200-change"), included_in=LAND_BLOCKING_AND_C), + RunGroupConfig(expected_tps=22, key=RunGroupKey("smart-table-picture30-k-with200-change"), included_in=LAND_BLOCKING_AND_C), RunGroupConfig(expected_tps=86, key=RunGroupKey("smart-table-picture30-k-with200-change", module_working_set_size=20), included_in=Flow.CONTINUOUS), # RunGroupConfig(expected_tps=3.6, key=RunGroupKey("smart-table-picture1-m-with1-k-change"), included_in=LAND_BLOCKING_AND_C), # RunGroupConfig(expected_tps=12.8, key=RunGroupKey("smart-table-picture1-m-with1-k-change", module_working_set_size=20), included_in=Flow.CONTINUOUS), diff --git a/third_party/move/move-binary-format/src/file_format.rs b/third_party/move/move-binary-format/src/file_format.rs index 5f1611b0737b6..fcf7400a5c392 100644 --- a/third_party/move/move-binary-format/src/file_format.rs +++ b/third_party/move/move-binary-format/src/file_format.rs @@ -1260,6 +1260,10 @@ impl SignatureToken { stack: vec![(self, 1)], } } + + pub fn num_nodes(&self) -> usize { + self.preorder_traversal().count() + } } /// A `Constant` is a serialized value along with its type. That type will be deserialized by the diff --git a/third_party/move/move-core/types/src/gas_algebra.rs b/third_party/move/move-core/types/src/gas_algebra.rs index d64a084724820..5a2cb0cc89011 100644 --- a/third_party/move/move-core/types/src/gas_algebra.rs +++ b/third_party/move/move-core/types/src/gas_algebra.rs @@ -37,6 +37,9 @@ pub enum AbstractMemoryUnit {} /// Unit for counting arguments. pub enum Arg {} +/// Unit for counting the number of nodes in a type. +pub enum TypeNode {} + /// A derived unit resulted from the division of two given units. /// This is used to permit type-safe multiplications. /// @@ -77,6 +80,10 @@ pub type InternalGasPerAbstractMemoryUnit = pub type InternalGasPerArg = GasQuantity>; +pub type InternalGasPerTypeNode = GasQuantity>; + +pub type NumTypeNodes = GasQuantity; + /*************************************************************************************************** * Get Unit * diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs index 7aab579f2cd52..f0d40b76b12aa 100644 --- a/third_party/move/move-vm/runtime/src/interpreter.rs +++ b/third_party/move/move-vm/runtime/src/interpreter.rs @@ -12,11 +12,14 @@ use crate::{ use fail::fail_point; use move_binary_format::{ errors::*, - file_format::{Ability, AbilitySet, Bytecode, FunctionHandleIndex, FunctionInstantiationIndex}, + file_format::{ + Ability, AbilitySet, Bytecode, FieldInstantiationIndex, FunctionHandleIndex, + FunctionInstantiationIndex, SignatureIndex, StructDefInstantiationIndex, + }, }; use move_core_types::{ account_address::AccountAddress, - gas_algebra::{NumArgs, NumBytes}, + gas_algebra::{NumArgs, NumBytes, NumTypeNodes}, language_storage::TypeTag, vm_status::{StatusCode, StatusType}, }; @@ -31,7 +34,12 @@ use move_vm_types::{ }, views::TypeView, }; -use std::{cmp::min, collections::VecDeque, fmt::Write, sync::Arc}; +use std::{ + cmp::min, + collections::{BTreeMap, VecDeque}, + fmt::Write, + sync::Arc, +}; macro_rules! set_err_info { ($frame:ident, $e:expr) => {{ @@ -125,7 +133,7 @@ impl Interpreter { } let mut current_frame = self - .make_new_frame(loader, module_store, function, ty_args, locals) + .make_new_frame(gas_meter, loader, module_store, function, ty_args, locals) .map_err(|err| self.set_location(err))?; loop { let resolver = current_frame.resolver(loader, module_store); @@ -196,7 +204,7 @@ impl Interpreter { continue; } let frame = self - .make_call_frame(loader, module_store, func, vec![]) + .make_call_frame(gas_meter, loader, module_store, func, vec![]) .map_err(|e| self.set_location(e)) .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?; self.call_stack.push(current_frame).map_err(|frame| { @@ -208,9 +216,8 @@ impl Interpreter { current_frame = frame; }, ExitCode::CallGeneric(idx) => { - // TODO(Gas): We should charge gas as we do type substitution... let ty_args = resolver - .instantiate_generic_function(idx, current_frame.ty_args()) + .instantiate_generic_function(Some(gas_meter), idx, current_frame.ty_args()) .map_err(|e| set_err_info!(current_frame, e))?; let func = resolver .function_from_instantiation(idx) @@ -248,7 +255,7 @@ impl Interpreter { continue; } let frame = self - .make_call_frame(loader, module_store, func, ty_args) + .make_call_frame(gas_meter, loader, module_store, func, ty_args) .map_err(|e| self.set_location(e)) .map_err(|err| self.maybe_core_dump(err, ¤t_frame))?; self.call_stack.push(current_frame).map_err(|frame| { @@ -269,6 +276,7 @@ impl Interpreter { /// function are incorrectly attributed to the caller. fn make_call_frame( &mut self, + gas_meter: &mut impl GasMeter, loader: &Loader, module_store: &ModuleStorageAdapter, func: Arc, @@ -277,6 +285,7 @@ impl Interpreter { let mut locals = Locals::new(func.local_count()); let arg_count = func.arg_count(); let is_generic = !ty_args.is_empty(); + for i in 0..arg_count { locals.store_loc( arg_count - i - 1, @@ -299,7 +308,7 @@ impl Interpreter { } } } - self.make_new_frame(loader, module_store, func, ty_args, locals) + self.make_new_frame(gas_meter, loader, module_store, func, ty_args, locals) } /// Create a new `Frame` given a `Function` and the function `Locals`. @@ -307,12 +316,18 @@ impl Interpreter { /// The locals must be loaded before calling this. fn make_new_frame( &self, + gas_meter: &mut impl GasMeter, loader: &Loader, module_store: &ModuleStorageAdapter, function: Arc, ty_args: Vec, locals: Locals, ) -> PartialVMResult { + for ty in function.local_types() { + gas_meter + .charge_create_ty(NumTypeNodes::new(ty.num_nodes_in_subst(&ty_args)? as u64))?; + } + let local_tys = if self.paranoid_type_checks { if ty_args.is_empty() { function.local_types().to_vec() @@ -333,6 +348,7 @@ impl Interpreter { function, ty_args, local_tys, + ty_cache: FrameTypeCache::default(), }) } @@ -1101,6 +1117,19 @@ struct Frame { function: Arc, ty_args: Vec, local_tys: Vec, + ty_cache: FrameTypeCache, +} + +#[derive(Default)] +struct FrameTypeCache { + struct_field_type_instantiation: + BTreeMap>, + struct_def_instantiation_type: BTreeMap, + /// For a given field instantiation, the: + /// ((Type of the field, size of the field type) and (Type of its defining struct, size of its defining struct) + field_instantiation: + BTreeMap, + single_sig_token_type: BTreeMap, } /// An `ExitCode` from `execute_code_unit`. @@ -1123,6 +1152,103 @@ fn check_ability(has_ability: bool) -> PartialVMResult<()> { } } +impl FrameTypeCache { + #[inline(always)] + fn get_or( + map: &mut BTreeMap, + idx: K, + ty_func: F, + ) -> PartialVMResult<&V> + where + F: FnOnce(K) -> PartialVMResult, + { + match map.entry(idx) { + std::collections::btree_map::Entry::Occupied(entry) => Ok(entry.into_mut()), + std::collections::btree_map::Entry::Vacant(entry) => { + let v = ty_func(idx)?; + Ok(entry.insert(v)) + }, + } + } + + #[inline(always)] + fn get_field_type_and_struct_type( + &mut self, + idx: FieldInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<((&Type, NumTypeNodes), (&Type, NumTypeNodes))> { + let ((field_ty, field_type_count), (struct_ty, struct_type_count)) = + Self::get_or(&mut self.field_instantiation, idx, |idx| { + let struct_type = resolver.field_instantiation_to_struct(idx, ty_args)?; + let struct_type_count = NumTypeNodes::new(struct_type.num_nodes() as u64); + let field_type = resolver.get_field_type_generic(idx, ty_args)?; + let field_type_count = NumTypeNodes::new(field_type.num_nodes() as u64); + Ok(( + (field_type, field_type_count), + (struct_type, struct_type_count), + )) + })?; + Ok(( + (field_ty, *field_type_count), + (struct_ty, *struct_type_count), + )) + } + + #[inline(always)] + fn get_struct_type( + &mut self, + idx: StructDefInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<(&Type, NumTypeNodes)> { + let (ty, ty_count) = Self::get_or(&mut self.struct_def_instantiation_type, idx, |idx| { + let ty = resolver.get_struct_type_generic(idx, ty_args)?; + let ty_count = NumTypeNodes::new(ty.num_nodes() as u64); + Ok((ty, ty_count)) + })?; + Ok((ty, *ty_count)) + } + + #[inline(always)] + fn get_struct_fields_types( + &mut self, + idx: StructDefInstantiationIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<&[(Type, NumTypeNodes)]> { + Ok(Self::get_or( + &mut self.struct_field_type_instantiation, + idx, + |idx| { + Ok(resolver + .instantiate_generic_struct_fields(idx, ty_args)? + .into_iter() + .map(|ty| { + let num_nodes = NumTypeNodes::new(ty.num_nodes() as u64); + (ty, num_nodes) + }) + .collect::>()) + }, + )?) + } + + #[inline(always)] + fn get_signature_index_type( + &mut self, + idx: SignatureIndex, + resolver: &Resolver, + ty_args: &[Type], + ) -> PartialVMResult<(&Type, NumTypeNodes)> { + let (ty, ty_count) = Self::get_or(&mut self.single_sig_token_type, idx, |idx| { + let ty = resolver.instantiate_single_type(idx, ty_args)?; + let ty_count = NumTypeNodes::new(ty.num_nodes() as u64); + Ok((ty, ty_count)) + })?; + Ok((ty, *ty_count)) + } +} + impl Frame { /// Execute a Move function until a return or a call opcode is found. fn execute_code( @@ -1265,6 +1391,7 @@ impl Frame { ty_args: &[Type], resolver: &Resolver, interpreter: &mut Interpreter, + ty_cache: &mut FrameTypeCache, instruction: &Bytecode, ) -> PartialVMResult<()> { match instruction { @@ -1337,22 +1464,24 @@ impl Frame { )))?; }, Bytecode::ImmBorrowFieldGeneric(idx) => { - let expected_ty = resolver.field_instantiation_to_struct(*idx, ty_args)?; + let ((expected_field_ty, _), (expected_struct_ty, _)) = + ty_cache.get_field_type_and_struct_type(*idx, resolver, ty_args)?; let top_ty = interpreter.operand_stack.pop_ty()?; - top_ty.check_ref_eq(&expected_ty)?; - interpreter.operand_stack.push_ty(Type::Reference(Box::new( - resolver.get_field_type_generic(*idx, ty_args)?, - )))?; + top_ty.check_ref_eq(expected_struct_ty)?; + interpreter + .operand_stack + .push_ty(Type::Reference(Box::new(expected_field_ty.clone())))?; }, Bytecode::MutBorrowFieldGeneric(idx) => { - let expected_ty = resolver.field_instantiation_to_struct(*idx, ty_args)?; + let ((expected_field_ty, _), (expected_struct_ty, _)) = + ty_cache.get_field_type_and_struct_type(*idx, resolver, ty_args)?; let top_ty = interpreter.operand_stack.pop_ty()?; - top_ty.check_eq(&Type::MutableReference(Box::new(expected_ty)))?; + top_ty.check_eq(&Type::MutableReference(Box::new( + expected_struct_ty.clone(), + )))?; interpreter .operand_stack - .push_ty(Type::MutableReference(Box::new( - resolver.get_field_type_generic(*idx, ty_args)?, - )))?; + .push_ty(Type::MutableReference(Box::new(expected_field_ty.clone())))?; }, Bytecode::Pack(idx) => { let field_count = resolver.field_count(*idx); @@ -1392,8 +1521,8 @@ impl Frame { }, Bytecode::PackGeneric(idx) => { let field_count = resolver.field_instantiation_count(*idx); - let args_ty = resolver.instantiate_generic_struct_fields(*idx, ty_args)?; - let output_ty = resolver.get_struct_type_generic(*idx, ty_args)?; + let output_ty = ty_cache.get_struct_type(*idx, resolver, ty_args)?.0.clone(); + let args_ty = ty_cache.get_struct_fields_types(*idx, resolver, ty_args)?; let ability = output_ty.abilities()?; // If the struct has a key ability, we expects all of its field to have store ability but not key ability. @@ -1412,7 +1541,7 @@ impl Frame { ); } - for (ty, expected_ty) in interpreter + for (ty, (expected_ty, _)) in interpreter .operand_stack .popn_tys(field_count)? .into_iter() @@ -1436,10 +1565,12 @@ impl Frame { }, Bytecode::UnpackGeneric(idx) => { let struct_ty = interpreter.operand_stack.pop_ty()?; - struct_ty.check_eq(&resolver.get_struct_type_generic(*idx, ty_args)?)?; - let struct_decl = resolver.instantiate_generic_struct_fields(*idx, ty_args)?; - for ty in struct_decl.into_iter() { + struct_ty.check_eq(ty_cache.get_struct_type(*idx, resolver, ty_args)?.0)?; + + let struct_fields_types = + ty_cache.get_struct_fields_types(*idx, resolver, ty_args)?; + for (ty, _) in struct_fields_types { interpreter.operand_stack.push_ty(ty.clone())?; } }, @@ -1576,7 +1707,7 @@ impl Frame { .operand_stack .pop_ty()? .check_eq(&Type::Address)?; - let ty = resolver.get_struct_type_generic(*idx, ty_args)?; + let ty = ty_cache.get_struct_type(*idx, resolver, ty_args)?.0.clone(); check_ability(ty.abilities()?.has_key())?; interpreter .operand_stack @@ -1587,7 +1718,7 @@ impl Frame { .operand_stack .pop_ty()? .check_eq(&Type::Address)?; - let ty = resolver.get_struct_type_generic(*idx, ty_args)?; + let ty = ty_cache.get_struct_type(*idx, resolver, ty_args)?.0.clone(); check_ability(ty.abilities()?.has_key())?; interpreter .operand_stack @@ -1615,7 +1746,7 @@ impl Frame { .operand_stack .pop_ty()? .check_eq(&Type::Reference(Box::new(Type::Signer)))?; - ty.check_eq(&resolver.get_struct_type_generic(*idx, ty_args)?)?; + ty.check_eq(ty_cache.get_struct_type(*idx, resolver, ty_args)?.0)?; check_ability(ty.abilities()?.has_key())?; }, Bytecode::MoveFrom(idx) => { @@ -1632,7 +1763,7 @@ impl Frame { .operand_stack .pop_ty()? .check_eq(&Type::Address)?; - let ty = resolver.get_struct_type_generic(*idx, ty_args)?; + let ty = ty_cache.get_struct_type(*idx, resolver, ty_args)?.0.clone(); check_ability(ty.abilities()?.has_key())?; interpreter.operand_stack.push_ty(ty)?; }, @@ -1655,67 +1786,67 @@ impl Frame { interpreter.operand_stack.push_ty(Type::Bool)?; }, Bytecode::VecPack(si, num) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; let elem_tys = interpreter.operand_stack.popn_tys(*num as u16)?; for elem_ty in elem_tys.iter() { - elem_ty.check_eq(&ty)?; + elem_ty.check_eq(ty)?; } interpreter .operand_stack - .push_ty(Type::Vector(triomphe::Arc::new(ty)))?; + .push_ty(Type::Vector(triomphe::Arc::new(ty.clone())))?; }, Bytecode::VecLen(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, false)?; + .check_vec_ref(ty, false)?; interpreter.operand_stack.push_ty(Type::U64)?; }, Bytecode::VecImmBorrow(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; interpreter.operand_stack.pop_ty()?.check_eq(&Type::U64)?; let inner_ty = interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, false)?; + .check_vec_ref(ty, false)?; interpreter .operand_stack .push_ty(Type::Reference(Box::new(inner_ty)))?; }, Bytecode::VecMutBorrow(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; interpreter.operand_stack.pop_ty()?.check_eq(&Type::U64)?; let inner_ty = interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, true)?; + .check_vec_ref(ty, true)?; interpreter .operand_stack .push_ty(Type::MutableReference(Box::new(inner_ty)))?; }, Bytecode::VecPushBack(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; - interpreter.operand_stack.pop_ty()?.check_eq(&ty)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; + interpreter.operand_stack.pop_ty()?.check_eq(ty)?; interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, true)?; + .check_vec_ref(ty, true)?; }, Bytecode::VecPopBack(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; let inner_ty = interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, true)?; + .check_vec_ref(ty, true)?; interpreter.operand_stack.push_ty(inner_ty)?; }, Bytecode::VecUnpack(si, num) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; let vec_ty = interpreter.operand_stack.pop_ty()?; match vec_ty { Type::Vector(v) => { - v.check_eq(&ty)?; + v.check_eq(ty)?; for _ in 0..*num { interpreter.operand_stack.push_ty(v.as_ref().clone())?; } @@ -1729,13 +1860,13 @@ impl Frame { }; }, Bytecode::VecSwap(si) => { - let ty = resolver.instantiate_single_type(*si, ty_args)?; + let (ty, _) = ty_cache.get_signature_index_type(*si, resolver, ty_args)?; interpreter.operand_stack.pop_ty()?.check_eq(&Type::U64)?; interpreter.operand_stack.pop_ty()?.check_eq(&Type::U64)?; interpreter .operand_stack .pop_ty()? - .check_vec_ref(&ty, true)?; + .check_vec_ref(ty, true)?; }, } Ok(()) @@ -1858,6 +1989,11 @@ impl Frame { }, Bytecode::LdConst(idx) => { let constant = resolver.constant_at(*idx); + + gas_meter.charge_create_ty(NumTypeNodes::new( + constant.type_.num_nodes() as u64, + ))?; + gas_meter.charge_ld_const(NumBytes::new(constant.data.len() as u64))?; let val = Value::deserialize_constant(constant).ok_or_else(|| { @@ -1941,6 +2077,17 @@ impl Frame { }, Bytecode::ImmBorrowFieldGeneric(fi_idx) | Bytecode::MutBorrowFieldGeneric(fi_idx) => { + // TODO: Even though the types are not needed for execution, we still + // instantiate them for gas metering. + // + // This is a bit wasteful since the newly created types are + // dropped immediately. + let ((_, field_ty_count), (_, struct_ty_count)) = self + .ty_cache + .get_field_type_and_struct_type(*fi_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(struct_ty_count)?; + gas_meter.charge_create_ty(field_ty_count)?; + let instr = match instruction { Bytecode::MutBorrowFieldGeneric(_) => S::MutBorrowFieldGeneric, _ => S::ImmBorrowFieldGeneric, @@ -1967,9 +2114,28 @@ impl Frame { .push(Value::struct_(Struct::pack(args)))?; }, Bytecode::PackGeneric(si_idx) => { + // TODO: Even though the types are not needed for execution, we still + // instantiate them for gas metering. + // + // This is a bit wasteful since the newly created types are + // dropped immediately. + let field_tys = self.ty_cache.get_struct_fields_types( + *si_idx, + resolver, + &self.ty_args, + )?; + + for (_, ty_count) in field_tys { + gas_meter.charge_create_ty(*ty_count)?; + } + + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; + check_depth_of_type(resolver, ty)?; + let field_count = resolver.field_instantiation_count(*si_idx); - let ty = resolver.get_struct_type_generic(*si_idx, self.ty_args())?; - check_depth_of_type(resolver, &ty)?; gas_meter.charge_pack( true, interpreter.operand_stack.last_n(field_count as usize)?, @@ -1988,7 +2154,28 @@ impl Frame { interpreter.operand_stack.push(value)?; } }, - Bytecode::UnpackGeneric(_si_idx) => { + Bytecode::UnpackGeneric(si_idx) => { + // TODO: Even though the types are not needed for execution, we still + // instantiate them for gas metering. + // + // This is a bit wasteful since the newly created types are + // dropped immediately. + let ty_and_field_counts = self.ty_cache.get_struct_fields_types( + *si_idx, + resolver, + &self.ty_args, + )?; + for (_, ty_count) in ty_and_field_counts { + gas_meter.charge_create_ty(*ty_count)?; + } + + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; + + check_depth_of_type(resolver, ty)?; + let struct_ = interpreter.operand_stack.pop_as::()?; gas_meter.charge_unpack(true, struct_.field_views())?; @@ -2174,7 +2361,10 @@ impl Frame { | Bytecode::ImmBorrowGlobalGeneric(si_idx) => { let is_mut = matches!(instruction, Bytecode::MutBorrowGlobalGeneric(_)); let addr = interpreter.operand_stack.pop_as::()?; - let ty = resolver.get_struct_type_generic(*si_idx, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; interpreter.borrow_global( is_mut, true, @@ -2183,7 +2373,7 @@ impl Frame { module_store, gas_meter, addr, - &ty, + ty, )?; }, Bytecode::Exists(sd_idx) => { @@ -2201,7 +2391,10 @@ impl Frame { }, Bytecode::ExistsGeneric(si_idx) => { let addr = interpreter.operand_stack.pop_as::()?; - let ty = resolver.get_struct_type_generic(*si_idx, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; interpreter.exists( true, resolver.loader(), @@ -2209,7 +2402,7 @@ impl Frame { module_store, gas_meter, addr, - &ty, + ty, )?; }, Bytecode::MoveFrom(sd_idx) => { @@ -2227,7 +2420,10 @@ impl Frame { }, Bytecode::MoveFromGeneric(si_idx) => { let addr = interpreter.operand_stack.pop_as::()?; - let ty = resolver.get_struct_type_generic(*si_idx, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; interpreter.move_from( true, resolver.loader(), @@ -2235,7 +2431,7 @@ impl Frame { module_store, gas_meter, addr, - &ty, + ty, )?; }, Bytecode::MoveTo(sd_idx) => { @@ -2267,7 +2463,10 @@ impl Frame { .value_as::()? .read_ref()? .value_as::()?; - let ty = resolver.get_struct_type_generic(*si_idx, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_struct_type(*si_idx, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; interpreter.move_to( true, resolver.loader(), @@ -2275,7 +2474,7 @@ impl Frame { module_store, gas_meter, addr, - &ty, + ty, resource, )?; }, @@ -2293,19 +2492,25 @@ impl Frame { gas_meter.charge_simple_instr(S::Nop)?; }, Bytecode::VecPack(si, num) => { - let ty = resolver.instantiate_single_type(*si, self.ty_args())?; - check_depth_of_type(resolver, &ty)?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; + check_depth_of_type(resolver, ty)?; gas_meter.charge_vec_pack( - make_ty!(&ty), + make_ty!(ty), interpreter.operand_stack.last_n(*num as usize)?, )?; let elements = interpreter.operand_stack.popn(*num as u16)?; - let value = Vector::pack(&ty, elements)?; + let value = Vector::pack(ty, elements)?; interpreter.operand_stack.push(value)?; }, Bytecode::VecLen(si) => { let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; gas_meter.charge_vec_len(TypeWithLoader { ty, loader: resolver.loader(), @@ -2316,15 +2521,21 @@ impl Frame { Bytecode::VecImmBorrow(si) => { let idx = interpreter.operand_stack.pop_as::()? as usize; let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = resolver.instantiate_single_type(*si, self.ty_args())?; - let res = vec_ref.borrow_elem(idx, &ty); - gas_meter.charge_vec_borrow(false, make_ty!(&ty), res.is_ok())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; + let res = vec_ref.borrow_elem(idx, ty); + gas_meter.charge_vec_borrow(false, make_ty!(ty), res.is_ok())?; interpreter.operand_stack.push(res?)?; }, Bytecode::VecMutBorrow(si) => { let idx = interpreter.operand_stack.pop_as::()? as usize; let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; let res = vec_ref.borrow_elem(idx, ty); gas_meter.charge_vec_borrow(true, make_ty!(ty), res.is_ok())?; interpreter.operand_stack.push(res?)?; @@ -2332,20 +2543,29 @@ impl Frame { Bytecode::VecPushBack(si) => { let elem = interpreter.operand_stack.pop()?; let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; gas_meter.charge_vec_push_back(make_ty!(ty), &elem)?; vec_ref.push_back(elem, ty)?; }, Bytecode::VecPopBack(si) => { let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; let res = vec_ref.pop(ty); gas_meter.charge_vec_pop_back(make_ty!(ty), res.as_ref().ok())?; interpreter.operand_stack.push(res?)?; }, Bytecode::VecUnpack(si, num) => { let vec_val = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; gas_meter.charge_vec_unpack( make_ty!(ty), NumArgs::new(*num), @@ -2360,7 +2580,10 @@ impl Frame { let idx2 = interpreter.operand_stack.pop_as::()? as usize; let idx1 = interpreter.operand_stack.pop_as::()? as usize; let vec_ref = interpreter.operand_stack.pop_as::()?; - let ty = &resolver.instantiate_single_type(*si, self.ty_args())?; + let (ty, ty_count) = + self.ty_cache + .get_signature_index_type(*si, resolver, &self.ty_args)?; + gas_meter.charge_create_ty(ty_count)?; gas_meter.charge_vec_swap(make_ty!(ty))?; vec_ref.swap(idx1, idx2, ty)?; }, @@ -2371,6 +2594,7 @@ impl Frame { &self.ty_args, resolver, interpreter, + &mut self.ty_cache, instruction, )?; diff --git a/third_party/move/move-vm/runtime/src/loader/mod.rs b/third_party/move/move-vm/runtime/src/loader/mod.rs index 18ff625297b63..9dbc7741afa34 100644 --- a/third_party/move/move-vm/runtime/src/loader/mod.rs +++ b/third_party/move/move-vm/runtime/src/loader/mod.rs @@ -21,14 +21,18 @@ use move_binary_format::{ use move_bytecode_verifier::{self, cyclic_dependencies, dependencies}; use move_core_types::{ account_address::AccountAddress, + gas_algebra::NumTypeNodes, ident_str, identifier::IdentStr, language_storage::{ModuleId, StructTag, TypeTag}, value::{IdentifierMappingKind, MoveFieldLayout, MoveStructLayout, MoveTypeLayout}, vm_status::StatusCode, }; -use move_vm_types::loaded_data::runtime_types::{ - AbilityInfo, DepthFormula, StructIdentifier, StructNameIndex, StructType, Type, +use move_vm_types::{ + gas::GasMeter, + loaded_data::runtime_types::{ + AbilityInfo, DepthFormula, StructIdentifier, StructNameIndex, StructType, Type, + }, }; use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard}; use sha3::{Digest, Sha3_256}; @@ -1266,21 +1270,30 @@ impl<'a> Resolver<'a> { pub(crate) fn instantiate_generic_function( &self, + gas_meter: Option<&mut impl GasMeter>, idx: FunctionInstantiationIndex, - type_params: &[Type], + ty_args: &[Type], ) -> PartialVMResult> { let func_inst = match &self.binary { BinaryType::Module(module) => module.function_instantiation_at(idx.0), BinaryType::Script(script) => script.function_instantiation_at(idx.0), }; + + if let Some(gas_meter) = gas_meter { + for ty in &func_inst.instantiation { + gas_meter + .charge_create_ty(NumTypeNodes::new(ty.num_nodes_in_subst(ty_args)? as u64))?; + } + } + let mut instantiation = vec![]; for ty in &func_inst.instantiation { - instantiation.push(self.subst(ty, type_params)?); + instantiation.push(self.subst(ty, ty_args)?); } // Check if the function instantiation over all generics is larger // than MAX_TYPE_INSTANTIATION_NODES. let mut sum_nodes = 1u64; - for ty in type_params.iter().chain(instantiation.iter()) { + for ty in ty_args.iter().chain(instantiation.iter()) { sum_nodes = sum_nodes.saturating_add(self.loader.count_type_nodes(ty)); if sum_nodes > MAX_TYPE_INSTANTIATION_NODES { return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES)); @@ -1378,6 +1391,8 @@ impl<'a> Resolver<'a> { .iter() .map(|inst_ty| inst_ty.subst(ty_args)) .collect::>>()?; + + // TODO: Is this type substitution unbounded? field_instantiation.definition_struct_type.fields[field_instantiation.offset] .subst(&instantiation_types) } @@ -1408,6 +1423,7 @@ impl<'a> Resolver<'a> { .iter() .map(|inst_ty| inst_ty.subst(ty_args)) .collect::>>()?; + struct_type .fields .iter() @@ -1428,6 +1444,7 @@ impl<'a> Resolver<'a> { ty_args: &[Type], ) -> PartialVMResult { let ty = self.single_type_at(idx); + if !ty_args.is_empty() { self.subst(ty, ty_args) } else { @@ -1491,11 +1508,14 @@ impl<'a> Resolver<'a> { ) -> PartialVMResult { match &self.binary { BinaryType::Module(module) => { - let struct_ = &module.field_instantiations[idx.0 as usize].definition_struct_type; + let field_inst = &module.field_instantiations[idx.0 as usize]; + + let struct_ = &field_inst.definition_struct_type; + Ok(Type::StructInstantiation { idx: struct_.idx, ty_args: triomphe::Arc::new( - module.field_instantiations[idx.0 as usize] + field_inst .instantiation .iter() .map(|ty| ty.subst(args)) diff --git a/third_party/move/move-vm/test-utils/src/gas_schedule.rs b/third_party/move/move-vm/test-utils/src/gas_schedule.rs index bdac20e02b4f7..cbba922d3296c 100644 --- a/third_party/move/move-vm/test-utils/src/gas_schedule.rs +++ b/third_party/move/move-vm/test-utils/src/gas_schedule.rs @@ -20,7 +20,7 @@ use move_core_types::{ account_address::AccountAddress, gas_algebra::{ AbstractMemorySize, GasQuantity, InternalGas, InternalGasPerAbstractMemoryUnit, - InternalGasUnit, NumArgs, NumBytes, ToUnit, + InternalGasUnit, NumArgs, NumBytes, NumTypeNodes, ToUnit, }, language_storage::ModuleId, u256, @@ -505,6 +505,10 @@ impl<'b> GasMeter for GasStatus<'b> { ) -> PartialVMResult<()> { Ok(()) } + + fn charge_create_ty(&mut self, _num_nodes: NumTypeNodes) -> PartialVMResult<()> { + Ok(()) + } } pub fn new_from_instructions(mut instrs: Vec<(Bytecode, GasCost)>) -> CostTable { diff --git a/third_party/move/move-vm/types/src/gas.rs b/third_party/move/move-vm/types/src/gas.rs index 5cd0c01d776aa..38536102985f2 100644 --- a/third_party/move/move-vm/types/src/gas.rs +++ b/third_party/move/move-vm/types/src/gas.rs @@ -7,7 +7,7 @@ use move_binary_format::{ }; use move_core_types::{ account_address::AccountAddress, - gas_algebra::{InternalGas, NumArgs, NumBytes}, + gas_algebra::{InternalGas, NumArgs, NumBytes, NumTypeNodes}, language_storage::ModuleId, }; @@ -297,6 +297,8 @@ pub trait GasMeter { &mut self, locals: impl Iterator + Clone, ) -> PartialVMResult<()>; + + fn charge_create_ty(&mut self, num_nodes: NumTypeNodes) -> PartialVMResult<()>; } /// A dummy gas meter that does not meter anything. @@ -528,4 +530,8 @@ impl GasMeter for UnmeteredGasMeter { ) -> PartialVMResult<()> { Ok(()) } + + fn charge_create_ty(&mut self, _num_nodes: NumTypeNodes) -> PartialVMResult<()> { + Ok(()) + } } diff --git a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs index 8fd5d5d996d91..ebc4611524f92 100644 --- a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs +++ b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs @@ -16,7 +16,13 @@ use move_core_types::{ vm_status::StatusCode, }; use smallbitvec::SmallBitVec; -use std::{cmp::max, collections::BTreeMap, fmt::Debug}; +use smallvec::{smallvec, SmallVec}; +use std::{ + cell::RefCell, + cmp::max, + collections::{btree_map, BTreeMap}, + fmt::Debug, +}; use triomphe::Arc as TriompheArc; pub const TYPE_DEPTH_MAX: usize = 256; @@ -195,6 +201,48 @@ pub enum Type { U256, } +pub struct TypePreorderTraversalIter<'a> { + stack: SmallVec<[&'a Type; 32]>, +} + +impl<'a> Iterator for TypePreorderTraversalIter<'a> { + type Item = &'a Type; + + fn next(&mut self) -> Option { + use Type::*; + + match self.stack.pop() { + Some(ty) => { + match ty { + Signer + | Bool + | Address + | U8 + | U16 + | U32 + | U64 + | U128 + | U256 + | Struct { .. } + | TyParam(..) => (), + + Reference(ty) | MutableReference(ty) => { + self.stack.push(ty); + }, + + Vector(ty) => { + self.stack.push(ty); + }, + + StructInstantiation { ty_args, .. } => self.stack.extend(ty_args.iter().rev()), + } + Some(ty) + }, + None => None, + } + } +} + // Cache for the ability of struct. They will be ignored when comparing equality or Ord as they are just used for caching purpose. #[derive(Derivative)] #[derivative(Debug, Clone, Eq, Hash, PartialEq, Ord, PartialOrd)] @@ -458,4 +506,146 @@ impl Type { }, } } + + pub fn preorder_traversal(&self) -> TypePreorderTraversalIter<'_> { + TypePreorderTraversalIter { + stack: smallvec![self], + } + } + + /// Returns the number of nodes the type has. + /// + /// For example + /// - `u64` has one node + /// - `vector` has two nodes -- one for the vector and one for the element type u64. + /// - `Foo>` has 5 nodes. + pub fn num_nodes(&self) -> usize { + self.preorder_traversal().count() + } + + /// Calculates the number of nodes in the substituted type. + pub fn num_nodes_in_subst(&self, ty_args: &[Type]) -> PartialVMResult { + use Type::*; + + thread_local! { + static CACHE: RefCell> = RefCell::new(BTreeMap::new()); + } + + CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + cache.clear(); + let mut num_nodes_in_arg = |idx: usize| -> PartialVMResult { + Ok(match cache.entry(idx) { + btree_map::Entry::Occupied(entry) => *entry.into_mut(), + btree_map::Entry::Vacant(entry) => { + let ty = ty_args.get(idx).ok_or_else(|| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!( + "type substitution failed: index out of bounds -- len {} got {}", + ty_args.len(), + idx + )) + })?; + *entry.insert(ty.num_nodes()) + }, + }) + }; + + let mut n = 0; + for ty in self.preorder_traversal() { + match ty { + TyParam(idx) => { + n += num_nodes_in_arg(*idx as usize)?; + }, + Address + | Bool + | Signer + | U8 + | U16 + | U32 + | U64 + | U128 + | U256 + | Vector(..) + | Struct { .. } + | Reference(..) + | MutableReference(..) + | StructInstantiation { .. } => n += 1, + } + } + + Ok(n) + }) + } +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + fn struct_inst_for_test(ty_args: Vec) -> Type { + Type::StructInstantiation { + idx: StructNameIndex(0), + ability: AbilityInfo::struct_(AbilitySet::EMPTY), + ty_args: TriompheArc::new(ty_args), + } + } + + fn struct_for_test() -> Type { + Type::Struct { + idx: StructNameIndex(0), + ability: AbilityInfo::struct_(AbilitySet::EMPTY), + } + } + + #[test] + fn test_num_nodes_in_type() { + use Type::*; + + let cases = [ + (U8, 1), + (Vector(TriompheArc::new(U8)), 2), + (Vector(TriompheArc::new(Vector(TriompheArc::new(U8)))), 3), + (Reference(Box::new(Bool)), 2), + (TyParam(0), 1), + (struct_for_test(), 1), + (struct_inst_for_test(vec![U8, U8]), 3), + ( + struct_inst_for_test(vec![U8, struct_inst_for_test(vec![Bool, Bool, Bool]), U8]), + 7, + ), + ]; + + for (ty, expected) in cases { + assert_eq!(ty.num_nodes(), expected); + } + } + + #[test] + fn test_num_nodes_in_subst() { + use Type::*; + + let cases: Vec<(Type, Vec, usize)> = vec![ + (TyParam(0), vec![Bool], 1), + (TyParam(0), vec![Vector(TriompheArc::new(Bool))], 2), + (Bool, vec![], 1), + ( + struct_inst_for_test(vec![TyParam(0), TyParam(0)]), + vec![Vector(TriompheArc::new(Bool))], + 5, + ), + ( + struct_inst_for_test(vec![TyParam(0), TyParam(1)]), + vec![ + Vector(TriompheArc::new(Bool)), + Vector(TriompheArc::new(Vector(TriompheArc::new(Bool)))), + ], + 6, + ), + ]; + + for (ty, ty_args, expected) in cases { + assert_eq!(ty.num_nodes_in_subst(&ty_args).unwrap(), expected); + } + } }