use core::fmt;
use core::mem::take;
use core::slice;
use tracing::instrument_ast;
use crate::alloc::prelude::*;
use crate::alloc::BTreeMap;
use crate::ast::{self, Spanned};
use crate::compile::ir;
use crate::compile::{self, Assembly, ErrorKind, ItemId, ModId, Options, WithSpan};
use crate::hir;
use crate::query::{ConstFn, Query, Used};
use crate::runtime::{
ConstValue, ConstValueKind, Inline, Inst, InstAddress, InstArithmeticOp, InstBitwiseOp, InstOp,
InstRange, InstShiftOp, InstTarget, InstValue, InstVariant, Label, Output, PanicReason,
Protocol, TypeCheck,
};
use crate::shared::FixedVec;
use crate::{Hash, SourceId};
use super::{Address, Any, Break, Breaks, Linear, Needs, ScopeHandle, Scopes};
macro_rules! converge {
($expr:expr $(, $method:ident($($diverge:expr),* $(,)?))?) => {
match $expr {
Asm {
outcome: Outcome::Converge(data),
..
} => data,
Asm {
span,
outcome: Outcome::Diverge,
} => {
$($($diverge.$method()?;)*)*
return Ok(Asm {
span,
outcome: Outcome::Diverge,
})
}
}
};
}
enum Pattern {
Irrefutable,
Refutable,
}
pub(crate) struct Ctxt<'a, 'hir, 'arena> {
pub(crate) source_id: SourceId,
pub(crate) q: Query<'a, 'arena>,
pub(crate) asm: &'a mut Assembly,
pub(crate) scopes: &'a Scopes<'hir>,
pub(crate) contexts: Vec<&'hir dyn Spanned>,
pub(crate) breaks: Breaks<'hir>,
pub(crate) options: &'a Options,
pub(crate) select_branches: Vec<(Label, &'hir hir::ExprSelectBranch<'hir>)>,
pub(crate) drop: Vec<InstAddress>,
}
impl<'hir> Ctxt<'_, 'hir, '_> {
fn drop_dangling(&mut self, span: &dyn Spanned) -> compile::Result<()> {
self.scopes
.drain_dangling_into(&mut self.drop)
.with_span(span)?;
let mut drop_set = self.q.unit.drop_set();
for addr in self.drop.drain(..).rev() {
drop_set.push(addr)?;
}
if let Some(set) = drop_set.finish()? {
self.asm.push(Inst::Drop { set }, span)?;
}
Ok(())
}
pub(crate) fn context(&self) -> Option<&'hir dyn Spanned> {
self.contexts.last().copied()
}
pub(crate) fn call_const_fn(
&mut self,
span: &dyn Spanned,
from_module: ModId,
from_item: ItemId,
query_const_fn: &ConstFn,
args: &[hir::Expr<'_>],
) -> compile::Result<ConstValue> {
if query_const_fn.ir_fn.args.len() != args.len() {
return Err(compile::Error::new(
span,
ErrorKind::BadArgumentCount {
expected: query_const_fn.ir_fn.args.len(),
actual: args.len(),
},
));
}
let mut compiler = ir::Ctxt {
source_id: self.source_id,
q: self.q.borrow(),
};
let mut compiled = Vec::new();
for (hir, name) in args.iter().zip(&query_const_fn.ir_fn.args) {
compiled.try_push((ir::compiler::expr(hir, &mut compiler)?, name))?;
}
let mut interpreter = ir::Interpreter {
budget: ir::Budget::new(1_000_000),
scopes: ir::Scopes::new()?,
module: from_module,
item: from_item,
q: self.q.borrow(),
};
for (ir, name) in compiled {
let value = interpreter.eval_value(&ir, Used::Used)?;
interpreter.scopes.decl(*name, value).with_span(span)?;
}
interpreter.module = query_const_fn.item_meta.module;
interpreter.item = query_const_fn.item_meta.item;
let value = interpreter.eval_value(&query_const_fn.ir_fn.ir, Used::Used)?;
Ok(crate::from_value(value).with_span(span)?)
}
}
enum Outcome<T> {
Converge(T),
Diverge,
}
#[must_use = "Assembly should be checked for convergence to reduce code generation"]
struct Asm<'hir, T = ()> {
span: &'hir dyn Spanned,
outcome: Outcome<T>,
}
impl<'hir, T> Asm<'hir, T> {
#[inline]
fn new(span: &'hir dyn Spanned, data: T) -> Self {
Self {
span,
outcome: Outcome::Converge(data),
}
}
#[inline]
fn diverge(span: &'hir dyn Spanned) -> Self {
Self {
span,
outcome: Outcome::Diverge,
}
}
#[inline]
fn ignore(self) {}
}
impl<T> Asm<'_, T> {
#[inline]
fn into_converging(self) -> Option<T> {
match self.outcome {
Outcome::Converge(data) => Some(data),
Outcome::Diverge => None,
}
}
#[inline]
fn diverging(self) -> bool {
matches!(self.outcome, Outcome::Diverge)
}
#[inline]
fn converging(self) -> bool {
matches!(self.outcome, Outcome::Converge(..))
}
}
impl fmt::Debug for Asm<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Asm")
.field("span", &self.span.span())
.finish()
}
}
#[instrument_ast(span = hir)]
pub(crate) fn fn_from_item_fn<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: &'hir hir::ItemFn<'hir>,
instance_fn: bool,
) -> compile::Result<()> {
let mut first = true;
let mut arguments = cx.scopes.linear(hir, hir.args.len())?;
for (arg, needs) in hir.args.iter().zip(&mut arguments) {
match arg {
hir::FnArg::SelfValue(span, name) => {
if !instance_fn || !first {
return Err(compile::Error::new(span, ErrorKind::UnsupportedSelf));
}
cx.scopes.define(span, *name, needs)?;
}
hir::FnArg::Pat(pat) => {
let asm = pattern_panic(cx, pat, move |cx, false_label| {
fn_arg_pat(cx, pat, needs, false_label)
})?;
asm.ignore();
}
}
first = false;
}
if hir.body.value.is_some() {
return_(cx, hir, &hir.body, block_without_scope)?.ignore();
} else {
let mut needs = Any::ignore(&hir.body);
if block_without_scope(cx, &hir.body, &mut needs)?.converging() {
cx.asm.push(Inst::ReturnUnit, hir)?;
}
}
arguments.free()?;
cx.scopes.pop_last(hir)?;
Ok(())
}
#[instrument_ast(span = hir.block.span)]
pub(crate) fn async_block_secondary<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: &'hir hir::AsyncBlock<'hir>,
) -> compile::Result<()> {
let linear = cx.scopes.linear(&hir.block, hir.captures.len())?;
for (name, needs) in hir.captures.iter().copied().zip(&linear) {
cx.scopes.define(&hir.block, name, needs)?;
}
return_(cx, &hir.block, hir.block, block_without_scope)?.ignore();
linear.free()?;
cx.scopes.pop_last(&hir.block)?;
Ok(())
}
#[instrument_ast(span = hir)]
pub(crate) fn expr_closure_secondary<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: &'hir hir::ExprClosure<'hir>,
) -> compile::Result<()> {
let mut arguments = cx.scopes.linear(hir, hir.args.len())?;
let environment = cx.scopes.linear(hir, hir.captures.len())?;
if !hir.captures.is_empty() {
cx.asm.push(
Inst::Environment {
addr: environment.addr(),
count: hir.captures.len(),
out: environment.addr().output(),
},
hir,
)?;
for (capture, needs) in hir.captures.iter().copied().zip(&environment) {
cx.scopes.define(hir, capture, needs)?;
}
}
for (arg, needs) in hir.args.iter().zip(&mut arguments) {
match arg {
hir::FnArg::SelfValue(span, _) => {
return Err(compile::Error::new(span, ErrorKind::UnsupportedSelf))
}
hir::FnArg::Pat(pat) => {
let asm = pattern_panic(cx, pat, move |cx, false_label| {
fn_arg_pat(cx, pat, needs, false_label)
})?;
asm.ignore();
}
}
}
return_(cx, hir, hir.body, expr)?.ignore();
environment.free()?;
arguments.free()?;
cx.scopes.pop_last(hir)?;
Ok(())
}
#[instrument_ast(span = pat)]
fn fn_arg_pat<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
pat: &'hir hir::PatBinding<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
false_label: &Label,
) -> compile::Result<Asm<'hir, Pattern>> {
let Some(addr) = needs.try_as_addr()? else {
return Err(compile::Error::msg(
needs.span(),
"Expected need to be populated outside of pattern",
));
};
let addr = addr.addr();
let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
needs.assign_addr(cx, addr)?;
Ok(Asm::new(pat, ()))
};
let out = match pat.names {
[name] => pat_binding_with_single(cx, pat, &pat.pat, *name, false_label, &mut load, needs)?,
_ => pat_binding(cx, pat, false_label, &mut load)?,
};
Ok(out)
}
fn return_<'a, 'hir, T>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
hir: T,
asm: impl FnOnce(&mut Ctxt<'a, 'hir, '_>, T, &mut dyn Needs<'a, 'hir>) -> compile::Result<Asm<'hir>>,
) -> compile::Result<Asm<'hir>> {
let mut needs = cx.scopes.defer(span).with_name("return value");
converge!(asm(cx, hir, &mut needs)?, free(needs));
cx.asm.push(
Inst::Return {
addr: needs.addr()?.addr(),
},
span,
)?;
needs.free()?;
Ok(Asm::new(span, ()))
}
fn pattern_panic<'a, 'hir, 'arena, F>(
cx: &mut Ctxt<'a, 'hir, 'arena>,
span: &'hir dyn Spanned,
f: F,
) -> compile::Result<Asm<'hir>>
where
F: FnOnce(&mut Ctxt<'a, 'hir, 'arena>, &Label) -> compile::Result<Asm<'hir, Pattern>>,
{
let false_label = cx.asm.new_label("pattern_panic");
if matches!(converge!(f(cx, &false_label)?), Pattern::Refutable) {
cx.q.diagnostics
.let_pattern_might_panic(cx.source_id, span, cx.context())?;
let match_label = cx.asm.new_label("patter_match");
cx.asm.jump(&match_label, span)?;
cx.asm.label(&false_label)?;
cx.asm.push(
Inst::Panic {
reason: PanicReason::UnmatchedPattern,
},
span,
)?;
cx.asm.label(&match_label)?;
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = hir)]
fn pat_binding<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::PatBinding<'hir>,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
) -> compile::Result<Asm<'hir, Pattern>> {
let mut linear = cx.scopes.linear(hir, hir.names.len())?;
let pat = pat_binding_with(cx, hir, &hir.pat, hir.names, false_label, load, &mut linear)?;
linear.forget()?;
Ok(pat)
}
#[instrument_ast(span = span)]
fn pat_binding_with<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
pat: &'hir hir::Pat<'hir>,
names: &[hir::Variable],
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
linear: &mut [Address<'a, 'hir>],
) -> compile::Result<Asm<'hir, Pattern>> {
let mut bindings = BTreeMap::<_, &mut dyn Needs<'a, 'hir>>::new();
for (name, needs) in names.iter().copied().zip(linear.iter_mut()) {
bindings.try_insert(name, needs).with_span(span)?;
}
let asm = self::pat(cx, pat, false_label, load, &mut bindings)?;
if let Some(key) = bindings.into_keys().next() {
return Err(compile::Error::msg(
span,
format!("Unbound name in pattern: {key:?}"),
));
}
for (name, needs) in names.iter().copied().zip(linear.iter()) {
cx.scopes.define(needs.span(), name, needs)?;
}
Ok(asm)
}
#[instrument_ast(span = span)]
fn pat_binding_with_single<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
pat: &'hir hir::Pat<'hir>,
name: hir::Variable,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir, Pattern>> {
let mut bindings = Some::<(_, &mut dyn Needs<'a, 'hir>)>((name, needs));
let asm = self::pat(cx, pat, false_label, load, &mut bindings)?;
if let Some((name, _)) = bindings {
return Err(compile::Error::msg(
span,
format!("Unbound name in pattern: {name:?}"),
));
}
let Some(addr) = needs.try_as_addr()? else {
return Err(compile::Error::msg(
needs.span(),
"Expected need to be populated by pattern",
));
};
cx.scopes.define(needs.span(), name, addr)?;
Ok(asm)
}
trait Bindings<K, T> {
fn remove(&mut self, name: &K) -> Option<T>;
}
impl<K, T> Bindings<K, T> for BTreeMap<K, T>
where
K: Ord,
{
#[inline]
fn remove(&mut self, name: &K) -> Option<T> {
BTreeMap::remove(self, name)
}
}
impl<K, T> Bindings<K, T> for Option<(K, T)>
where
K: PartialEq,
{
#[inline]
fn remove(&mut self, name: &K) -> Option<T> {
let (current, value) = self.take()?;
if current != *name {
*self = Some((current, value));
return None;
}
Some(value)
}
}
#[instrument_ast(span = hir)]
fn pat<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Pat<'hir>,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
bindings: &mut dyn Bindings<hir::Variable, &mut dyn Needs<'a, 'hir>>,
) -> compile::Result<Asm<'hir, Pattern>> {
let span = hir;
match hir.kind {
hir::PatKind::Ignore => {
converge!(load(cx, &mut Any::ignore(hir))?);
Ok(Asm::new(span, Pattern::Irrefutable))
}
hir::PatKind::Path(kind) => match *kind {
hir::PatPathKind::Kind(kind) => {
let mut needs = cx.scopes.defer(hir);
converge!(load(cx, &mut needs)?, free(needs));
let cond = cx.scopes.alloc(hir)?;
let inst = pat_sequence_kind_to_inst(*kind, needs.addr()?.addr(), cond.output());
cx.asm.push(inst, hir)?;
cx.asm.jump_if_not(cond.addr(), false_label, hir)?;
cond.free()?;
needs.free()?;
Ok(Asm::new(span, Pattern::Refutable))
}
hir::PatPathKind::Ident(name) => {
let Some(binding) = bindings.remove(&name) else {
return Err(compile::Error::msg(hir, format!("No binding for {name:?}")));
};
converge!(load(cx, binding)?);
Ok(Asm::new(span, Pattern::Irrefutable))
}
},
hir::PatKind::Lit(hir) => Ok(pat_lit(cx, hir, false_label, load)?),
hir::PatKind::Sequence(hir) => pat_sequence(cx, hir, span, false_label, load, bindings),
hir::PatKind::Object(hir) => pat_object(cx, hir, span, false_label, load, bindings),
}
}
#[instrument_ast(span = hir)]
fn pat_lit<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Expr<'_>,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
) -> compile::Result<Asm<'hir, Pattern>> {
let mut needs = cx.scopes.defer(hir);
converge!(load(cx, &mut needs)?, free(needs));
let cond = cx.scopes.alloc(hir)?;
let Some(inst) = pat_lit_inst(cx, hir, needs.addr()?.addr(), cond.addr())? else {
return Err(compile::Error::new(hir, ErrorKind::UnsupportedPatternExpr));
};
cx.asm.push(inst, hir)?;
cx.asm.jump_if_not(cond.addr(), false_label, hir)?;
cond.free()?;
needs.free()?;
Ok(Asm::new(hir, Pattern::Refutable))
}
#[instrument_ast(span = hir)]
fn pat_lit_inst(
cx: &mut Ctxt<'_, '_, '_>,
hir: &hir::Expr<'_>,
addr: InstAddress,
cond: InstAddress,
) -> compile::Result<Option<Inst>> {
let hir::ExprKind::Lit(lit) = hir.kind else {
return Ok(None);
};
let out = cond.output();
let inst = match lit {
hir::Lit::Char(value) => Inst::EqChar { addr, value, out },
hir::Lit::Str(string) => Inst::EqString {
addr,
slot: cx.q.unit.new_static_string(hir, string)?,
out,
},
hir::Lit::ByteStr(bytes) => Inst::EqBytes {
addr,
slot: cx.q.unit.new_static_bytes(hir, bytes)?,
out,
},
hir::Lit::Unsigned(value) => Inst::EqUnsigned { addr, value, out },
hir::Lit::Signed(value) => Inst::EqSigned { addr, value, out },
hir::Lit::Bool(value) => Inst::EqBool { addr, value, out },
_ => return Ok(None),
};
Ok(Some(inst))
}
#[instrument_ast(span = hir)]
fn condition<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::Condition<'hir>,
then_label: &Label,
false_label: &Label,
linear: &mut [Address<'a, 'hir>],
) -> compile::Result<Asm<'hir, (ScopeHandle, Pattern)>> {
match *hir {
hir::Condition::Expr(hir) => {
let scope = cx.scopes.child(hir)?;
let mut addr = cx.scopes.alloc(hir)?.with_name("expression condition");
let asm = if expr(cx, hir, &mut addr)?.converging() {
cx.asm.jump_if(addr.addr(), then_label, hir)?;
addr.free()?;
Asm::new(hir, (scope, Pattern::Irrefutable))
} else {
addr.free()?;
cx.scopes.pop(hir, scope)?;
Asm::diverge(hir)
};
Ok(asm)
}
hir::Condition::ExprLet(hir) => {
let span = hir;
let scope = cx.scopes.child(span)?;
let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
expr(cx, &hir.expr, needs)
};
let asm = pat_binding_with(
cx,
&hir.pat,
&hir.pat.pat,
hir.pat.names,
false_label,
&mut load,
linear,
)?;
if let Some(pat) = asm.into_converging() {
cx.asm.jump(then_label, span)?;
Ok(Asm::new(span, (scope, pat)))
} else {
cx.scopes.pop(span, scope)?;
Ok(Asm::diverge(span))
}
}
}
}
#[instrument_ast(span = span)]
fn pat_sequence<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::PatSequence<'hir>,
span: &'hir dyn Spanned,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
bindings: &mut dyn Bindings<hir::Variable, &mut dyn Needs<'a, 'hir>>,
) -> compile::Result<Asm<'hir, Pattern>> {
let mut addr = cx.scopes.defer(span).with_name("loaded pattern sequence");
converge!(load(cx, &mut addr)?, free(addr));
let addr = addr.into_addr()?;
let cond = cx.scopes.alloc(span)?.with_name("loaded pattern condition");
if matches!(
hir.kind,
hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Tuple,
count: 0,
is_open: false
}
) {
cx.asm.push(
Inst::IsUnit {
addr: addr.addr(),
out: cond.output(),
},
span,
)?;
cx.asm.jump_if_not(cond.addr(), false_label, span)?;
} else {
let inst = pat_sequence_kind_to_inst(hir.kind, addr.addr(), cond.output());
cx.asm.push(inst, span)?;
cx.asm.jump_if_not(cond.addr(), false_label, span)?;
for (index, p) in hir.items.iter().enumerate() {
let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
cx.asm.push(
Inst::TupleIndexGetAt {
addr: addr.addr(),
index,
out: needs.alloc_output()?,
},
p,
)?;
Ok(Asm::new(p, ()))
};
converge!(
self::pat(cx, p, false_label, &mut load, bindings)?,
free(cond, addr)
);
}
}
cond.free()?;
addr.free()?;
Ok(Asm::new(span, Pattern::Refutable))
}
fn pat_sequence_kind_to_inst(kind: hir::PatSequenceKind, addr: InstAddress, out: Output) -> Inst {
match kind {
hir::PatSequenceKind::Type { hash } => Inst::MatchType { hash, addr, out },
hir::PatSequenceKind::BuiltInVariant { type_check } => Inst::MatchBuiltIn {
type_check,
addr,
out,
},
hir::PatSequenceKind::Variant {
variant_hash,
enum_hash,
index,
} => Inst::MatchVariant {
variant_hash,
enum_hash,
index,
addr,
out,
},
hir::PatSequenceKind::Anonymous {
type_check,
count,
is_open,
} => Inst::MatchSequence {
type_check,
len: count,
exact: !is_open,
addr,
out,
},
}
}
#[instrument_ast(span = span)]
fn pat_object<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::PatObject<'hir>,
span: &'hir dyn Spanned,
false_label: &Label,
load: &mut dyn FnMut(
&mut Ctxt<'a, 'hir, '_>,
&mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>>,
bindings: &mut dyn Bindings<hir::Variable, &mut dyn Needs<'a, 'hir>>,
) -> compile::Result<Asm<'hir, Pattern>> {
let mut needs = cx.scopes.defer(span);
converge!(load(cx, &mut needs)?, free(needs));
let addr = needs.addr()?;
let cond = cx.scopes.alloc(span)?;
let mut string_slots = Vec::new();
for binding in hir.bindings {
string_slots.try_push(cx.q.unit.new_static_string(span, binding.key())?)?;
}
let inst = match hir.kind {
hir::PatSequenceKind::Type { hash } => Inst::MatchType {
hash,
addr: addr.addr(),
out: cond.output(),
},
hir::PatSequenceKind::BuiltInVariant { type_check } => Inst::MatchBuiltIn {
type_check,
addr: addr.addr(),
out: cond.output(),
},
hir::PatSequenceKind::Variant {
variant_hash,
enum_hash,
index,
} => Inst::MatchVariant {
variant_hash,
enum_hash,
index,
addr: addr.addr(),
out: cond.output(),
},
hir::PatSequenceKind::Anonymous { is_open, .. } => {
let keys =
cx.q.unit
.new_static_object_keys_iter(span, hir.bindings.iter().map(|b| b.key()))?;
Inst::MatchObject {
slot: keys,
exact: !is_open,
addr: addr.addr(),
out: cond.output(),
}
}
};
cx.asm.push(inst, span)?;
cx.asm.jump_if_not(cond.addr(), false_label, span)?;
cond.free()?;
for (binding, slot) in hir.bindings.iter().zip(string_slots) {
match binding {
hir::Binding::Binding(span, _, p) => {
let mut load =
move |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
cx.asm.push(
Inst::ObjectIndexGetAt {
addr: addr.addr(),
slot,
out: needs.alloc_output()?,
},
span,
)?;
Ok(Asm::new(span, ()))
};
converge!(
self::pat(cx, p, false_label, &mut load, bindings)?,
free(needs)
);
}
hir::Binding::Ident(span, name, id) => {
let Some(binding) = bindings.remove(id) else {
return Err(compile::Error::msg(
binding,
format!("No binding for {name:?}"),
));
};
cx.asm.push(
Inst::ObjectIndexGetAt {
addr: addr.addr(),
slot,
out: binding.output()?,
},
&span,
)?;
}
}
}
needs.free()?;
Ok(Asm::new(span, Pattern::Refutable))
}
#[instrument_ast(span = hir)]
fn block<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Block<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let break_label = if let Some(label) = hir.label {
let break_label = cx.asm.new_label("block_break");
cx.breaks.push(Break {
label: Some(label),
continue_label: None,
break_label: break_label.try_clone()?,
output: Some(needs.alloc_output()?),
drop: None,
})?;
Some(break_label)
} else {
None
};
let scope = cx.scopes.child(hir)?;
let asm = block_without_scope(cx, hir, needs)?;
cx.scopes.pop(hir, scope)?;
cx.drop_dangling(hir)?;
if let Some(break_label) = break_label {
cx.asm.label(&break_label)?;
cx.breaks.pop();
}
Ok(asm)
}
#[instrument_ast(span = hir)]
fn block_without_scope<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Block<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut diverge = None;
cx.contexts.try_push(hir)?;
for stmt in hir.statements {
let mut needs = Any::ignore(hir).with_name("statement ignore");
if let Some(cause) = diverge {
cx.q.diagnostics.unreachable(cx.source_id, stmt, cause)?;
continue;
}
let asm = match stmt {
hir::Stmt::Local(hir) => local(cx, hir, &mut needs)?,
hir::Stmt::Expr(hir) => expr(cx, hir, &mut needs)?,
};
if asm.diverging() && diverge.is_none() {
diverge = Some(stmt);
}
}
if let Some(cause) = diverge {
if let Some(e) = hir.value {
cx.q.diagnostics.unreachable(cx.source_id, e, cause)?;
}
} else if let Some(e) = hir.value {
if expr(cx, e, needs)?.diverging() {
diverge = Some(e);
}
} else if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), hir)?;
}
cx.contexts
.pop()
.ok_or("Missing parent context")
.with_span(hir)?;
if diverge.is_some() {
return Ok(Asm::diverge(hir));
}
Ok(Asm::new(hir, ()))
}
#[instrument_ast(span = format)]
fn builtin_format<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
format: &'hir hir::BuiltInFormat<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
use crate::runtime::format;
let fill = format.spec.fill.unwrap_or(' ');
let align = format.spec.align.unwrap_or_default();
let flags = format.spec.flags.unwrap_or_default();
let width = format.spec.width;
let precision = format.spec.precision;
let format_type = format.spec.format_type.unwrap_or_default();
let spec = format::FormatSpec::new(flags, fill, align, width, precision, format_type);
converge!(expr(cx, format.value, needs)?);
if let Some(addr) = needs.try_alloc_addr()? {
cx.asm.push(
Inst::Format {
addr: addr.addr(),
spec,
out: addr.output(),
},
format,
)?;
}
Ok(Asm::new(format, ()))
}
#[instrument_ast(span = hir)]
fn builtin_template<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::BuiltInTemplate<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let span = hir;
let mut size_hint = 0;
let mut expansions = 0;
let mut linear = cx.scopes.linear(hir, hir.exprs.len())?;
let mut converge = true;
for (hir, addr) in hir.exprs.iter().zip(&mut linear) {
if let hir::ExprKind::Lit(hir::Lit::Str(s)) = hir.kind {
size_hint += s.len();
let slot = cx.q.unit.new_static_string(span, s)?;
cx.asm.push(
Inst::String {
slot,
out: addr.output(),
},
span,
)?;
continue;
}
expansions += 1;
if expr(cx, hir, addr)?.diverging() {
converge = false;
break;
}
}
if hir.from_literal && expansions == 0 {
cx.q.diagnostics
.template_without_expansions(cx.source_id, span, cx.context())?;
}
if converge {
cx.asm.push(
Inst::StringConcat {
addr: linear.addr(),
len: hir.exprs.len(),
size_hint,
out: needs.alloc_output()?,
},
span,
)?;
}
linear.free()?;
if converge {
Ok(Asm::new(span, ()))
} else {
Ok(Asm::diverge(span))
}
}
#[instrument_ast(span = span)]
fn const_<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
value: &ConstValue,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<()> {
let Some(addr) = needs.try_alloc_addr()? else {
cx.q.diagnostics
.not_used(cx.source_id, span, cx.context())?;
return Ok(());
};
let out = addr.output();
match *value.as_kind() {
ConstValueKind::Inline(value) => match value {
Inline::Empty => {
return Err(compile::Error::msg(
span,
"Empty inline constant value is not supported",
));
}
Inline::Unit => {
cx.asm.push(Inst::unit(out), span)?;
}
Inline::Char(v) => {
cx.asm.push(Inst::char(v, out), span)?;
}
Inline::Signed(v) => {
cx.asm.push(Inst::signed(v, out), span)?;
}
Inline::Unsigned(v) => {
cx.asm.push(Inst::unsigned(v, out), span)?;
}
Inline::Float(v) => {
cx.asm.push(Inst::float(v, out), span)?;
}
Inline::Bool(v) => {
cx.asm.push(Inst::bool(v, out), span)?;
}
Inline::Type(v) => {
cx.asm.push(Inst::ty(v, out), span)?;
}
Inline::Ordering(v) => {
cx.asm.push(Inst::ordering(v, out), span)?;
}
},
ConstValueKind::String(ref s) => {
let slot = cx.q.unit.new_static_string(span, s)?;
cx.asm.push(Inst::String { slot, out }, span)?;
}
ConstValueKind::Bytes(ref b) => {
let slot = cx.q.unit.new_static_bytes(span, b)?;
cx.asm.push(Inst::Bytes { slot, out }, span)?;
}
ConstValueKind::Option(ref option) => match option {
Some(value) => {
const_(cx, value, span, addr)?;
cx.asm.push(
Inst::Variant {
variant: InstVariant::Some,
addr: addr.addr(),
out,
},
span,
)?;
}
None => {
cx.asm.push(
Inst::Variant {
variant: InstVariant::None,
addr: addr.addr(),
out,
},
span,
)?;
}
},
ConstValueKind::Vec(ref vec) => {
let mut linear = cx.scopes.linear(span, vec.len())?;
for (value, needs) in vec.iter().zip(&mut linear) {
const_(cx, value, span, needs)?;
}
cx.asm.push(
Inst::Vec {
addr: linear.addr(),
count: vec.len(),
out,
},
span,
)?;
linear.free_non_dangling()?;
}
ConstValueKind::Tuple(ref tuple) => {
let mut linear = cx.scopes.linear(span, tuple.len())?;
for (value, needs) in tuple.iter().zip(&mut linear) {
const_(cx, value, span, needs)?;
}
cx.asm.push(
Inst::Tuple {
addr: linear.addr(),
count: tuple.len(),
out,
},
span,
)?;
linear.free_non_dangling()?;
}
ConstValueKind::Object(ref object) => {
let mut linear = cx.scopes.linear(span, object.len())?;
let mut entries = object.iter().try_collect::<Vec<_>>()?;
entries.sort_by_key(|k| k.0);
for ((_, value), needs) in entries.iter().copied().zip(&mut linear) {
const_(cx, value, span, needs)?;
}
let slot =
cx.q.unit
.new_static_object_keys_iter(span, entries.iter().map(|e| e.0))?;
cx.asm.push(
Inst::Object {
addr: linear.addr(),
slot,
out,
},
span,
)?;
linear.free_non_dangling()?;
}
ConstValueKind::Struct(hash, ref values) => {
let mut linear = cx.scopes.linear(span, values.len())?;
for (value, needs) in values.iter().zip(&mut linear) {
const_(cx, value, span, needs)?;
}
cx.asm.push(
Inst::ConstConstruct {
addr: linear.addr(),
hash,
count: values.len(),
out,
},
span,
)?;
}
}
Ok(())
}
#[instrument_ast(span = hir)]
fn expr<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Expr<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let span = hir;
let asm = match hir.kind {
hir::ExprKind::Variable(name) => {
let var = cx.scopes.get(&mut cx.q, span, name)?;
needs.assign_addr(cx, var.addr)?;
Asm::new(span, ())
}
hir::ExprKind::Type(ty) => {
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(
Inst::Store {
value: InstValue::Type(ty),
out,
},
span,
)?;
}
Asm::new(span, ())
}
hir::ExprKind::Fn(hash) => {
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::LoadFn { hash, out }, span)?;
}
Asm::new(span, ())
}
hir::ExprKind::For(hir) => expr_for(cx, hir, span, needs)?,
hir::ExprKind::Loop(hir) => expr_loop(cx, hir, span, needs)?,
hir::ExprKind::Let(hir) => expr_let(cx, hir, needs)?,
hir::ExprKind::Group(hir) => expr(cx, hir, needs)?,
hir::ExprKind::Unary(hir) => expr_unary(cx, hir, span, needs)?,
hir::ExprKind::Assign(hir) => expr_assign(cx, hir, span, needs)?,
hir::ExprKind::Binary(hir) => expr_binary(cx, hir, span, needs)?,
hir::ExprKind::If(hir) => expr_if(cx, hir, span, needs)?,
hir::ExprKind::Index(hir) => expr_index(cx, hir, span, needs)?,
hir::ExprKind::Break(hir) => expr_break(cx, hir, span)?,
hir::ExprKind::Continue(hir) => expr_continue(cx, hir, span, needs)?,
hir::ExprKind::Yield(hir) => expr_yield(cx, hir, span, needs)?,
hir::ExprKind::Block(hir) => block(cx, hir, needs)?,
hir::ExprKind::Return(hir) => expr_return(cx, hir, span)?,
hir::ExprKind::Match(hir) => expr_match(cx, hir, span, needs)?,
hir::ExprKind::Await(hir) => expr_await(cx, hir, span, needs)?,
hir::ExprKind::Try(hir) => expr_try(cx, hir, span, needs)?,
hir::ExprKind::Select(hir) => expr_select(cx, hir, span, needs)?,
hir::ExprKind::Call(hir) => expr_call(cx, hir, span, needs)?,
hir::ExprKind::FieldAccess(hir) => expr_field_access(cx, hir, span, needs)?,
hir::ExprKind::CallClosure(hir) => expr_call_closure(cx, hir, span, needs)?,
hir::ExprKind::Lit(hir) => lit(cx, hir, span, needs)?,
hir::ExprKind::Tuple(hir) => expr_tuple(cx, hir, span, needs)?,
hir::ExprKind::Vec(hir) => expr_vec(cx, hir, span, needs)?,
hir::ExprKind::Object(hir) => expr_object(cx, hir, span, needs)?,
hir::ExprKind::Range(hir) => expr_range(cx, hir, span, needs)?,
hir::ExprKind::Template(template) => builtin_template(cx, template, needs)?,
hir::ExprKind::Format(format) => builtin_format(cx, format, needs)?,
hir::ExprKind::AsyncBlock(hir) => expr_async_block(cx, hir, span, needs)?,
hir::ExprKind::Const(id) => const_item(cx, id, span, needs)?,
hir::ExprKind::Path => {
return Err(compile::Error::msg(
span,
"Path expression is not supported here",
))
}
};
Ok(asm)
}
#[instrument_ast(span = span)]
fn expr_assign<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprAssign<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let supported = match hir.lhs.kind {
hir::ExprKind::Variable(name) => {
let var = cx.scopes.get(&mut cx.q, span, name)?;
let mut needs = Address::assigned(var.span, cx.scopes, var.addr);
converge!(expr(cx, &hir.rhs, &mut needs)?, free(needs));
needs.free()?;
true
}
hir::ExprKind::FieldAccess(field_access) => {
let mut target = cx.scopes.defer(&field_access.expr);
let mut value = cx.scopes.defer(&hir.rhs);
let asm = expr_array(
cx,
span,
[(&field_access.expr, &mut target), (&hir.rhs, &mut value)],
)?;
match field_access.expr_field {
hir::ExprField::Ident(ident) => {
if let Some([target, value]) = asm.into_converging() {
let slot = cx.q.unit.new_static_string(span, ident)?;
cx.asm.push(
Inst::ObjectIndexSet {
target: target.addr(),
slot,
value: value.addr(),
},
span,
)?;
}
}
hir::ExprField::Index(index) => {
if let Some([target, value]) = asm.into_converging() {
cx.asm.push(
Inst::TupleIndexSet {
target: target.addr(),
index,
value: value.addr(),
},
span,
)?;
}
}
_ => {
return Err(compile::Error::new(span, ErrorKind::BadFieldAccess));
}
};
target.free()?;
value.free()?;
true
}
hir::ExprKind::Index(expr_index_get) => {
let mut target = cx.scopes.defer(&expr_index_get.target);
let mut index = cx.scopes.defer(&expr_index_get.index);
let mut value = cx.scopes.defer(&hir.rhs);
let asm = expr_array(
cx,
span,
[
(&expr_index_get.target, &mut target),
(&expr_index_get.index, &mut index),
(&hir.rhs, &mut value),
],
)?;
if let Some([target, index, value]) = asm.into_converging() {
cx.asm.push(
Inst::IndexSet {
target: target.addr(),
index: index.addr(),
value: value.addr(),
},
span,
)?;
}
value.free()?;
index.free()?;
target.free()?;
true
}
_ => false,
};
if !supported {
return Err(compile::Error::new(span, ErrorKind::UnsupportedAssignExpr));
}
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), span)?;
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = hir)]
fn expr_await<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Expr<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut addr = cx.scopes.defer(span);
converge!(expr(cx, hir, &mut addr)?, free(addr));
cx.asm.push(
Inst::Await {
addr: addr.addr()?.addr(),
out: needs.alloc_output()?,
},
span,
)?;
addr.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_binary<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprBinary<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
if hir.op.is_assign() {
return compile_assign_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs);
}
if hir.op.is_conditional() {
return compile_conditional_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs);
}
let mut a = cx.scopes.defer(span);
let mut b = cx.scopes.defer(span);
let asm = expr_array(cx, span, [(&hir.lhs, &mut a), (&hir.rhs, &mut b)])?;
if let Some([a, b]) = asm.into_converging() {
let a = a.addr();
let b = b.addr();
let out = needs.alloc_output()?;
let inst = match hir.op {
ast::BinOp::Eq(..) => Inst::Op {
op: InstOp::Eq,
a,
b,
out,
},
ast::BinOp::Neq(..) => Inst::Op {
op: InstOp::Neq,
a,
b,
out,
},
ast::BinOp::Lt(..) => Inst::Op {
op: InstOp::Lt,
a,
b,
out,
},
ast::BinOp::Gt(..) => Inst::Op {
op: InstOp::Gt,
a,
b,
out,
},
ast::BinOp::Lte(..) => Inst::Op {
op: InstOp::Le,
a,
b,
out,
},
ast::BinOp::Gte(..) => Inst::Op {
op: InstOp::Ge,
a,
b,
out,
},
ast::BinOp::As(..) => Inst::Op {
op: InstOp::As,
a,
b,
out,
},
ast::BinOp::Is(..) => Inst::Op {
op: InstOp::Is,
a,
b,
out,
},
ast::BinOp::IsNot(..) => Inst::Op {
op: InstOp::IsNot,
a,
b,
out,
},
ast::BinOp::And(..) => Inst::Op {
op: InstOp::And,
a,
b,
out,
},
ast::BinOp::Or(..) => Inst::Op {
op: InstOp::Or,
a,
b,
out,
},
ast::BinOp::Add(..) => Inst::Arithmetic {
op: InstArithmeticOp::Add,
a,
b,
out,
},
ast::BinOp::Sub(..) => Inst::Arithmetic {
op: InstArithmeticOp::Sub,
a,
b,
out,
},
ast::BinOp::Div(..) => Inst::Arithmetic {
op: InstArithmeticOp::Div,
a,
b,
out,
},
ast::BinOp::Mul(..) => Inst::Arithmetic {
op: InstArithmeticOp::Mul,
a,
b,
out,
},
ast::BinOp::Rem(..) => Inst::Arithmetic {
op: InstArithmeticOp::Rem,
a,
b,
out,
},
ast::BinOp::BitAnd(..) => Inst::Bitwise {
op: InstBitwiseOp::BitAnd,
a,
b,
out,
},
ast::BinOp::BitXor(..) => Inst::Bitwise {
op: InstBitwiseOp::BitXor,
a,
b,
out,
},
ast::BinOp::BitOr(..) => Inst::Bitwise {
op: InstBitwiseOp::BitOr,
a,
b,
out,
},
ast::BinOp::Shl(..) => Inst::Shift {
op: InstShiftOp::Shl,
a,
b,
out,
},
ast::BinOp::Shr(..) => Inst::Shift {
op: InstShiftOp::Shr,
a,
b,
out,
},
op => {
return Err(compile::Error::new(
span,
ErrorKind::UnsupportedBinaryOp { op },
));
}
};
cx.asm.push(inst, span)?;
}
a.free()?;
b.free()?;
Ok(Asm::new(span, ()))
}
fn compile_conditional_binop<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
lhs: &'hir hir::Expr<'hir>,
rhs: &'hir hir::Expr<'hir>,
bin_op: &ast::BinOp,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
converge!(expr(cx, lhs, needs)?);
let end_label = cx.asm.new_label("conditional_end");
let addr = needs.addr()?;
match bin_op {
ast::BinOp::And(..) => {
cx.asm.jump_if_not(addr.addr(), &end_label, lhs)?;
}
ast::BinOp::Or(..) => {
cx.asm.jump_if(addr.addr(), &end_label, lhs)?;
}
op => {
return Err(compile::Error::new(
span,
ErrorKind::UnsupportedBinaryOp { op: *op },
));
}
}
expr(cx, rhs, needs)?.ignore();
cx.asm.label(&end_label)?;
Ok(Asm::new(span, ()))
}
fn compile_assign_binop<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
lhs: &'hir hir::Expr<'hir>,
rhs: &'hir hir::Expr<'hir>,
bin_op: &ast::BinOp,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let (target, value) = match lhs.kind {
hir::ExprKind::Variable(name) => {
let var = cx.scopes.get(&mut cx.q, lhs, name)?;
let mut value = cx.scopes.defer(rhs);
converge!(expr(cx, rhs, &mut value)?, free(value));
let value = value.into_addr()?;
let inst_target = InstTarget::Address(var.addr);
(inst_target, value)
}
hir::ExprKind::FieldAccess(field_access) => {
let mut target = cx.scopes.defer(&field_access.expr);
converge!(expr(cx, &field_access.expr, &mut target)?, free(target));
let target = target.into_addr()?;
let mut value = cx.scopes.defer(rhs);
converge!(expr(cx, rhs, &mut value)?, free(target, value));
let value = value.into_addr()?;
let inst_target = match field_access.expr_field {
hir::ExprField::Index(index) => InstTarget::TupleField(target.addr(), index),
hir::ExprField::Ident(ident) => {
let slot = cx.q.unit.new_static_string(&field_access.expr, ident)?;
InstTarget::Field(target.addr(), slot)
}
_ => {
return Err(compile::Error::new(span, ErrorKind::BadFieldAccess));
}
};
target.free()?;
(inst_target, value)
}
_ => {
return Err(compile::Error::new(span, ErrorKind::UnsupportedBinaryExpr));
}
};
let inst = match bin_op {
ast::BinOp::AddAssign(..) => Inst::AssignArithmetic {
op: InstArithmeticOp::Add,
target,
rhs: value.addr(),
},
ast::BinOp::SubAssign(..) => Inst::AssignArithmetic {
op: InstArithmeticOp::Sub,
target,
rhs: value.addr(),
},
ast::BinOp::MulAssign(..) => Inst::AssignArithmetic {
op: InstArithmeticOp::Mul,
target,
rhs: value.addr(),
},
ast::BinOp::DivAssign(..) => Inst::AssignArithmetic {
op: InstArithmeticOp::Div,
target,
rhs: value.addr(),
},
ast::BinOp::RemAssign(..) => Inst::AssignArithmetic {
op: InstArithmeticOp::Rem,
target,
rhs: value.addr(),
},
ast::BinOp::BitAndAssign(..) => Inst::AssignBitwise {
op: InstBitwiseOp::BitAnd,
target,
rhs: value.addr(),
},
ast::BinOp::BitXorAssign(..) => Inst::AssignBitwise {
op: InstBitwiseOp::BitXor,
target,
rhs: value.addr(),
},
ast::BinOp::BitOrAssign(..) => Inst::AssignBitwise {
op: InstBitwiseOp::BitOr,
target,
rhs: value.addr(),
},
ast::BinOp::ShlAssign(..) => Inst::AssignShift {
op: InstShiftOp::Shl,
target,
rhs: value.addr(),
},
ast::BinOp::ShrAssign(..) => Inst::AssignShift {
op: InstShiftOp::Shr,
target,
rhs: value.addr(),
},
_ => {
return Err(compile::Error::new(span, ErrorKind::UnsupportedBinaryExpr));
}
};
cx.asm.push(inst, span)?;
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), span)?;
}
value.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_async_block<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprAsyncBlock<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let linear = cx.scopes.linear(span, hir.captures.len())?;
for (capture, needs) in hir.captures.iter().copied().zip(&linear) {
let out = needs.output();
if hir.do_move {
let var = cx.scopes.take(&mut cx.q, span, capture)?;
var.move_(cx.asm, span, Some(&"capture"), out)?;
} else {
let var = cx.scopes.get(&mut cx.q, span, capture)?;
var.copy(cx.asm, span, Some(&"capture"), out)?;
}
}
cx.asm.push_with_comment(
Inst::Call {
hash: hir.hash,
addr: linear.addr(),
args: hir.captures.len(),
out: needs.alloc_output()?,
},
span,
&"async block",
)?;
linear.free_non_dangling()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn const_item<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hash: Hash,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let Some(const_value) = cx.q.get_const_value(hash) else {
return Err(compile::Error::msg(
span,
try_format!("Missing constant value for hash {hash}"),
));
};
let const_value = const_value.try_clone().with_span(span)?;
const_(cx, &const_value, span, needs)?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_break<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: &hir::ExprBreak<'hir>,
span: &'hir dyn Spanned,
) -> compile::Result<Asm<'hir>> {
let (break_label, output) = match hir.label {
Some(label) => {
let l = cx.breaks.walk_until_label(span, label, &mut cx.drop)?;
(l.break_label.try_clone()?, l.output)
}
None => {
let Some(l) = cx.breaks.last() else {
return Err(compile::Error::new(span, ErrorKind::BreakUnsupported));
};
cx.drop.clear();
cx.drop.try_extend(l.drop).with_span(span)?;
(l.break_label.try_clone()?, l.output)
}
};
if let Some(hir) = hir.expr {
let Some(output) = output else {
return Err(compile::Error::new(span, ErrorKind::BreakUnsupportedValue));
};
let mut needs = match output.as_addr() {
Some(addr) => Any::assigned(span, cx.scopes, addr),
None => Any::ignore(span),
};
converge!(expr(cx, hir, &mut needs)?, free(needs));
needs.free()?;
} else if let Some(out) = output {
cx.asm.push(Inst::unit(out), span)?;
}
let mut drop_set = cx.q.unit.drop_set();
for addr in cx.drop.drain(..) {
drop_set.push(addr)?;
}
if let Some(set) = drop_set.finish()? {
cx.asm.push(Inst::Drop { set }, span)?;
}
cx.asm.jump(&break_label, span)?;
Ok(Asm::diverge(span))
}
#[instrument_ast(span = span)]
fn expr_call<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprCall<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let args = hir.args.len();
match hir.call {
hir::Call::Var { name, .. } => {
let linear = converge!(exprs(cx, span, hir.args)?);
let var = cx.scopes.get(&mut cx.q, span, name)?;
cx.asm.push(
Inst::CallFn {
function: var.addr,
addr: linear.addr(),
args: hir.args.len(),
out: needs.alloc_output()?,
},
span,
)?;
linear.free_non_dangling()?;
}
hir::Call::Associated { target, hash } => {
let linear = converge!(exprs_2(cx, span, slice::from_ref(target), hir.args)?);
cx.asm.push(
Inst::CallAssociated {
hash,
addr: linear.addr(),
args: args + 1,
out: needs.alloc_output()?,
},
span,
)?;
linear.free_non_dangling()?;
}
hir::Call::Meta { hash } => {
let linear = converge!(exprs(cx, span, hir.args)?);
cx.asm.push(
Inst::Call {
hash,
addr: linear.addr(),
args: hir.args.len(),
out: needs.alloc_output()?,
},
span,
)?;
linear.free_non_dangling()?;
}
hir::Call::Expr { expr: e } => {
let mut function = cx.scopes.defer(span);
converge!(expr(cx, e, &mut function)?, free(function));
let linear = converge!(exprs(cx, span, hir.args)?, free(function));
cx.asm.push(
Inst::CallFn {
function: function.addr()?.addr(),
addr: linear.addr(),
args: hir.args.len(),
out: needs.alloc_output()?,
},
span,
)?;
linear.free_non_dangling()?;
function.free()?;
}
hir::Call::ConstFn {
from_module,
from_item,
id,
} => {
let const_fn = cx.q.const_fn_for(id).with_span(span)?;
let value = cx.call_const_fn(span, from_module, from_item, &const_fn, hir.args)?;
const_(cx, &value, span, needs)?;
}
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_array<'a, 'hir, 'needs, const N: usize>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
array: [(&'hir hir::Expr<'hir>, &'needs mut dyn Needs<'a, 'hir>); N],
) -> compile::Result<Asm<'hir, [&'needs Address<'a, 'hir>; N]>> {
let mut out = FixedVec::new();
for (expr, needs) in array {
converge!(self::expr(cx, expr, needs)?);
let addr = needs.addr()?;
out.try_push(addr).with_span(span)?;
}
Ok(Asm::new(span, out.into_inner()))
}
#[instrument_ast(span = span)]
fn exprs<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
args: &'hir [hir::Expr<'hir>],
) -> compile::Result<Asm<'hir, Linear<'a, 'hir>>> {
exprs_2(cx, span, args, &[])
}
#[instrument_ast(span = span)]
fn exprs_with<'a, 'hir, T>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
args: &'hir [T],
map: fn(&'hir T) -> &'hir hir::Expr<'hir>,
) -> compile::Result<Asm<'hir, Linear<'a, 'hir>>> {
exprs_2_with(cx, span, args, &[], map)
}
#[instrument_ast(span = span)]
fn exprs_2<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
a: &'hir [hir::Expr<'hir>],
b: &'hir [hir::Expr<'hir>],
) -> compile::Result<Asm<'hir, Linear<'a, 'hir>>> {
exprs_2_with(cx, span, a, b, |e| e)
}
fn exprs_2_with<'a, 'hir, T>(
cx: &mut Ctxt<'a, 'hir, '_>,
span: &'hir dyn Spanned,
a: &'hir [T],
b: &'hir [T],
map: fn(&'hir T) -> &'hir hir::Expr<'hir>,
) -> compile::Result<Asm<'hir, Linear<'a, 'hir>>> {
let mut linear;
match (a, b) {
([], []) => {
linear = Linear::empty();
}
([e], []) | ([], [e]) => {
let e = map(e);
let mut needs = cx.scopes.alloc(e)?;
converge!(expr(cx, e, &mut needs)?, free(needs));
linear = Linear::single(needs);
}
_ => {
let len = a.len() + b.len();
linear = cx.scopes.linear(span, len)?;
let mut diverge = false;
for (e, needs) in a.iter().chain(b.iter()).zip(&mut linear) {
if expr(cx, map(e), needs)?.diverging() {
diverge = true;
break;
};
}
if diverge {
linear.free()?;
return Ok(Asm::diverge(span));
}
}
}
Ok(Asm::new(span, linear))
}
#[instrument_ast(span = span)]
fn expr_call_closure<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprCallClosure<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let Some(out) = needs.try_alloc_output()? else {
cx.q.diagnostics
.not_used(cx.source_id, span, cx.context())?;
return Ok(Asm::new(span, ()));
};
tracing::trace!(?hir.captures, "assemble call closure");
let linear = cx.scopes.linear(span, hir.captures.len())?;
for (capture, needs) in hir.captures.iter().copied().zip(&linear) {
let out = needs.output();
if hir.do_move {
let var = cx.scopes.take(&mut cx.q, span, capture)?;
var.move_(cx.asm, span, Some(&"capture"), out)?;
} else {
let var = cx.scopes.get(&mut cx.q, span, capture)?;
var.copy(cx.asm, span, Some(&"capture"), out)?;
}
}
cx.asm.push(
Inst::Closure {
hash: hir.hash,
addr: linear.addr(),
count: hir.captures.len(),
out,
},
span,
)?;
linear.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_continue<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprContinue<'hir>,
span: &'hir dyn Spanned,
_: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let last_loop = if let Some(label) = hir.label {
cx.breaks.find_label(span, label)?
} else {
let Some(current_loop) = cx.breaks.last() else {
return Err(compile::Error::new(span, ErrorKind::ContinueUnsupported));
};
current_loop
};
let Some(label) = &last_loop.continue_label else {
return Err(compile::Error::new(
span,
ErrorKind::ContinueUnsupportedBlock,
));
};
cx.asm.jump(label, span)?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_field_access<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprFieldAccess<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
if let (hir::ExprKind::Variable(name), hir::ExprField::Index(index)) =
(hir.expr.kind, hir.expr_field)
{
let var = cx.scopes.get(&mut cx.q, span, name)?;
cx.asm.push_with_comment(
Inst::TupleIndexGetAt {
addr: var.addr,
index,
out: needs.alloc_output()?,
},
span,
&var,
)?;
return Ok(Asm::new(span, ()));
}
let mut addr = cx.scopes.defer(span);
if expr(cx, &hir.expr, &mut addr)?.converging() {
let addr = addr.addr()?;
match hir.expr_field {
hir::ExprField::Index(index) => {
cx.asm.push(
Inst::TupleIndexGetAt {
addr: addr.addr(),
index,
out: needs.alloc_output()?,
},
span,
)?;
}
hir::ExprField::Ident(field) => {
let slot = cx.q.unit.new_static_string(span, field)?;
cx.asm.push(
Inst::ObjectIndexGetAt {
addr: addr.addr(),
slot,
out: needs.alloc_output()?,
},
span,
)?;
}
_ => return Err(compile::Error::new(span, ErrorKind::BadFieldAccess)),
}
}
addr.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_for<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprFor<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut iter = cx.scopes.defer(span).with_name("iter");
if !expr(cx, &hir.iter, &mut iter)?.converging() {
iter.free()?;
cx.q.diagnostics
.unreachable(cx.source_id, &hir.body, &hir.iter)?;
return Ok(Asm::diverge(span));
}
let continue_label = cx.asm.new_label("for_continue");
let end_label = cx.asm.new_label("for_end");
let break_label = cx.asm.new_label("for_break");
let iter = iter.into_addr()?;
let into_iter = cx.scopes.alloc(span)?.with_name("into_iter");
let binding = cx.scopes.alloc(&hir.binding)?.with_name("binding");
cx.asm.push_with_comment(
Inst::Copy {
addr: iter.addr(),
out: into_iter.output(),
},
span,
&"Protocol::INTO_ITER",
)?;
cx.asm.push_with_comment(
Inst::CallAssociated {
addr: into_iter.addr(),
hash: Protocol::INTO_ITER.hash,
args: 1,
out: into_iter.output(),
},
&hir.iter,
&"Protocol::INTO_ITER",
)?;
let next_offset = if cx.options.memoize_instance_fn {
let offset = cx.scopes.alloc(&hir.iter)?.with_name("memoized next");
cx.asm.push_with_comment(
Inst::LoadInstanceFn {
addr: into_iter.addr(),
hash: Protocol::NEXT.hash,
out: offset.output(),
},
&hir.iter,
&"Protocol::NEXT",
)?;
Some(offset)
} else {
None
};
cx.asm.label(&continue_label)?;
cx.breaks.push(Break {
label: hir.label,
continue_label: Some(continue_label.try_clone()?),
break_label: break_label.try_clone()?,
output: None,
drop: Some(into_iter.addr()),
})?;
let into_iter_copy = cx.scopes.alloc(span)?.with_name("into_iter_copy");
cx.asm.push(
Inst::Copy {
addr: into_iter.addr(),
out: into_iter_copy.output(),
},
span,
)?;
if let Some(next_offset) = &next_offset {
cx.asm.push(
Inst::CallFn {
function: next_offset.addr(),
addr: into_iter_copy.addr(),
args: 1,
out: binding.output(),
},
span,
)?;
} else {
cx.asm.push_with_comment(
Inst::CallAssociated {
addr: into_iter_copy.addr(),
hash: Protocol::NEXT.hash,
args: 1,
out: binding.output(),
},
span,
&"Protocol::NEXT",
)?;
}
into_iter_copy.free()?;
cx.asm
.iter_next(binding.addr(), &end_label, &hir.binding, binding.output())?;
let inner_loop_scope = cx.scopes.child(&hir.body)?;
let mut bindings = cx.scopes.linear(&hir.binding, hir.binding.names.len())?;
let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
needs.assign_addr(cx, binding.addr())?;
Ok(Asm::new(&hir.binding, ()))
};
let asm = pattern_panic(cx, &hir.binding, |cx, false_label| {
pat_binding_with(
cx,
&hir.binding,
&hir.binding.pat,
hir.binding.names,
false_label,
&mut load,
&mut bindings,
)
})?;
asm.ignore();
let asm = block(cx, &hir.body, &mut Any::ignore(span))?;
bindings.free()?;
cx.scopes.pop(span, inner_loop_scope)?;
if asm.converging() {
cx.asm.jump(&continue_label, span)?;
}
cx.asm.label(&end_label)?;
let mut drop_set = cx.q.unit.drop_set();
drop_set.push(into_iter.addr())?;
if let Some(set) = drop_set.finish()? {
cx.asm.push(Inst::Drop { set }, span)?;
}
cx.asm.label(&break_label)?;
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), span)?;
}
if let Some(next_offset) = next_offset {
next_offset.free()?;
}
binding.free()?;
into_iter.free()?;
iter.free()?;
cx.breaks.pop();
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_if<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::Conditional<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let output_addr = if hir.fallback.is_none() {
needs.try_alloc_output()?
} else {
None
};
let end_label = cx.asm.new_label("if_end");
let values = hir
.branches
.iter()
.flat_map(|c| c.condition.count())
.max()
.unwrap_or(0);
let mut linear = cx.scopes.linear(span, values)?;
let mut branches = Vec::new();
for branch in hir.branches {
let then_label = cx.asm.new_label("if_branch");
let false_label = cx.asm.new_label("if_false");
if let Some((scope, pat)) =
condition(cx, branch.condition, &then_label, &false_label, &mut linear)?
.into_converging()
{
if matches!(pat, Pattern::Refutable) {
cx.asm.label(&false_label)?;
}
let scope = cx.scopes.dangle(branch, scope)?;
branches.try_push((branch, then_label, scope))?;
}
}
let asm = if let Some(b) = hir.fallback {
block(cx, b, needs)?
} else if let Some(out) = output_addr {
cx.asm.push(Inst::unit(out), span)?;
Asm::new(span, ())
} else {
Asm::new(span, ())
};
if asm.converging() {
cx.asm.jump(&end_label, span)?;
}
let mut it = branches.into_iter().peekable();
while let Some((branch, label, scope)) = it.next() {
cx.asm.label(&label)?;
let scope = cx.scopes.restore(scope);
let asm = if hir.fallback.is_none() {
let asm = block(cx, &branch.block, &mut Any::ignore(branch))?;
if asm.converging() {
if let Some(out) = output_addr {
cx.asm.push(Inst::unit(out), span)?;
}
Asm::new(span, ())
} else {
Asm::diverge(span)
}
} else {
block(cx, &branch.block, needs)?
};
cx.scopes.pop(branch, scope)?;
if asm.converging() && it.peek().is_some() {
cx.asm.jump(&end_label, branch)?;
}
}
cx.asm.label(&end_label)?;
linear.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_index<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprIndex<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut target = cx.scopes.defer(span);
let mut index = cx.scopes.defer(span);
if let Some([target, index]) = expr_array(
cx,
span,
[(&hir.target, &mut target), (&hir.index, &mut index)],
)?
.into_converging()
{
cx.asm.push(
Inst::IndexGet {
index: index.addr(),
target: target.addr(),
out: needs.alloc_output()?,
},
span,
)?;
}
index.free()?;
target.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = hir)]
fn expr_let<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprLet<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut load =
|cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| expr(cx, &hir.expr, needs);
converge!(pattern_panic(cx, &hir.pat, move |cx, false_label| {
pat_binding(cx, &hir.pat, false_label, &mut load)
})?);
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), hir)?;
}
Ok(Asm::new(hir, ()))
}
#[instrument_ast(span = span)]
fn expr_match<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprMatch<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut value = cx.scopes.defer(span);
converge!(expr(cx, hir.expr, &mut value)?, free(value));
let value = value.into_addr()?;
let end_label = cx.asm.new_label("match_end");
let mut branches = Vec::new();
let count = hir
.branches
.iter()
.map(|b| b.pat.names.len())
.max()
.unwrap_or_default();
let mut linear = cx.scopes.linear(span, count)?;
let mut is_irrefutable = false;
for branch in hir.branches {
let span = branch;
let branch_label = cx.asm.new_label("match_branch");
let match_false = cx.asm.new_label("match_false");
let pattern_scope = cx.scopes.child(span)?;
let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| {
needs.assign_addr(cx, value.addr())?;
Ok(Asm::new(branch, ()))
};
let asm = pat_binding_with(
cx,
&branch.pat,
&branch.pat.pat,
branch.pat.names,
&match_false,
&mut load,
&mut linear,
)?;
if let Some(pat) = asm.into_converging() {
let mut converges = true;
if let Some(condition) = branch.condition {
let scope = cx.scopes.child(condition)?;
let mut cond = cx.scopes.alloc(condition)?.with_name("match condition");
if expr(cx, condition, &mut cond)?.converging() {
cx.asm.jump_if_not(cond.addr(), &match_false, condition)?;
cx.asm.jump(&branch_label, condition)?;
} else {
converges = false;
}
cond.free()?;
cx.scopes.pop(span, scope)?;
} else {
is_irrefutable = matches!(pat, Pattern::Irrefutable);
}
if converges {
cx.asm.jump(&branch_label, span)?;
let pattern_scope = cx.scopes.dangle(span, pattern_scope)?;
branches.try_push((branch_label, pattern_scope))?;
} else {
is_irrefutable = matches!(pat, Pattern::Irrefutable);
cx.scopes.pop(span, pattern_scope)?;
}
}
if is_irrefutable {
break;
}
cx.asm.label(&match_false)?;
}
if !is_irrefutable {
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), span)?;
}
cx.asm.jump(&end_label, span)?;
}
let mut it = hir.branches.iter().zip(branches).peekable();
while let Some((branch, (label, scope))) = it.next() {
let span = branch;
cx.asm.label(&label)?;
let scope = cx.scopes.restore(scope);
if expr(cx, &branch.body, needs)?.converging() && it.peek().is_some() {
cx.asm.jump(&end_label, span)?;
}
cx.scopes.pop(span, scope)?;
}
cx.asm.label(&end_label)?;
value.free()?;
linear.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_object<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprObject<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
if let Some(linear) =
exprs_with(cx, span, hir.assignments, |hir| &hir.assign)?.into_converging()
{
match hir.kind {
hir::ExprObjectKind::EmptyStruct { hash } => {
cx.asm.push(
Inst::EmptyStruct {
hash,
out: needs.alloc_output()?,
},
span,
)?;
}
hir::ExprObjectKind::Struct { hash } | hir::ExprObjectKind::StructVariant { hash } => {
cx.asm.push(
Inst::Struct {
addr: linear.addr(),
hash,
out: needs.alloc_output()?,
},
span,
)?;
}
hir::ExprObjectKind::ExternalType { hash, args } => {
reorder_field_assignments(cx, hir, linear.addr(), span)?;
cx.asm.push(
Inst::Call {
hash,
addr: linear.addr(),
args,
out: needs.alloc_output()?,
},
span,
)?;
}
hir::ExprObjectKind::Anonymous => {
let slot = cx
.q
.unit
.new_static_object_keys_iter(span, hir.assignments.iter().map(|a| a.key.1))?;
cx.asm.push(
Inst::Object {
addr: linear.addr(),
slot,
out: needs.alloc_output()?,
},
span,
)?;
}
};
linear.free_non_dangling()?;
}
Ok(Asm::new(span, ()))
}
fn reorder_field_assignments<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: &hir::ExprObject<'hir>,
base: InstAddress,
span: &dyn Spanned,
) -> compile::Result<()> {
let mut order = Vec::try_with_capacity(hir.assignments.len())?;
for assign in hir.assignments {
let Some(position) = assign.position else {
return Err(compile::Error::msg(
span,
try_format!("Missing position for field assignment {}", assign.key.1),
));
};
order.try_push(position)?;
}
let base = base.offset();
for a in 0..hir.assignments.len() {
loop {
let Some(&b) = order.get(a) else {
return Err(compile::Error::msg(span, "Order out-of-bounds"));
};
if a == b {
break;
}
order.swap(a, b);
let (Some(a), Some(b)) = (base.checked_add(a), base.checked_add(b)) else {
return Err(compile::Error::msg(
span,
"Field repositioning out-of-bounds",
));
};
let a = InstAddress::new(a);
let b = InstAddress::new(b);
cx.asm.push(Inst::Swap { a, b }, span)?;
}
}
Ok(())
}
#[instrument_ast(span = span)]
fn expr_range<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprRange<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let range;
let vars;
match hir {
hir::ExprRange::RangeFrom { start } => {
let mut s = cx.scopes.defer(start);
converge!(expr(cx, start, &mut s)?, free(s));
let start = s.into_addr()?;
range = InstRange::RangeFrom {
start: start.addr(),
};
vars = [Some(start), None];
}
hir::ExprRange::RangeFull => {
range = InstRange::RangeFull;
vars = [None, None];
}
hir::ExprRange::RangeInclusive { start, end } => {
let mut s = cx.scopes.defer(start);
converge!(expr(cx, start, &mut s)?, free(s));
let mut e = cx.scopes.defer(end);
converge!(expr(cx, end, &mut e)?, free(s, e));
let start = s.into_addr()?;
let end = e.into_addr()?;
range = InstRange::RangeInclusive {
start: start.addr(),
end: end.addr(),
};
vars = [Some(start), Some(end)];
}
hir::ExprRange::RangeToInclusive { end } => {
let mut e = cx.scopes.defer(end);
converge!(expr(cx, end, &mut e)?, free(e));
let end = e.into_addr()?;
range = InstRange::RangeToInclusive { end: end.addr() };
vars = [Some(end), None];
}
hir::ExprRange::RangeTo { end } => {
let mut e = cx.scopes.defer(end);
converge!(expr(cx, end, &mut e)?, free(e));
let end = e.into_addr()?;
range = InstRange::RangeTo { end: end.addr() };
vars = [Some(end), None];
}
hir::ExprRange::Range { start, end } => {
let mut s = cx.scopes.defer(start);
converge!(expr(cx, start, &mut s)?, free(s));
let mut e = cx.scopes.defer(end);
converge!(expr(cx, end, &mut e)?, free(s, e));
let start = s.into_addr()?;
let end = e.into_addr()?;
range = InstRange::Range {
start: start.addr(),
end: end.addr(),
};
vars = [Some(start), Some(end)];
}
};
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::Range { range, out }, span)?;
}
for var in vars.into_iter().flatten() {
var.free()?;
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_return<'hir>(
cx: &mut Ctxt<'_, 'hir, '_>,
hir: Option<&'hir hir::Expr<'hir>>,
span: &'hir dyn Spanned,
) -> compile::Result<Asm<'hir>> {
if let Some(e) = hir {
converge!(return_(cx, span, e, expr)?);
} else {
cx.asm.push(Inst::ReturnUnit, span)?;
}
Ok(Asm::diverge(span))
}
fn expr_select_inner<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprSelect<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut default_branch = None;
let end_label = cx.asm.new_label("select_end");
for branch in hir.branches {
let label = cx.asm.new_label("select_branch");
cx.select_branches.try_push((label, branch))?;
}
if let Some(def) = hir.default {
let label = cx.asm.new_label("select_default");
default_branch = Some((def, label));
}
let linear = converge!(exprs(cx, span, hir.exprs)?);
let mut value_addr = cx.scopes.alloc(span)?;
let select_label = cx.asm.new_label("select");
cx.asm.label(&select_label)?;
cx.asm.push(
Inst::Select {
addr: linear.addr(),
len: hir.exprs.len(),
value: value_addr.output(),
},
span,
)?;
for (label, _) in &cx.select_branches {
cx.asm.jump(label, span)?;
}
if let Some((_, label)) = &default_branch {
cx.asm.jump(label, span)?;
} else {
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(
Inst::Copy {
addr: value_addr.addr(),
out,
},
span,
)?;
}
if !cx.select_branches.is_empty() || default_branch.is_some() {
cx.asm.jump(&end_label, span)?;
}
}
let mut branches = take(&mut cx.select_branches);
for (label, branch) in branches.drain(..) {
cx.asm.label(&label)?;
let scope = cx.scopes.child(&branch.body)?;
if fn_arg_pat(cx, &branch.pat, &mut value_addr, &select_label)?.converging()
&& expr(cx, &branch.body, needs)?.converging()
{
cx.asm.jump(&end_label, span)?;
}
cx.scopes.pop(&branch.body, scope)?;
}
cx.select_branches = branches;
if let Some((branch, label)) = default_branch {
cx.asm.label(&label)?;
expr(cx, branch, needs)?.ignore();
}
cx.asm.label(&end_label)?;
let mut drop_set = cx.q.unit.drop_set();
for addr in &linear {
drop_set.push(addr.addr())?;
}
if let Some(set) = drop_set.finish()? {
cx.asm.push(Inst::Drop { set }, span)?;
}
value_addr.free()?;
linear.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_select<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprSelect<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
cx.contexts.try_push(span)?;
cx.select_branches.clear();
let asm = expr_select_inner(cx, hir, span, needs)?;
cx.contexts
.pop()
.ok_or("Missing parent context")
.with_span(span)?;
Ok(asm)
}
#[instrument_ast(span = span)]
fn expr_try<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Expr<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut e = cx.scopes.defer(span);
converge!(expr(cx, hir, &mut e)?);
cx.asm.push(
Inst::Try {
addr: e.addr()?.addr(),
out: needs.alloc_output()?,
},
span,
)?;
e.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_tuple<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprSeq<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
macro_rules! tuple {
($variant:ident $(, $var:ident, $expr:ident)* $(,)?) => {{
$(let mut $var = cx.scopes.defer(span);)*
let asm = expr_array(cx, span, [$(($expr, &mut $var)),*])?;
let [$($expr),*] = converge!(asm, free($($var),*));
cx.asm.push(
Inst::$variant {
addr: [$($expr.addr(),)*],
out: needs.alloc_output()?,
},
span,
)?;
$($var.free()?;)*
}};
}
match hir.items {
[] => {
cx.asm.push(Inst::unit(needs.alloc_output()?), span)?;
}
[e1] => tuple!(Tuple1, v1, e1),
[e1, e2] => tuple!(Tuple2, v1, e1, v2, e2),
[e1, e2, e3] => tuple!(Tuple3, v1, e1, v2, e2, v3, e3),
[e1, e2, e3, e4] => tuple!(Tuple4, v1, e1, v2, e2, v3, e3, v4, e4),
_ => {
let linear = converge!(exprs(cx, span, hir.items)?);
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(
Inst::Tuple {
addr: linear.addr(),
count: hir.items.len(),
out,
},
span,
)?;
linear.free_non_dangling()?;
} else {
linear.free()?;
}
}
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_unary<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprUnary<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut addr = cx.scopes.defer(span);
converge!(expr(cx, &hir.expr, &mut addr)?, free(addr));
let addr = addr.into_addr()?;
match hir.op {
ast::UnOp::Not(..) => {
cx.asm.push(
Inst::Not {
addr: addr.addr(),
out: needs.alloc_output()?,
},
span,
)?;
}
ast::UnOp::Neg(..) => {
cx.asm.push(
Inst::Neg {
addr: addr.addr(),
out: needs.alloc_output()?,
},
span,
)?;
}
op => {
return Err(compile::Error::new(
span,
ErrorKind::UnsupportedUnaryOp { op },
));
}
}
addr.free()?;
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_vec<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &hir::ExprSeq<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut linear = cx.scopes.linear(span, hir.items.len())?;
let count = hir.items.len();
for (e, needs) in hir.items.iter().zip(&mut linear) {
converge!(expr(cx, e, needs)?, free(linear));
}
if let Some(out) = needs.try_alloc_addr()? {
cx.asm.push(
Inst::Vec {
addr: linear.addr(),
count,
out: out.output(),
},
span,
)?;
linear.free_non_dangling()?;
} else {
linear.free()?;
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_loop<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::ExprLoop<'hir>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let continue_label = cx.asm.new_label("while_continue");
let then_label = cx.asm.new_label("while_then");
let end_label = cx.asm.new_label("while_end");
let break_label = cx.asm.new_label("while_break");
cx.breaks.push(Break {
label: hir.label,
continue_label: Some(continue_label.try_clone()?),
break_label: break_label.try_clone()?,
output: Some(needs.alloc_output()?),
drop: None,
})?;
cx.asm.label(&continue_label)?;
let count = hir.condition.and_then(|c| c.count()).unwrap_or_default();
let mut linear = cx.scopes.linear(span, count)?;
let condition_scope = if let Some(hir) = hir.condition {
if let Some((scope, _)) =
condition(cx, hir, &then_label, &end_label, &mut linear)?.into_converging()
{
cx.asm.jump(&end_label, span)?;
cx.asm.label(&then_label)?;
Some(scope)
} else {
None
}
} else {
None
};
block(cx, &hir.body, &mut Any::ignore(span))?.ignore();
if let Some(scope) = condition_scope {
cx.scopes.pop(span, scope)?;
}
cx.asm.jump(&continue_label, span)?;
cx.asm.label(&end_label)?;
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), span)?;
}
cx.asm.label(&break_label)?;
linear.free()?;
cx.breaks.pop();
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn expr_yield<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: Option<&'hir hir::Expr<'hir>>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let out = needs.alloc_output()?;
if let Some(e) = hir {
let mut addr = cx.scopes.alloc(span)?.with_name("yield argument");
converge!(expr(cx, e, &mut addr)?, free(addr));
cx.asm.push(
Inst::Yield {
addr: addr.addr(),
out,
},
span,
)?;
addr.free()?;
} else {
cx.asm.push(Inst::YieldUnit { out }, span)?;
}
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = span)]
fn lit<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: hir::Lit<'_>,
span: &'hir dyn Spanned,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let Some(addr) = needs.try_alloc_addr()? else {
cx.q.diagnostics
.not_used(cx.source_id, span, cx.context())?;
return Ok(Asm::new(span, ()));
};
let out = addr.output();
match hir {
hir::Lit::Bool(v) => {
cx.asm.push(Inst::bool(v, out), span)?;
}
hir::Lit::Char(v) => {
cx.asm.push(Inst::char(v, out), span)?;
}
hir::Lit::Unsigned(v) => {
cx.asm.push(Inst::unsigned(v, out), span)?;
}
hir::Lit::Signed(v) => {
cx.asm.push(Inst::signed(v, out), span)?;
}
hir::Lit::Float(v) => {
cx.asm.push(Inst::float(v, out), span)?;
}
hir::Lit::Str(string) => {
let slot = cx.q.unit.new_static_string(span, string)?;
cx.asm.push(Inst::String { slot, out }, span)?;
}
hir::Lit::ByteStr(bytes) => {
let slot = cx.q.unit.new_static_bytes(span, bytes)?;
cx.asm.push(Inst::Bytes { slot, out }, span)?;
}
};
Ok(Asm::new(span, ()))
}
#[instrument_ast(span = hir)]
fn local<'a, 'hir>(
cx: &mut Ctxt<'a, 'hir, '_>,
hir: &'hir hir::Local<'hir>,
needs: &mut dyn Needs<'a, 'hir>,
) -> compile::Result<Asm<'hir>> {
let mut load =
|cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| expr(cx, &hir.expr, needs);
converge!(pattern_panic(cx, &hir.pat, |cx, false_label| {
pat_binding(cx, &hir.pat, false_label, &mut load)
})?);
if let Some(out) = needs.try_alloc_output()? {
cx.asm.push(Inst::unit(out), hir)?;
}
Ok(Asm::new(hir, ()))
}