use core::fmt;
use core::mem::replace;
use crate::ast::Spanned;
use crate::compile;
use crate::runtime::{Inst, InstAddress, Output};
use crate::shared::{rune_diagnose, Backtrace};
use super::{Ctxt, DisplayNamed, ScopeId, Scopes};
pub(super) trait Needs<'a, 'hir> {
fn span(&self) -> &'hir dyn Spanned;
fn output(&self) -> compile::Result<Output>;
fn try_alloc_output(&mut self) -> compile::Result<Option<Output>>;
fn assign_addr(
&mut self,
cx: &mut Ctxt<'_, 'hir, '_>,
from: InstAddress,
) -> compile::Result<()>;
fn alloc_output(&mut self) -> compile::Result<Output>;
fn try_alloc_addr(&mut self) -> compile::Result<Option<&mut Address<'a, 'hir>>>;
fn try_as_addr(&self) -> compile::Result<Option<&Address<'a, 'hir>>>;
fn addr(&self) -> compile::Result<&Address<'a, 'hir>> {
let Some(addr) = self.try_as_addr()? else {
return Err(compile::Error::msg(
self.span(),
"Expected need to be populated",
));
};
Ok(addr)
}
}
impl<'a, 'hir> Needs<'a, 'hir> for Any<'a, 'hir> {
#[inline]
fn span(&self) -> &'hir dyn Spanned {
self.span
}
#[inline]
fn output(&self) -> compile::Result<Output> {
Any::output(self)
}
#[inline]
fn assign_addr(
&mut self,
cx: &mut Ctxt<'_, 'hir, '_>,
from: InstAddress,
) -> compile::Result<()> {
Any::assign_addr(self, cx, from)
}
#[inline]
fn alloc_output(&mut self) -> compile::Result<Output> {
Any::alloc_output(self)
}
#[inline]
fn try_alloc_output(&mut self) -> compile::Result<Option<Output>> {
Any::try_alloc_output(self)
}
#[inline]
fn try_alloc_addr(&mut self) -> compile::Result<Option<&mut Address<'a, 'hir>>> {
Any::try_alloc_addr(self)
}
#[inline]
fn try_as_addr(&self) -> compile::Result<Option<&Address<'a, 'hir>>> {
Any::try_as_addr(self)
}
}
impl<'a, 'hir> Needs<'a, 'hir> for Address<'a, 'hir> {
#[inline]
fn span(&self) -> &'hir dyn Spanned {
self.span
}
#[inline]
fn output(&self) -> compile::Result<Output> {
Ok(Address::output(self))
}
#[inline]
fn assign_addr(
&mut self,
cx: &mut Ctxt<'_, 'hir, '_>,
from: InstAddress,
) -> compile::Result<()> {
Address::assign_addr(self, cx, from)
}
#[inline]
fn alloc_output(&mut self) -> compile::Result<Output> {
Address::alloc_output(self)
}
#[inline]
fn try_alloc_output(&mut self) -> compile::Result<Option<Output>> {
Ok(Some(Address::alloc_output(self)?))
}
#[inline]
fn try_alloc_addr(&mut self) -> compile::Result<Option<&mut Address<'a, 'hir>>> {
Ok(Some(Address::alloc_addr(self)?))
}
#[inline]
fn try_as_addr(&self) -> compile::Result<Option<&Address<'a, 'hir>>> {
Ok(Some(self))
}
}
#[derive(Clone, Copy)]
pub(super) enum AddressKind {
Local,
Dangling,
Assigned,
Scope(ScopeId),
Freed,
}
impl fmt::Display for AddressKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AddressKind::Local => write!(f, "local"),
AddressKind::Dangling => write!(f, "dangling"),
AddressKind::Assigned => write!(f, "assigned"),
AddressKind::Scope(scope) => write!(f, "scope({scope})"),
AddressKind::Freed => write!(f, "freed"),
}
}
}
#[must_use = "An allocated address must be freed"]
pub(super) struct Address<'a, 'hir> {
span: &'hir dyn Spanned,
scopes: &'a Scopes<'hir>,
address: InstAddress,
kind: AddressKind,
name: Option<&'static str>,
#[cfg_attr(not(feature = "tracing"), allow(unused))]
backtrace: Backtrace,
}
impl<'a, 'hir> Address<'a, 'hir> {
#[inline]
#[track_caller]
pub(super) fn local(
span: &'hir dyn Spanned,
scopes: &'a Scopes<'hir>,
addr: InstAddress,
) -> Self {
Self {
span,
scopes,
address: addr,
kind: AddressKind::Local,
name: None,
backtrace: Backtrace::capture(),
}
}
#[inline]
#[track_caller]
pub(super) fn assigned(
span: &'hir dyn Spanned,
scopes: &'a Scopes<'hir>,
addr: InstAddress,
) -> Self {
Self {
span,
scopes,
address: addr,
kind: AddressKind::Assigned,
name: None,
backtrace: Backtrace::capture(),
}
}
#[inline]
#[track_caller]
pub(super) fn dangling(
span: &'hir dyn Spanned,
scopes: &'a Scopes<'hir>,
addr: InstAddress,
) -> Self {
Self {
span,
scopes,
address: addr,
kind: AddressKind::Dangling,
name: None,
backtrace: Backtrace::capture(),
}
}
#[inline]
pub(super) fn with_name(mut self, name: &'static str) -> Self {
self.name = Some(name);
self
}
#[inline]
pub(super) fn addr(&self) -> InstAddress {
self.address
}
#[inline]
pub(super) fn alloc_addr(&mut self) -> compile::Result<&mut Self> {
if matches!(self.kind, AddressKind::Dangling) {
self.kind = AddressKind::Local;
}
Ok(self)
}
#[inline]
pub(super) fn alloc_output(&mut self) -> compile::Result<Output> {
Ok(self.alloc_addr()?.output())
}
#[inline]
pub(super) fn output(&self) -> Output {
self.address.output()
}
pub(super) fn assign_addr(
&self,
cx: &mut Ctxt<'_, '_, '_>,
from: InstAddress,
) -> compile::Result<()> {
if from != self.address {
cx.asm.push(
Inst::Copy {
addr: from,
out: self.address.output(),
},
self.span,
)?;
}
Ok(())
}
pub(super) fn forget(mut self) -> compile::Result<()> {
self.kind = AddressKind::Freed;
Ok(())
}
pub(super) fn free(self) -> compile::Result<()> {
self.free_inner(true)
}
pub(super) fn free_non_dangling(self) -> compile::Result<()> {
self.free_inner(false)
}
fn free_inner(mut self, dangling: bool) -> compile::Result<()> {
match replace(&mut self.kind, AddressKind::Freed) {
AddressKind::Local | AddressKind::Dangling => {
self.scopes
.free_addr(self.span, self.address, self.name, dangling)?;
}
AddressKind::Scope(scope) => {
if self.scopes.top_id() == scope {
self.scopes
.free_addr(self.span, self.address, self.name, dangling)?;
}
}
AddressKind::Freed => {
return Err(compile::Error::msg(
self.span,
"Address has already been freed",
));
}
_ => {}
}
Ok(())
}
}
impl fmt::Display for Address<'_, '_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} address {}",
self.kind,
DisplayNamed::new(self.address, self.name)
)
}
}
impl fmt::Debug for Address<'_, '_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl Drop for Address<'_, '_> {
fn drop(&mut self) {
if matches!(self.kind, AddressKind::Freed) {
return;
}
rune_diagnose!("{self} was not freed:\nallocated at:\n{}", self.backtrace);
}
}
enum AnyKind<'a, 'hir> {
Defer {
scopes: &'a Scopes<'hir>,
scope: ScopeId,
name: Option<&'static str>,
},
Address {
address: Address<'a, 'hir>,
},
Ignore {
#[allow(unused)]
name: Option<&'static str>,
},
Freed,
}
impl fmt::Display for AnyKind<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AnyKind::Defer { scope, name, .. } => {
write!(f, "defer({})", DisplayNamed::new(scope, *name))
}
AnyKind::Address { address } => address.fmt(f),
AnyKind::Ignore { name } => DisplayNamed::new("ignore", *name).fmt(f),
AnyKind::Freed => write!(f, "freed"),
}
}
}
impl fmt::Debug for AnyKind<'_, '_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[must_use = "A need must be freed when its no longer in use"]
pub(super) struct Any<'a, 'hir> {
span: &'hir dyn Spanned,
kind: AnyKind<'a, 'hir>,
#[cfg_attr(not(feature = "tracing"), allow(unused))]
backtrace: Backtrace,
}
impl<'a, 'hir> Any<'a, 'hir> {
#[track_caller]
pub(super) fn ignore(span: &'hir dyn Spanned) -> Self {
Self {
span,
kind: AnyKind::Ignore { name: None },
backtrace: Backtrace::capture(),
}
}
#[track_caller]
pub(super) fn defer(scopes: &'a Scopes<'hir>, scope: ScopeId, span: &'hir dyn Spanned) -> Self {
Self {
span,
kind: AnyKind::Defer {
scopes,
scope,
name: None,
},
backtrace: Backtrace::capture(),
}
}
#[track_caller]
pub(super) fn assigned(
span: &'hir dyn Spanned,
scopes: &'a Scopes<'hir>,
addr: InstAddress,
) -> Self {
Self {
span,
kind: AnyKind::Address {
address: Address::assigned(span, scopes, addr),
},
backtrace: Backtrace::capture(),
}
}
pub(super) fn with_name(mut self, new_name: &'static str) -> Self {
match &mut self.kind {
AnyKind::Defer { name, .. } => {
*name = Some(new_name);
}
AnyKind::Address { address } => {
address.name = Some(new_name);
}
AnyKind::Ignore { name, .. } => {
*name = Some(new_name);
}
AnyKind::Freed => {}
};
self
}
pub(super) fn assign_addr(
&mut self,
cx: &mut Ctxt<'_, 'hir, '_>,
from: InstAddress,
) -> compile::Result<()> {
match &self.kind {
AnyKind::Defer { scopes, name, .. } => {
self.kind = AnyKind::Address {
address: Address {
span: self.span,
scopes,
address: from,
kind: AddressKind::Assigned,
name: *name,
backtrace: Backtrace::capture(),
},
};
}
AnyKind::Address { address } => {
address.assign_addr(cx, from)?;
}
_ => {}
}
Ok(())
}
#[inline]
pub(super) fn try_alloc_addr(&mut self) -> compile::Result<Option<&mut Address<'a, 'hir>>> {
if let AnyKind::Defer {
scopes,
scope,
name,
} = self.kind
{
let address = Address {
span: self.span,
scopes,
address: scopes.alloc_in(self.span, scope)?,
kind: AddressKind::Scope(scope),
name,
backtrace: Backtrace::capture(),
};
self.kind = AnyKind::Address { address };
}
match &mut self.kind {
AnyKind::Address { address } => Ok(Some(address.alloc_addr()?)),
_ => Ok(None),
}
}
#[inline]
pub(super) fn try_alloc_output(&mut self) -> compile::Result<Option<Output>> {
let Some(addr) = self.try_alloc_addr()? else {
return Ok(None);
};
Ok(Some(addr.output()))
}
#[inline(always)]
pub(super) fn alloc_output(&mut self) -> compile::Result<Output> {
let Some(addr) = self.try_alloc_addr()? else {
return Ok(Output::discard());
};
Ok(addr.output())
}
#[inline]
pub(super) fn addr(&self) -> compile::Result<&Address<'a, 'hir>> {
match &self.kind {
AnyKind::Address { address } => Ok(address),
kind => Err(compile::Error::msg(
self.span,
format!("No address for need {kind}"),
)),
}
}
#[inline]
pub(super) fn into_addr(mut self) -> compile::Result<Address<'a, 'hir>> {
match replace(&mut self.kind, AnyKind::Freed) {
AnyKind::Address { address } => Ok(address),
kind => Err(compile::Error::msg(
self.span,
format!("No address for need {kind}"),
)),
}
}
#[inline]
pub(super) fn output(&self) -> compile::Result<Output> {
match &self.kind {
AnyKind::Address { address } => Ok(Output::keep(address.address.offset())),
AnyKind::Ignore { .. } => Ok(Output::discard()),
kind => Err(compile::Error::msg(
self.span,
format!("Needs {kind} has not been allocated for output"),
)),
}
}
#[inline]
pub(super) fn try_as_addr(&self) -> compile::Result<Option<&Address<'a, 'hir>>> {
match &self.kind {
AnyKind::Address { address } => {
if matches!(address.kind, AddressKind::Dangling) {
return Err(compile::Error::msg(
self.span,
"Expected address to be initialized",
));
}
Ok(Some(address))
}
_ => Ok(None),
}
}
pub(super) fn free(mut self) -> compile::Result<()> {
if let AnyKind::Address { address } = replace(&mut self.kind, AnyKind::Freed) {
address.free()?;
}
Ok(())
}
}
impl fmt::Display for Any<'_, '_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.kind.fmt(f)
}
}
impl fmt::Debug for Any<'_, '_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl Drop for Any<'_, '_> {
fn drop(&mut self) {
if matches!(self.kind, AnyKind::Ignore { .. } | AnyKind::Freed) {
return;
}
rune_diagnose!("{self} was not freed:\nallocated at:\n{}", self.backtrace);
}
}