Skip to content
Merged
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
11 changes: 9 additions & 2 deletions src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,18 @@ impl CairoDebugger {
let stop = match &self.state.step_action {
Some(StepAction::StepIn { prev_line }) if *prev_line != current_line => true,
Some(StepAction::Next { prev_line, depth })
if *depth >= self.state.call_stack.depth() && *prev_line != current_line =>
if *depth
>= self.state.call_stack.depth(self.state.current_statement_idx, &self.ctx)
&& *prev_line != current_line =>
{
true
}
Some(StepAction::StepOut { depth })
if *depth
> self.state.call_stack.depth(self.state.current_statement_idx, &self.ctx) =>
{
true
}
Some(StepAction::StepOut { depth }) if *depth > self.state.call_stack.depth() => true,
_ => false,
};

Expand Down
127 changes: 82 additions & 45 deletions src/debugger/call_stack.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::iter::once;
use std::iter;
use std::path::Path;

use cairo_annotations::annotations::coverage::{CodeLocation, SourceFileFullPath};
Expand All @@ -12,13 +12,27 @@ use crate::debugger::context::Context;

#[derive(Default)]
pub struct CallStack {
/// Stack of indexes of sierra statements that are function calls and values of variables in frames corresponding to these functions.
/// Does ***not*** contain a current function frame.
/// Stack of Cairo function frames and values of variables in frames corresponding
/// to these functions.
///
/// 1. Each function frame corresponds to a Cairo function that is currently on the Cairo call
/// stack.
/// This includes inlined functions, even though they are not present on the call stack
/// if one looks from Sierra/CASM POV.
///
/// 2. The stack is divided into substacks.
/// Each substack corresponds to a Sierra function call statement that is currently
/// on the Sierra call stack.
/// The substack contains zero to many frames of inlined Cairo functions together
/// with exactly one non-inlined Cairo function frame at the end of the substack.
///
/// 3. The stack does ***not*** contain a substack corresponding to the current statement.
///
/// [Object references](https://microsoft.github.io/debug-adapter-protocol/overview#lifetime-of-objects-references):
/// object reference for each stack frame is equal to its `1 + 2 * index` where `index` is its
/// position in this vector. For variables, it is `2 + 2 * index`.
call_ids: Vec<(StatementIdx, FunctionVariables)>,
/// object reference for each stack frame is equal to its `1 + 2 * flat_index`
/// where `flat_index` is its position in the flattened vector (vector of tuples).
/// For the variables' scope, the object reference is equal to `2 + 2 * flat_index`.
call_frames_and_vars: Vec<SubStack>,

/// Modification that should be applied to the stack when a new sierra statement is reached.
///
Expand All @@ -28,48 +42,56 @@ pub struct CallStack {
action_on_new_statement: Option<Action>,
}

type SubStack = Vec<(StackFrame, FunctionVariables)>;

enum Action {
Push(StatementIdx),
Push(SubStack),
Pop,
}

impl CallStack {
pub fn depth(&self) -> usize {
self.call_ids.len()
pub fn depth(&self, statement_idx: StatementIdx, ctx: &Context) -> usize {
self.flat_length() + self.build_stack_frames(ctx, statement_idx).count()
}

pub fn update(&mut self, statement_idx: StatementIdx, ctx: &Context) {
// We can be sure that the `statement_idx` is different from the one which was the arg when
// `action_on_new_statement` was set.
// The reason is that both function call and return in sierra compile to one CASM instruction each.
// https://github.com/starkware-libs/cairo/blob/20eca60c88a35f7da13f573b2fc68818506703a9/crates/cairo-lang-sierra-to-casm/src/invocations/function_call.rs#L46
// https://github.com/starkware-libs/cairo/blob/d52acf845fc234f1746f814de7c64b535563d479/crates/cairo-lang-sierra-to-casm/src/compiler.rs#L533
match self.action_on_new_statement.take() {
Some(Action::Push(statement)) => {
Some(Action::Push(frames_and_variables)) => {
// TODO(#16)
self.call_ids.push((statement, FunctionVariables {}));
self.call_frames_and_vars.push(frames_and_variables);
}
Some(Action::Pop) => {
self.call_ids.pop();
self.call_frames_and_vars.pop();
}
None => {}
}

if ctx.is_function_call_statement(statement_idx) {
self.action_on_new_statement = Some(Action::Push(statement_idx));
self.action_on_new_statement = Some(Action::Push(
self.build_stack_frames(ctx, statement_idx)
// TODO(#16)
.zip(iter::repeat_with(|| FunctionVariables {}))
.collect(),
));
} else if ctx.is_return_statement(statement_idx) {
self.action_on_new_statement = Some(Action::Pop);
}
}

pub fn get_frames(&self, statement_idx: StatementIdx, ctx: &Context) -> Vec<StackFrame> {
// DAP expects frames to start from the most nested element.
self.call_ids
self.call_frames_and_vars
.iter()
.map(|(call_statement_idx, _)| call_statement_idx)
.flatten()
.map(|(frame, _)| frame)
.cloned()
.chain(once(statement_idx))
.chain(self.build_stack_frames(ctx, statement_idx))
// DAP expects frames to start from the most nested element.
.rev()
.flat_map(|statement_idx| self.build_stack_frames(ctx, statement_idx))
.collect()
}

Expand All @@ -84,35 +106,43 @@ impl CallStack {
}

pub fn get_variables(&self, variables_reference: i64) -> Vec<Variable> {
let index = variables_reference / 2 - 1;
let &FunctionVariables {} = if index == self.call_ids.len() as i64 {
let flat_index = (variables_reference / 2 - 1) as usize;
let &FunctionVariables {} = if flat_index >= self.flat_length() {
// TODO(#16)
// Build them on demand.
&FunctionVariables {}
} else {
&self.call_ids[index as usize].1
self.call_frames_and_vars
.iter()
.flatten()
.map(|(_, vars)| vars)
.nth(flat_index)
.unwrap()
};

vec![]
}

/// Builds a vector of stack frames, ordered from the most nested (innermost) to the least nested (outermost) element.
fn build_stack_frames(&self, ctx: &Context, statement_idx: StatementIdx) -> Vec<StackFrame> {
/// Builds a vector of stack frames, ordered from the least nested to the most nested element.
fn build_stack_frames<'a>(
&'a self,
ctx: &'a Context,
statement_idx: StatementIdx,
) -> Box<dyn DoubleEndedIterator<Item = StackFrame> + 'a> {
let Some(code_locations) = ctx.code_locations_for_statement_idx(statement_idx) else {
return vec![unknown_frame()];
return Box::new(vec![self.unknown_frame()].into_iter());
};

let default_function_names = vec![FunctionName("test".to_string())];
let function_names =
ctx.function_names_for_statement_idx(statement_idx).unwrap_or(&default_function_names);
let function_names = ctx
.function_names_for_statement_idx(statement_idx)
.cloned()
.unwrap_or_else(|| vec![FunctionName("test".to_string())]);

code_locations
.iter()
.zip(function_names)
.map(|(code_location, function_name)| {
self.build_stack_frame(code_location, function_name, ctx)
})
.collect()
Box::new(code_locations.clone().into_iter().rev().zip(function_names).map(
|(code_location, function_name)| {
self.build_stack_frame(&code_location, &function_name, ctx)
},
))
}

fn build_stack_frame(
Expand All @@ -121,7 +151,6 @@ impl CallStack {
FunctionName(function_name): &FunctionName,
ctx: &Context,
) -> StackFrame {
let id = MIN_OBJECT_REFERENCE + 2 * self.call_ids.len() as i64;
let file_path = Path::new(&source_file);
let name = function_name.clone();

Expand All @@ -138,7 +167,7 @@ impl CallStack {
let column = (code_span.start.col.0 + 1) as i64;

StackFrame {
id,
id: self.next_frame_id(),
name,
source: Some(Source {
name: None,
Expand All @@ -151,16 +180,24 @@ impl CallStack {
..Default::default()
}
}
}

fn unknown_frame() -> StackFrame {
StackFrame {
id: 1,
name: "Unknown".to_string(),
line: 1,
column: 1,
presentation_hint: Some(StackFramePresentationhint::Subtle),
..Default::default()
fn unknown_frame(&self) -> StackFrame {
StackFrame {
id: self.next_frame_id(),
name: "Unknown".to_string(),
line: 1,
column: 1,
presentation_hint: Some(StackFramePresentationhint::Subtle),
..Default::default()
}
}

fn next_frame_id(&self) -> i64 {
MIN_OBJECT_REFERENCE + 2 * self.flat_length() as i64
}

fn flat_length(&self) -> usize {
self.call_frames_and_vars.iter().map(|frames| frames.len()).sum()
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/debugger/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ pub fn handle_request(
// This effectively "steps over" any function calls.
let line = Line::create_from_statement_idx(state.current_statement_idx, ctx);

state.step_action =
Some(StepAction::Next { depth: state.call_stack.depth(), prev_line: line });
state.step_action = Some(StepAction::Next {
depth: state.call_stack.depth(state.current_statement_idx, ctx),
prev_line: line,
});

state.resume_execution();
Ok(ResponseBody::Next.into())
Expand All @@ -208,7 +210,7 @@ pub fn handle_request(
// We record the current call stack depth. The debugger will resume execution
// and only stop when it reaches a line in a shallower call stack depth, which
// happens when the current function returns.
let depth = state.call_stack.depth();
let depth = state.call_stack.depth(state.current_statement_idx, ctx);
if depth > 0 {
state.step_action = Some(StepAction::StepOut { depth });
}
Expand Down