Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions aptos-move/aptos-gas-profiling/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,11 @@ impl ExecutionAndIOCosts {
}

if total != self.total {
panic!(
"Execution & IO costs do not add up. Check if the gas meter & the gas profiler have been implemented correctly. From gas meter: {}. Calculated: {}.",
self.total, total
)
// Aosen: This panic is disabled because we are changing the gas meter.
// panic!(
// "Execution & IO costs do not add up. Check if the gas meter & the gas profiler have been implemented correctly. From gas meter: {}. Calculated: {}.",
// self.total, total
// )
}
}
}
Expand Down
166 changes: 137 additions & 29 deletions aptos-move/aptos-gas-profiling/src/profiler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;
use crate::log::{
CallFrame, Dependency, EventStorage, EventTransient, ExecutionAndIOCosts, ExecutionGasEvent,
FrameName, StorageFees, TransactionGasLog, WriteOpType, WriteStorage, WriteTransient,
Expand Down Expand Up @@ -41,6 +42,8 @@ pub struct GasProfiler<G> {
events_transient: Vec<EventTransient>,
write_set_transient: Vec<WriteTransient>,
storage_fees: Option<StorageFees>,
module_gas_usage: HashMap<ModuleId, InternalGas>,
gas_cost_stack: Vec<InternalGas>
}

// TODO: consider switching to a library like https://docs.rs/delegate/latest/delegate/.
Expand Down Expand Up @@ -98,6 +101,8 @@ impl<G> GasProfiler<G> {
events_transient: vec![],
write_set_transient: vec![],
storage_fees: None,
module_gas_usage: HashMap::new(),
gas_cost_stack: vec![]
}
}

Expand All @@ -118,14 +123,132 @@ impl<G> GasProfiler<G> {
events_transient: vec![],
write_set_transient: vec![],
storage_fees: None,
module_gas_usage: HashMap::new(),
gas_cost_stack: vec![]
}
}
}

// 0 -> 800 -> 1500 -> 2000 -> 2500
// A::Foo() { Bar()}
// B::Bar() { Barz()}
// C::Barz() {}
// TODO: add a new value if it is intermodule call, also think about return intermodule.

impl<G> GasProfiler<G>
where
G: AptosGasMeter,
{
// Method to print the module_gas_usage hash table
pub fn print_module_gas_usage(&self) {
for (module_id, gas) in &self.module_gas_usage {
println!("Module: {:?}, Gas Used: {:?}", module_id, gas);
}
}

fn is_intra_module_call(&self, module_id: &ModuleId) -> bool {
if module_id.address() == &AccountAddress::ONE {
return true;
}
if let Some(frame) = self.frames.last() {
if let FrameName::Function { module_id: current_module_id, .. } = &frame.name {
return current_module_id == module_id;
}
}
false
}

// Add a method to get the total gas usage for a module
pub fn get_module_gas_usage(&self, module_id: &ModuleId) -> InternalGas {
*self.module_gas_usage.get(module_id).unwrap_or(&InternalGas::zero())
}

fn charge_call_with_metering(
&mut self,
module_id: &ModuleId,
func_name: &str,
args: impl ExactSizeIterator<Item = impl ValueView> + Clone,
num_locals: NumArgs,
) -> PartialVMResult<()> {
let is_intra_module = self.is_intra_module_call(module_id);
let base_cost = if is_intra_module {
InternalGas::new(0)
} else {
InternalGas::new(20)
};

let (cost, res) = self.delegate_charge(|base| {
base.charge_call(module_id, func_name, args, num_locals)
});
let total_cost = cost + base_cost;

// if !is_intra_module {
// print!("Inter module call\n");
// print!("Old cost: {}\n", cost);
// print!("New Cost: {}\n", cost+base_cost);
// }
// println!("Module ID: {:?}, Total Cost: {:?}", module_id, total_cost);

// Record gas usage for the current module
*self.module_gas_usage.entry(module_id.clone()).or_insert(InternalGas::zero()) += total_cost;
// Push the current gas cost onto the stack
self.gas_cost_stack.push(self.base.balance_internal());
self.record_bytecode(Opcodes::CALL, cost + base_cost);
self.frames.push(CallFrame::new_function(
module_id.clone(),
Identifier::new(func_name).unwrap(),
vec![],
));

res
}

fn charge_call_generic_with_metering(
&mut self,
module_id: &ModuleId,
func_name: &str,
ty_args: impl ExactSizeIterator<Item = impl TypeView> + Clone,
args: impl ExactSizeIterator<Item = impl ValueView> + Clone,
num_locals: NumArgs,
) -> PartialVMResult<()> {
let is_intra_module = self.is_intra_module_call(module_id);
let base_cost = if is_intra_module {
InternalGas::new(0)
} else {
InternalGas::new(20)
};

let ty_tags = ty_args
.clone()
.map(|ty| ty.to_type_tag())
.collect::<Vec<_>>();

let (cost, res) = self.delegate_charge(|base| {
base.charge_call_generic(module_id, func_name, ty_args, args, num_locals)
});

let total_cost = cost + base_cost;

if !is_intra_module {
print!("Inter module call\n");
print!("Old cost: {}\n", cost);
print!("New Cost: {}\n", cost + base_cost);
}
// println!("Module ID: {:?}, Total Cost: {:?}", module_id, total_cost);
// Record gas usage for the current module
*self.module_gas_usage.entry(module_id.clone()).or_insert(InternalGas::zero()) += total_cost;
// Push the current gas cost onto the stack
self.gas_cost_stack.push(self.base.balance_internal());
self.record_bytecode(Opcodes::CALL_GENERIC, cost + base_cost);
self.frames.push(CallFrame::new_function(
module_id.clone(),
Identifier::new(func_name).unwrap(),
ty_tags,
));

res
}

fn active_event_stream(&mut self) -> &mut Vec<ExecutionGasEvent> {
&mut self.frames.last_mut().unwrap().events
}
Expand Down Expand Up @@ -399,7 +522,16 @@ where
if matches!(instr, SimpleInstruction::Ret) && self.frames.len() > 1 {
let cur_frame = self.frames.pop().expect("frame must exist");
let last_frame = self.frames.last_mut().expect("frame must exist");
last_frame.events.push(ExecutionGasEvent::Call(cur_frame));
last_frame.events.push(ExecutionGasEvent::Call(cur_frame.clone()));
// Pop the gas cost from the stack and calculate the difference
let start_gas = self.gas_cost_stack.pop().expect("stack must not be empty");
let end_gas = self.base.balance_internal();
let function_cost = start_gas.checked_sub(end_gas).expect("gas cost must be non-negative");

// Print the function name and cost
if let FrameName::Function { name, .. } = &cur_frame.name {
println!("Function: {}, Cost: {:?}", name, function_cost);
}
}

res
Expand All @@ -412,17 +544,7 @@ where
args: impl ExactSizeIterator<Item = impl ValueView> + Clone,
num_locals: NumArgs,
) -> PartialVMResult<()> {
let (cost, res) =
self.delegate_charge(|base| base.charge_call(module_id, func_name, args, num_locals));

self.record_bytecode(Opcodes::CALL, cost);
self.frames.push(CallFrame::new_function(
module_id.clone(),
Identifier::new(func_name).unwrap(),
vec![],
));

res
self.charge_call_with_metering(module_id, func_name, args, num_locals)
}

fn charge_call_generic(
Expand All @@ -433,23 +555,7 @@ where
args: impl ExactSizeIterator<Item = impl ValueView> + Clone,
num_locals: NumArgs,
) -> PartialVMResult<()> {
let ty_tags = ty_args
.clone()
.map(|ty| ty.to_type_tag())
.collect::<Vec<_>>();

let (cost, res) = self.delegate_charge(|base| {
base.charge_call_generic(module_id, func_name, ty_args, args, num_locals)
});

self.record_bytecode(Opcodes::CALL_GENERIC, cost);
self.frames.push(CallFrame::new_function(
module_id.clone(),
Identifier::new(func_name).unwrap(),
ty_tags,
));

res
self.charge_call_generic_with_metering(module_id, func_name, ty_args, args, num_locals)
}

fn charge_load_resource(
Expand Down Expand Up @@ -668,6 +774,8 @@ where
G: AptosGasMeter,
{
pub fn finish(mut self) -> TransactionGasLog {
// Print the module_gas_usage hash table
self.print_module_gas_usage();
while self.frames.len() > 1 {
let cur = self.frames.pop().expect("frame must exist");
let last = self.frames.last_mut().expect("frame must exist");
Expand Down
8 changes: 4 additions & 4 deletions aptos-move/aptos-vm-logging/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ pub fn speculative_log(level: Level, context: &AdapterLogSchema, message: String
};
},
None => {
speculative_alert!(
"Speculative state not initialized to log message = {}",
message
);
// speculative_alert!(
// "Speculative state not initialized to log message = {}",
// message
// );
},
};
}
Expand Down
115 changes: 115 additions & 0 deletions third_party/move/move-bytecode-verifier/src/call_edge_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) The Diem Core Contributors
// Copyright (c) The Move Contributors
// SPDX-License-Identifier: Apache-2.0

//! This module implements a checker for verifying that each vector in a CompiledModule contains
//! distinct values. Successful verification implies that an index in vector can be used to
//! uniquely name the entry at that index. Additionally, the checker also verifies the
//! following:
//! - struct and field definitions are consistent
//! - the handles in struct and function definitions point to the self module index
//! - all struct and function handles pointing to the self module index have a definition
use move_binary_format::{
access::{ModuleAccess},
errors::{Location, PartialVMResult, VMResult},
file_format::{
CompiledModule
},
};
use move_binary_format::file_format::Bytecode;
use move_core_types::account_address::AccountAddress;

pub struct CallEdgeDetector<'a> {
module: &'a CompiledModule,
}

impl<'a> CallEdgeDetector<'a> {
pub fn verify_module(module: &'a CompiledModule) -> VMResult<()> {
Self::verify_module_impl(module).map_err(|e| e.finish(Location::Module(module.self_id())))
}

fn verify_module_impl(module: &'a CompiledModule) -> PartialVMResult<()> {
Self::print_module_addresses(module);
Self::call_edges_print(module);
Ok(())
}

pub fn print_module_addresses(module: &CompiledModule) {
// println!("Module address: {:?}", module.self_id().address());
//
// // Print the addresses of all the module's dependencies
// for dep in module.immediate_dependencies() {
// println!("Dependency address: {:?}", dep.address());
// }
//
// // Print the addresses of all the module's friends
// for friend in module.immediate_friends() {
// println!("Friend address: {:?}", friend.address());
// }
}

// Print the function calls and module address from and to in the module
pub fn call_edges_print(module: &CompiledModule) {
// Iterate over all the functions in the module
for function_def in module.function_defs().iter() {
let function_handle = &module.function_handle_at(function_def.function);
let function_name = module.identifier_at(function_handle.name);
// Iterate over all the bytecodes that represent function calls in the function
if let Some(code) = &function_def.code {
for bytecode in &code.code {
// Case 1: Call instruction; Case 2: CallGeneric instruction; Case 3: Ret instruction
match bytecode {
Bytecode::Call(handle_index) => {
let called_function_handle = module.function_handle_at(*handle_index);
let called_function_name = module.identifier_at(called_function_handle.name);
let module_id = module.self_id();
let source_module = module_id.address();
let self_address = module.self_id().address().clone();
let target_module = module.address_identifiers().get(called_function_handle.module.0 as usize)
.unwrap_or_else(|| &self_address);
if target_module != &AccountAddress::ONE {
println!("Function: {}", function_name);
println!(
" Calls: {} from module: {:x} to module: {:x}",
called_function_name, source_module, target_module
);
}
}
Bytecode::CallGeneric(inst_index) => {
let inst = module.function_instantiation_at(*inst_index);
let called_function_handle = module.function_handle_at(inst.handle);
let called_function_name = module.identifier_at(called_function_handle.name);
let module_id = module.self_id();
let source_module = module_id.address();
let self_address = module.self_id().address().clone();
let target_module = module.address_identifiers().get(called_function_handle.module.0 as usize)
.unwrap_or_else(|| &self_address);
if target_module != &AccountAddress::ONE {
println!("Function: {}", function_name);
println!(
" Calls: {} from module: {:x} to module: {:x}",
called_function_name, source_module, target_module
);
}
}
Bytecode::Ret => {
let module_id = module.self_id();
let source_module = module_id.address();
let target_module = module.address_identifiers().get(0).unwrap_or_else(|| &source_module);
if source_module != &AccountAddress::ONE {
println!("Function: {}", function_name);
println!(
" Returns from module: {:x} to module: {:x}",
source_module, target_module
);
}
}
_ => {}
}
}
}
}
}
//TODO how to add gas metering for distinguishing cross container and in container function call?
//TODO how the gas should be calculated for cross container function call?
}
2 changes: 2 additions & 0 deletions third_party/move/move-bytecode-verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ mod reference_safety;
mod regression_tests;
mod stack_usage_verifier;
mod type_safety;

mod call_edge_detection;
2 changes: 2 additions & 0 deletions third_party/move/move-bytecode-verifier/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use move_binary_format::{
use move_core_types::{state::VMState, vm_status::StatusCode};
use serde::Serialize;
use std::time::Instant;
use crate::call_edge_detection::CallEdgeDetector;

#[derive(Debug, Clone, Serialize)]
pub struct VerifierConfig {
Expand Down Expand Up @@ -104,6 +105,7 @@ pub fn verify_module_with_config_for_test_with_version(
pub fn verify_module_with_config(config: &VerifierConfig, module: &CompiledModule) -> VMResult<()> {
let prev_state = move_core_types::state::set_state(VMState::VERIFIER);
let result = std::panic::catch_unwind(|| {
CallEdgeDetector::verify_module(module)?;
// Always needs to run bound checker first as subsequent passes depend on it
BoundsChecker::verify_module(module).map_err(|e| {
// We can't point the error at the module, because if bounds-checking
Expand Down
Loading