use core::cell::{Cell, RefCell};
use core::fmt;
use crate::alloc::prelude::*;
use crate::alloc::{self, HashMap};
use crate::ast::Spanned;
use crate::compile::{self, Assembly, ErrorKind, WithSpan};
use crate::hir;
use crate::query::Query;
use crate::runtime::{Inst, InstAddress, Output};
use crate::SourceId;
use super::{Address, Any, DisplayNamed, Linear, Slab, Slots};
const ROOT: ScopeId = ScopeId { index: 0, id: 0 };
#[derive(Debug)]
pub(super) struct Scope<'hir> {
parent: ScopeId,
id: ScopeId,
names: HashMap<hir::Variable, VarInner<'hir>>,
locals: Dangling,
}
impl Scope<'_> {
fn new(parent: ScopeId, id: ScopeId) -> Self {
Self {
parent,
id,
names: HashMap::new(),
locals: Dangling::default(),
}
}
fn parent(&self) -> Option<ScopeId> {
(self.parent != self.id).then_some(self.parent)
}
}
#[must_use = "Scope handles must be handed back to Scopes to be freed"]
pub(super) struct ScopeHandle {
pub(super) id: ScopeId,
}
#[must_use = "Scope handles must be handed back to Scopes to be freed"]
pub(super) struct DanglingScope {
pub(super) id: ScopeId,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[must_use]
pub(super) struct ScopeId {
index: usize,
id: usize,
}
impl fmt::Display for ScopeId {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.index, self.id)
}
}
impl fmt::Debug for ScopeId {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Default)]
struct Dangling {
addresses: Vec<Option<InstAddress>>,
address_to_index: HashMap<InstAddress, usize>,
}
impl Dangling {
fn clear(&mut self) {
self.addresses.clear();
self.address_to_index.clear();
}
fn insert(&mut self, addr: InstAddress) -> alloc::Result<()> {
if self.address_to_index.contains_key(&addr) {
return Ok(());
}
self.address_to_index
.try_insert(addr, self.addresses.len())?;
self.addresses.try_push(Some(addr))?;
Ok(())
}
fn remove(&mut self, addr: InstAddress) -> bool {
if let Some(index) = self.address_to_index.remove(&addr) {
self.addresses[index] = None;
true
} else {
false
}
}
#[inline]
fn addresses(&self) -> impl Iterator<Item = InstAddress> + '_ {
self.addresses.iter().filter_map(|addr| *addr)
}
}
impl fmt::Debug for Dangling {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.addresses.fmt(f)
}
}
pub(crate) struct Scopes<'hir> {
scopes: RefCell<Slab<Scope<'hir>>>,
dangling: RefCell<Dangling>,
source_id: SourceId,
size: Cell<usize>,
slots: RefCell<Slots>,
id: Cell<usize>,
top: Cell<ScopeId>,
}
impl<'hir> Scopes<'hir> {
pub(crate) fn new(source_id: SourceId) -> alloc::Result<Self> {
let mut scopes = Slab::new();
scopes.insert(Scope::new(ROOT, ROOT))?;
Ok(Self {
scopes: RefCell::new(scopes),
dangling: RefCell::new(Dangling::default()),
source_id,
size: Cell::new(0),
slots: RefCell::new(Slots::new()),
id: Cell::new(1),
top: Cell::new(ROOT),
})
}
pub(crate) fn drain_dangling_into(&self, out: &mut Vec<InstAddress>) -> alloc::Result<()> {
let mut dangling = self.dangling.borrow_mut();
for addr in dangling.addresses.drain(..).flatten() {
out.try_push(addr)?;
}
dangling.clear();
Ok(())
}
#[inline]
pub(crate) fn size(&self) -> usize {
self.size.get()
}
#[inline]
pub(super) fn top_id(&self) -> ScopeId {
self.top.get()
}
#[tracing::instrument(skip(self, q, span))]
pub(super) fn get(
&self,
q: &mut Query<'_, '_>,
span: &dyn Spanned,
name: hir::Variable,
) -> compile::Result<Var<'hir>> {
let scopes = self.scopes.borrow();
let mut current = Some(self.top.get());
while let Some(id) = current.take() {
let Some(scope) = scopes.get(id.index) else {
return Err(compile::Error::msg(span, format!("Missing scope {id}")));
};
current = scope.parent();
let Some(var) = scope.names.get(&name) else {
continue;
};
if let Some(_moved_at) = var.moved_at.get() {
return Err(compile::Error::new(
span,
ErrorKind::VariableMoved {
#[cfg(feature = "emit")]
moved_at: _moved_at.span(),
},
));
}
q.visitor
.visit_variable_use(self.source_id, var.span, span)
.with_span(span)?;
let var = Var {
span: var.span,
name: var.name,
addr: var.addr,
};
tracing::trace!(?scope, ?var);
return Ok(var);
}
Err(compile::Error::msg(
span,
try_format!("Missing variable `{name}`"),
))
}
#[tracing::instrument(skip(self, q, span))]
pub(super) fn take(
&self,
q: &mut Query<'_, '_>,
span: &'hir dyn Spanned,
name: hir::Variable,
) -> compile::Result<Var<'hir>> {
let scopes = self.scopes.borrow();
let mut current = Some(self.top.get());
while let Some(id) = current.take() {
let Some(scope) = scopes.get(id.index) else {
return Err(compile::Error::msg(span, format!("Missing scope {id}")));
};
current = scope.parent();
let Some(var) = scope.names.get(&name) else {
continue;
};
if let Some(_moved_at) = var.moved_at.get() {
return Err(compile::Error::new(
span,
ErrorKind::VariableMoved {
#[cfg(feature = "emit")]
moved_at: _moved_at.span(),
},
));
}
q.visitor
.visit_variable_use(self.source_id, var.span, span)
.with_span(span)?;
var.moved_at.set(Some(span));
let var = Var {
span: var.span,
name: var.name,
addr: var.addr,
};
tracing::trace!(?scope, ?var);
return Ok(var);
}
Err(compile::Error::msg(
span,
try_format!("Missing variable `{name}` to take"),
))
}
#[tracing::instrument(skip(self, span))]
pub(super) fn define(
&self,
span: &'hir dyn Spanned,
name: hir::Variable,
addr: &Address<'_, 'hir>,
) -> compile::Result<()> {
let mut scopes = self.scopes.borrow_mut();
let Some(scope) = scopes.get_mut(self.top.get().index) else {
return Err(compile::Error::msg(span, "Missing top scope"));
};
let var = VarInner {
span,
name,
addr: addr.addr(),
moved_at: Cell::new(None),
};
scope.names.try_insert(name, var).with_span(span)?;
tracing::trace!(?scope, ?name);
Ok(())
}
#[tracing::instrument(skip_all)]
pub(super) fn defer(&self, span: &'hir dyn Spanned) -> Any<'_, 'hir> {
Any::defer(self, self.top.get(), span)
}
#[tracing::instrument(skip_all)]
pub(super) fn alloc(&self, span: &'hir dyn Spanned) -> compile::Result<Address<'_, 'hir>> {
let mut scopes = self.scopes.borrow_mut();
let Some(scope) = scopes.get_mut(self.top.get().index) else {
return Err(compile::Error::msg(span, "Missing top scope"));
};
let addr = InstAddress::new(self.slots.borrow_mut().insert()?);
self.size.set(self.size.get().max(addr.offset() + 1));
scope.locals.insert(addr).with_span(span)?;
self.dangling.borrow_mut().remove(addr);
tracing::trace!(?scope, ?addr, size = self.size.get());
Ok(Address::local(span, self, addr))
}
#[tracing::instrument(skip(self, span))]
pub(super) fn alloc_in(&self, span: &dyn Spanned, id: ScopeId) -> compile::Result<InstAddress> {
let mut scopes = self.scopes.borrow_mut();
let Some(scope) = scopes.get_mut(id.index) else {
return Err(compile::Error::msg(
span,
format!("Missing scope {id} to allocate in"),
));
};
if scope.id != id {
return Err(compile::Error::msg(
span,
try_format!(
"Scope id mismatch, {} (actual) != {id} (expected)",
scope.id
),
));
}
let addr = InstAddress::new(self.slots.borrow_mut().insert()?);
scope.locals.insert(addr).with_span(span)?;
self.size.set(self.size.get().max(addr.offset() + 1));
self.dangling.borrow_mut().remove(addr);
tracing::trace!(?scope, ?addr, size = self.size.get());
Ok(addr)
}
#[tracing::instrument(ret(level = "trace"), skip(self, span))]
pub(super) fn linear(
&self,
span: &'hir dyn Spanned,
n: usize,
) -> compile::Result<Linear<'_, 'hir>> {
let mut scopes = self.scopes.borrow_mut();
let Some(scope) = scopes.get_mut(self.top.get().index) else {
return Err(compile::Error::msg(span, "Missing top scope"));
};
let mut dangling = self.dangling.borrow_mut();
let linear = match n {
0 => Linear::empty(),
1 => {
let addr = InstAddress::new(self.slots.borrow_mut().insert()?);
scope.locals.insert(addr).with_span(span)?;
dangling.remove(addr);
self.size.set(self.size.get().max(addr.offset() + 1));
Linear::single(Address::local(span, self, addr))
}
n => {
let mut slots = self.slots.borrow_mut();
let mut addresses = Vec::try_with_capacity(n).with_span(span)?;
for _ in 0..n {
let addr = InstAddress::new(slots.push().with_span(span)?);
scope.locals.insert(addr).with_span(span)?;
dangling.remove(addr);
addresses.try_push(Address::dangling(span, self, addr))?;
self.size.set(self.size.get().max(addr.offset() + 1));
}
Linear::new(addresses)
}
};
tracing::trace!(?scope, ?linear, size = self.size.get());
Ok(linear)
}
#[tracing::instrument(skip(self, span))]
pub(super) fn free_addr(
&self,
span: &dyn Spanned,
addr: InstAddress,
name: Option<&'static str>,
dangling: bool,
) -> compile::Result<()> {
let mut scopes = self.scopes.borrow_mut();
let Some(scope) = scopes.get_mut(self.top.get().index) else {
return Err(compile::Error::msg(
span,
format!(
"Freed address {} does not have an implicit scope",
DisplayNamed::new(addr, name)
),
));
};
tracing::trace!(?scope);
if !scope.locals.remove(addr) {
return Err(compile::Error::msg(
span,
format!(
"Freed address {} is not defined in scope {}",
DisplayNamed::new(addr, name),
scope.id
),
));
}
if dangling {
self.dangling.borrow_mut().insert(addr).with_span(span)?;
}
let mut slots = self.slots.borrow_mut();
if !slots.remove(addr.offset()) {
return Err(compile::Error::msg(
span,
format!(
"Freed adddress {} does not have a global slot in scope {}",
DisplayNamed::new(addr, name),
scope.id
),
));
}
Ok(())
}
#[tracing::instrument(skip(self, span, handle), fields(id = ?handle.id))]
pub(super) fn pop(&self, span: &dyn Spanned, handle: ScopeHandle) -> compile::Result<()> {
let ScopeHandle { id } = handle;
let Some(mut scope) = self.scopes.borrow_mut().try_remove(id.index) else {
return Err(compile::Error::msg(
span,
format!("Missing scope while expected {id}"),
));
};
if scope.id != id {
return Err(compile::Error::msg(
span,
try_format!(
"Scope id mismatch, {} (actual) != {id} (expected)",
scope.id
),
));
}
tracing::trace!(?scope, "freeing locals");
let mut slots = self.slots.borrow_mut();
let mut dangling = self.dangling.borrow_mut();
for addr in scope.locals.addresses() {
if !slots.remove(addr.offset()) {
return Err(compile::Error::msg(
span,
format!(
"Address {addr} is not globally allocated in {:?}",
self.slots
),
));
}
dangling.insert(addr).with_span(span)?;
}
scope.locals.clear();
self.top.set(scope.parent);
Ok(())
}
#[tracing::instrument(skip(self, span))]
pub(super) fn pop_last(&self, span: &dyn Spanned) -> compile::Result<()> {
self.pop(span, ScopeHandle { id: ROOT })?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub(super) fn child(&self, span: &dyn Spanned) -> compile::Result<ScopeHandle> {
let mut scopes = self.scopes.borrow_mut();
let id = ScopeId {
index: scopes.vacant_key(),
id: self.id.replace(self.id.get().wrapping_add(1)),
};
let scope = Scope::new(self.top.replace(id), id);
tracing::trace!(?scope);
scopes.insert(scope).with_span(span)?;
Ok(ScopeHandle { id })
}
#[tracing::instrument(skip(self, span, handle), fields(id = ?handle.id))]
pub(super) fn dangle(
&self,
span: &dyn Spanned,
handle: ScopeHandle,
) -> compile::Result<DanglingScope> {
let ScopeHandle { id } = handle;
let scopes = self.scopes.borrow();
let Some(scope) = scopes.get(id.index) else {
return Err(compile::Error::msg(
span,
format!("Missing scope while expected {id}"),
));
};
if scope.id != id {
return Err(compile::Error::msg(
span,
try_format!(
"Scope id mismatch, {} (actual) != {id} (expected)",
scope.id
),
));
}
self.top.set(scope.parent);
tracing::trace!(?scope);
Ok(DanglingScope { id })
}
#[tracing::instrument(skip_all)]
pub(super) fn restore(&self, handle: DanglingScope) -> ScopeHandle {
let DanglingScope { id } = handle;
tracing::trace!(?id);
self.top.set(id);
ScopeHandle { id }
}
}
pub(super) struct Var<'hir> {
pub(super) span: &'hir dyn Spanned,
name: hir::Variable,
pub(super) addr: InstAddress,
}
impl fmt::Debug for Var<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Var")
.field("span", &self.span.span())
.field("name", &self.name)
.field("addr", &self.addr)
.finish()
}
}
impl fmt::Display for Var<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.name.fmt(f)
}
}
impl Var<'_> {
pub(super) fn copy(
&self,
asm: &mut Assembly,
span: &dyn Spanned,
comment: Option<&dyn fmt::Display>,
out: Output,
) -> compile::Result<()> {
asm.push_with_comment(
Inst::Copy {
addr: self.addr,
out,
},
span,
&format_args!("var `{}`{}", self.name, Append("; ", comment)),
)
}
pub(super) fn move_(
&self,
asm: &mut Assembly,
span: &dyn Spanned,
comment: Option<&dyn fmt::Display>,
out: Output,
) -> compile::Result<()> {
asm.push_with_comment(
Inst::Move {
addr: self.addr,
out,
},
span,
&format_args!("var `{}`{}", self.name, Append("; ", comment)),
)
}
}
struct Append<P, T>(P, T);
impl<P, T> fmt::Display for Append<P, T>
where
P: fmt::Display,
T: Copy + IntoIterator,
T::Item: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for item in self.1 {
self.0.fmt(f)?;
item.fmt(f)?;
}
Ok(())
}
}
struct VarInner<'hir> {
span: &'hir dyn Spanned,
name: hir::Variable,
addr: InstAddress,
moved_at: Cell<Option<&'hir dyn Spanned>>,
}
impl fmt::Debug for VarInner<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Var")
.field("span", &self.span.span())
.field("name", &self.name)
.field("addr", &self.addr)
.field("moved_at", &self.moved_at.get().map(|s| s.span()))
.finish()
}
}