-
Notifications
You must be signed in to change notification settings - Fork 91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Execution trace recording #881
Changes from 8 commits
0c79e90
dc62daf
eb07ba9
2cd645d
16f8c32
d6fd604
bb0d554
3fa2ebe
dbfc12f
d8da2be
c0793d9
162646d
c7a9aeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,10 @@ use crate::error::{ | |
IoResult, | ||
RuntimeError, | ||
}; | ||
use alloc::vec::Vec; | ||
use alloc::{ | ||
vec, | ||
vec::Vec, | ||
}; | ||
use fuel_storage::{ | ||
Mappable, | ||
StorageRead, | ||
|
@@ -54,6 +57,9 @@ mod impl_tests; | |
#[cfg(test)] | ||
mod allocation_tests; | ||
|
||
#[cfg(test)] | ||
mod diff_tests; | ||
|
||
#[cfg(test)] | ||
mod stack_tests; | ||
|
||
|
@@ -130,6 +136,15 @@ impl MemoryInstance { | |
self.hp = MEM_SIZE; | ||
} | ||
|
||
/// Make memory equal to another instance, keeping the original allocations. | ||
pub fn make_equal(&mut self, other: &Self) { | ||
self.stack.truncate(0); | ||
self.stack.extend_from_slice(&other.stack); | ||
self.hp = other.hp; | ||
self.heap.truncate(0); | ||
self.heap.extend_from_slice(&other.heap); | ||
} | ||
|
||
/// Offset of the heap section | ||
fn heap_offset(&self) -> usize { | ||
MEM_SIZE.saturating_sub(self.heap.len()) | ||
|
@@ -403,6 +418,37 @@ impl MemoryInstance { | |
&self.heap | ||
} | ||
|
||
/// Diff of from `self` to `new` memory state. | ||
/// Panics if new instance is not possible to reach from the current one, | ||
/// for instance if it has smaller stack or heap allocation. | ||
pub fn diff_patches(&self, new: &MemoryInstance) -> Vec<MemorySliceChange> { | ||
let mut changes = Vec::new(); | ||
let mut current_change: Option<MemorySliceChange> = None; | ||
|
||
for i in 0..MEM_SIZE { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh, it will be very expensive to do 10^7 iterations per each instruction=D It would be nice to have something more performant=) If we go for now with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm very aware of this. I have lots of ideas on how to optimize this, and I'm going to write some benchmarks to see what approach is the right one. |
||
let [old_value] = self.read_bytes(i).unwrap_or([0]); | ||
let [new_value] = new.read_bytes(i).unwrap_or([0]); | ||
|
||
if old_value != new_value { | ||
if let Some(change) = current_change.as_mut() { | ||
change.data.push(new_value); | ||
} else { | ||
current_change = Some(MemorySliceChange { | ||
global_start: i, | ||
data: vec![new_value], | ||
}); | ||
} | ||
} else if let Some(change) = current_change.take() { | ||
changes.push(change); | ||
} | ||
} | ||
if let Some(change) = current_change.take() { | ||
changes.push(change); | ||
} | ||
|
||
changes | ||
} | ||
|
||
/// Returns a `MemoryRollbackData` that can be used to achieve the state of the | ||
/// `desired_memory_state` instance. | ||
pub fn collect_rollback_data( | ||
|
@@ -499,14 +545,19 @@ fn get_changes( | |
changes | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
struct MemorySliceChange { | ||
global_start: usize, | ||
data: Vec<u8>, | ||
/// Memory change at a specific location. | ||
#[derive(Debug, Clone, PartialEq)] | ||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
pub struct MemorySliceChange { | ||
/// Start address of the change. Global address. | ||
pub global_start: usize, | ||
/// Data that was changed. | ||
pub data: Vec<u8>, | ||
} | ||
|
||
/// The container for the data used to rollback memory changes. | ||
#[derive(Debug, Clone)] | ||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
pub struct MemoryRollbackData { | ||
/// Desired stack pointer. | ||
sp: usize, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will be more beneficial if, on the creation of the interpreter, we will pass some object that implements traits like
Tracer
or something.In this case, we will have more flexibility in the implementation and can define logic to get traces on the
fuel-core
side. In the case of tooling, they can maybe create a local debugger. In the case of indexers, they can index something inside of these functions.Of course, this solution means that we need to have getters for all internal fields, including
MemoryInstance
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think nobody is actually going to use this, and it's an unnecessary complicaton. But it would be nice to be wrong about this, and it's not that much extra complication.
Fortunately we already support almost all fields this needs, as ECAL support is also done like this.