use std::ops::{Deref, DerefMut, Index};
use cairo_lang_debug::DebugWithDb;
use cairo_lang_defs::diagnostic_utils::StableLocation;
use cairo_lang_diagnostics::{DiagnosticNote, Diagnostics};
use cairo_lang_proc_macros::HeapSize;
use cairo_lang_semantic as semantic;
use cairo_lang_semantic::corelib::{concrete_destruct_trait, concrete_panic_destruct_trait};
use cairo_lang_semantic::expr::inference::InferenceError;
use cairo_lang_semantic::expr::inference::solver::Ambiguity;
use cairo_lang_semantic::items::constant::ConstValueId;
use cairo_lang_semantic::items::imp::ImplLookupContextId;
use cairo_lang_semantic::types::{TypeInfo, TypesSemantic};
use cairo_lang_semantic::{ConcreteEnumId, ConcreteVariant};
use cairo_lang_utils::Intern;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use id_arena::{Arena, DefaultArenaBehavior, Id};
pub mod blocks;
pub use blocks::BlockId;
use salsa::Database;
use semantic::MatchArmSelector;
use self::blocks::Blocks;
use crate::analysis::StatementLocation;
use crate::diagnostic::LoweringDiagnostic;
use crate::fmt::LoweredFormatter;
use crate::ids::{FunctionId, LocationId, Signature};
#[derive(Clone, Debug, Eq, Hash, PartialEq, HeapSize, salsa::Update)]
pub struct Location<'db> {
pub stable_location: StableLocation<'db>,
pub notes: Vec<DiagnosticNote<'db>>,
pub inline_locations: Vec<StableLocation<'db>>,
}
impl<'db> Location<'db> {
pub fn new(stable_location: StableLocation<'db>) -> Self {
Self { stable_location, notes: vec![], inline_locations: vec![] }
}
pub fn with_note(mut self, note: DiagnosticNote<'db>) -> Self {
self.notes.push(note);
self
}
pub fn maybe_with_note(mut self, note: Option<DiagnosticNote<'db>>) -> Self {
let Some(note) = note else {
return self;
};
self.notes.push(note);
self
}
pub fn add_note_with_location(
self,
db: &'db dyn Database,
text: &str,
location: LocationId<'db>,
) -> Self {
self.with_note(DiagnosticNote::with_location(
text.into(),
location.long(db).stable_location.span_in_file(db),
))
}
}
impl<'db> DebugWithDb<'db> for Location<'db> {
type Db = dyn Database;
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db dyn Database) -> std::fmt::Result {
self.stable_location.span_in_file(db).fmt(f, db)?;
for note in &self.notes {
f.write_str("\nnote: ")?;
note.fmt(f, db)?;
}
Ok(())
}
}
impl<'db> LocationId<'db> {
pub fn inlined(self, db: &'db dyn Database, inlining_location: StableLocation<'db>) -> Self {
let mut location = self.long(db).clone();
location.inline_locations.push(inlining_location);
location.intern(db)
}
pub fn all_locations(self, db: &'db dyn Database) -> Vec<StableLocation<'db>> {
let location = self.long(db);
let mut all_locations = vec![location.stable_location];
all_locations.extend(location.inline_locations.iter().cloned());
all_locations
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VariableMarker;
pub type VariableId = Id<VariableMarker>;
pub type VariableArena<'db> = Arena<Variable<'db>, DefaultArenaBehavior<VariableMarker>>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct VarUsage<'db> {
pub var_id: VariableId,
pub location: LocationId<'db>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Lowered<'db> {
pub diagnostics: Diagnostics<'db, LoweringDiagnostic<'db>>,
pub signature: Signature<'db>,
pub variables: VariableArena<'db>,
pub blocks: Blocks<'db>,
pub parameters: Vec<VariableId>,
}
impl<'db> Index<StatementLocation> for Lowered<'db> {
type Output = Statement<'db>;
fn index(&self, location: StatementLocation) -> &Self::Output {
&self.blocks[location]
}
}
unsafe impl<'db> salsa::Update for Lowered<'db> {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_value = unsafe { &mut *old_pointer };
let res = unsafe {
Diagnostics::maybe_update(&mut old_value.diagnostics, new_value.diagnostics)
| Signature::maybe_update(&mut old_value.signature, new_value.signature)
} | (old_value.blocks != new_value.blocks);
if res {
old_value.variables = new_value.variables;
old_value.parameters = new_value.parameters;
old_value.blocks = new_value.blocks;
}
res
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct VarRemapping<'db> {
pub remapping: OrderedHashMap<VariableId, VarUsage<'db>>,
}
impl<'db> Deref for VarRemapping<'db> {
type Target = OrderedHashMap<VariableId, VarUsage<'db>>;
fn deref(&self) -> &Self::Target {
&self.remapping
}
}
impl<'db> DerefMut for VarRemapping<'db> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.remapping
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Block<'db> {
pub statements: Vec<Statement<'db>>,
pub end: BlockEnd<'db>,
}
impl<'db> Default for Block<'db> {
fn default() -> Self {
Self { statements: Default::default(), end: BlockEnd::NotSet }
}
}
impl<'db> Block<'db> {
pub fn is_set(&self) -> bool {
!matches!(self.end, BlockEnd::NotSet)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BlockEnd<'db> {
NotSet,
Return(Vec<VarUsage<'db>>, LocationId<'db>),
Panic(VarUsage<'db>),
Goto(BlockId, VarRemapping<'db>),
Match {
info: MatchInfo<'db>,
},
}
impl<'db> BlockEnd<'db> {
pub fn location(&self) -> Option<LocationId<'db>> {
match self {
BlockEnd::Return(_, location) => Some(*location),
BlockEnd::Panic(var) => Some(var.location),
BlockEnd::Match { info } => Some(*info.location()),
BlockEnd::Goto(_, _) | BlockEnd::NotSet => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variable<'db> {
pub ty: semantic::TypeId<'db>,
pub location: LocationId<'db>,
pub info: TypeInfo<'db>,
}
impl<'db> DebugWithDb<'db> for Variable<'db> {
type Db = LoweredFormatter<'db>;
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, _ctx: &Self::Db) -> std::fmt::Result {
write!(f, "Variable({:?})", self.ty)
}
}
impl<'db> Variable<'db> {
pub fn new(
db: &'db dyn Database,
ctx: ImplLookupContextId<'db>,
ty: semantic::TypeId<'db>,
location: LocationId<'db>,
) -> Self {
Self { ty, location, info: db.type_info(ctx, ty) }
}
pub fn with_default_context(
db: &'db dyn Database,
ty: semantic::TypeId<'db>,
location: LocationId<'db>,
) -> Self {
Self {
ty,
location,
info: TypeInfo {
copyable: db.copyable(ty),
droppable: db.droppable(ty),
destruct_impl: Err(InferenceError::Ambiguity(Ambiguity::WillNotInfer(
concrete_destruct_trait(db, ty),
))),
panic_destruct_impl: Err(InferenceError::Ambiguity(Ambiguity::WillNotInfer(
concrete_panic_destruct_trait(db, ty),
))),
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Statement<'db> {
Const(StatementConst<'db>),
Call(StatementCall<'db>),
StructConstruct(StatementStructConstruct<'db>),
StructDestructure(StatementStructDestructure<'db>),
EnumConstruct(StatementEnumConstruct<'db>),
Snapshot(StatementSnapshot<'db>),
Desnap(StatementDesnap<'db>),
IntoBox(StatementIntoBox<'db>),
Unbox(StatementUnbox<'db>),
}
impl<'db> Statement<'db> {
pub fn inputs(&self) -> &[VarUsage<'db>] {
match &self {
Statement::Const(_stmt) => &[],
Statement::Call(stmt) => stmt.inputs.as_slice(),
Statement::StructConstruct(stmt) => stmt.inputs.as_slice(),
Statement::StructDestructure(stmt) => std::slice::from_ref(&stmt.input),
Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.input),
Statement::Snapshot(stmt) => std::slice::from_ref(&stmt.input),
Statement::Desnap(stmt) => std::slice::from_ref(&stmt.input),
Statement::IntoBox(stmt) => std::slice::from_ref(&stmt.input),
Statement::Unbox(stmt) => std::slice::from_ref(&stmt.input),
}
}
pub fn inputs_mut(&mut self) -> &mut [VarUsage<'db>] {
match self {
Statement::Const(_stmt) => &mut [],
Statement::Call(stmt) => stmt.inputs.as_mut_slice(),
Statement::StructConstruct(stmt) => stmt.inputs.as_mut_slice(),
Statement::StructDestructure(stmt) => std::slice::from_mut(&mut stmt.input),
Statement::EnumConstruct(stmt) => std::slice::from_mut(&mut stmt.input),
Statement::Snapshot(stmt) => std::slice::from_mut(&mut stmt.input),
Statement::Desnap(stmt) => std::slice::from_mut(&mut stmt.input),
Statement::IntoBox(stmt) => std::slice::from_mut(&mut stmt.input),
Statement::Unbox(stmt) => std::slice::from_mut(&mut stmt.input),
}
}
pub fn outputs(&self) -> &[VariableId] {
match self {
Statement::Const(stmt) => std::slice::from_ref(&stmt.output),
Statement::Call(stmt) => stmt.outputs.as_slice(),
Statement::StructConstruct(stmt) => std::slice::from_ref(&stmt.output),
Statement::StructDestructure(stmt) => stmt.outputs.as_slice(),
Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.output),
Statement::Snapshot(stmt) => stmt.outputs.as_slice(),
Statement::Desnap(stmt) => std::slice::from_ref(&stmt.output),
Statement::IntoBox(stmt) => std::slice::from_ref(&stmt.output),
Statement::Unbox(stmt) => std::slice::from_ref(&stmt.output),
}
}
pub fn outputs_mut(&mut self) -> &mut [VariableId] {
match self {
Statement::Const(stmt) => std::slice::from_mut(&mut stmt.output),
Statement::Call(stmt) => stmt.outputs.as_mut_slice(),
Statement::StructConstruct(stmt) => std::slice::from_mut(&mut stmt.output),
Statement::StructDestructure(stmt) => stmt.outputs.as_mut_slice(),
Statement::EnumConstruct(stmt) => std::slice::from_mut(&mut stmt.output),
Statement::Snapshot(stmt) => stmt.outputs.as_mut_slice(),
Statement::Desnap(stmt) => std::slice::from_mut(&mut stmt.output),
Statement::IntoBox(stmt) => std::slice::from_mut(&mut stmt.output),
Statement::Unbox(stmt) => std::slice::from_mut(&mut stmt.output),
}
}
pub fn location(&self) -> Option<LocationId<'db>> {
match &self {
Statement::Const(_) => None,
Statement::Call(stmt) => Some(stmt.location),
Statement::StructConstruct(_) => None,
Statement::StructDestructure(stmt) => Some(stmt.input.location),
Statement::EnumConstruct(stmt) => Some(stmt.input.location),
Statement::Snapshot(stmt) => Some(stmt.input.location),
Statement::Desnap(stmt) => Some(stmt.input.location),
Statement::IntoBox(stmt) => Some(stmt.input.location),
Statement::Unbox(stmt) => Some(stmt.input.location),
}
}
pub fn location_mut(&mut self) -> Option<&mut LocationId<'db>> {
match self {
Statement::Const(_) => None,
Statement::Call(stmt) => Some(&mut stmt.location),
Statement::StructConstruct(_) => None,
Statement::StructDestructure(stmt) => Some(&mut stmt.input.location),
Statement::EnumConstruct(stmt) => Some(&mut stmt.input.location),
Statement::Snapshot(stmt) => Some(&mut stmt.input.location),
Statement::Desnap(stmt) => Some(&mut stmt.input.location),
Statement::IntoBox(stmt) => Some(&mut stmt.input.location),
Statement::Unbox(stmt) => Some(&mut stmt.input.location),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementConst<'db> {
pub value: ConstValueId<'db>,
pub output: VariableId,
pub boxed: bool,
}
impl<'db> StatementConst<'db> {
pub fn new(value: ConstValueId<'db>, output: VariableId, boxed: bool) -> Self {
Self { value, output, boxed }
}
pub fn new_flat(value: ConstValueId<'db>, output: VariableId) -> Self {
Self::new(value, output, false)
}
pub fn new_boxed(value: ConstValueId<'db>, output: VariableId) -> Self {
Self::new(value, output, true)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementCall<'db> {
pub function: FunctionId<'db>,
pub inputs: Vec<VarUsage<'db>>,
pub with_coupon: bool,
pub outputs: Vec<VariableId>,
pub is_specialization_base_call: bool,
pub location: LocationId<'db>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementEnumConstruct<'db> {
pub variant: ConcreteVariant<'db>,
pub input: VarUsage<'db>,
pub output: VariableId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementStructConstruct<'db> {
pub inputs: Vec<VarUsage<'db>>,
pub output: VariableId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementStructDestructure<'db> {
pub input: VarUsage<'db>,
pub outputs: Vec<VariableId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementSnapshot<'db> {
pub input: VarUsage<'db>,
pub outputs: [VariableId; 2],
}
impl<'db> StatementSnapshot<'db> {
pub fn new(
input: VarUsage<'db>,
output_original: VariableId,
output_snapshot: VariableId,
) -> Self {
Self { input, outputs: [output_original, output_snapshot] }
}
pub fn original(&self) -> VariableId {
self.outputs[0]
}
pub fn snapshot(&self) -> VariableId {
self.outputs[1]
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementDesnap<'db> {
pub input: VarUsage<'db>,
pub output: VariableId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementIntoBox<'db> {
pub input: VarUsage<'db>,
pub output: VariableId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementUnbox<'db> {
pub input: VarUsage<'db>,
pub output: VariableId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchArm<'db> {
pub arm_selector: MatchArmSelector<'db>,
pub block_id: BlockId,
pub var_ids: Vec<VariableId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchExternInfo<'db> {
pub function: FunctionId<'db>,
pub inputs: Vec<VarUsage<'db>>,
pub arms: Vec<MatchArm<'db>>,
pub location: LocationId<'db>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchEnumInfo<'db> {
pub concrete_enum_id: ConcreteEnumId<'db>,
pub input: VarUsage<'db>,
pub arms: Vec<MatchArm<'db>>,
pub location: LocationId<'db>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchEnumValue<'db> {
pub num_of_arms: usize,
pub input: VarUsage<'db>,
pub arms: Vec<MatchArm<'db>>,
pub location: LocationId<'db>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MatchInfo<'db> {
Enum(MatchEnumInfo<'db>),
Extern(MatchExternInfo<'db>),
Value(MatchEnumValue<'db>),
}
impl<'db> MatchInfo<'db> {
pub fn inputs(&self) -> &[VarUsage<'db>] {
match self {
MatchInfo::Enum(s) => std::slice::from_ref(&s.input),
MatchInfo::Extern(s) => s.inputs.as_slice(),
MatchInfo::Value(s) => std::slice::from_ref(&s.input),
}
}
pub fn inputs_mut(&mut self) -> &mut [VarUsage<'db>] {
match self {
MatchInfo::Enum(s) => std::slice::from_mut(&mut s.input),
MatchInfo::Extern(s) => s.inputs.as_mut_slice(),
MatchInfo::Value(s) => std::slice::from_mut(&mut s.input),
}
}
pub fn arms(&self) -> &[MatchArm<'db>] {
match self {
MatchInfo::Enum(s) => &s.arms,
MatchInfo::Extern(s) => &s.arms,
MatchInfo::Value(s) => &s.arms,
}
}
pub fn arms_mut(&mut self) -> &mut [MatchArm<'db>] {
match self {
MatchInfo::Enum(s) => &mut s.arms,
MatchInfo::Extern(s) => &mut s.arms,
MatchInfo::Value(s) => &mut s.arms,
}
}
pub fn location(&self) -> &LocationId<'db> {
match self {
MatchInfo::Enum(s) => &s.location,
MatchInfo::Extern(s) => &s.location,
MatchInfo::Value(s) => &s.location,
}
}
pub fn location_mut(&mut self) -> &mut LocationId<'db> {
match self {
MatchInfo::Enum(s) => &mut s.location,
MatchInfo::Extern(s) => &mut s.location,
MatchInfo::Value(s) => &mut s.location,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DependencyType {
Call,
Cost,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LoweringStage {
Monomorphized,
PreOptimizations,
PostBaseline,
Final,
}