use std::{
cell::Cell,
intrinsics::unlikely,
mem::{self, MaybeUninit},
path::Path,
};
use gazebo::{any::AnyLifetime, cast};
use thiserror::Error;
use crate::{
codemap::{FileSpan, FileSpanRef},
collections::{alloca::Alloca, string_pool::StringPool},
environment::{slots::ModuleSlotId, EnvironmentError, FrozenModuleRef, Module},
errors::Diagnostic,
eval::{
bc::frame::BcFramePtr,
compiler::def::DefInfo,
runtime::{
bc_profile::BcProfile,
before_stmt::BeforeStmt,
call_stack::{CheapCallStack, FrozenFileSpan},
flame_profile::FlameProfile,
heap_profile::{HeapProfile, HeapProfileFormat},
profile::ProfileMode,
slots::LocalSlotId,
stmt_profile::StmtProfile,
},
CallStack, FileLoader,
},
stdlib::{
breakpoint::{BreakpointConsole, RealBreakpointConsole},
extra::{PrintHandler, StderrPrintHandler},
},
values::{
layout::value_captured::{value_captured_get, ValueCaptured},
FrozenHeap, FrozenRef, Heap, Trace, Tracer, Value, ValueLike,
},
};
#[derive(Error, Debug)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum EvaluatorError {
#[error("Can't call `write_heap_profile` unless you first call `enable_heap_profile`.")]
HeapProfilingNotEnabled,
#[error("Can't call `write_stmt_profile` unless you first call `enable_stmt_profile`.")]
StmtProfilingNotEnabled,
#[error("Can't call `write_flame_profile` unless you first call `enable_flame_profile`.")]
FlameProfilingNotEnabled,
#[error("Can't call `write_bc_profile` unless you first call `enable_bc_profile`.")]
BcProfilingNotEnabled,
}
pub(crate) const GC_THRESHOLD: usize = 100000;
pub struct Evaluator<'v, 'a> {
pub(crate) module_env: &'v Module,
pub(crate) module_variables: Option<FrozenRef<'static, FrozenModuleRef>>,
pub(crate) current_frame: BcFramePtr<'v>,
pub(crate) loader: Option<&'a dyn FileLoader>,
pub(crate) def_info: FrozenRef<'static, DefInfo>,
pub(crate) heap_profile: HeapProfile,
pub(crate) flame_profile: FlameProfile<'v>,
pub(crate) heap_or_flame_profile: bool,
pub(crate) disable_gc: bool,
pub(crate) verbose_gc: bool,
pub(crate) next_gc_level: usize,
pub(crate) before_stmt: BeforeStmt<'v, 'a>,
stmt_profile: StmtProfile,
pub(crate) bc_profile: BcProfile,
alloca: Alloca,
pub(crate) string_pool: StringPool,
pub extra: Option<&'a dyn AnyLifetime<'a>>,
pub extra_v: Option<&'a dyn AnyLifetime<'v>>,
pub(crate) breakpoint_handler: Option<Box<dyn Fn() -> Box<dyn BreakpointConsole>>>,
pub(crate) print_handler: &'a (dyn PrintHandler + 'a),
pub(crate) call_stack: CheapCallStack<'v>,
}
unsafe impl<'v> Trace<'v> for Evaluator<'v, '_> {
fn trace(&mut self, tracer: &Tracer<'v>) {
let mut roots = self.module_env.slots().get_slots_mut();
roots.trace(tracer);
self.current_frame.trace(tracer);
self.call_stack.trace(tracer);
self.flame_profile.trace(tracer);
}
}
impl<'v, 'a> Evaluator<'v, 'a> {
pub fn new(module: &'v Module) -> Self {
Evaluator {
call_stack: CheapCallStack::default(),
module_env: module,
module_variables: None,
current_frame: BcFramePtr::null(),
loader: None,
extra: None,
extra_v: None,
next_gc_level: GC_THRESHOLD,
disable_gc: false,
alloca: Alloca::new(),
heap_profile: HeapProfile::new(),
stmt_profile: StmtProfile::new(),
bc_profile: BcProfile::new(),
flame_profile: FlameProfile::new(),
heap_or_flame_profile: false,
before_stmt: BeforeStmt::default(),
def_info: DefInfo::empty(), string_pool: StringPool::default(),
breakpoint_handler: None,
print_handler: &StderrPrintHandler,
verbose_gc: false,
}
}
pub fn disable_gc(&mut self) {
self.disable_gc = true;
}
pub fn verbose_gc(&mut self) {
self.verbose_gc = true;
}
pub fn set_loader(&mut self, loader: &'a dyn FileLoader) {
self.loader = Some(loader);
}
pub fn enable_profile(&mut self, mode: &ProfileMode) {
match mode {
ProfileMode::Heap | ProfileMode::HeapFlame => {
self.heap_profile.enable();
self.heap_or_flame_profile = true;
self.disable_gc = true;
}
ProfileMode::Stmt => {
self.stmt_profile.enable();
self.before_stmt(&|span, eval| eval.stmt_profile.before_stmt(span));
}
ProfileMode::Flame => {
self.flame_profile.enable();
self.heap_or_flame_profile = true;
}
ProfileMode::Bytecode => {
self.bc_profile.enable_1();
}
ProfileMode::BytecodePairs => {
self.bc_profile.enable_2();
}
}
}
pub fn enable_before_stmt_instrumentation(&mut self) {
self.before_stmt.instrument = true;
}
pub fn enable_profile_instrumentation(&mut self, mode: &ProfileMode) {
match mode {
ProfileMode::Bytecode | ProfileMode::BytecodePairs => {
self.bc_profile.enable_1();
}
_ => {
self.before_stmt.instrument = true;
}
}
}
pub fn write_profile<P: AsRef<Path>>(
&self,
mode: &ProfileMode,
filename: P,
) -> anyhow::Result<()> {
match mode {
ProfileMode::Heap => self
.heap_profile
.write(filename.as_ref(), self.heap(), HeapProfileFormat::Summary)
.unwrap_or_else(|| Err(EvaluatorError::HeapProfilingNotEnabled.into())),
ProfileMode::HeapFlame => self
.heap_profile
.write(
filename.as_ref(),
self.heap(),
HeapProfileFormat::FlameGraph,
)
.unwrap_or_else(|| Err(EvaluatorError::HeapProfilingNotEnabled.into())),
ProfileMode::Stmt => self
.stmt_profile
.write(filename.as_ref())
.unwrap_or_else(|| Err(EvaluatorError::StmtProfilingNotEnabled.into())),
ProfileMode::Bytecode | ProfileMode::BytecodePairs => {
self.bc_profile.write_csv(filename.as_ref())
}
ProfileMode::Flame => self
.flame_profile
.write(filename.as_ref())
.unwrap_or_else(|| Err(EvaluatorError::FlameProfilingNotEnabled.into())),
}
}
pub fn enable_terminal_breakpoint_console(&mut self) {
self.breakpoint_handler = Some(RealBreakpointConsole::factory());
}
pub fn call_stack(&self) -> CallStack {
self.call_stack.to_diagnostic_frames()
}
pub fn call_stack_top_location(&self) -> Option<FileSpan> {
self.call_stack.top_location()
}
pub fn before_stmt(&mut self, f: &'a dyn Fn(FileSpanRef, &mut Evaluator<'v, 'a>)) {
self.before_stmt.before_stmt.push(f)
}
pub fn set_print_handler(&mut self, handler: &'a (dyn PrintHandler + 'a)) {
self.print_handler = handler;
}
pub(crate) fn check_types(&self) -> bool {
true
}
#[inline(always)]
pub(crate) fn with_call_stack<R>(
&mut self,
function: Value<'v>,
span: Option<FrozenRef<'static, FrozenFileSpan>>,
within: impl FnOnce(&mut Self) -> anyhow::Result<R>,
) -> anyhow::Result<R> {
#[cold]
#[inline(never)]
fn add_diagnostics(e: anyhow::Error, me: &Evaluator) -> anyhow::Error {
Diagnostic::modify(e, |d: &mut Diagnostic| {
d.set_call_stack(|| me.call_stack.to_diagnostic_frames());
})
}
self.call_stack.push(function, span)?;
if unlikely(self.heap_or_flame_profile) {
self.heap_profile.record_call_enter(function, self.heap());
self.flame_profile.record_call_enter(function);
}
let res = within(self).map_err(|e| add_diagnostics(e, self));
self.call_stack.pop();
if unlikely(self.heap_or_flame_profile) {
self.heap_profile.record_call_exit(self.heap());
self.flame_profile.record_call_exit();
}
res
}
#[inline(always)] pub(crate) fn with_function_context<R>(
&mut self,
module: Option<FrozenRef<'static, FrozenModuleRef>>, def_info: FrozenRef<'static, DefInfo>,
within: impl FnOnce(&mut Self) -> R,
) -> R {
let old_def_info = mem::replace(&mut self.def_info, def_info);
let old_module_variables = mem::replace(&mut self.module_variables, module);
let res = within(self);
self.def_info = old_def_info;
self.module_variables = old_module_variables;
res
}
pub fn heap(&self) -> &'v Heap {
self.module_env.heap()
}
pub fn frozen_heap(&self) -> &FrozenHeap {
self.module_env.frozen_heap()
}
pub(crate) fn get_slot_module(&self, slot: ModuleSlotId) -> anyhow::Result<Value<'v>> {
#[cold]
#[inline(never)]
fn error<'v>(eval: &Evaluator<'v, '_>, slot: ModuleSlotId) -> anyhow::Error {
let name = match &eval.module_variables {
None => eval.module_env.names().get_slot(slot),
Some(e) => e.0.get_slot_name(slot),
}
.unwrap_or_else(|| "<unknown>".to_owned());
EnvironmentError::LocalVariableReferencedBeforeAssignment(name).into()
}
match &self.module_variables {
None => self.module_env.slots().get_slot(slot),
Some(e) => e.0.get_slot(slot).map(Value::new_frozen),
}
.ok_or_else(|| error(self, slot))
}
#[cold]
#[inline(never)]
pub(crate) fn local_var_referenced_before_assignment(
&self,
slot: LocalSlotId,
) -> anyhow::Error {
let names = &self.def_info.scope_names.used;
let name = names[slot.0 as usize].clone();
EnvironmentError::LocalVariableReferencedBeforeAssignment(name).into()
}
#[inline(always)]
pub(crate) fn get_slot_local(&self, slot: LocalSlotId) -> anyhow::Result<Value<'v>> {
self.current_frame
.get_slot(slot)
.ok_or_else(|| self.local_var_referenced_before_assignment(slot))
}
pub(crate) fn get_slot_local_captured(&self, slot: LocalSlotId) -> anyhow::Result<Value<'v>> {
let value_captured = self.get_slot_local(slot)?;
let value_captured = value_captured_get(value_captured);
value_captured.ok_or_else(|| self.local_var_referenced_before_assignment(slot))
}
pub(crate) fn clone_slot_capture(&self, slot: LocalSlotId) -> Value<'v> {
match self.current_frame.get_slot(slot) {
Some(value_captured) => {
debug_assert!(
value_captured.downcast_ref::<ValueCaptured>().is_some(),
"slot {:?} is expected to be ValueCaptured, it is {:?} ({})",
slot,
value_captured,
value_captured.get_type()
);
value_captured
}
None => {
let value_captured = self.heap().alloc_complex(ValueCaptured(Cell::new(None)));
self.current_frame.set_slot(slot, value_captured);
value_captured
}
}
}
pub fn set_module_variable_at_some_point(
&mut self,
name: &str,
value: Value<'v>,
) -> anyhow::Result<()> {
value.export_as(name, self);
self.module_env.set(name, value);
Ok(())
}
pub(crate) fn set_slot_module(&mut self, slot: ModuleSlotId, value: Value<'v>) {
self.module_env.slots().set_slot(slot, value);
}
pub(crate) fn set_slot_local(&mut self, slot: LocalSlotId, value: Value<'v>) {
self.current_frame.set_slot(slot, value)
}
pub(crate) fn set_slot_local_captured(&mut self, slot: LocalSlotId, value: Value<'v>) {
match self.current_frame.get_slot(slot) {
Some(value_captured) => {
let value_captured = value_captured
.downcast_ref::<ValueCaptured>()
.expect("not a ValueCaptured");
value_captured.set(value);
}
None => {
let value_captured = self
.heap()
.alloc_complex(ValueCaptured(Cell::new(Some(value))));
self.current_frame.set_slot(slot, value_captured);
}
};
}
pub(crate) fn wrap_local_slot_captured(&mut self, slot: LocalSlotId) {
let value = self.current_frame.get_slot(slot).expect("slot unset");
debug_assert!(value.downcast_ref::<ValueCaptured>().is_none());
let value_captured = self
.heap()
.alloc_complex(ValueCaptured(Cell::new(Some(value))));
self.current_frame.set_slot(slot, value_captured);
}
pub(crate) fn trigger_gc(&mut self) {
self.next_gc_level = 0;
}
pub unsafe fn garbage_collect(&mut self) {
if self.verbose_gc {
eprintln!(
"Starlark: allocated bytes: {}, starting GC...",
self.heap().allocated_bytes()
);
}
self.heap().garbage_collect(|tracer| self.trace(tracer));
if self.verbose_gc {
eprintln!(
"Starlark: GC complete. Allocated bytes: {}.",
self.heap().allocated_bytes()
);
}
}
#[inline(always)]
pub(crate) fn alloca_uninit<T, R, F>(&mut self, len: usize, k: F) -> R
where
F: FnOnce(&mut [MaybeUninit<T>], &mut Self) -> R,
{
let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
alloca.alloca_uninit(len, |xs| k(xs, self))
}
#[inline(always)]
pub(crate) fn alloca_init<T, R, F>(&mut self, len: usize, init: impl Fn() -> T, k: F) -> R
where
F: FnOnce(&mut [T], &mut Self) -> R,
{
let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
alloca.alloca_init(len, init, |xs| k(xs, self))
}
pub(crate) fn alloca_concat<T: Clone, R, F>(&mut self, x: &[T], y: &[T], k: F) -> R
where
F: FnOnce(&[T], &mut Self) -> R,
{
let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
alloca.alloca_concat(x, y, |xs| k(xs, self))
}
}