use std::fmt;
use std::fmt::Debug;
use std::vec;
use dupe::Dupe;
use starlark_syntax::codemap::FileSpan;
use starlark_syntax::slice_vec_ext::SliceExt;
use starlark_syntax::ErrorKind;
use crate::errors::Frame;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::inlined_frame::InlinedFrames;
use crate::eval::CallStack;
use crate::hint::unlikely;
use crate::values::FrozenRef;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::Value;
#[derive(Clone, Copy, Dupe)]
struct CheapFrame<'v> {
function: Value<'v>,
span: Option<FrozenRef<'static, FrameSpan>>,
}
impl CheapFrame<'_> {
fn location(&self) -> Option<FileSpan> {
self.span.map(|span| span.span.to_file_span())
}
fn extend_frames(&self, frames: &mut Vec<Frame>) {
if let Some(span) = self.span {
span.inlined_frames.extend_frames(frames);
}
frames.push(self.to_frame());
}
fn to_frame(&self) -> Frame {
Frame {
name: self.function.name_for_call_stack(),
location: self.location(),
}
}
}
impl Debug for CheapFrame<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut x = f.debug_struct("Frame");
x.field("function", &self.function);
x.field("span", &self.span);
x.finish()
}
}
#[derive(Debug, thiserror::Error)]
enum CallStackError {
#[error("Requested {0}-th top frame, but stack size is {1} (internal error)")]
StackIsTooShallowForNthTopFrame(usize, usize),
#[error("Starlark call stack overflow")]
Overflow,
#[error("Starlark call stack is already allocated")]
AlreadyAllocated,
}
#[derive(Debug)]
pub(crate) struct CheapCallStack<'v> {
count: usize,
stack: Box<[CheapFrame<'v>]>,
}
impl<'v> Default for CheapCallStack<'v> {
fn default() -> Self {
Self {
count: 0,
stack: Box::new(
[CheapFrame {
function: Value::new_none(),
span: None,
}; 0],
),
}
}
}
unsafe impl<'v> Trace<'v> for CheapCallStack<'v> {
fn trace(&mut self, tracer: &Tracer<'v>) {
let (used, unused) = self.stack.split_at_mut(self.count);
for x in used {
x.function.trace(tracer);
}
for x in unused {
x.function = Value::new_none();
x.span = None;
}
}
}
impl<'v> CheapCallStack<'v> {
pub(crate) fn alloc_if_needed(&mut self, max_size: usize) -> anyhow::Result<()> {
if self.stack.len() != 0 {
return if self.stack.len() == max_size {
Ok(())
} else {
Err(CallStackError::AlreadyAllocated.into())
};
}
self.stack = vec![
CheapFrame {
function: Value::new_none(),
span: None,
};
max_size
]
.into_boxed_slice();
Ok(())
}
pub(crate) fn push(
&mut self,
function: Value<'v>,
span: Option<FrozenRef<'static, FrameSpan>>,
) -> crate::Result<()> {
if unlikely(self.count >= self.stack.len()) {
return Err(crate::Error::new_kind(ErrorKind::StackOverflow(
CallStackError::Overflow.into(),
)));
}
self.stack[self.count] = CheapFrame { function, span };
self.count += 1;
Ok(())
}
pub(crate) fn pop(&mut self) {
debug_assert!(self.count >= 1);
self.count -= 1;
}
pub(crate) fn count(&self) -> usize {
self.count
}
pub(crate) fn top_frame(&self) -> Option<Frame> {
Some(self.stack.last().as_ref()?.to_frame())
}
pub(crate) fn top_location(&self) -> Option<FileSpan> {
if self.count == 0 {
None
} else {
self.stack[self.count - 1].location()
}
}
pub(crate) fn top_nth_function(&self, n: usize) -> anyhow::Result<Value<'v>> {
self.top_nth_function_opt(n)
.ok_or_else(|| CallStackError::StackIsTooShallowForNthTopFrame(n, self.count).into())
}
pub(crate) fn top_nth_function_opt(&self, n: usize) -> Option<Value<'v>> {
let index = self.count.checked_sub(1).and_then(|x| x.checked_sub(n))?;
Some(self.stack[index].function)
}
pub(crate) fn to_diagnostic_frames(&self, inlined_frames: InlinedFrames) -> CallStack {
let mut frames = Vec::new();
for frame in &self.stack[1..self.count] {
frame.extend_frames(&mut frames);
}
inlined_frames.extend_frames(&mut frames);
CallStack { frames }
}
pub(crate) fn to_function_values(&self) -> Vec<Value<'v>> {
self.stack[1..self.count].map(|x| x.function)
}
}