use alloc::{
boxed::Box,
format,
string::{
String,
ToString,
},
vec::Vec,
};
use core::fmt;
use hashbrown::HashMap;
use dyn_clone::DynClone;
use fuel_types::ContractId;
use crate::prelude::*;
pub use crate::constraints::InstructionLocation;
#[cfg(feature = "serde")]
impl serde::Serialize for InstructionLocation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(ctx) = self.context {
serializer.serialize_str(&format!("{}:{}", ctx, self.offset))
} else {
serializer.serialize_str(&format!("{}", self.offset))
}
}
}
#[cfg(feature = "serde")]
struct InstructionLocationVisitor;
#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for InstructionLocationVisitor {
type Value = InstructionLocation;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A valid instruction location")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
use core::str::FromStr;
Ok(if let Some((l, r)) = value.split_once(':') {
let context = Some(ContractId::from_str(l).map_err(|_| {
serde::de::Error::custom("Invalid ContractId in InstructionLocation")
})?);
let offset = r.parse().unwrap();
InstructionLocation { context, offset }
} else {
let offset = value.parse().unwrap();
InstructionLocation {
context: None,
offset,
}
})
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for InstructionLocation {
fn deserialize<D>(deserializer: D) -> Result<InstructionLocation, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(InstructionLocationVisitor)
}
}
impl InstructionLocation {
pub const fn new(context: Option<ContractId>, offset: u64) -> Self {
Self { context, offset }
}
pub const fn context(&self) -> Option<ContractId> {
self.context
}
pub const fn offset(&self) -> u64 {
self.offset
}
}
impl fmt::Display for InstructionLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Location({}, offset={})",
self.context
.map(|contract_id| format!(
"contract_id={}",
contract_id.iter().fold(String::new(), |mut output, b| {
use core::fmt::Write;
let _ = write!(output, "{b:02x?}");
output
})
),)
.unwrap_or_else(|| "script".to_string()),
self.offset
)
}
}
type PerLocation<T> = HashMap<InstructionLocation, T>;
pub struct PerLocationIter<'a, T>(hashbrown::hash_map::Iter<'a, InstructionLocation, T>);
impl<'a, T> Iterator for PerLocationIter<'a, T> {
type Item = (&'a InstructionLocation, &'a T);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct PerLocationKeys<'a, T>(hashbrown::hash_map::Keys<'a, InstructionLocation, T>);
impl<'a, T> Iterator for PerLocationKeys<'a, T> {
type Item = &'a InstructionLocation;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct PerLocationValues<'a, T>(
hashbrown::hash_map::Values<'a, InstructionLocation, T>,
);
impl<'a, T> Iterator for PerLocationValues<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait ProfileReceiver: DynClone {
fn on_transaction(
&mut self,
state: Result<&ProgramState, InterpreterError<String>>,
data: &ProfilingData,
);
}
dyn_clone::clone_trait_object!(ProfileReceiver);
#[derive(Clone)]
pub struct StderrReceiver;
impl ProfileReceiver for StderrReceiver {
#[cfg_attr(not(feature = "std"), allow(unused_variables))]
fn on_transaction(
&mut self,
state: Result<&ProgramState, InterpreterError<String>>,
data: &ProfilingData,
) {
#[cfg(feature = "std")]
eprintln!("PROFILER: {state:?} {data:?}");
}
}
#[derive(Default, Clone)]
pub struct Profiler {
receiver: Option<Box<dyn ProfileReceiver + Send + Sync>>,
data: ProfilingData,
}
impl Profiler {
pub fn on_transaction(
&mut self,
state_result: Result<&ProgramState, InterpreterError<String>>,
) {
if let Some(r) = &mut self.receiver {
r.on_transaction(state_result, &self.data);
}
}
pub fn set_receiver(&mut self, receiver: Box<dyn ProfileReceiver + Send + Sync>) {
self.receiver = Some(receiver);
}
pub fn data(&self) -> &ProfilingData {
&self.data
}
pub fn data_mut(&mut self) -> &mut ProfilingData {
&mut self.data
}
}
impl Profiler {
pub fn set_coverage(&mut self, location: InstructionLocation) {
self.data_mut().coverage_mut().set(location);
}
pub fn add_gas(&mut self, location: InstructionLocation, gas_use: u64) {
self.data_mut().gas_mut().add(location, gas_use);
}
}
impl fmt::Debug for Profiler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Profiler(receiver={:?}, data=",
match self.receiver {
Some(_) => "enabled",
None => "disabled",
}
)?;
self.data.fmt(f)?;
write!(f, ")")
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProfilingData {
#[cfg(feature = "profile-coverage")]
coverage: CoverageProfilingData,
#[cfg(feature = "profile-gas")]
gas: GasProfilingData,
}
impl ProfilingData {
#[cfg(feature = "profile-gas")]
pub fn gas(&self) -> &GasProfilingData {
&self.gas
}
#[cfg(feature = "profile-gas")]
pub fn gas_mut(&mut self) -> &mut GasProfilingData {
&mut self.gas
}
#[cfg(feature = "profile-coverage")]
pub fn coverage(&self) -> &CoverageProfilingData {
&self.coverage
}
#[cfg(feature = "profile-coverage")]
pub fn coverage_mut(&mut self) -> &mut CoverageProfilingData {
&mut self.coverage
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CoverageProfilingData {
executed: PerLocation<()>,
}
impl<'a> CoverageProfilingData {
pub fn get(&self, location: &InstructionLocation) -> bool {
self.executed.contains_key(location)
}
pub fn set(&mut self, location: InstructionLocation) {
self.executed.insert(location, ());
}
pub fn iter(&'a self) -> PerLocationKeys<'a, ()> {
PerLocationKeys(self.executed.keys())
}
}
impl fmt::Display for CoverageProfilingData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut items: Vec<_> = self.iter().collect();
items.sort();
writeln!(f, "{items:?}")
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GasProfilingData {
gas_use: PerLocation<u64>,
}
impl<'a> GasProfilingData {
pub fn get(&self, location: &InstructionLocation) -> u64 {
self.gas_use.get(location).copied().unwrap_or(0)
}
pub fn add(&mut self, location: InstructionLocation, amount: u64) {
let gas_use = self.gas_use.entry(location).or_insert(0);
*gas_use = gas_use.saturating_add(amount);
}
pub fn iter(&'a self) -> PerLocationIter<'a, u64> {
PerLocationIter(self.gas_use.iter())
}
pub fn keys(&'a self) -> PerLocationKeys<'a, u64> {
PerLocationKeys(self.gas_use.keys())
}
pub fn values(&'a self) -> PerLocationValues<'a, u64> {
PerLocationValues(self.gas_use.values())
}
}
impl fmt::Display for GasProfilingData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut items: Vec<(_, _)> = self.iter().collect();
items.sort();
for (addr, count) in items {
writeln!(f, "{addr}: {count}")?;
}
Ok(())
}
}