use std::cmp;
use starlark_derive::VisitSpanMut;
use starlark_syntax::slice_vec_ext::SliceExt;
use starlark_syntax::syntax::ast::AssignOp;
use starlark_syntax::syntax::ast::AssignP;
use starlark_syntax::syntax::ast::AssignTargetP;
use starlark_syntax::syntax::ast::DefP;
use starlark_syntax::syntax::ast::ForP;
use starlark_syntax::syntax::ast::StmtP;
use thiserror::Error;
use crate::codemap::Span;
use crate::codemap::Spanned;
use crate::environment::slots::ModuleSlotId;
use crate::environment::FrozenModuleData;
use crate::eval::compiler::error::CompilerInternalError;
use crate::eval::compiler::expr::Builtin1;
use crate::eval::compiler::expr::ExprCompiled;
use crate::eval::compiler::expr::ExprLogicalBinOp;
use crate::eval::compiler::expr_bool::ExprCompiledBool;
use crate::eval::compiler::known::list_to_tuple;
use crate::eval::compiler::opt_ctx::OptCtx;
use crate::eval::compiler::scope::payload::CstAssignTarget;
use crate::eval::compiler::scope::payload::CstExpr;
use crate::eval::compiler::scope::payload::CstStmt;
use crate::eval::compiler::scope::Captured;
use crate::eval::compiler::scope::Slot;
use crate::eval::compiler::small_vec_1::SmallVec1;
use crate::eval::compiler::span::IrSpanned;
use crate::eval::compiler::Compiler;
use crate::eval::runtime::evaluator::Evaluator;
use crate::eval::runtime::evaluator::GC_THRESHOLD;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
use crate::eval::runtime::slots::LocalCapturedSlotId;
use crate::eval::runtime::slots::LocalSlotId;
use crate::values::dict::Dict;
use crate::values::dict::DictMut;
use crate::values::dict::DictRef;
use crate::values::types::list::value::ListData;
use crate::values::typing::type_compiled::compiled::TypeCompiled;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
use crate::values::Heap;
use crate::values::Value;
use crate::values::ValueError;
#[derive(Clone, Debug)]
pub(crate) enum AssignModifyLhs {
Dot(IrSpanned<ExprCompiled>, String),
Array(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>),
Local(IrSpanned<LocalSlotId>),
LocalCaptured(IrSpanned<LocalCapturedSlotId>),
Module(IrSpanned<ModuleSlotId>),
}
#[derive(Clone, Debug)]
pub(crate) enum StmtCompiled {
PossibleGc,
Return(IrSpanned<ExprCompiled>),
Expr(IrSpanned<ExprCompiled>),
Assign(
IrSpanned<AssignCompiledValue>,
Option<IrSpanned<TypeCompiled<FrozenValue>>>,
IrSpanned<ExprCompiled>,
),
AssignModify(AssignModifyLhs, AssignOp, IrSpanned<ExprCompiled>),
If(Box<(IrSpanned<ExprCompiled>, StmtsCompiled, StmtsCompiled)>),
For(
Box<(
IrSpanned<AssignCompiledValue>,
IrSpanned<ExprCompiled>,
StmtsCompiled,
)>,
),
Break,
Continue,
}
#[derive(Debug, Default)]
pub(crate) struct StmtCompileContext {
pub(crate) has_return_type: bool,
}
pub(crate) struct OptimizeOnFreezeContext<'v, 'a> {
pub(crate) module: &'a FrozenModuleData,
pub(crate) heap: &'v Heap,
pub(crate) frozen_heap: &'a FrozenHeap,
}
impl AssignModifyLhs {
fn optimize(&self, ctx: &mut OptCtx) -> AssignModifyLhs {
match self {
AssignModifyLhs::Dot(expr, name) => {
AssignModifyLhs::Dot(expr.optimize(ctx), name.clone())
}
AssignModifyLhs::Array(expr, index) => {
AssignModifyLhs::Array(expr.optimize(ctx), index.optimize(ctx))
}
l @ (AssignModifyLhs::Local(..)
| AssignModifyLhs::LocalCaptured(..)
| AssignModifyLhs::Module(..)) => l.clone(),
}
}
}
impl IrSpanned<StmtCompiled> {
fn optimize(&self, ctx: &mut OptCtx) -> StmtsCompiled {
let span = self.span;
match &self.node {
StmtCompiled::Return(e) => StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Return(e.optimize(ctx)),
}),
StmtCompiled::Expr(expr) => {
let expr = expr.optimize(ctx);
StmtsCompiled::expr(expr)
}
StmtCompiled::Assign(lhs, ty, rhs) => {
let lhs = lhs.optimize(ctx);
let rhs = rhs.optimize(ctx);
StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Assign(lhs, *ty, rhs),
})
}
StmtCompiled::If(cond_t_f) => {
let (cond, t, f) = &**cond_t_f;
let cond = cond.optimize(ctx);
let t = t.optimize(ctx);
let f = f.optimize(ctx);
StmtsCompiled::if_stmt(span, cond, t, f)
}
StmtCompiled::For(var_over_body) => {
let (var, over, body) = &**var_over_body;
let var = var.optimize(ctx);
let over = over.optimize(ctx);
let body = body.optimize(ctx);
StmtsCompiled::for_stmt(span, var, over, body)
}
s @ (StmtCompiled::PossibleGc | StmtCompiled::Break | StmtCompiled::Continue) => {
StmtsCompiled::one(IrSpanned {
span,
node: s.clone(),
})
}
StmtCompiled::AssignModify(lhs, op, rhs) => StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::AssignModify(lhs.optimize(ctx), *op, rhs.optimize(ctx)),
}),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct StmtsCompiled(SmallVec1<IrSpanned<StmtCompiled>>);
impl StmtsCompiled {
pub(crate) fn empty() -> StmtsCompiled {
StmtsCompiled(SmallVec1::new())
}
pub(crate) fn one(stmt: IrSpanned<StmtCompiled>) -> StmtsCompiled {
StmtsCompiled(SmallVec1::One(stmt))
}
pub(crate) fn is_empty(&self) -> bool {
match &self.0 {
SmallVec1::One(_) => false,
SmallVec1::Vec(stmts) => stmts.is_empty(),
}
}
pub(crate) fn stmts(&self) -> &[IrSpanned<StmtCompiled>] {
self.0.as_slice()
}
fn is_terminal(&self) -> bool {
if let Some(stmt) = self.last() {
match &stmt.node {
StmtCompiled::Break | StmtCompiled::Continue | StmtCompiled::Return(..) => true,
_ => false,
}
} else {
false
}
}
pub(crate) fn extend(&mut self, right: StmtsCompiled) {
if self.is_terminal() {
return;
}
self.0.extend(right.0);
}
pub(crate) fn optimize(&self, ctx: &mut OptCtx) -> StmtsCompiled {
let mut stmts = StmtsCompiled::empty();
match &self.0 {
SmallVec1::One(s) => stmts.extend(s.optimize(ctx)),
SmallVec1::Vec(ss) => {
for s in ss {
if stmts.is_terminal() {
break;
}
stmts.extend(s.optimize(ctx));
}
}
}
stmts
}
pub(crate) fn first(&self) -> Option<&IrSpanned<StmtCompiled>> {
match &self.0 {
SmallVec1::One(s) => Some(s),
SmallVec1::Vec(ss) => ss.first(),
}
}
pub(crate) fn last(&self) -> Option<&IrSpanned<StmtCompiled>> {
match &self.0 {
SmallVec1::One(s) => Some(s),
SmallVec1::Vec(ss) => ss.last(),
}
}
fn expr(expr: IrSpanned<ExprCompiled>) -> StmtsCompiled {
let span = expr.span;
match expr.node {
expr if expr.is_pure_infallible() => StmtsCompiled::empty(),
ExprCompiled::List(xs) | ExprCompiled::Tuple(xs) => {
let mut stmts = StmtsCompiled::empty();
for x in xs {
stmts.extend(Self::expr(x));
}
stmts
}
ExprCompiled::Builtin1(Builtin1::Not | Builtin1::TypeIs(_), x) => Self::expr(*x),
ExprCompiled::LogicalBinOp(ExprLogicalBinOp::And, x_y) => {
let (x, y) = *x_y;
Self::if_stmt(expr.span, x, Self::expr(y), StmtsCompiled::empty())
}
ExprCompiled::LogicalBinOp(ExprLogicalBinOp::Or, x_y) => {
let (x, y) = *x_y;
Self::if_stmt(expr.span, x, StmtsCompiled::empty(), Self::expr(y))
}
expr => {
if let Some(t) = expr.as_type() {
StmtsCompiled::expr(t.clone())
} else {
StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Expr(IrSpanned { span, node: expr }),
})
}
}
}
}
fn if_stmt(
span: FrameSpan,
cond: IrSpanned<ExprCompiled>,
t: StmtsCompiled,
f: StmtsCompiled,
) -> StmtsCompiled {
let cond = ExprCompiledBool::new(cond);
match cond.node {
ExprCompiledBool::Const(true) => t,
ExprCompiledBool::Const(false) => f,
ExprCompiledBool::Expr(cond) => match cond {
ExprCompiled::Builtin1(Builtin1::Not, cond) => Self::if_stmt(span, *cond, f, t),
ExprCompiled::Seq(x_cond) => {
let (x, cond) = *x_cond;
let mut stmt = StmtsCompiled::empty();
stmt.extend(Self::expr(x));
stmt.extend(Self::if_stmt(span, cond, t, f));
stmt
}
cond => {
let cond = IrSpanned { span, node: cond };
if t.is_empty() && f.is_empty() {
Self::expr(cond)
} else {
StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::If(Box::new((cond, t, f))),
})
}
}
},
}
}
fn for_stmt(
span: FrameSpan,
var: IrSpanned<AssignCompiledValue>,
over: IrSpanned<ExprCompiled>,
body: StmtsCompiled,
) -> StmtsCompiled {
if over.is_iterable_empty() {
return StmtsCompiled::empty();
}
StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::For(Box::new((var, over, body))),
})
}
}
#[derive(Debug, Error)]
pub(crate) enum AssignError {
#[error("Unpacked {1} values but expected {0}")]
IncorrectNumberOfValueToUnpack(i32, i32),
}
#[derive(Clone, Debug, VisitSpanMut)]
pub(crate) enum AssignCompiledValue {
Dot(IrSpanned<ExprCompiled>, String),
Index(IrSpanned<ExprCompiled>, IrSpanned<ExprCompiled>),
Tuple(Vec<IrSpanned<AssignCompiledValue>>),
Local(LocalSlotId),
LocalCaptured(LocalCapturedSlotId),
Module(ModuleSlotId, String),
}
impl AssignCompiledValue {
pub(crate) fn as_local_non_captured(&self) -> Option<LocalSlotId> {
match self {
AssignCompiledValue::Local(id) => Some(*id),
_ => None,
}
}
}
impl IrSpanned<AssignCompiledValue> {
pub(crate) fn optimize(&self, ctx: &mut OptCtx) -> IrSpanned<AssignCompiledValue> {
let span = self.span;
let assign = match self.node {
AssignCompiledValue::Dot(ref object, ref field) => {
let object = object.optimize(ctx);
let field = field.clone();
AssignCompiledValue::Dot(object, field)
}
AssignCompiledValue::Index(ref array, ref index) => {
let array = array.optimize(ctx);
let index = index.optimize(ctx);
AssignCompiledValue::Index(array, index)
}
AssignCompiledValue::Tuple(ref xs) => {
let xs = xs.map(|x| x.optimize(ctx));
AssignCompiledValue::Tuple(xs)
}
ref e @ (AssignCompiledValue::Local(..)
| AssignCompiledValue::LocalCaptured(..)
| AssignCompiledValue::Module(..)) => e.clone(),
};
IrSpanned { node: assign, span }
}
}
impl Compiler<'_, '_, '_, '_> {
pub fn assign_target(
&mut self,
expr: &CstAssignTarget,
) -> Result<IrSpanned<AssignCompiledValue>, CompilerInternalError> {
let span = FrameSpan::new(FrozenFileSpan::new(self.codemap, expr.span));
let assign = match &expr.node {
AssignTargetP::Dot(e, s) => {
let e = self.expr(e)?;
let s = &s.node;
AssignCompiledValue::Dot(e, s.to_owned())
}
AssignTargetP::Index(e_idx) => {
let (e, idx) = &**e_idx;
let e = self.expr(e)?;
let idx = self.expr(idx)?;
AssignCompiledValue::Index(e, idx)
}
AssignTargetP::Tuple(v) => {
let v = v
.iter()
.map(|x| self.assign_target(x))
.collect::<Result<_, CompilerInternalError>>()?;
AssignCompiledValue::Tuple(v)
}
AssignTargetP::Identifier(ident) => {
let name = ident.node.ident.as_str();
let binding_id = ident
.node
.payload
.unwrap_or_else(|| panic!("unresolved binding: `{}`", name));
let binding = self.scope_data.get_binding(binding_id);
let slot = binding.resolved_slot(&self.codemap).unwrap();
match (slot, binding.captured) {
(Slot::Local(slot), Captured::No) => {
AssignCompiledValue::Local(LocalSlotId(slot.0))
}
(Slot::Local(slot), Captured::Yes) => {
AssignCompiledValue::LocalCaptured(LocalCapturedSlotId(slot.0))
}
(Slot::Module(slot), _) => AssignCompiledValue::Module(slot, name.to_owned()),
}
}
};
Ok(IrSpanned { node: assign, span })
}
fn assign_modify(
&mut self,
span_stmt: Span,
lhs: &CstAssignTarget,
rhs: IrSpanned<ExprCompiled>,
op: AssignOp,
) -> Result<StmtsCompiled, CompilerInternalError> {
let span_stmt = FrameSpan::new(FrozenFileSpan::new(self.codemap, span_stmt));
let span_lhs = FrameSpan::new(FrozenFileSpan::new(self.codemap, lhs.span));
match &lhs.node {
AssignTargetP::Dot(e, s) => {
let e = self.expr(e)?;
Ok(StmtsCompiled::one(IrSpanned {
span: span_stmt,
node: StmtCompiled::AssignModify(
AssignModifyLhs::Dot(e, s.node.clone()),
op,
rhs,
),
}))
}
AssignTargetP::Index(e_idx) => {
let (e, idx) = &**e_idx;
let e = self.expr(e)?;
let idx = self.expr(idx)?;
Ok(StmtsCompiled::one(IrSpanned {
span: span_stmt,
node: StmtCompiled::AssignModify(AssignModifyLhs::Array(e, idx), op, rhs),
}))
}
AssignTargetP::Identifier(ident) => {
let (slot, captured) = self.scope_data.get_assign_ident_slot(ident, &self.codemap);
match (slot, captured) {
(Slot::Local(slot), Captured::No) => {
let lhs = IrSpanned {
node: LocalSlotId(slot.0),
span: span_lhs,
};
Ok(StmtsCompiled::one(IrSpanned {
span: span_stmt,
node: StmtCompiled::AssignModify(AssignModifyLhs::Local(lhs), op, rhs),
}))
}
(Slot::Local(slot), Captured::Yes) => {
let lhs = IrSpanned {
node: LocalCapturedSlotId(slot.0),
span: span_lhs,
};
Ok(StmtsCompiled::one(IrSpanned {
span: span_stmt,
node: StmtCompiled::AssignModify(
AssignModifyLhs::LocalCaptured(lhs),
op,
rhs,
),
}))
}
(Slot::Module(slot), _) => {
let lhs = IrSpanned {
node: slot,
span: span_lhs,
};
Ok(StmtsCompiled::one(IrSpanned {
span: span_stmt,
node: StmtCompiled::AssignModify(AssignModifyLhs::Module(lhs), op, rhs),
}))
}
}
}
AssignTargetP::Tuple(_) => {
unreachable!("Assign modify validates that the LHS is never a tuple")
}
}
}
}
pub(crate) fn possible_gc(eval: &mut Evaluator) {
if !eval.disable_gc && eval.heap().allocated_bytes() >= eval.next_gc_level {
unsafe { eval.garbage_collect() }
eval.next_gc_level = cmp::max(eval.heap().allocated_bytes() * 2, GC_THRESHOLD);
}
}
pub(crate) fn bit_or_assign<'v>(
lhs: Value<'v>,
rhs: Value<'v>,
heap: &'v Heap,
) -> crate::Result<Value<'v>> {
let lhs_aref = lhs.get_ref();
let lhs_ty = lhs_aref.vtable().static_type_of_value.get();
if Dict::is_dict_type(lhs_ty) {
let mut dict = DictMut::from_value(lhs)?;
if lhs.ptr_eq(rhs) {
} else {
let rhs = DictRef::from_value(rhs).map_or_else(
|| {
ValueError::unsupported_owned(
lhs_aref.vtable().type_name,
"|=",
Some(rhs.get_type()),
)
},
Ok,
)?;
for (k, v) in rhs.iter_hashed() {
dict.aref.insert_hashed(k, v);
}
}
Ok(lhs)
} else {
lhs_aref.bit_or(rhs, heap)
}
}
pub(crate) fn add_assign<'v>(
lhs: Value<'v>,
rhs: Value<'v>,
heap: &'v Heap,
) -> crate::Result<Value<'v>> {
if lhs.unpack_inline_int().is_some() || lhs.is_str() {
return lhs.add(rhs, heap);
}
let lhs_aref = lhs.get_ref();
let lhs_ty = lhs_aref.vtable().static_type_of_value.get();
if ListData::is_list_type(lhs_ty) {
if let Some(v) = rhs.get_ref().radd(lhs, heap) {
v
} else {
let list = ListData::from_value_mut(lhs)?;
if lhs.ptr_eq(rhs) {
list.double(heap);
} else {
list.extend(rhs.iterate(heap)?, heap);
}
Ok(lhs)
}
} else {
lhs.add(rhs, heap)
}
}
impl Compiler<'_, '_, '_, '_> {
pub(crate) fn compile_context(&self, has_return_type: bool) -> StmtCompileContext {
StmtCompileContext { has_return_type }
}
pub(crate) fn stmt(
&mut self,
stmt: &CstStmt,
allow_gc: bool,
) -> Result<StmtsCompiled, CompilerInternalError> {
let span = FrameSpan::new(FrozenFileSpan::new(self.codemap, stmt.span));
let is_statements = matches!(&stmt.node, StmtP::Statements(_));
let res = self.stmt_direct(stmt, allow_gc)?;
if allow_gc && !is_statements {
let mut with_gc = StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::PossibleGc,
});
with_gc.extend(res);
Ok(with_gc)
} else {
Ok(res)
}
}
pub(crate) fn module_top_level_stmt(
&mut self,
stmt: &CstStmt,
) -> Result<StmtsCompiled, CompilerInternalError> {
match &stmt.node {
StmtP::Statements(..) => {
unreachable!("top level statement lists are handled by outer loop")
}
StmtP::Expression(expr) => {
let stmt = Spanned {
span: expr.span,
node: StmtP::Return(Some(expr.clone())),
};
self.stmt(&stmt, true)
}
_ => self.stmt(stmt, true),
}
}
fn stmt_if(
&mut self,
span: FrameSpan,
cond: &CstExpr,
then_block: &CstStmt,
allow_gc: bool,
) -> Result<StmtsCompiled, CompilerInternalError> {
let cond = self.expr(cond)?;
let then_block = self.stmt(then_block, allow_gc)?;
Ok(StmtsCompiled::if_stmt(
span,
cond,
then_block,
StmtsCompiled::empty(),
))
}
fn stmt_if_else(
&mut self,
span: FrameSpan,
cond: &CstExpr,
then_block: &CstStmt,
else_block: &CstStmt,
allow_gc: bool,
) -> Result<StmtsCompiled, CompilerInternalError> {
let cond = self.expr(cond)?;
let then_block = self.stmt(then_block, allow_gc)?;
let else_block = self.stmt(else_block, allow_gc)?;
Ok(StmtsCompiled::if_stmt(span, cond, then_block, else_block))
}
fn stmt_expr(&mut self, expr: &CstExpr) -> Result<StmtsCompiled, CompilerInternalError> {
let expr = self.expr(expr)?;
Ok(StmtsCompiled::expr(expr))
}
fn stmt_direct(
&mut self,
stmt: &CstStmt,
allow_gc: bool,
) -> Result<StmtsCompiled, CompilerInternalError> {
let span = FrameSpan::new(FrozenFileSpan::new(self.codemap, stmt.span));
match &stmt.node {
StmtP::Def(def) => {
let signature_span = def.signature_span();
let signature_span = FrozenFileSpan::new(self.codemap, signature_span);
let DefP {
name,
params,
return_type,
body,
payload: scope_id,
} = def;
let rhs = IrSpanned {
node: self.function(
&name.ident,
signature_span,
*scope_id,
params,
return_type.as_deref(),
body,
)?,
span,
};
let lhs = self.assign_target(&Spanned {
span: name.span,
node: AssignTargetP::Identifier(name.clone()),
})?;
Ok(StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Assign(lhs, None, rhs),
}))
}
StmtP::For(ForP { var, over, body }) => {
let over = list_to_tuple(over);
let var = self.assign_target(var)?;
let over = self.expr(&over)?;
let st = self.stmt(body, false)?;
Ok(StmtsCompiled::for_stmt(span, var, over, st))
}
StmtP::Return(None) => Ok(StmtsCompiled::one(IrSpanned {
node: StmtCompiled::Return(IrSpanned {
span,
node: ExprCompiled::Value(FrozenValue::new_none()),
}),
span,
})),
StmtP::Return(Some(e)) => Ok(StmtsCompiled::one(IrSpanned {
node: StmtCompiled::Return(self.expr(e)?),
span,
})),
StmtP::If(cond, then_block) => self.stmt_if(span, cond, then_block, allow_gc),
StmtP::IfElse(cond, then_block_else_block) => {
let (then_block, else_block) = &**then_block_else_block;
self.stmt_if_else(span, cond, then_block, else_block, allow_gc)
}
StmtP::Statements(stmts) => {
let mut r = StmtsCompiled::empty();
for stmt in stmts {
if r.is_terminal() {
break;
}
r.extend(self.stmt(stmt, allow_gc)?);
}
Ok(r)
}
StmtP::Expression(e) => self.stmt_expr(e),
StmtP::Assign(AssignP { lhs, ty, rhs }) => {
let rhs = self.expr(rhs)?;
let ty = self.expr_for_type(ty.as_ref());
let lhs = self.assign_target(lhs)?;
Ok(StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Assign(lhs, ty, rhs),
}))
}
StmtP::AssignModify(lhs, op, rhs) => {
let rhs = self.expr(rhs)?;
self.assign_modify(span.span.span(), lhs, rhs, *op)
}
StmtP::Load(..) => unreachable!(),
StmtP::Pass => Ok(StmtsCompiled::empty()),
StmtP::Break => Ok(StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Break,
})),
StmtP::Continue => Ok(StmtsCompiled::one(IrSpanned {
span,
node: StmtCompiled::Continue,
})),
}
}
}