diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b60e47249..9135e3524 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,3 +162,4 @@ jobs: - run: cargo test --doc - run: cargo run --bin rune -- check --recursive --experimental scripts - run: cargo run --bin rune -- test --recursive --experimental scripts --opt include-std + - run: cargo run --bin rune -- test diff --git a/Rune.toml b/Rune.toml index 0703e2970..72de39809 100644 --- a/Rune.toml +++ b/Rune.toml @@ -2,4 +2,5 @@ members = [ "benches", "examples", + "crates/rune", ] diff --git a/crates/rune-alloc/src/btree/map/tests.rs b/crates/rune-alloc/src/btree/map/tests.rs index fd6204245..9903a4f67 100644 --- a/crates/rune-alloc/src/btree/map/tests.rs +++ b/crates/rune-alloc/src/btree/map/tests.rs @@ -1,6 +1,7 @@ #![allow(clippy::ifs_same_cond)] #![allow(clippy::redundant_closure)] #![allow(clippy::useless_vec)] +#![allow(unused_must_use)] use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering::SeqCst}; diff --git a/crates/rune-alloc/src/btree/set/tests.rs b/crates/rune-alloc/src/btree/set/tests.rs index a6898c47d..dd8bd8159 100644 --- a/crates/rune-alloc/src/btree/set/tests.rs +++ b/crates/rune-alloc/src/btree/set/tests.rs @@ -2,6 +2,7 @@ #![allow(clippy::needless_borrow)] #![allow(clippy::redundant_closure)] #![allow(clippy::useless_vec)] +#![allow(unused_must_use)] use core::ops::Bound::{Excluded, Included}; diff --git a/crates/rune-alloc/src/vec/mod.rs b/crates/rune-alloc/src/vec/mod.rs index 77a9f3acd..40c680de2 100644 --- a/crates/rune-alloc/src/vec/mod.rs +++ b/crates/rune-alloc/src/vec/mod.rs @@ -1998,7 +1998,7 @@ impl Vec { /// # Ok::<_, rune::alloc::Error>(()) /// ``` #[inline] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.len } diff --git a/crates/rune-core/src/item/item.rs b/crates/rune-core/src/item/item.rs index 4ad7a45cc..fc10caffd 100644 --- a/crates/rune-core/src/item/item.rs +++ b/crates/rune-core/src/item/item.rs @@ -31,7 +31,7 @@ impl Item { #[inline] pub const fn new() -> &'static Self { // SAFETY: an empty slice is a valid bit pattern for the root. - unsafe { Self::from_raw(&[]) } + unsafe { Self::from_bytes(&[]) } } /// Construct an [Item] from an [ItemBuf]. @@ -39,7 +39,20 @@ impl Item { /// # Safety /// /// Caller must ensure that content has a valid [ItemBuf] representation. - pub(super) const unsafe fn from_raw(content: &[u8]) -> &Self { + /// The easiest way to accomplish this is to use the `rune::item!` macro. + /// + /// # Examples + /// + /// ``` + /// use rune::compile::{Item, ItemBuf}; + /// + /// let item = ItemBuf::with_item(["foo", "bar"])?; + /// + /// // SAFETY: item is constructed from a valid buffer. + /// let item = unsafe { Item::from_bytes(item.as_bytes()) }; + /// # Ok::<_, rune::alloc::Error>(()) + /// ``` + pub const unsafe fn from_bytes(content: &[u8]) -> &Self { &*(content as *const _ as *const _) } diff --git a/crates/rune-core/src/item/item_buf.rs b/crates/rune-core/src/item/item_buf.rs index e85a04881..ac93eb499 100644 --- a/crates/rune-core/src/item/item_buf.rs +++ b/crates/rune-core/src/item/item_buf.rs @@ -69,7 +69,7 @@ pub struct ItemBuf { impl ItemBuf { /// Construct a new item buffer inside of the given allocator. - pub(crate) fn new_in(alloc: A) -> Self { + pub(crate) const fn new_in(alloc: A) -> Self { Self { content: Vec::new_in(alloc), } @@ -320,7 +320,7 @@ impl Deref for ItemBuf { fn deref(&self) -> &Self::Target { // SAFETY: Item ensures that content is valid. - unsafe { Item::from_raw(self.content.as_ref()) } + unsafe { Item::from_bytes(self.content.as_ref()) } } } diff --git a/crates/rune-core/src/item/iter.rs b/crates/rune-core/src/item/iter.rs index c864339ac..d461e445d 100644 --- a/crates/rune-core/src/item/iter.rs +++ b/crates/rune-core/src/item/iter.rs @@ -32,14 +32,14 @@ impl<'a> Iter<'a> { #[inline] pub fn as_item(&self) -> &Item { // SAFETY: Iterator ensures that content is valid. - unsafe { Item::from_raw(self.content) } + unsafe { Item::from_bytes(self.content) } } /// Coerce the iterator into an item with the lifetime of the iterator. #[inline] pub fn into_item(self) -> &'a Item { // SAFETY: Iterator ensures that content is valid. - unsafe { Item::from_raw(self.content) } + unsafe { Item::from_bytes(self.content) } } /// Get the next component as a string. diff --git a/crates/rune-macros/src/item.rs b/crates/rune-macros/src/item.rs new file mode 100644 index 000000000..730266a91 --- /dev/null +++ b/crates/rune-macros/src/item.rs @@ -0,0 +1,56 @@ +use core::mem::take; + +use proc_macro2::Span; +use rune_core::{ComponentRef, ItemBuf}; + +/// Construct a static item from a path. +pub(crate) fn build_item(path: &syn::Path) -> syn::Result { + let mut buf = ItemBuf::new(); + let mut first = path.leading_colon.is_some(); + + for s in &path.segments { + let ident = s.ident.to_string(); + + let c = if take(&mut first) { + ComponentRef::Crate(&ident) + } else { + ComponentRef::Str(&ident) + }; + + buf.push(c) + .map_err(|error| syn::Error::new_spanned(s, error))?; + + match &s.arguments { + syn::PathArguments::None => {} + syn::PathArguments::AngleBracketed(generics) => { + return Err(syn::Error::new_spanned( + generics, + "Generic arguments are not supported", + )); + } + syn::PathArguments::Parenthesized(generics) => { + return Err(syn::Error::new_spanned( + generics, + "Generic arguments are not supported", + )); + } + } + } + + let mut elems = syn::punctuated::Punctuated::new(); + + for &byte in buf.as_bytes() { + let byte = syn::LitByte::new(byte, Span::call_site()); + + elems.push(syn::Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: syn::Lit::Byte(byte), + })); + } + + Ok(syn::ExprArray { + attrs: Vec::new(), + bracket_token: syn::token::Bracket::default(), + elems, + }) +} diff --git a/crates/rune-macros/src/lib.rs b/crates/rune-macros/src/lib.rs index ebb5c1209..d669cdd35 100644 --- a/crates/rune-macros/src/lib.rs +++ b/crates/rune-macros/src/lib.rs @@ -37,6 +37,7 @@ mod hash; mod inst_display; mod instrument; mod internals; +mod item; mod macro_; mod module; mod opaque; @@ -191,14 +192,14 @@ pub fn any(input: proc_macro::TokenStream) -> proc_macro::TokenStream { builder.expand().into() } -/// Calculate a type hash. +/// Calculate a type hash at compile time. /// /// # Examples /// /// ``` -/// use rune_core::Hash; +/// use rune::Hash; /// -/// let hash: Hash = rune_macros::hash!(::std::ops::Generator); +/// let hash: Hash = rune::hash!(::std::ops::Generator); /// ``` #[proc_macro] pub fn hash(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -207,7 +208,37 @@ pub fn hash(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let stream = match self::hash::build_type_hash(&path) { Ok(hash) => { let hash = hash.into_inner(); - ::quote::quote!(Hash::new(#hash)) + ::quote::quote!(rune::Hash::new(#hash)) + } + Err(error) => to_compile_errors([error]), + }; + + stream.into() +} + +/// Calculate an item reference at compile time. +/// +/// # Examples +/// +/// ``` +/// use rune::{Item, ItemBuf}; +/// +/// static ITEM: &Item = rune::item!(::std::ops::Generator); +/// +/// let mut item = ItemBuf::with_crate("std")?; +/// item.push("ops")?; +/// item.push("Generator")?; +/// +/// assert_eq!(item, ITEM); +/// # Ok::<_, rune::alloc::Error>(()) +/// ``` +#[proc_macro] +pub fn item(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let path = syn::parse_macro_input!(input as syn::Path); + + let stream = match self::item::build_item(&path) { + Ok(hash) => { + ::quote::quote!(unsafe { rune::Item::from_bytes(&#hash) }) } Err(error) => to_compile_errors([error]), }; diff --git a/crates/rune-shim/src/lib.rs b/crates/rune-shim/src/lib.rs index 7c9e33711..8ad78fc52 100644 --- a/crates/rune-shim/src/lib.rs +++ b/crates/rune-shim/src/lib.rs @@ -10,3 +10,6 @@ pub mod support { pub use anyhow::Error; pub use anyhow::Result; } + +#[cfg(feature = "rune-core")] +pub use rune_core::{Item, ItemBuf}; diff --git a/crates/rune-wasm/src/lib.rs b/crates/rune-wasm/src/lib.rs index 781475a82..f4ecaf148 100644 --- a/crates/rune-wasm/src/lib.rs +++ b/crates/rune-wasm/src/lib.rs @@ -330,6 +330,7 @@ async fn inner_compile( let start = WasmPosition::from( source.pos_to_utf8_linecol(inst.span.start.into_usize()), ); + let end = WasmPosition::from( source.pos_to_utf8_linecol(inst.span.end.into_usize()), ); diff --git a/crates/rune/Rune.toml b/crates/rune/Rune.toml new file mode 100644 index 000000000..f48a750ca --- /dev/null +++ b/crates/rune/Rune.toml @@ -0,0 +1,3 @@ +[package] +name = "rune" +version = "0.0.0" diff --git a/crates/rune/src/ast.rs b/crates/rune/src/ast.rs index 20ba37598..c710afe77 100644 --- a/crates/rune/src/ast.rs +++ b/crates/rune/src/ast.rs @@ -187,7 +187,7 @@ pub use self::expr_match::{ExprMatch, ExprMatchBranch}; pub use self::expr_object::{ExprObject, FieldAssign, ObjectIdent, ObjectKey}; pub use self::expr_range::{ExprRange, ExprRangeLimits}; pub use self::expr_return::ExprReturn; -pub use self::expr_select::{ExprSelect, ExprSelectBranch, ExprSelectPatBranch}; +pub use self::expr_select::{ExprDefaultBranch, ExprSelect, ExprSelectBranch, ExprSelectPatBranch}; pub use self::expr_try::ExprTry; pub use self::expr_tuple::ExprTuple; pub use self::expr_unary::{ExprUnary, UnOp}; diff --git a/crates/rune/src/ast/attribute.rs b/crates/rune/src/ast/attribute.rs index e650e07b7..cfb455f03 100644 --- a/crates/rune/src/ast/attribute.rs +++ b/crates/rune/src/ast/attribute.rs @@ -152,24 +152,6 @@ impl Parse for AttrStyle { } } -/// Helper struct to only parse inner attributes. -#[allow(unused)] -pub(crate) struct InnerAttribute(pub(crate) Attribute); - -impl Parse for InnerAttribute { - fn parse(p: &mut Parser) -> Result { - let attribute: Attribute = p.parse()?; - - match attribute.style { - AttrStyle::Inner => Ok(Self(attribute)), - _ => Err(compile::Error::expected( - attribute, - "inner attribute like `#![allow(unused)]`", - )), - } - } -} - /// Tag struct to assist peeking for an outer `#![...]` attributes at the top of /// a module/file #[non_exhaustive] diff --git a/crates/rune/src/ast/expr.rs b/crates/rune/src/ast/expr.rs index e85fa441e..de9d53474 100644 --- a/crates/rune/src/ast/expr.rs +++ b/crates/rune/src/ast/expr.rs @@ -515,13 +515,14 @@ fn base( K!['['] => Expr::Vec(ast::ExprVec::parse_with_meta(p, take(attributes))?), ast::Kind::Open(ast::Delimiter::Empty) => empty_group(p, take(attributes))?, K!['('] => paren_group(p, take(attributes))?, - K!['{'] => Expr::Block(ast::ExprBlock::parse_with_meta( - p, - take(attributes), - take(&mut async_token), - take(&mut const_token), - take(&mut move_token), - )?), + K!['{'] => Expr::Block(ast::ExprBlock { + attributes: take(attributes), + async_token: take(&mut async_token), + const_token: take(&mut const_token), + move_token: take(&mut move_token), + label: take(&mut label), + block: p.parse()?, + }), K![break] => Expr::Break(ast::ExprBreak::parse_with_meta(p, take(attributes))?), K![continue] => Expr::Continue(ast::ExprContinue::parse_with_meta(p, take(attributes))?), K![yield] => Expr::Yield(ast::ExprYield::parse_with_meta(p, take(attributes))?), diff --git a/crates/rune/src/ast/expr_block.rs b/crates/rune/src/ast/expr_block.rs index f919f52c1..c6d6de7ea 100644 --- a/crates/rune/src/ast/expr_block.rs +++ b/crates/rune/src/ast/expr_block.rs @@ -16,6 +16,10 @@ fn ast_parse() { let expr = rt::("async { 42 }"); assert_eq!(expr.block.statements.len(), 1); + let expr = rt::("'foo: { 42 }"); + assert_eq!(expr.block.statements.len(), 1); + assert!(expr.label.is_some()); + let expr = rt::("#[retry] async { 42 }"); assert_eq!(expr.block.statements.len(), 1); assert_eq!(expr.attributes.len(), 1); @@ -26,8 +30,7 @@ fn ast_parse() { /// * ``. /// * `async `. /// * `const `. -#[derive(Debug, TryClone, PartialEq, Eq, Parse, ToTokens, Spanned)] -#[rune(parse = "meta_only")] +#[derive(Debug, TryClone, PartialEq, Eq, ToTokens, Spanned)] #[non_exhaustive] pub struct ExprBlock { /// The attributes for the block. @@ -42,6 +45,9 @@ pub struct ExprBlock { /// The optional move token. #[rune(iter, meta)] pub move_token: Option, + /// An optional label for the block. + #[rune(iter)] + pub label: Option<(ast::Label, T![:])>, /// The close brace. pub block: ast::Block, } diff --git a/crates/rune/src/ast/expr_for.rs b/crates/rune/src/ast/expr_for.rs index 262fca090..e4b9e57ed 100644 --- a/crates/rune/src/ast/expr_for.rs +++ b/crates/rune/src/ast/expr_for.rs @@ -49,7 +49,7 @@ impl ExprFor { binding: parser.parse()?, in_: parser.parse()?, iter: Box::try_new(ast::Expr::parse_without_eager_brace(parser)?)?, - body: parser.parse()?, + body: Box::try_new(parser.parse()?)?, }) } } diff --git a/crates/rune/src/cli/run.rs b/crates/rune/src/cli/run.rs index 1d0746897..b6f986015 100644 --- a/crates/rune/src/cli/run.rs +++ b/crates/rune/src/cli/run.rs @@ -14,6 +14,10 @@ pub(super) struct Flags { /// Provide detailed tracing for each instruction executed. #[arg(short, long)] trace: bool, + /// When tracing is enabled, do not include source references if they are + /// available. + #[arg(long)] + without_source: bool, /// Time how long the script took to execute. #[arg(long)] time: bool, @@ -52,9 +56,6 @@ pub(super) struct Flags { /// Dump native types. #[arg(long)] dump_native_types: bool, - /// Include source code references where appropriate (only available if -O debug-info=true). - #[arg(long)] - with_source: bool, /// When tracing, limit the number of instructions to run with `limit`. This /// implies `--trace`. #[arg(long)] @@ -83,25 +84,21 @@ impl CommandBase for Flags { self.dump_native_types = true; } - if self.trace_limit.is_some() { - self.trace = true; + if self.dump_functions + || self.dump_native_functions + || self.dump_stack + || self.dump_types + || self.dump_constants { + self.dump_unit = true; } - } -} -impl Flags { - fn emit_instructions(&self) -> bool { - self.dump_unit || self.emit_instructions - } + if self.dump_unit { + self.emit_instructions = true; + } - fn dump_unit(&self) -> bool { - self.dump_unit - || self.dump_functions - || self.dump_native_functions - || self.dump_stack - || self.dump_types - || self.dump_constants - || self.emit_instructions + if self.trace_limit.is_some() { + self.trace = true; + } } } @@ -151,17 +148,17 @@ pub(super) async fn run( } } - if args.dump_unit() { + if args.dump_unit { writeln!( io.stdout, "Unit size: {} bytes", unit.instructions().bytes() )?; - if args.emit_instructions() { + if args.emit_instructions { let mut o = io.stdout.lock(); writeln!(o, "# instructions")?; - unit.emit_instructions(&mut o, sources, args.with_source)?; + unit.emit_instructions(&mut o, sources, args.without_source)?; } let mut functions = unit.iter_functions().peekable(); @@ -219,7 +216,7 @@ pub(super) async fn run( &mut execution, sources, args.dump_stack, - args.with_source, + args.without_source, args.trace_limit.unwrap_or(usize::MAX), ) .await @@ -252,6 +249,13 @@ pub(super) async fn run( } }; + let exit = if let Some(error) = errored { + error.emit(io.stdout, sources)?; + ExitCode::VmError + } else { + ExitCode::Success + }; + if args.dump_stack { writeln!(io.stdout, "# full stack dump after halting")?; @@ -264,15 +268,15 @@ pub(super) async fn run( while let Some((count, frame)) = it.next() { let stack_top = match it.peek() { - Some((_, next)) => next.stack_bottom, - None => stack.stack_bottom(), + Some((_, next)) => next.top, + None => stack.top(), }; let values = stack - .get(frame.stack_bottom..stack_top) + .get(frame.top..stack_top) .expect("bad stack slice"); - writeln!(io.stdout, " frame #{} (+{})", count, frame.stack_bottom)?; + writeln!(io.stdout, " frame #{} (+{})", count, frame.top)?; if values.is_empty() { writeln!(io.stdout, " *empty*")?; @@ -280,7 +284,7 @@ pub(super) async fn run( vm.with(|| { for (n, value) in stack.iter().enumerate() { - writeln!(io.stdout, "{}+{} = {:?}", frame.stack_bottom, n, value)?; + writeln!(io.stdout, " {}+{n} = {value:?}", frame.top)?; } Ok::<_, crate::support::Error>(()) @@ -292,10 +296,10 @@ pub(super) async fn run( io.stdout, " frame #{} (+{})", frames.len(), - stack.stack_bottom() + stack.top() )?; - let values = stack.get(stack.stack_bottom()..).expect("bad stack slice"); + let values = stack.get(stack.top()..).expect("bad stack slice"); if values.is_empty() { writeln!(io.stdout, " *empty*")?; @@ -303,25 +307,14 @@ pub(super) async fn run( vm.with(|| { for (n, value) in values.iter().enumerate() { - writeln!( - io.stdout, - " {}+{} = {:?}", - stack.stack_bottom(), - n, - value - )?; + writeln!(io.stdout, " {}+{n} = {value:?}", stack.top())?; } Ok::<_, crate::support::Error>(()) })?; } - if let Some(error) = errored { - error.emit(io.stdout, sources)?; - Ok(ExitCode::VmError) - } else { - Ok(ExitCode::Success) - } + Ok(exit) } /// Perform a detailed trace of the program. @@ -330,103 +323,115 @@ async fn do_trace( execution: &mut VmExecution, sources: &Sources, dump_stack: bool, - with_source: bool, + without_source: bool, mut limit: usize, ) -> Result where T: AsRef + AsMut, { let mut current_frame_len = execution.vm().call_frames().len(); + let mut result = VmResult::Ok(None); while limit > 0 { - limit = limit.wrapping_sub(1); + let vm = execution.vm(); + let ip = vm.ip(); + let mut o = io.stdout.lock(); + if let Some((hash, signature)) = vm + .unit() + .debug_info() + .and_then(|d| d.function_at(ip)) { - let vm = execution.vm(); - let mut o = io.stdout.lock(); - - if let Some((hash, signature)) = vm - .unit() - .debug_info() - .and_then(|d| d.function_at(vm.last_ip())) - { - writeln!(o, "fn {} ({}):", signature, hash)?; - } - - let debug = vm - .unit() - .debug_info() - .and_then(|d| d.instruction_at(vm.last_ip())); + writeln!(o, "fn {} ({}):", signature, hash)?; + } - if with_source { - let debug_info = debug.and_then(|d| sources.get(d.source_id).map(|s| (s, d.span))); - if let Some((source, span)) = debug_info { - source.emit_source_line(&mut o, span)?; - } - } + let debug = vm + .unit() + .debug_info() + .and_then(|d| d.instruction_at(ip)); - for label in debug.map(|d| d.labels.as_slice()).unwrap_or_default() { - writeln!(o, "{}:", label)?; - } + for label in debug.map(|d| d.labels.as_slice()).unwrap_or_default() { + writeln!(o, "{}:", label)?; + } - if let Some((inst, _)) = vm - .unit() - .instruction_at(vm.last_ip()) - .map_err(VmError::from)? - { - write!(o, " {:04} = {}", vm.last_ip(), inst)?; - } else { - write!(o, " {:04} = *out of bounds*", vm.last_ip())?; - } + if !without_source { + let debug_info = debug.and_then(|d| sources.get(d.source_id).map(|s| (s, d.span))); - if let Some(comment) = debug.and_then(|d| d.comment.as_ref()) { - write!(o, " // {}", comment)?; + if let Some(line) = debug_info.and_then(|(s, span)| s.source_line(span)) { + write!(o, " ")?; + line.write(&mut o)?; + writeln!(o)?; } - - writeln!(o)?; } - let result = match execution.async_step().await { - VmResult::Ok(result) => result, - VmResult::Err(e) => return Err(TraceError::VmError(e)), - }; - - let mut o = io.stdout.lock(); - if dump_stack { - let vm = execution.vm(); let frames = vm.call_frames(); - let stack = vm.stack(); if current_frame_len != frames.len() { - if current_frame_len < frames.len() { - writeln!(o, "=> frame {} ({}):", frames.len(), stack.stack_bottom())?; + let op = if current_frame_len < frames.len() { "push" } else { "pop" }; + write!(o, " {op} frame {} (+{})", frames.len(), stack.top())?; + + if let Some(frame) = frames.last() { + writeln!(o, " {frame:?}")?; } else { - writeln!(o, "<= frame {} ({}):", frames.len(), stack.stack_bottom())?; + writeln!(o, " *root*")?; } current_frame_len = frames.len(); } + } - let values = stack.get(stack.stack_bottom()..).expect("bad stack slice"); + if let Some((inst, _)) = vm + .unit() + .instruction_at(ip) + .map_err(VmError::from)? + { + write!(o, " {:04} = {}", ip, inst)?; + } else { + write!(o, " {:04} = *out of bounds*", ip)?; + } - if values.is_empty() { - writeln!(o, " *empty*")?; - } + if let Some(comment) = debug.and_then(|d| d.comment.as_ref()) { + write!(o, " // {}", comment)?; + } + + writeln!(o)?; + + if dump_stack { + let stack = vm.stack(); + let values = stack.get(stack.top()..).expect("bad stack slice"); vm.with(|| { for (n, value) in values.iter().enumerate() { - writeln!(o, " {}+{} = {:?}", stack.stack_bottom(), n, value)?; + writeln!(o, " {}+{n} = {value:?}", stack.top())?; } Ok::<_, TraceError>(()) })?; } - if let Some(result) = result { - return Ok(result); + match result { + VmResult::Ok(result) => { + if let Some(result) = result { + return Ok(result); + } + } + VmResult::Err(error) => { + return Err(TraceError::VmError(error)); + } } + + result = execution.async_step().await; + + result = match result { + VmResult::Err(error) => { + return Err(TraceError::VmError(error)); + } + result => result, + }; + + limit = limit.wrapping_sub(1); } Err(TraceError::Limited) diff --git a/crates/rune/src/compile/assembly.rs b/crates/rune/src/compile/assembly.rs index 62d3075c0..230463e8d 100644 --- a/crates/rune/src/compile/assembly.rs +++ b/crates/rune/src/compile/assembly.rs @@ -8,19 +8,35 @@ use crate::alloc::prelude::*; use crate::alloc::{hash_map, HashMap}; use crate::ast::{Span, Spanned}; use crate::compile::{self, Location}; -use crate::runtime::{Inst, Label}; +use crate::runtime::{Inst, InstAddress, Label, Output}; use crate::{Hash, SourceId}; #[derive(Debug, TryClone)] pub(crate) enum AssemblyInst { - Jump { label: Label }, - JumpIf { label: Label }, - JumpIfOrPop { label: Label }, - JumpIfNotOrPop { label: Label }, - JumpIfBranch { branch: i64, label: Label }, - PopAndJumpIfNot { count: usize, label: Label }, - IterNext { offset: usize, label: Label }, - Raw { raw: Inst }, + Jump { + label: Label, + }, + JumpIf { + addr: InstAddress, + label: Label, + }, + JumpIfNot { + addr: InstAddress, + label: Label, + }, + JumpIfBranch { + addr: InstAddress, + branch: i64, + label: Label, + }, + IterNext { + addr: InstAddress, + label: Label, + out: Output, + }, + Raw { + raw: Inst, + }, } /// Helper structure to build instructions and maintain certain invariants. @@ -92,26 +108,15 @@ impl Assembly { } /// Add a conditional jump to the given label. - pub(crate) fn jump_if(&mut self, label: &Label, span: &dyn Spanned) -> compile::Result<()> { - self.inner_push( - AssemblyInst::JumpIf { - label: label.try_clone()?, - }, - span, - )?; - - Ok(()) - } - - /// Add a conditional jump to the given label. Only pops the top of the - /// stack if the jump is not executed. - pub(crate) fn jump_if_or_pop( + pub(crate) fn jump_if( &mut self, + addr: InstAddress, label: &Label, span: &dyn Spanned, ) -> compile::Result<()> { self.inner_push( - AssemblyInst::JumpIfOrPop { + AssemblyInst::JumpIf { + addr, label: label.try_clone()?, }, span, @@ -120,15 +125,16 @@ impl Assembly { Ok(()) } - /// Add a conditional jump to the given label. Only pops the top of the - /// stack if the jump is not executed. - pub(crate) fn jump_if_not_or_pop( + /// Add jump-if-not instruction to a label. + pub(crate) fn jump_if_not( &mut self, + addr: InstAddress, label: &Label, span: &dyn Spanned, ) -> compile::Result<()> { self.inner_push( - AssemblyInst::JumpIfNotOrPop { + AssemblyInst::JumpIfNot { + addr, label: label.try_clone()?, }, span, @@ -140,12 +146,14 @@ impl Assembly { /// Add a conditional jump-if-branch instruction. pub(crate) fn jump_if_branch( &mut self, + addr: InstAddress, branch: i64, label: &Label, span: &dyn Spanned, ) -> compile::Result<()> { self.inner_push( AssemblyInst::JumpIfBranch { + addr, branch, label: label.try_clone()?, }, @@ -155,35 +163,19 @@ impl Assembly { Ok(()) } - /// Add a pop-and-jump-if-not instruction to a label. - pub(crate) fn pop_and_jump_if_not( - &mut self, - count: usize, - label: &Label, - span: &dyn Spanned, - ) -> compile::Result<()> { - self.inner_push( - AssemblyInst::PopAndJumpIfNot { - count, - label: label.try_clone()?, - }, - span, - )?; - - Ok(()) - } - /// Add an instruction that advanced an iterator. pub(crate) fn iter_next( &mut self, - offset: usize, + addr: InstAddress, label: &Label, span: &dyn Spanned, + out: Output, ) -> compile::Result<()> { self.inner_push( AssemblyInst::IterNext { - offset, + addr, label: label.try_clone()?, + out, }, span, )?; diff --git a/crates/rune/src/compile/compile.rs b/crates/rune/src/compile/compile.rs index 0aadcff0a..322d4356b 100644 --- a/crates/rune/src/compile/compile.rs +++ b/crates/rune/src/compile/compile.rs @@ -134,15 +134,18 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { location: Location, span: &dyn Spanned, asm: &'a mut Assembly, + scopes: &'a mut v1::Scopes<'hir>, ) -> alloc::Result> { Ok(v1::Ctxt { source_id: location.source_id, q: self.q.borrow(), asm, - scopes: self::v1::Scopes::new(location.source_id)?, + scopes, contexts: try_vec![span.span()], - loops: self::v1::Loops::new(), + breaks: self::v1::Breaks::new(), options: self.options, + select_branches: Vec::new(), + drop: Vec::new(), }) } @@ -246,8 +249,10 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { let count = hir.args.len(); - let mut c = self.compiler1(location, span, &mut asm)?; + let mut scopes = self::v1::Scopes::new(location.source_id)?; + let mut c = self.compiler1(location, span, &mut asm, &mut scopes)?; assemble::fn_from_item_fn(&mut c, &hir, f.is_instance)?; + let size = c.scopes.size(); if !self.q.is_used(&item_meta) { self.q @@ -272,6 +277,7 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { f.call, debug_args, unit_storage, + size, )?; } } @@ -297,8 +303,10 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { )?; let hir = hir::lowering::expr_closure_secondary(&mut cx, &closure.ast, captures)?; - let mut c = self.compiler1(location, &closure.ast, &mut asm)?; + let mut scopes = self::v1::Scopes::new(location.source_id)?; + let mut c = self.compiler1(location, &closure.ast, &mut asm, &mut scopes)?; assemble::expr_closure_secondary(&mut c, &hir, &closure.ast)?; + let size = c.scopes.size(); if !c.q.is_used(&item_meta) { c.q.diagnostics @@ -325,6 +333,7 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { closure.call, debug_args, unit_storage, + size, )?; } } @@ -342,8 +351,10 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { item_meta.location.source_id, )?; let hir = hir::lowering::async_block_secondary(&mut cx, &b.ast, captures)?; - let mut c = self.compiler1(location, &b.ast, &mut asm)?; + let mut scopes = self::v1::Scopes::new(location.source_id)?; + let mut c = self.compiler1(location, &b.ast, &mut asm, &mut scopes)?; assemble::async_block_secondary(&mut c, &hir)?; + let size = c.scopes.size(); if !self.q.is_used(&item_meta) { self.q @@ -362,6 +373,7 @@ impl<'arena> CompileBuildEntry<'_, 'arena> { b.call, Default::default(), unit_storage, + size, )?; } } diff --git a/crates/rune/src/compile/error.rs b/crates/rune/src/compile/error.rs index cb393ae0f..b659e9bf3 100644 --- a/crates/rune/src/compile/error.rs +++ b/crates/rune/src/compile/error.rs @@ -18,6 +18,7 @@ use crate::query::MissingId; use crate::runtime::debug::DebugSignature; use crate::runtime::unit::EncodeError; use crate::runtime::{AccessError, RuntimeError, TypeInfo, TypeOf, ValueKind, VmError}; +use crate::shared::CapacityError; #[cfg(feature = "std")] use crate::source; use crate::{Hash, SourceId}; @@ -222,6 +223,9 @@ pub(crate) enum ErrorKind { AllocError { error: alloc::Error, }, + CapacityError { + error: CapacityError, + }, IrError(IrErrorKind), MetaError(MetaError), AccessError(AccessError), @@ -295,7 +299,6 @@ pub(crate) enum ErrorKind { UnsupportedAssignExpr, UnsupportedBinaryExpr, UnsupportedRef, - UnsupportedSelectPattern, UnsupportedArgumentCount { expected: usize, actual: usize, @@ -312,8 +315,10 @@ pub(crate) enum ErrorKind { UnsupportedTupleIndex { number: ast::Number, }, - BreakOutsideOfLoop, - ContinueOutsideOfLoop, + BreakUnsupported, + BreakUnsupportedValue, + ContinueUnsupported, + ContinueUnsupportedBlock, SelectMultipleDefaults, ExpectedBlockSemiColon { #[cfg(feature = "emit")] @@ -364,7 +369,7 @@ pub(crate) enum ErrorKind { current: Box<[String]>, existing: Box<[String]>, }, - MissingLoopLabel { + MissingLabel { label: Box, }, ExpectedLeadingPathSegment, @@ -525,7 +530,6 @@ cfg_std! { impl std::error::Error for ErrorKind { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - ErrorKind::AllocError { error, .. } => Some(error), ErrorKind::IrError(source) => Some(source), ErrorKind::MetaError(source) => Some(source), ErrorKind::AccessError(source) => Some(source), @@ -559,6 +563,9 @@ impl fmt::Display for ErrorKind { ErrorKind::AllocError { error } => { error.fmt(f)?; } + ErrorKind::CapacityError { error } => { + error.fmt(f)?; + } ErrorKind::IrError(error) => { error.fmt(f)?; } @@ -678,9 +685,6 @@ impl fmt::Display for ErrorKind { ErrorKind::UnsupportedRef => { write!(f, "Cannot take reference of expression")?; } - ErrorKind::UnsupportedSelectPattern => { - write!(f, "Unsupported select pattern")?; - } ErrorKind::UnsupportedArgumentCount { expected, actual } => { write!( f, @@ -702,12 +706,21 @@ impl fmt::Display for ErrorKind { ErrorKind::UnsupportedTupleIndex { number } => { write!(f, "Unsupported tuple index `{number}`")?; } - ErrorKind::BreakOutsideOfLoop => { + ErrorKind::BreakUnsupported => { write!(f, "Break outside of loop")?; } - ErrorKind::ContinueOutsideOfLoop => { + ErrorKind::BreakUnsupportedValue => { + write!( + f, + "Can only break with a value inside `loop` or breakable block" + )?; + } + ErrorKind::ContinueUnsupported => { write!(f, "Continue outside of loop")?; } + ErrorKind::ContinueUnsupportedBlock => { + write!(f, "Labeled blocks cannot be `continue`'d")?; + } ErrorKind::SelectMultipleDefaults => { write!(f, "Multiple `default` branches in select")?; } @@ -818,8 +831,8 @@ impl fmt::Display for ErrorKind { } => { write!(f,"Conflicting static object keys for hash `{hash}` between `{existing:?}` and `{current:?}`")?; } - ErrorKind::MissingLoopLabel { label } => { - write!(f, "Missing loop label `{label}`")?; + ErrorKind::MissingLabel { label } => { + write!(f, "Missing label '{label}")?; } ErrorKind::ExpectedLeadingPathSegment => { write!(f, "Segment is only supported in the first position")?; @@ -1048,6 +1061,13 @@ impl From for ErrorKind { } } +impl From for ErrorKind { + #[inline] + fn from(error: CapacityError) -> Self { + ErrorKind::CapacityError { error } + } +} + impl From for Error { #[inline] fn from(error: alloc::alloc::AllocError) -> Self { diff --git a/crates/rune/src/compile/ir.rs b/crates/rune/src/compile/ir.rs index 1afa25f01..00825dd50 100644 --- a/crates/rune/src/compile/ir.rs +++ b/crates/rune/src/compile/ir.rs @@ -188,8 +188,12 @@ impl IrFn { let mut args = Vec::new(); for arg in hir.args { - if let hir::FnArg::Pat(hir::Pat { - kind: hir::PatKind::Path(&hir::PatPathKind::Ident(name)), + if let hir::FnArg::Pat(hir::PatBinding { + pat: + hir::Pat { + kind: hir::PatKind::Path(&hir::PatPathKind::Ident(name)), + .. + }, .. }) = arg { diff --git a/crates/rune/src/compile/ir/compiler.rs b/crates/rune/src/compile/ir/compiler.rs index 518234ee4..126f8ed4e 100644 --- a/crates/rune/src/compile/ir/compiler.rs +++ b/crates/rune/src/compile/ir/compiler.rs @@ -356,7 +356,7 @@ fn builtin_template( fn local(hir: &hir::Local<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result { let span = hir.span(); - let name = match hir.pat.kind { + let name = match hir.pat.pat.kind { hir::PatKind::Ignore => { return expr(&hir.expr, c); } @@ -381,7 +381,7 @@ fn condition(hir: &hir::Condition<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result< match hir { hir::Condition::Expr(e) => Ok(ir::IrCondition::Ir(expr(e, c)?)), hir::Condition::ExprLet(hir) => { - let pat = ir::IrPat::compile_ast(&hir.pat)?; + let pat = ir::IrPat::compile_ast(&hir.pat.pat)?; let ir = expr(&hir.expr, c)?; Ok(ir::IrCondition::Let(ir::IrLet { @@ -400,20 +400,18 @@ fn expr_if( hir: &hir::Conditional<'_>, ) -> compile::Result { let mut branches = Vec::new(); - let mut default_branch = None; for hir in hir.branches { - let Some(cond) = hir.condition else { - let ir = block(&hir.block, c)?; - default_branch = Some(ir); - continue; - }; - - let cond = condition(cond, c)?; + let cond = condition(hir.condition, c)?; let ir = block(&hir.block, c)?; branches.try_push((cond, ir))?; } + let default_branch = match hir.fallback { + Some(hir) => Some(block(hir, c)?), + None => None, + }; + Ok(ir::IrBranches { span, branches, diff --git a/crates/rune/src/compile/unit_builder.rs b/crates/rune/src/compile/unit_builder.rs index 2a381d222..3641d97c0 100644 --- a/crates/rune/src/compile/unit_builder.rs +++ b/crates/rune/src/compile/unit_builder.rs @@ -668,6 +668,7 @@ impl UnitBuilder { call: Call, debug_args: Box<[Box]>, unit_storage: &mut dyn UnitEncoder, + size: usize, ) -> compile::Result<()> { tracing::trace!("instance fn: {}", item); @@ -728,7 +729,7 @@ impl UnitBuilder { self.debug_mut()?.functions.try_insert(hash, signature)?; self.functions_rev.try_insert(offset, hash)?; - self.add_assembly(location, assembly, unit_storage)?; + self.add_assembly(location, assembly, unit_storage, size)?; Ok(()) } @@ -771,10 +772,16 @@ impl UnitBuilder { location: Location, assembly: Assembly, storage: &mut dyn UnitEncoder, + size: usize, ) -> compile::Result<()> { self.label_count = assembly.label_count; + storage + .encode(Inst::Allocate { size }) + .with_span(location.span)?; + let base = storage.extend_offsets(assembly.labels.len())?; + self.required_functions .try_extend(assembly.required_functions)?; @@ -820,33 +827,7 @@ impl UnitBuilder { storage.encode(Inst::Jump { jump }).with_span(span)?; } - AssemblyInst::JumpIf { label } => { - let jump = label - .jump() - .ok_or(ErrorKind::MissingLabelLocation { - name: label.name, - index: label.index, - }) - .with_span(span)?; - - write!(comment, "label:{}", label)?; - - storage.encode(Inst::JumpIf { jump }).with_span(span)?; - } - AssemblyInst::JumpIfOrPop { label } => { - let jump = label - .jump() - .ok_or(ErrorKind::MissingLabelLocation { - name: label.name, - index: label.index, - }) - .with_span(span)?; - - write!(comment, "label:{}", label)?; - - storage.encode(Inst::JumpIfOrPop { jump }).with_span(span)?; - } - AssemblyInst::JumpIfNotOrPop { label } => { + AssemblyInst::JumpIf { addr, label } => { let jump = label .jump() .ok_or(ErrorKind::MissingLabelLocation { @@ -858,10 +839,10 @@ impl UnitBuilder { write!(comment, "label:{}", label)?; storage - .encode(Inst::JumpIfNotOrPop { jump }) + .encode(Inst::JumpIf { cond: addr, jump }) .with_span(span)?; } - AssemblyInst::JumpIfBranch { branch, label } => { + AssemblyInst::JumpIfNot { addr, label } => { let jump = label .jump() .ok_or(ErrorKind::MissingLabelLocation { @@ -873,10 +854,14 @@ impl UnitBuilder { write!(comment, "label:{}", label)?; storage - .encode(Inst::JumpIfBranch { branch, jump }) + .encode(Inst::JumpIfNot { cond: addr, jump }) .with_span(span)?; } - AssemblyInst::PopAndJumpIfNot { count, label } => { + AssemblyInst::JumpIfBranch { + addr, + branch, + label, + } => { let jump = label .jump() .ok_or(ErrorKind::MissingLabelLocation { @@ -888,10 +873,14 @@ impl UnitBuilder { write!(comment, "label:{}", label)?; storage - .encode(Inst::PopAndJumpIfNot { count, jump }) + .encode(Inst::JumpIfBranch { + branch: addr, + value: branch, + jump, + }) .with_span(span)?; } - AssemblyInst::IterNext { offset, label } => { + AssemblyInst::IterNext { addr, label, out } => { let jump = label .jump() .ok_or(ErrorKind::MissingLabelLocation { @@ -903,21 +892,28 @@ impl UnitBuilder { write!(comment, "label:{}", label)?; storage - .encode(Inst::IterNext { offset, jump }) + .encode(Inst::IterNext { addr, jump, out }) .with_span(span)?; } AssemblyInst::Raw { raw } => { // Optimization to avoid performing lookups for recursive // function calls. let inst = match raw { - inst @ Inst::Call { hash, args } => { + inst @ Inst::Call { + hash, + addr, + args, + out, + } => { if let Some(UnitFn::Offset { offset, call, .. }) = self.functions.get(&hash) { Inst::CallOffset { offset: *offset, call: *call, + addr, args, + out, } } else { inst diff --git a/crates/rune/src/compile/v1.rs b/crates/rune/src/compile/v1.rs deleted file mode 100644 index 03cbc8520..000000000 --- a/crates/rune/src/compile/v1.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub(crate) mod assemble; -pub(crate) use self::assemble::{Ctxt, Needs}; - -mod loops; -pub(crate) use self::loops::{Loop, Loops}; - -mod scopes; -pub(crate) use self::scopes::{Layer, ScopeGuard, Scopes, Var}; diff --git a/crates/rune/src/compile/v1/assemble.rs b/crates/rune/src/compile/v1/assemble.rs index 6c5675a85..49a57ac97 100644 --- a/crates/rune/src/compile/v1/assemble.rs +++ b/crates/rune/src/compile/v1/assemble.rs @@ -1,36 +1,49 @@ -use crate as rune; +use core::fmt; +use core::slice; + use crate::alloc::prelude::*; -use crate::alloc::{try_format, Vec}; +use crate::alloc::BTreeMap; use crate::ast::{self, Span, Spanned}; use crate::compile::ir; -use crate::compile::v1::{Layer, Loop, Loops, ScopeGuard, Scopes, Var}; use crate::compile::{self, Assembly, ErrorKind, ItemId, ModId, Options, WithSpan}; use crate::hir; use crate::query::{ConstFn, Query, Used}; use crate::runtime::{ ConstValue, Inst, InstAddress, InstAssignOp, InstOp, InstRange, InstTarget, InstValue, - InstVariant, Label, PanicReason, Protocol, TypeCheck, + InstVariant, Label, Output, PanicReason, Protocol, TypeCheck, }; +use crate::shared::FixedVec; use crate::{Hash, SourceId}; +use super::{Address, Any, Break, Breaks, Linear, Needs, ScopeHandle, Scopes}; + use rune_macros::instrument; -/// A needs hint for an expression. -/// This is used to contextually determine what an expression is expected to -/// produce. -#[derive(Debug, TryClone, Clone, Copy)] -#[try_clone(copy)] -pub(crate) enum Needs { - Value, - None, +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, + }) + } + } + }; } -impl Needs { - /// Test if any sort of value is needed. - #[inline(always)] - pub(crate) fn value(self) -> bool { - matches!(self, Self::Value) - } +enum Pattern { + Irrefutable, + Refutable, } /// Assemble context. @@ -42,70 +55,20 @@ pub(crate) struct Ctxt<'a, 'hir, 'arena> { /// The assembly we are generating. pub(crate) asm: &'a mut Assembly, /// Scopes defined in the compiler. - pub(crate) scopes: Scopes<'hir>, + pub(crate) scopes: &'a Scopes<'hir>, /// Context for which to emit warnings. pub(crate) contexts: Vec, /// The nesting of loop we are currently in. - pub(crate) loops: Loops<'hir>, + pub(crate) breaks: Breaks<'hir>, /// Enabled optimizations. pub(crate) options: &'a Options, + /// Work buffer for select branches. + pub(crate) select_branches: Vec<(Label, &'hir hir::ExprSelectBranch<'hir>)>, + /// Values to drop. + pub(crate) drop: Vec, } impl<'a, 'hir, 'arena> Ctxt<'a, 'hir, 'arena> { - /// Pop locals by simply popping them. - pub(crate) fn locals_pop( - &mut self, - total_var_count: usize, - span: &dyn Spanned, - ) -> compile::Result<()> { - match total_var_count { - 0 => (), - 1 => { - self.asm.push(Inst::Pop, span)?; - } - count => { - self.asm.push(Inst::PopN { count }, span)?; - } - } - - Ok(()) - } - - /// Clean up local variables by preserving the value that is on top and - /// popping the rest. - pub(crate) fn locals_clean( - &mut self, - total_var_count: usize, - span: &dyn Spanned, - ) -> compile::Result<()> { - match total_var_count { - 0 => (), - count => { - self.asm.push(Inst::Clean { count }, span)?; - } - } - - Ok(()) - } - - /// Clean the last scope. - pub(crate) fn clean_last_scope( - &mut self, - span: &dyn Spanned, - expected: ScopeGuard, - needs: Needs, - ) -> compile::Result<()> { - let scope = self.scopes.pop(expected, span)?; - - if needs.value() { - self.locals_clean(scope.local, span)?; - } else { - self.locals_pop(scope.local, span)?; - } - - Ok(()) - } - /// Get the latest relevant warning context. pub(crate) fn context(&self) -> Option { self.contexts.last().copied() @@ -162,314 +125,496 @@ impl<'a, 'hir, 'arena> Ctxt<'a, 'hir, 'arena> { } } -#[derive(Debug)] -#[must_use = "must be consumed to make sure the value is realized"] -struct Asm<'hir> { - span: Span, - kind: AsmKind<'hir>, +enum Outcome { + Converge(T), + Diverge, } -impl<'hir> Asm<'hir> { - /// Construct an assembly result that leaves the value on the top of the - /// stack. - fn top(span: &dyn Spanned) -> Self { +#[must_use = "Assembly should be checked for convergence to reduce code generation"] +struct Asm<'hir, T = ()> { + span: &'hir dyn Spanned, + outcome: Outcome, +} + +impl<'hir, T> Asm<'hir, T> { + #[inline] + fn new(span: &'hir dyn Spanned, data: T) -> Self { Self { - span: span.span(), - kind: AsmKind::Top, + span, + outcome: Outcome::Converge(data), } } - fn var(span: &dyn Spanned, var: Var<'hir>) -> Self { + #[inline] + fn diverge(span: &'hir dyn Spanned) -> Self { Self { - span: span.span(), - kind: AsmKind::Var(var), + span, + outcome: Outcome::Diverge, } } -} -#[derive(Debug)] -pub(crate) enum AsmKind<'hir> { - // Result is pushed onto the top of the stack. - Top, - // Result belongs to the the given stack offset. - Var(Var<'hir>), + /// Used as to ignore divergence. + #[inline] + fn ignore(self) {} } -impl<'hir> Asm<'hir> { - /// Assemble into an instruction. - fn apply(self, cx: &mut Ctxt) -> compile::Result<()> { - if let AsmKind::Var(var) = self.kind { - var.copy(cx, &self.span, None)?; +impl<'hir, T> Asm<'hir, T> { + /// Test if the assembly converges and return the data associated with it. + #[inline] + fn into_converging(self) -> Option { + match self.outcome { + Outcome::Converge(data) => Some(data), + Outcome::Diverge => None, } + } - Ok(()) + /// Test if the assembly diverges. + #[inline] + fn diverging(self) -> bool { + matches!(self.outcome, Outcome::Diverge) } - /// Assemble into an instruction declaring an anonymous variable if appropriate. - fn apply_targeted(self, cx: &mut Ctxt) -> compile::Result { - let address = match self.kind { - AsmKind::Top => { - cx.scopes.alloc(&self.span)?; - InstAddress::Top - } - AsmKind::Var(var) => InstAddress::Offset(var.offset), - }; + /// Test if the assembly converges. + #[inline] + fn converging(self) -> bool { + matches!(self.outcome, Outcome::Converge(..)) + } +} - Ok(address) +impl fmt::Debug for Asm<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Asm") + .field("span", &self.span.span()) + .finish() } } /// Assemble a function from an [hir::ItemFn<'_>]. #[instrument(span = hir)] -pub(crate) fn fn_from_item_fn<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - hir: &hir::ItemFn<'hir>, +pub(crate) fn fn_from_item_fn<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + hir: &'hir hir::ItemFn<'hir>, instance_fn: bool, ) -> compile::Result<()> { - let mut patterns = Vec::new(); let mut first = true; - for arg in hir.args { + 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) => { if !instance_fn || !first { - return Err(compile::Error::new(*span, ErrorKind::UnsupportedSelf)); + return Err(compile::Error::new(span, ErrorKind::UnsupportedSelf)); } - cx.scopes.define(hir::Name::SelfValue, span)?; + cx.scopes.define(span, hir::Name::SelfValue, needs)?; } hir::FnArg::Pat(pat) => { - let offset = cx.scopes.alloc(pat)?; - patterns.try_push((pat, offset))?; + let asm = pattern_panic(cx, pat, move |cx, false_label| { + fn_arg_pat(cx, pat, needs, false_label) + })?; + + asm.ignore(); } } first = false; } - for (pat, offset) in patterns { - pat_with_offset(cx, pat, offset)?; - } - - if hir.body.statements.is_empty() && hir.body.value.is_none() { - let total_var_count = cx.scopes.total(hir)?; - cx.locals_pop(total_var_count, hir)?; - cx.asm.push(Inst::ReturnUnit, hir)?; - return Ok(()); - } - if hir.body.value.is_some() { - return_(cx, hir, &hir.body, block)?; + return_(cx, hir, &hir.body, block_without_scope)?.ignore(); } else { - block(cx, &hir.body, Needs::None)?.apply(cx)?; + let mut needs = Any::ignore(&hir.body); - let total_var_count = cx.scopes.total(hir)?; - cx.locals_pop(total_var_count, hir)?; - cx.asm.push(Inst::ReturnUnit, hir)?; + if block_without_scope(cx, &hir.body, &mut needs)?.converging() { + cx.asm.push(Inst::ReturnUnit, hir)?; + } } + arguments.free()?; cx.scopes.pop_last(hir)?; Ok(()) } /// Assemble an async block. #[instrument(span = hir.block.span)] -pub(crate) fn async_block_secondary<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +pub(crate) fn async_block_secondary<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::AsyncBlock<'hir>, ) -> compile::Result<()> { - for name in hir.captures.iter().copied() { - cx.scopes.define(name, &hir.block)?; + 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)?; + return_(cx, &hir.block, &hir.block, block_without_scope)?.ignore(); + + linear.free()?; cx.scopes.pop_last(&hir.block)?; Ok(()) } /// Assemble the body of a closure function. #[instrument(span = span)] -pub(crate) fn expr_closure_secondary<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +pub(crate) fn expr_closure_secondary<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprClosure<'hir>, span: &'hir dyn Spanned, ) -> compile::Result<()> { - let mut patterns = Vec::new(); - - for arg in hir.args { - match arg { - hir::FnArg::SelfValue(..) => { - return Err(compile::Error::new(arg, ErrorKind::UnsupportedSelf)) - } - hir::FnArg::Pat(pat) => { - let offset = cx.scopes.alloc(pat)?; - patterns.try_push((pat, offset))?; - } - } - } + let mut arguments = cx.scopes.linear(span, hir.args.len())?; + let environment = cx.scopes.linear(span, hir.captures.len())?; if !hir.captures.is_empty() { cx.asm.push( - Inst::PushEnvironment { + Inst::Environment { + addr: environment.addr(), count: hir.captures.len(), + out: environment.addr().output(), }, span, )?; - for capture in hir.captures.iter().copied() { - cx.scopes.define(capture, span)?; + for (capture, needs) in hir.captures.iter().copied().zip(&environment) { + cx.scopes.define(span, capture, needs)?; } } - for (pat, offset) in patterns { - pat_with_offset(cx, pat, offset)?; + 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, span, &hir.body, expr)?; + return_(cx, span, &hir.body, expr)?.ignore(); + + environment.free()?; + arguments.free()?; cx.scopes.pop_last(span)?; Ok(()) } -/// Assemble a return statement from the given Assemble. -fn return_<'hir, T>( - cx: &mut Ctxt<'_, 'hir, '_>, - span: &dyn Spanned, - hir: T, - asm: impl FnOnce(&mut Ctxt<'_, 'hir, '_>, T, Needs) -> compile::Result>, -) -> compile::Result<()> { - let address = asm(cx, hir, Needs::Value)?.apply_targeted(cx)?; - cx.asm.push(Inst::Return { address }, span)?; +#[instrument(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> { + let Some(addr) = needs.try_as_addr()? else { + return Err(compile::Error::msg( + needs.span(), + "Expected need to be populated outside of pattern", + )); + }; - // Top address produces an anonymous variable, which is consumed by the - // return statement. - if let InstAddress::Top = address { - cx.scopes.free(span, 1)?; - } + let addr = addr.addr(); - Ok(()) + 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) } -/// Compile a pattern based on the given offset. -#[instrument(span = hir)] -fn pat_with_offset<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - hir: &'hir hir::Pat<'hir>, - offset: usize, -) -> compile::Result<()> { - let load = |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - if needs.value() { - cx.asm.push(Inst::Copy { offset }, hir)?; - } +/// Assemble a return statement from the given Assemble. +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>, +) -> compile::Result> { + let mut needs = cx.scopes.defer(span).with_name("return value"); + converge!(asm(cx, hir, &mut needs)?, free(needs)); - Ok(()) - }; + cx.asm.push( + Inst::Return { + addr: needs.addr()?.addr(), + }, + span, + )?; - let false_label = cx.asm.new_label("let_panic"); + needs.free()?; + Ok(Asm::new(span, ())) +} - if pat(cx, hir, &false_label, &load)? { +fn pattern_panic<'a, 'hir, 'arena, F>( + cx: &mut Ctxt<'a, 'hir, 'arena>, + span: &'hir dyn Spanned, + f: F, +) -> compile::Result> +where + F: FnOnce(&mut Ctxt<'a, 'hir, 'arena>, &Label) -> compile::Result>, +{ + 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, hir, cx.context())?; + .let_pattern_might_panic(cx.source_id, span, cx.context())?; - let ok_label = cx.asm.new_label("let_ok"); - cx.asm.jump(&ok_label, hir)?; + 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, }, - hir, + span, )?; - cx.asm.label(&ok_label)?; + cx.asm.label(&match_label)?; } - Ok(()) + Ok(Asm::new(span, ())) +} + +/// Encode a pattern from a known set of bindings. +/// +/// Returns a boolean indicating if the label was used. +#[instrument(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>, +) -> compile::Result> { + 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(span = span)] +fn pat_binding_with<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + span: &'hir dyn Spanned, + pat: &'hir hir::Pat<'hir>, + names: &[hir::Name<'hir>], + false_label: &Label, + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, + linear: &mut [Address<'a, 'hir>], +) -> compile::Result> { + 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(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::Name<'hir>, + false_label: &Label, + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, + needs: &mut dyn Needs<'a, 'hir>, +) -> compile::Result> { + 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 { + fn remove(&mut self, name: &K) -> Option; +} + +impl Bindings for BTreeMap +where + K: Ord, +{ + #[inline] + fn remove(&mut self, name: &K) -> Option { + BTreeMap::remove(self, name) + } +} + +impl Bindings for Option<(K, T)> +where + K: PartialEq, +{ + #[inline] + fn remove(&mut self, name: &K) -> Option { + let (current, value) = self.take()?; + + if current != *name { + *self = Some((current, value)); + return None; + } + + Some(value) + } } /// Encode a pattern. /// /// Returns a boolean indicating if the label was used. #[instrument(span = hir)] -fn pat<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn pat<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::Pat<'hir>, false_label: &Label, - load: &dyn Fn(&mut Ctxt<'_, 'hir, '_>, Needs) -> compile::Result<()>, -) -> compile::Result { + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, + bindings: &mut dyn Bindings, &mut dyn Needs<'a, 'hir>>, +) -> compile::Result> { let span = hir; match hir.kind { hir::PatKind::Ignore => { - // ignore binding, but might still have side effects, so must - // call the load generator. - load(cx, Needs::None)?; - Ok(false) + // ignore binding, but might still have effects, so must call load. + converge!(load(cx, &mut Any::ignore(hir))?); + Ok(Asm::new(span, Pattern::Irrefutable)) } hir::PatKind::Path(kind) => match *kind { hir::PatPathKind::Kind(kind) => { - load(cx, Needs::Value)?; - cx.asm.push(pat_sequence_kind_to_inst(*kind), hir)?; - cx.asm - .pop_and_jump_if_not(cx.scopes.local(hir)?, false_label, hir)?; - Ok(true) + let mut needs = cx.scopes.defer(hir); + converge!(load(cx, &mut needs)?, free(needs)); + + let cond = cx.scopes.alloc(hir)?; + + cx.asm.push( + pat_sequence_kind_to_inst(*kind, needs.addr()?.addr(), cond.output()), + 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) => { - load(cx, Needs::Value)?; - cx.scopes.define(hir::Name::Str(name), hir)?; - Ok(false) + let name = hir::Name::Str(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)?; - Ok(true) - } - hir::PatKind::Object(hir) => { - pat_object(cx, hir, span, false_label, &load)?; - Ok(true) - } + 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), } } /// Assemble a pattern literal. #[instrument(span = hir)] -fn pat_lit<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - hir: &hir::Expr<'_>, +fn pat_lit<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + hir: &'hir hir::Expr<'_>, false_label: &Label, - load: &dyn Fn(&mut Ctxt<'_, 'hir, '_>, Needs) -> compile::Result<()>, -) -> compile::Result { - let Some(inst) = pat_lit_inst(cx, hir)? else { + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, +) -> compile::Result> { + 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)); }; - load(cx, Needs::Value)?; cx.asm.push(inst, hir)?; - cx.asm - .pop_and_jump_if_not(cx.scopes.local(hir)?, false_label, hir)?; - Ok(true) + cx.asm.jump_if_not(cond.addr(), false_label, hir)?; + cond.free()?; + needs.free()?; + Ok(Asm::new(hir, Pattern::Refutable)) } #[instrument(span = hir)] -fn pat_lit_inst<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn pat_lit_inst<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::Expr<'_>, + addr: InstAddress, + cond: InstAddress, ) -> compile::Result> { let hir::ExprKind::Lit(lit) = hir.kind else { return Ok(None); }; + let out = cond.output(); + let inst = match lit { - hir::Lit::Byte(byte) => Inst::EqByte { byte }, - hir::Lit::Char(char) => Inst::EqChar { char }, + hir::Lit::Byte(value) => Inst::EqByte { addr, value, out }, + 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::Integer(integer) => Inst::EqInteger { integer }, - hir::Lit::Bool(boolean) => Inst::EqBool { boolean }, + hir::Lit::Integer(value) => Inst::EqInteger { addr, value, out }, + hir::Lit::Bool(value) => Inst::EqBool { addr, value, out }, _ => return Ok(None), }; @@ -477,53 +622,78 @@ fn pat_lit_inst<'hir>( } /// Assemble an [hir::Condition<'_>]. -#[instrument(span = condition)] -fn condition<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - condition: &hir::Condition<'hir>, +#[instrument(span = hir)] +fn condition<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + hir: &hir::Condition<'hir>, then_label: &Label, -) -> compile::Result> { - match *condition { - hir::Condition::Expr(e) => { - let guard = cx.scopes.child(e)?; - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.asm.jump_if(then_label, e)?; - Ok(cx.scopes.pop(guard, e)?) - } - hir::Condition::ExprLet(expr_let) => { - let span = expr_let; + false_label: &Label, + linear: &mut [Address<'a, 'hir>], +) -> compile::Result> { + match *hir { + hir::Condition::Expr(hir) => { + let scope = cx.scopes.child(hir)?; + let mut addr = cx.scopes.defer(hir); + + let asm = if expr(cx, hir, &mut addr)?.converging() { + cx.asm.jump_if(addr.addr()?.addr(), then_label, hir)?; + Asm::new(hir, (scope, Pattern::Irrefutable)) + } else { + cx.scopes.pop(hir, scope)?; + Asm::diverge(hir) + }; - let false_label = cx.asm.new_label("if_condition_false"); + addr.free()?; + Ok(asm) + } + hir::Condition::ExprLet(hir) => { + let span = hir; - let expected = cx.scopes.child(span)?; + let scope = cx.scopes.child(span)?; - let load = |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - expr(cx, &expr_let.expr, needs)?.apply(cx)?; - Ok(()) + let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| { + expr(cx, &hir.expr, needs) }; - if pat(cx, &expr_let.pat, &false_label, &load)? { + 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)?; - cx.asm.label(&false_label)?; + Ok(Asm::new(span, (scope, pat))) } else { - cx.asm.jump(then_label, span)?; - }; - - Ok(cx.scopes.pop(expected, span)?) + cx.scopes.pop(span, scope)?; + Ok(Asm::diverge(span)) + } } } } /// Encode a vector pattern match. #[instrument(span = span)] -fn pat_sequence<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn pat_sequence<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::PatSequence<'hir>, - span: &dyn Spanned, + span: &'hir dyn Spanned, false_label: &Label, - load: &dyn Fn(&mut Ctxt<'_, 'hir, '_>, Needs) -> compile::Result<()>, -) -> compile::Result<()> { - load(cx, Needs::Value)?; + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, + bindings: &mut dyn Bindings, &mut dyn Needs<'a, 'hir>>, +) -> compile::Result> { + 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, @@ -533,43 +703,53 @@ fn pat_sequence<'hir>( is_open: false } ) { - cx.asm.push(Inst::IsUnit, span)?; - cx.asm - .pop_and_jump_if_not(cx.scopes.local(span)?, false_label, span)?; - return Ok(()); - } - - // Assign the yet-to-be-verified tuple to an anonymous slot, so we can - // interact with it multiple times. - let offset = cx.scopes.alloc(span)?; - - let inst = pat_sequence_kind_to_inst(hir.kind); - - cx.asm.push(Inst::Copy { offset }, span)?; - cx.asm.push(inst, span)?; - - cx.asm - .pop_and_jump_if_not(cx.scopes.local(span)?, false_label, span)?; + cx.asm.push( + Inst::IsUnit { + addr: addr.addr(), + out: cond.output(), + }, + span, + )?; - for (index, p) in hir.items.iter().enumerate() { - let load = move |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - if needs.value() { - cx.asm.push(Inst::TupleIndexGetAt { offset, index }, p)?; - } + 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)?; - Ok(()) - }; + 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, ())) + }; - pat(cx, p, false_label, &load)?; + converge!( + self::pat(cx, p, false_label, &mut load, bindings)?, + free(cond, addr) + ); + } } - Ok(()) + cond.free()?; + addr.free()?; + Ok(Asm::new(span, Pattern::Refutable)) } -fn pat_sequence_kind_to_inst(kind: hir::PatSequenceKind) -> Inst { +fn pat_sequence_kind_to_inst(kind: hir::PatSequenceKind, addr: InstAddress, out: Output) -> Inst { match kind { - hir::PatSequenceKind::Type { hash } => Inst::MatchType { hash }, - hir::PatSequenceKind::BuiltInVariant { type_check } => Inst::MatchBuiltIn { type_check }, + 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, @@ -578,6 +758,8 @@ fn pat_sequence_kind_to_inst(kind: hir::PatSequenceKind) -> Inst { variant_hash, enum_hash, index, + addr, + out, }, hir::PatSequenceKind::Anonymous { type_check, @@ -587,24 +769,30 @@ fn pat_sequence_kind_to_inst(kind: hir::PatSequenceKind) -> Inst { type_check, len: count, exact: !is_open, + addr, + out, }, } } /// Assemble an object pattern. #[instrument(span = span)] -fn pat_object<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn pat_object<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::PatObject<'hir>, - span: &dyn Spanned, + span: &'hir dyn Spanned, false_label: &Label, - load: &dyn Fn(&mut Ctxt<'_, 'hir, '_>, Needs) -> compile::Result<()>, -) -> compile::Result<()> { - // NB: bind the loaded variable (once) to an anonymous var. - // We reduce the number of copy operations by having specialized - // operations perform the load from the given offset. - load(cx, Needs::Value)?; - let offset = cx.scopes.alloc(span)?; + load: &mut dyn FnMut( + &mut Ctxt<'a, 'hir, '_>, + &mut dyn Needs<'a, 'hir>, + ) -> compile::Result>, + bindings: &mut dyn Bindings, &mut dyn Needs<'a, 'hir>>, +) -> compile::Result> { + 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(); @@ -613,8 +801,16 @@ fn pat_object<'hir>( } let inst = match hir.kind { - hir::PatSequenceKind::Type { hash } => Inst::MatchType { hash }, - hir::PatSequenceKind::BuiltInVariant { type_check } => Inst::MatchBuiltIn { type_check }, + 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, @@ -623,6 +819,8 @@ fn pat_object<'hir>( variant_hash, enum_hash, index, + addr: addr.addr(), + out: cond.output(), }, hir::PatSequenceKind::Anonymous { is_open, .. } => { let keys = @@ -632,98 +830,158 @@ fn pat_object<'hir>( Inst::MatchObject { slot: keys, exact: !is_open, + addr: addr.addr(), + out: cond.output(), } } }; // Copy the temporary and check that its length matches the pattern and // that it is indeed a vector. - cx.asm.push(Inst::Copy { offset }, span)?; cx.asm.push(inst, span)?; - - cx.asm - .pop_and_jump_if_not(cx.scopes.local(span)?, false_label, 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 { + match binding { hir::Binding::Binding(span, _, p) => { - let load = move |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - if needs.value() { - cx.asm - .push(Inst::ObjectIndexGetAt { offset, slot }, &span)?; - } + 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) => { + let name = hir::Name::Str(name); - Ok(()) + let Some(binding) = bindings.remove(&name) else { + return Err(compile::Error::msg( + binding, + format!("No binding for {name:?}"), + )); }; - pat(cx, p, false_label, &load)?; - } - hir::Binding::Ident(span, name) => { - cx.asm - .push(Inst::ObjectIndexGetAt { offset, slot }, &span)?; - cx.scopes.define(hir::Name::Str(name), binding)?; + cx.asm.push( + Inst::ObjectIndexGetAt { + addr: addr.addr(), + slot, + out: binding.output()?, + }, + &span, + )?; } } } - Ok(()) + needs.free()?; + Ok(Asm::new(span, Pattern::Refutable)) } /// Call a block. #[instrument(span = hir)] -fn block<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - hir: &hir::Block<'hir>, - needs: Needs, +fn block<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + hir: &'hir hir::Block<'hir>, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - cx.contexts.try_push(hir.span())?; - let scopes_count = cx.scopes.child(hir)?; - - for stmt in hir.statements { - match stmt { - hir::Stmt::Local(hir) => { - local(cx, hir, Needs::None)?.apply(cx)?; - } - hir::Stmt::Expr(hir) => { - expr(cx, hir, Needs::None)?.apply(cx)?; - } - } - } - - let produced = if let Some(e) = hir.value { - expr(cx, e, needs)?.apply(cx)?; - true + 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 { - false + None }; - let scope = cx.scopes.pop(scopes_count, hir)?; + let scope = cx.scopes.child(hir)?; + let asm = block_without_scope(cx, hir, needs)?; + cx.scopes.pop(hir, scope)?; - if needs.value() { - if produced { - cx.locals_clean(scope.local, hir)?; - } else { - cx.locals_pop(scope.local, hir)?; - cx.asm.push(Inst::unit(), hir)?; - } - } else { - cx.locals_pop(scope.local, hir)?; + if let Some(break_label) = break_label { + cx.asm.label(&break_label)?; + cx.breaks.pop(); } - cx.contexts - .pop() - .ok_or("Missing parent context") - .with_span(hir)?; - - Ok(Asm::top(hir)) + Ok(asm) } -/// Assemble #[builtin] format_args!(...) macro. -#[instrument(span = format)] -fn builtin_format<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - format: &'hir hir::BuiltInFormat<'hir>, - needs: Needs, +/// Call a block. +#[instrument(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> { + let mut diverge = None; + cx.contexts.try_push(hir.span())?; + + 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, ())) +} + +/// Assemble #[builtin] format_args!(...) macro. +#[instrument(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> { use crate::runtime::format; @@ -736,112 +994,139 @@ fn builtin_format<'hir>( let spec = format::FormatSpec::new(flags, fill, align, width, precision, format_type); - expr(cx, &format.value, Needs::Value)?.apply(cx)?; - cx.asm.push(Inst::Format { spec }, format)?; + converge!(expr(cx, &format.value, needs)?); - if !needs.value() { - cx.asm.push(Inst::Pop, format)?; + if let Some(addr) = needs.try_alloc_addr()? { + cx.asm.push( + Inst::Format { + addr: addr.addr(), + spec, + out: addr.output(), + }, + format, + )?; } - Ok(Asm::top(format)) + Ok(Asm::new(format, ())) } /// Assemble #[builtin] template!(...) macro. -#[instrument(span = template)] -fn builtin_template<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - template: &hir::BuiltInTemplate<'hir>, - needs: Needs, +#[instrument(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> { - let span = template; + let span = hir; - let expected = cx.scopes.child(span)?; let mut size_hint = 0; let mut expansions = 0; - for hir in template.exprs { + 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 }, span)?; - cx.scopes.alloc(span)?; + cx.asm.push( + Inst::String { + slot, + out: addr.output(), + }, + span, + )?; + continue; } expansions += 1; - expr(cx, hir, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; + if expr(cx, hir, addr)?.diverging() { + converge = false; + break; + } } - if template.from_literal && expansions == 0 { + if hir.from_literal && expansions == 0 { cx.q.diagnostics .template_without_expansions(cx.source_id, span, cx.context())?; } - cx.asm.push( - Inst::StringConcat { - len: template.exprs.len(), - size_hint, - }, - span, - )?; - - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; + if converge { + cx.asm.push( + Inst::StringConcat { + addr: linear.addr(), + len: hir.exprs.len(), + size_hint, + out: needs.alloc_output()?, + }, + span, + )?; } - let _ = cx.scopes.pop(expected, span)?; - Ok(Asm::top(span)) + linear.free()?; + + if converge { + Ok(Asm::new(span, ())) + } else { + Ok(Asm::diverge(span)) + } } /// Assemble a constant value. #[instrument(span = span)] -fn const_<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn const_<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, value: &ConstValue, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result<()> { - if !needs.value() { + let Some(addr) = needs.try_alloc_addr()? else { cx.q.diagnostics .not_used(cx.source_id, span, cx.context())?; return Ok(()); - } + }; - match value { + let out = addr.output(); + + match *value { ConstValue::EmptyTuple => { - cx.asm.push(Inst::unit(), span)?; + cx.asm.push(Inst::unit(out), span)?; } - ConstValue::Byte(b) => { - cx.asm.push(Inst::byte(*b), span)?; + ConstValue::Byte(v) => { + cx.asm.push(Inst::byte(v, out), span)?; } - ConstValue::Char(ch) => { - cx.asm.push(Inst::char(*ch), span)?; + ConstValue::Char(v) => { + cx.asm.push(Inst::char(v, out), span)?; } - ConstValue::Integer(n) => { - cx.asm.push(Inst::integer(*n), span)?; + ConstValue::Integer(v) => { + cx.asm.push(Inst::integer(v, out), span)?; } - ConstValue::Float(n) => { - cx.asm.push(Inst::float(*n), span)?; + ConstValue::Float(v) => { + cx.asm.push(Inst::float(v, out), span)?; } - ConstValue::Bool(b) => { - cx.asm.push(Inst::bool(*b), span)?; + ConstValue::Bool(v) => { + cx.asm.push(Inst::bool(v, out), span)?; } - ConstValue::String(s) => { + ConstValue::String(ref s) => { let slot = cx.q.unit.new_static_string(span, s)?; - cx.asm.push(Inst::String { slot }, span)?; + cx.asm.push(Inst::String { slot, out }, span)?; } - ConstValue::Bytes(b) => { + ConstValue::Bytes(ref b) => { let slot = cx.q.unit.new_static_bytes(span, b)?; - cx.asm.push(Inst::Bytes { slot }, span)?; + cx.asm.push(Inst::Bytes { slot, out }, span)?; } - ConstValue::Option(option) => match option { + ConstValue::Option(ref option) => match option { Some(value) => { - const_(cx, value, span, Needs::Value)?; + const_(cx, value, span, addr)?; + cx.asm.push( Inst::Variant { variant: InstVariant::Some, + addr: addr.addr(), + out, }, span, )?; @@ -850,38 +1135,73 @@ fn const_<'hir>( cx.asm.push( Inst::Variant { variant: InstVariant::None, + addr: addr.addr(), + out, }, span, )?; } }, - ConstValue::Vec(vec) => { - for value in vec.iter() { - const_(cx, value, span, Needs::Value)?; + ConstValue::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 { count: vec.len() }, span)?; + cx.asm.push( + Inst::Vec { + addr: linear.addr(), + count: vec.len(), + out, + }, + span, + )?; + + linear.free()?; } - ConstValue::Tuple(tuple) => { - for value in tuple.iter() { - const_(cx, value, span, Needs::Value)?; + ConstValue::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 { count: tuple.len() }, span)?; + cx.asm.push( + Inst::Tuple { + addr: linear.addr(), + count: tuple.len(), + out, + }, + span, + )?; + + linear.free()?; } - ConstValue::Object(object) => { + ConstValue::Object(ref object) => { + let mut linear = cx.scopes.linear(span, object.len())?; + let mut entries = object.iter().try_collect::>()?; entries.sort_by_key(|k| k.0); - for (_, value) in entries.iter().copied() { - const_(cx, value, span, Needs::Value)?; + 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 { slot }, span)?; + cx.asm.push( + Inst::Object { + addr: linear.addr(), + slot, + out, + }, + span, + )?; + + linear.free()?; } } @@ -890,30 +1210,38 @@ fn const_<'hir>( /// Assemble an expression. #[instrument(span = hir)] -fn expr<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::Expr<'hir>, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { let span = hir; let asm = match hir.kind { hir::ExprKind::Variable(name) => { - let var = cx.scopes.get(&mut cx.q, name, span)?; - Asm::var(span, var) + let var = cx.scopes.get(&mut cx.q, span, name)?; + needs.assign_addr(cx, var.addr)?; + Asm::new(span, ()) } hir::ExprKind::Type(ty) => { - cx.asm.push( - Inst::Push { - value: InstValue::Type(ty), - }, - span, - )?; - Asm::top(span) + 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) => { - cx.asm.push(Inst::LoadFn { hash }, span)?; - Asm::top(span) + 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)?, @@ -924,11 +1252,11 @@ fn expr<'hir>( 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, 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, 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)?, @@ -958,67 +1286,98 @@ fn expr<'hir>( /// Assemble an assign expression. #[instrument(span = span)] -fn expr_assign<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_assign<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprAssign<'hir>, span: &'hir dyn Spanned, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { let supported = match hir.lhs.kind { // = hir::ExprKind::Variable(name) => { - expr(cx, &hir.rhs, Needs::Value)?.apply(cx)?; - let var = cx.scopes.get(&mut cx.q, name, span)?; - cx.asm.push_with_comment( - Inst::Replace { offset: var.offset }, - span, - &format_args!("var `{var}`"), - )?; + 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)], + )?; + // field assignment match field_access.expr_field { hir::ExprField::Ident(ident) => { - let slot = cx.q.unit.new_static_string(span, ident)?; - - expr(cx, &hir.rhs, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(&hir.rhs)?; - - expr(cx, &field_access.expr, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; - - cx.asm.push(Inst::ObjectIndexSet { slot }, span)?; - cx.scopes.free(span, 2)?; - true + 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) => { - expr(cx, &hir.rhs, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(&hir.rhs)?; - - expr(cx, &field_access.expr, Needs::Value)?.apply(cx)?; - cx.asm.push(Inst::TupleIndexSet { index }, span)?; - cx.scopes.free(span, 1)?; - true + 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) => { - expr(cx, &hir.rhs, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; + 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); - expr(cx, &expr_index_get.target, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; + let asm = expr_array( + cx, + span, + [ + (&expr_index_get.target, &mut target), + (&expr_index_get.index, &mut index), + (&hir.rhs, &mut value), + ], + )?; - expr(cx, &expr_index_get.index, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; + if let Some([target, index, value]) = asm.into_converging() { + cx.asm.push( + Inst::IndexSet { + target: target.addr(), + index: index.addr(), + value: value.addr(), + }, + span, + )?; + } - cx.asm.push(Inst::IndexSet, span)?; - cx.scopes.free(span, 3)?; + value.free()?; + index.free()?; + target.free()?; true } _ => false, @@ -1028,57 +1387,53 @@ fn expr_assign<'hir>( return Err(compile::Error::new(span, ErrorKind::UnsupportedAssignExpr)); } - if needs.value() { - cx.asm.push(Inst::unit(), span)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), span)?; } - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble an `.await` expression. #[instrument(span = hir)] -fn expr_await<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_await<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::Expr<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - expr(cx, hir, Needs::Value)?.apply(cx)?; - cx.asm.push(Inst::Await, span)?; + let mut addr = cx.scopes.defer(span); + converge!(expr(cx, hir, &mut addr)?, free(addr)); - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; - } + cx.asm.push( + Inst::Await { + addr: addr.addr()?.addr(), + out: needs.alloc_output()?, + }, + span, + )?; - Ok(Asm::top(span)) + addr.free()?; + Ok(Asm::new(span, ())) } /// Assemble a binary expression. #[instrument(span = span)] -fn expr_binary<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_binary<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprBinary<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { // Special expressions which operates on the stack in special ways. if hir.op.is_assign() { - compile_assign_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs)?; - return Ok(Asm::top(span)); + return compile_assign_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs); } if hir.op.is_conditional() { - compile_conditional_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs)?; - return Ok(Asm::top(span)); + return compile_conditional_binop(cx, &hir.lhs, &hir.rhs, &hir.op, span, needs); } - let guard = cx.scopes.child(span)?; - - // NB: need to declare these as anonymous local variables so that they - // get cleaned up in case there is an early break (return, try, ...). - let a = expr(cx, &hir.lhs, Needs::Value)?.apply_targeted(cx)?; - let b = expr(cx, &hir.rhs, Needs::Value)?.apply_targeted(cx)?; - let op = match hir.op { ast::BinOp::Eq(..) => InstOp::Eq, ast::BinOp::Neq(..) => InstOp::Neq, @@ -1110,162 +1465,184 @@ fn expr_binary<'hir>( } }; - cx.asm.push(Inst::Op { op, a, b }, span)?; + let mut a = cx.scopes.defer(span); + let mut b = cx.scopes.defer(span); - // NB: we put it here to preserve the call in case it has side effects. - // But if we don't need the value, then pop it from the stack. - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; - } + let asm = expr_array(cx, span, [(&hir.lhs, &mut a), (&hir.rhs, &mut b)])?; - cx.scopes.pop(guard, span)?; - return Ok(Asm::top(span)); + if let Some([a, b]) = asm.into_converging() { + cx.asm.push( + Inst::Op { + op, + a: a.addr(), + b: b.addr(), + out: needs.alloc_output()?, + }, + span, + )?; + } - fn compile_conditional_binop<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - lhs: &'hir hir::Expr<'hir>, - rhs: &'hir hir::Expr<'hir>, - bin_op: &ast::BinOp, - span: &dyn Spanned, - needs: Needs, - ) -> compile::Result<()> { - let end_label = cx.asm.new_label("conditional_end"); + a.free()?; + b.free()?; + Ok(Asm::new(span, ())) +} - expr(cx, lhs, Needs::Value)?.apply(cx)?; +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> { + let end_label = cx.asm.new_label("conditional_end"); + converge!(expr(cx, lhs, needs)?); + let addr = needs.addr()?; - match bin_op { - ast::BinOp::And(..) => { - cx.asm.jump_if_not_or_pop(&end_label, lhs)?; - } - ast::BinOp::Or(..) => { - cx.asm.jump_if_or_pop(&end_label, lhs)?; - } - op => { - return Err(compile::Error::new( - span, - ErrorKind::UnsupportedBinaryOp { op: *op }, - )); - } + 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::Value)?.apply(cx)?; - - cx.asm.label(&end_label)?; + // rhs needs to be ignored since it might be jumped over. + expr(cx, rhs, needs)?.ignore(); + cx.asm.label(&end_label)?; + Ok(Asm::new(span, ())) +} - if !needs.value() { - cx.asm.push(Inst::Pop, 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> { + let supported = match lhs.kind { + // + hir::ExprKind::Variable(name) => { + let var = cx.scopes.get(&mut cx.q, lhs, name)?; + Some(InstTarget::Address(var.addr)) } + // . + hir::ExprKind::FieldAccess(field_access) => { + let mut field = cx.scopes.defer(&field_access.expr); + converge!(expr(cx, &field_access.expr, &mut field)?, free(field)); + let field = field.into_addr()?; - Ok(()) - } - - fn compile_assign_binop<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - lhs: &'hir hir::Expr<'hir>, - rhs: &'hir hir::Expr<'hir>, - bin_op: &ast::BinOp, - span: &dyn Spanned, - needs: Needs, - ) -> compile::Result<()> { - let supported = match lhs.kind { - // - hir::ExprKind::Variable(name) => { - expr(cx, rhs, Needs::Value)?.apply(cx)?; - let var = cx.scopes.get(&mut cx.q, name, lhs)?; - Some(InstTarget::Offset(var.offset)) - } - // . - hir::ExprKind::FieldAccess(field_access) => { - expr(cx, &field_access.expr, Needs::Value)?.apply(cx)?; - expr(cx, rhs, Needs::Value)?.apply(cx)?; - - // field assignment - match field_access.expr_field { - hir::ExprField::Index(index) => Some(InstTarget::TupleField(index)), - hir::ExprField::Ident(ident) => { - let n = cx.q.unit.new_static_string(&field_access.expr, ident)?; - Some(InstTarget::Field(n)) - } - _ => { - return Err(compile::Error::new(span, ErrorKind::BadFieldAccess)); - } + // field assignment + let output = match field_access.expr_field { + hir::ExprField::Index(index) => Some(InstTarget::TupleField(field.addr(), index)), + hir::ExprField::Ident(ident) => { + let n = cx.q.unit.new_static_string(&field_access.expr, ident)?; + Some(InstTarget::Field(field.addr(), n)) } - } - _ => None, - }; + _ => { + return Err(compile::Error::new(span, ErrorKind::BadFieldAccess)); + } + }; + + field.free()?; + output + } + _ => None, + }; - let Some(target) = supported else { + let Some(target) = supported else { + return Err(compile::Error::new(span, ErrorKind::UnsupportedBinaryExpr)); + }; + + let op = match bin_op { + ast::BinOp::AddAssign(..) => InstAssignOp::Add, + ast::BinOp::SubAssign(..) => InstAssignOp::Sub, + ast::BinOp::MulAssign(..) => InstAssignOp::Mul, + ast::BinOp::DivAssign(..) => InstAssignOp::Div, + ast::BinOp::RemAssign(..) => InstAssignOp::Rem, + ast::BinOp::BitAndAssign(..) => InstAssignOp::BitAnd, + ast::BinOp::BitXorAssign(..) => InstAssignOp::BitXor, + ast::BinOp::BitOrAssign(..) => InstAssignOp::BitOr, + ast::BinOp::ShlAssign(..) => InstAssignOp::Shl, + ast::BinOp::ShrAssign(..) => InstAssignOp::Shr, + _ => { return Err(compile::Error::new(span, ErrorKind::UnsupportedBinaryExpr)); - }; + } + }; - let op = match bin_op { - ast::BinOp::AddAssign(..) => InstAssignOp::Add, - ast::BinOp::SubAssign(..) => InstAssignOp::Sub, - ast::BinOp::MulAssign(..) => InstAssignOp::Mul, - ast::BinOp::DivAssign(..) => InstAssignOp::Div, - ast::BinOp::RemAssign(..) => InstAssignOp::Rem, - ast::BinOp::BitAndAssign(..) => InstAssignOp::BitAnd, - ast::BinOp::BitXorAssign(..) => InstAssignOp::BitXor, - ast::BinOp::BitOrAssign(..) => InstAssignOp::BitOr, - ast::BinOp::ShlAssign(..) => InstAssignOp::Shl, - ast::BinOp::ShrAssign(..) => InstAssignOp::Shr, - _ => { - return Err(compile::Error::new(span, ErrorKind::UnsupportedBinaryExpr)); - } - }; + let mut value = cx.scopes.defer(rhs); - cx.asm.push(Inst::Assign { target, op }, span)?; + if expr(cx, rhs, &mut value)?.converging() { + cx.asm.push( + Inst::Assign { + target, + op, + value: value.addr()?.addr(), + }, + span, + )?; - if needs.value() { - cx.asm.push(Inst::unit(), span)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), span)?; } - - Ok(()) } + + value.free()?; + Ok(Asm::new(span, ())) } /// Assemble a block expression. #[instrument(span = span)] -fn expr_async_block<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_async_block<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprAsyncBlock<'hir>, span: &'hir dyn Spanned, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - for capture in hir.captures.iter().copied() { + 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, capture, span)?; - var.do_move(cx.asm, span, Some(&"capture"))?; + 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, capture, span)?; - var.copy(cx, span, Some(&"capture"))?; + 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", )?; - if !needs.value() { - cx.asm - .push_with_comment(Inst::Pop, span, &"value is not needed")?; - } - - Ok(Asm::top(span)) + linear.free()?; + Ok(Asm::new(span, ())) } /// Assemble a constant item. #[instrument(span = span)] -fn const_item<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn const_item<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hash: Hash, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { let Some(const_value) = cx.q.get_const_value(hash) else { return Err(compile::Error::msg( @@ -1276,130 +1653,132 @@ fn const_item<'hir>( let const_value = const_value.try_clone().with_span(span)?; const_(cx, &const_value, span, needs)?; - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a break expression. /// /// NB: loops are expected to produce a value at the end of their expression. #[instrument(span = span)] -fn expr_break<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_break<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprBreak<'hir>, - span: &dyn Spanned, - _: Needs, + span: &'hir dyn Spanned, ) -> compile::Result> { - let Some(current_loop) = cx.loops.last().try_cloned()? else { - return Err(compile::Error::new(span, ErrorKind::BreakOutsideOfLoop)); - }; + 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)); + }; - let (last_loop, to_drop, has_value) = match (hir.label, hir.expr) { - (None, Some(e)) => { - expr(cx, e, current_loop.needs)?.apply(cx)?; - let to_drop = current_loop.drop.into_iter().try_collect()?; - (current_loop, to_drop, true) - } - (Some(label), None) => { - let (last_loop, to_drop) = cx.loops.walk_until_label(label, span)?; - (last_loop.try_clone()?, to_drop, false) - } - (Some(label), Some(e)) => { - expr(cx, e, current_loop.needs)?.apply(cx)?; - let (last_loop, to_drop) = cx.loops.walk_until_label(label, span)?; - (last_loop.try_clone()?, to_drop, true) - } - (None, None) => { - let to_drop = current_loop.drop.into_iter().try_collect()?; - (current_loop, to_drop, false) + cx.drop.clear(); + cx.drop.try_extend(l.drop).with_span(span)?; + (l.break_label.try_clone()?, l.output) } }; - // Drop loop temporaries. Typically an iterator. - for offset in to_drop { - cx.asm.push(Inst::Drop { offset }, span)?; - } + if let Some(hir) = hir.expr { + let Some(output) = output else { + return Err(compile::Error::new(span, ErrorKind::BreakUnsupportedValue)); + }; - let vars = cx - .scopes - .total(span)? - .checked_sub(last_loop.break_var_count) - .ok_or("Var count should be larger") - .with_span(span)?; + let mut needs = match output.as_addr() { + Some(addr) => Any::assigned(span, cx.scopes, addr), + None => Any::ignore(span), + }; - if last_loop.needs.value() { - if has_value { - cx.locals_clean(vars, span)?; - } else { - cx.locals_pop(vars, span)?; - cx.asm.push(Inst::unit(), span)?; - } - } else { - cx.locals_pop(vars, span)?; + converge!(expr(cx, hir, &mut needs)?, free(needs)); + needs.free()?; } - cx.asm.jump(&last_loop.break_label, span)?; - Ok(Asm::top(span)) + // Drop loop temporaries. + for addr in cx.drop.drain(..) { + cx.asm.push(Inst::Drop { addr }, span)?; + } + + cx.asm.jump(&break_label, span)?; + Ok(Asm::diverge(span)) } /// Assemble a call expression. #[instrument(span = span)] -fn expr_call<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_call<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprCall<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { let args = hir.args.len(); match hir.call { hir::Call::Var { name, .. } => { - let var = cx.scopes.get(&mut cx.q, name, span)?; + let linear = converge!(exprs(cx, span, hir.args)?); - for e in hir.args { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; - } + let var = cx.scopes.get(&mut cx.q, span, name)?; - var.copy(cx, span, Some(&"call"))?; - cx.scopes.alloc(span)?; - - cx.asm.push(Inst::CallFn { args }, span)?; + cx.asm.push( + Inst::CallFn { + function: var.addr, + addr: linear.addr(), + args: hir.args.len(), + out: needs.alloc_output()?, + }, + span, + )?; - cx.scopes.free(span, hir.args.len() + 1)?; + linear.free()?; } hir::Call::Associated { target, hash } => { - expr(cx, target, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(target)?; + let linear = converge!(exprs_2(cx, span, slice::from_ref(target), hir.args)?); - for e in hir.args { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; - } + cx.asm.push( + Inst::CallAssociated { + hash, + addr: linear.addr(), + args: args + 1, + out: needs.alloc_output()?, + }, + span, + )?; - cx.asm.push(Inst::CallAssociated { hash, args }, span)?; - cx.scopes.free(span, hir.args.len() + 1)?; + linear.free()?; } hir::Call::Meta { hash } => { - for e in hir.args { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; - } + 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, + )?; - cx.asm.push(Inst::Call { hash, args }, span)?; - cx.scopes.free(span, args)?; + linear.free()?; } hir::Call::Expr { expr: e } => { - for e in hir.args { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; - } - - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(span)?; + 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 { args }, span)?; + cx.asm.push( + Inst::CallFn { + function: function.addr()?.addr(), + addr: linear.addr(), + args: hir.args.len(), + out: needs.alloc_output()?, + }, + span, + )?; - cx.scopes.free(span, args + 1)?; + linear.free()?; + function.free()?; } hir::Call::ConstFn { from_module, @@ -1408,94 +1787,185 @@ fn expr_call<'hir>( } => { 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::Value)?; + const_(cx, &value, span, needs)?; } } - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; + Ok(Asm::new(span, ())) +} + +/// Assemble an array of expressions. +#[instrument(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; 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(span = span)] +fn exprs<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, + span: &'hir dyn Spanned, + args: &'hir [hir::Expr<'hir>], +) -> compile::Result>> { + exprs_2(cx, span, args, &[]) +} + +#[instrument(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, +) -> compile::Result>> { + exprs_2_with(cx, span, args, &[], map) +} + +/// Assemble a linear sequence of expressions. +#[instrument(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>> { + 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, +) -> compile::Result>> { + let mut linear; + + match (a, b) { + ([], []) => { + linear = Linear::empty(); + } + ([e], []) | ([], [e]) => { + let e = map(e); + let mut needs = cx.scopes.defer(e); + converge!(expr(cx, e, &mut needs)?, free(needs)); + linear = Linear::single(needs.into_addr()?); + } + _ => { + 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::top(span)) + Ok(Asm::new(span, linear)) } /// Assemble a closure expression. #[instrument(span = span)] -fn expr_call_closure<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_call_closure<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprCallClosure<'hir>, span: &'hir dyn Spanned, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - if !needs.value() { + let Some(out) = needs.try_alloc_output()? else { cx.q.diagnostics .not_used(cx.source_id, span, cx.context())?; - return Ok(Asm::top(span)); - } + return Ok(Asm::new(span, ())); + }; tracing::trace!(?hir.captures, "assemble call closure"); + let linear = cx.scopes.linear(span, hir.captures.len())?; + // Construct a closure environment. - for capture in hir.captures.iter().copied() { + 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, capture, span)?; - var.do_move(cx.asm, span, Some(&"capture"))?; + 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, capture, span)?; - var.copy(cx, span, Some(&"capture"))?; + 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, )?; - Ok(Asm::top(span)) + linear.free()?; + Ok(Asm::new(span, ())) } /// Assemble a continue expression. #[instrument(span = span)] -fn expr_continue<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_continue<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprContinue<'hir>, - span: &dyn Spanned, - _: Needs, + span: &'hir dyn Spanned, + _: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let Some(current_loop) = cx.loops.last().try_cloned()? else { - return Err(compile::Error::new(span, ErrorKind::ContinueOutsideOfLoop)); - }; - let last_loop = if let Some(label) = hir.label { - let (last_loop, _) = cx.loops.walk_until_label(label, span)?; - last_loop.try_clone()? + 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 vars = cx - .scopes - .total(span)? - .checked_sub(last_loop.continue_var_count) - .ok_or("Var count should be larger") - .with_span(span)?; - - cx.locals_pop(vars, span)?; + let Some(label) = &last_loop.continue_label else { + return Err(compile::Error::new( + span, + ErrorKind::ContinueUnsupportedBlock, + )); + }; - cx.asm.jump(&last_loop.continue_label, span)?; - Ok(Asm::top(span)) + cx.asm.jump(label, span)?; + Ok(Asm::new(span, ())) } /// Assemble an expr field access, like `.`. #[instrument(span = span)] -fn expr_field_access<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_field_access<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprFieldAccess<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { // Optimizations! // @@ -1505,114 +1975,106 @@ fn expr_field_access<'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, name, span)?; + let var = cx.scopes.get(&mut cx.q, span, name)?; cx.asm.push_with_comment( Inst::TupleIndexGetAt { - offset: var.offset, + addr: var.addr, index, + out: needs.alloc_output()?, }, span, &var, )?; - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; - } - - return Ok(Asm::top(span)); + return Ok(Asm::new(span, ())); } - expr(cx, &hir.expr, Needs::Value)?.apply(cx)?; + let mut addr = cx.scopes.defer(span); - match hir.expr_field { - hir::ExprField::Index(index) => { - cx.asm.push(Inst::TupleIndexGet { index }, span)?; + if expr(cx, &hir.expr, &mut addr)?.converging() { + let addr = addr.addr()?; - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; + 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)?; - Ok(Asm::top(span)) - } - hir::ExprField::Ident(field) => { - let slot = cx.q.unit.new_static_string(span, field)?; - - cx.asm.push(Inst::ObjectIndexGet { slot }, span)?; - - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; + cx.asm.push( + Inst::ObjectIndexGetAt { + addr: addr.addr(), + slot, + out: needs.alloc_output()?, + }, + span, + )?; } - - Ok(Asm::top(span)) + _ => return Err(compile::Error::new(span, ErrorKind::BadFieldAccess)), } - _ => Err(compile::Error::new(span, ErrorKind::BadFieldAccess)), } + + addr.free()?; + Ok(Asm::new(span, ())) } /// Assemble an expression for loop. #[instrument(span = span)] -fn expr_for<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_for<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprFor<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { + 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 break_var_count = cx.scopes.total(span)?; - - let (iter_offset, loop_scope_expected) = { - let loop_scope_expected = cx.scopes.child(span)?; - expr(cx, &hir.iter, Needs::Value)?.apply(cx)?; + // Variables. + 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"); - let iter_offset = cx.scopes.alloc(span)?; - - cx.asm.push_with_comment( - Inst::CallAssociated { - hash: *Protocol::INTO_ITER, - args: 0, - }, - &hir.iter, - &format_args!("into_iter (offset: {})", iter_offset), - )?; - - (iter_offset, loop_scope_expected) - }; - - // Declare named loop variable. - let binding_offset = { - cx.asm.push(Inst::unit(), &hir.iter)?; - cx.scopes.alloc(&hir.binding)? - }; + cx.asm.push_with_comment( + Inst::CallAssociated { + addr: iter.addr(), + hash: *Protocol::INTO_ITER, + args: 1, + out: into_iter.output(), + }, + &hir.iter, + &"Protocol::INTO_ITER", + )?; // Declare storage for memoized `next` instance fn. let next_offset = if cx.options.memoize_instance_fn { - let offset = cx.scopes.alloc(&hir.iter)?; - - // Declare the named loop variable and put it in the scope. - cx.asm.push_with_comment( - Inst::Copy { - offset: iter_offset, - }, - &hir.iter, - &"copy iterator (memoize)", - )?; + 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, + out: offset.output(), }, &hir.iter, - &"load instance fn (memoize)", + &"Protocol::NEXT", )?; Some(offset) @@ -1620,368 +2082,450 @@ fn expr_for<'hir>( None }; - let continue_var_count = cx.scopes.total(span)?; cx.asm.label(&continue_label)?; - cx.loops.push(Loop { + cx.breaks.push(Break { label: hir.label, - continue_label: continue_label.try_clone()?, - continue_var_count, + continue_label: Some(continue_label.try_clone()?), break_label: break_label.try_clone()?, - break_var_count, - needs, - drop: Some(iter_offset), + output: None, + drop: Some(into_iter.addr()), })?; // Use the memoized loop variable. - if let Some(next_offset) = next_offset { - cx.asm.push_with_comment( - Inst::Copy { - offset: iter_offset, - }, - &hir.iter, - &"copy iterator", - )?; - - cx.asm.push_with_comment( - Inst::Copy { - offset: next_offset, - }, - &hir.iter, - &"copy next", - )?; - - cx.asm.push(Inst::CallFn { args: 1 }, span)?; - + if let Some(next_offset) = &next_offset { cx.asm.push( - Inst::Replace { - offset: binding_offset, + Inst::CallFn { + function: next_offset.addr(), + addr: into_iter.addr(), + args: 1, + out: binding.output(), }, - &hir.binding, + span, )?; } else { - // call the `next` function to get the next level of iteration, bind the - // result to the loop variable in the loop. - cx.asm.push( - Inst::Copy { - offset: iter_offset, - }, - &hir.iter, - )?; - cx.asm.push_with_comment( Inst::CallAssociated { + addr: into_iter.addr(), hash: *Protocol::NEXT, - args: 0, + args: 1, + out: binding.output(), }, span, - &"next", - )?; - - cx.asm.push( - Inst::Replace { - offset: binding_offset, - }, - &hir.binding, + &"Protocol::NEXT", )?; } // Test loop condition and unwrap the option, or jump to `end_label` if the current value is `None`. - cx.asm.iter_next(binding_offset, &end_label, &hir.binding)?; + cx.asm + .iter_next(binding.addr(), &end_label, &hir.binding, binding.output())?; - let guard = cx.scopes.child(&hir.body)?; + let inner_loop_scope = cx.scopes.child(&hir.body)?; + let mut bindings = cx.scopes.linear(&hir.binding, hir.binding.names.len())?; - pat_with_offset(cx, &hir.binding, binding_offset)?; + 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, + ) + })?; - block(cx, &hir.body, Needs::None)?.apply(cx)?; - cx.clean_last_scope(span, guard, Needs::None)?; + 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.jump(&continue_label, span)?; cx.asm.label(&end_label)?; - // Drop the iterator. + // NB: Dropping has to happen before the break label. When breaking, + // the break statement is responsible for ensuring that active + // iterators are dropped. cx.asm.push( Inst::Drop { - offset: iter_offset, + addr: into_iter.addr(), }, span, )?; - cx.clean_last_scope(span, loop_scope_expected, Needs::None)?; + cx.asm.label(&break_label)?; - // NB: If a value is needed from a for loop, encode it as a unit. - if needs.value() { - cx.asm.push(Inst::unit(), span)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), span)?; } - // NB: breaks produce their own value. - cx.asm.label(&break_label)?; - cx.loops.pop(); - Ok(Asm::top(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, ())) } /// Assemble an if expression. #[instrument(span = span)] -fn expr_if<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_if<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::Conditional<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { + 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(); - let mut fallback = None; for branch in hir.branches { - if fallback.is_some() { - continue; - } - - let Some(cond) = branch.condition else { - fallback = Some(&branch.block); - continue; - }; + 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 label = cx.asm.new_label("if_branch"); - let scope = condition(cx, cond, &label)?; - branches.try_push((branch, label, scope))?; + let scope = cx.scopes.dangle(branch, scope)?; + branches.try_push((branch, then_label, scope))?; + } } // use fallback as fall through. - if let Some(b) = fallback { - block(cx, b, needs)?.apply(cx)?; + 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 { - // NB: if we must produce a value and there is no fallback branch, - // encode the result of the statement as a unit. - if needs.value() { - cx.asm.push(Inst::unit(), span)?; - } - } + Asm::new(span, ()) + }; - cx.asm.jump(&end_label, 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 scopes = cx.scopes.push(scope)?; - block(cx, &branch.block, needs)?.apply(cx)?; - cx.clean_last_scope(branch, scopes, needs)?; + 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)? + }; - if it.peek().is_some() { + cx.scopes.pop(branch, scope)?; + + if !asm.converging() && it.peek().is_some() { cx.asm.jump(&end_label, branch)?; } } cx.asm.label(&end_label)?; - Ok(Asm::top(span)) + linear.free()?; + Ok(Asm::new(span, ())) } /// Assemble an expression. #[instrument(span = span)] -fn expr_index<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_index<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprIndex<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let guard = cx.scopes.child(span)?; - - let target = expr(cx, &hir.target, Needs::Value)?.apply_targeted(cx)?; - let index = expr(cx, &hir.index, Needs::Value)?.apply_targeted(cx)?; - - cx.asm.push(Inst::IndexGet { index, target }, span)?; + let mut target = cx.scopes.defer(span); + let mut index = cx.scopes.defer(span); - // NB: we still need to perform the operation since it might have side - // effects, but pop the result in case a value is not needed. - if !needs.value() { - cx.asm.push(Inst::Pop, 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, + )?; } - cx.scopes.pop(guard, span)?; - Ok(Asm::top(span)) + index.free()?; + target.free()?; + Ok(Asm::new(span, ())) } /// Assemble a let expression. #[instrument(span = hir)] -fn expr_let<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_let<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprLet<'hir>, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let load = |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - // NB: assignments "move" the value being assigned. - expr(cx, &hir.expr, needs)?.apply(cx)?; - Ok(()) - }; - - let false_label = cx.asm.new_label("let_panic"); - - if pat(cx, &hir.pat, &false_label, &load)? { - cx.q.diagnostics - .let_pattern_might_panic(cx.source_id, hir, cx.context())?; - - let ok_label = cx.asm.new_label("let_ok"); - cx.asm.jump(&ok_label, hir)?; - cx.asm.label(&false_label)?; - cx.asm.push( - Inst::Panic { - reason: PanicReason::UnmatchedPattern, - }, - hir, - )?; + let mut load = + |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| expr(cx, &hir.expr, needs); - cx.asm.label(&ok_label)?; - } + converge!(pattern_panic(cx, &hir.pat, move |cx, false_label| { + pat_binding(cx, &hir.pat, false_label, &mut load) + })?); // If a value is needed for a let expression, it is evaluated as a unit. - if needs.value() { - cx.asm.push(Inst::unit(), hir)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), hir)?; } - Ok(Asm::top(hir)) + Ok(Asm::new(hir, ())) } #[instrument(span = span)] -fn expr_match<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_match<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprMatch<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let expected_scopes = cx.scopes.child(span)?; - - expr(cx, &hir.expr, Needs::Value)?.apply(cx)?; - // Offset of the expression. - let offset = cx.scopes.alloc(span)?; + 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 parent_guard = cx.scopes.child(span)?; - - let load = move |this: &mut Ctxt, needs: Needs| { - if needs.value() { - this.asm.push(Inst::Copy { offset }, span)?; - } + let pattern_scope = cx.scopes.child(span)?; - Ok(()) + let mut load = |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| { + needs.assign_addr(cx, value.addr())?; + Ok(Asm::new(branch, ())) }; - pat(cx, &branch.pat, &match_false, &load)?; + 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; - let scope = if let Some(condition) = branch.condition { - let span = condition; + if let Some(condition) = branch.condition { + let span = condition; + let mut cond = cx.scopes.defer(condition); - let guard = cx.scopes.child(span)?; + let scope = cx.scopes.child(span)?; - expr(cx, condition, Needs::Value)?.apply(cx)?; - cx.clean_last_scope(span, guard, Needs::Value)?; - let scope = cx.scopes.pop(parent_guard, span)?; + if expr(cx, condition, &mut cond)?.converging() { + cx.asm + .jump_if_not(cond.addr()?.addr(), &match_false, span)?; + cx.asm.jump(&branch_label, span)?; + } else { + converges = false; + } - cx.asm - .pop_and_jump_if_not(scope.local, &match_false, span)?; + cond.free()?; + cx.scopes.pop(span, scope)?; + } else { + // If there is no branch condition, and the branch is + // irrefutable, there is no point in assembling the additional + // branches. + is_irrefutable = matches!(pat, Pattern::Irrefutable); + } - cx.asm.jump(&branch_label, span)?; - scope - } else { - cx.scopes.pop(parent_guard, span)? - }; + 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 { + // If the branch condition diverges, there is no reason to + // assemble the other branches if this one is irrefutable. + is_irrefutable = matches!(pat, Pattern::Irrefutable); + cx.scopes.pop(span, pattern_scope)?; + } + } - cx.asm.jump(&branch_label, span)?; - cx.asm.label(&match_false)?; + if is_irrefutable { + break; + } - branches.try_push((branch_label, scope))?; + cx.asm.label(&match_false)?; } - // what to do in case nothing matches and the pattern doesn't have any - // default match branch. - if needs.value() { - cx.asm.push(Inst::unit(), span)?; - } + if !is_irrefutable { + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), span)?; + } - cx.asm.jump(&end_label, span)?; + cx.asm.jump(&end_label, span)?; + } - let mut it = hir.branches.iter().zip(&branches).peekable(); + 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 expected = cx.scopes.push(scope.try_clone()?)?; - expr(cx, &branch.body, needs)?.apply(cx)?; - cx.clean_last_scope(span, expected, needs)?; + cx.asm.label(&label)?; + let scope = cx.scopes.restore(scope); - if it.peek().is_some() { + 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)?; - // pop the implicit scope where we store the anonymous match variable. - cx.clean_last_scope(span, expected_scopes, needs)?; - Ok(Asm::top(span)) + value.free()?; + linear.free()?; + Ok(Asm::new(span, ())) } /// Compile a literal object. #[instrument(span = span)] -fn expr_object<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_object<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprObject<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let guard = cx.scopes.child(span)?; - - let base = cx.scopes.total(span)?; - - for assign in hir.assignments.iter() { - expr(cx, &assign.assign, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(&span)?; - } + if let Some(linear) = + exprs_with(cx, span, hir.assignments, |hir| &hir.assign)?.into_converging() + { + let slot = + cx.q.unit + .new_static_object_keys_iter(span, hir.assignments.iter().map(|a| a.key.1))?; - let slot = - cx.q.unit - .new_static_object_keys_iter(span, hir.assignments.iter().map(|a| a.key.1))?; + match hir.kind { + hir::ExprObjectKind::EmptyStruct { hash } => { + cx.asm.push( + Inst::EmptyStruct { + hash, + out: needs.alloc_output()?, + }, + span, + )?; + } + hir::ExprObjectKind::Struct { hash } => { + cx.asm.push( + Inst::Struct { + addr: linear.addr(), + hash, + slot, + out: needs.alloc_output()?, + }, + span, + )?; + } + hir::ExprObjectKind::StructVariant { hash } => { + cx.asm.push( + Inst::StructVariant { + addr: linear.addr(), + hash, + slot, + out: needs.alloc_output()?, + }, + span, + )?; + } + hir::ExprObjectKind::ExternalType { hash, args } => { + reorder_field_assignments(cx, hir, linear.addr(), span)?; - match hir.kind { - hir::ExprObjectKind::EmptyStruct { hash } => { - cx.asm.push(Inst::EmptyStruct { hash }, span)?; - } - hir::ExprObjectKind::Struct { hash } => { - cx.asm.push(Inst::Struct { hash, slot }, span)?; - } - hir::ExprObjectKind::StructVariant { hash } => { - cx.asm.push(Inst::StructVariant { hash, slot }, span)?; - } - hir::ExprObjectKind::ExternalType { hash, args } => { - reorder_field_assignments(cx, hir, base, span)?; - cx.asm.push(Inst::Call { hash, args }, span)?; - } - hir::ExprObjectKind::Anonymous => { - cx.asm.push(Inst::Object { slot }, span)?; + cx.asm.push( + Inst::Call { + hash, + addr: linear.addr(), + args, + out: needs.alloc_output()?, + }, + span, + )?; + } + hir::ExprObjectKind::Anonymous => { + cx.asm.push( + Inst::Object { + addr: linear.addr(), + slot, + out: needs.alloc_output()?, + }, + span, + )?; + } } - } - // No need to encode an object since the value is not needed. - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; + linear.free()?; } - cx.scopes.pop(guard, span)?; - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Reorder the position of the field assignments on the stack so that they @@ -1989,7 +2533,7 @@ fn expr_object<'hir>( fn reorder_field_assignments<'hir>( cx: &mut Ctxt<'_, 'hir, '_>, hir: &hir::ExprObject<'hir>, - base: usize, + base: InstAddress, span: &dyn Spanned, ) -> compile::Result<()> { let mut order = Vec::try_with_capacity(hir.assignments.len())?; @@ -2005,6 +2549,8 @@ fn reorder_field_assignments<'hir>( order.try_push(position)?; } + let base = base.offset(); + for a in 0..hir.assignments.len() { loop { let Some(&b) = order.get(a) else { @@ -2024,6 +2570,8 @@ fn reorder_field_assignments<'hir>( )); }; + let a = InstAddress::new(a); + let b = InstAddress::new(b); cx.asm.push(Inst::Swap { a, b }, span)?; } } @@ -2033,288 +2581,314 @@ fn reorder_field_assignments<'hir>( /// Assemble a range expression. #[instrument(span = span)] -fn expr_range<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_range<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprRange<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let guard = cx.scopes.child(span)?; + let a: Option<&hir::Expr<'hir>>; + let b: Option<&hir::Expr<'hir>>; - let (range, count) = match hir { + let range = match hir { hir::ExprRange::RangeFrom { start } => { - expr(cx, start, needs)?.apply(cx)?; - cx.scopes.alloc(start)?; - (InstRange::RangeFrom, 1) + a = Some(start); + b = None; + InstRange::RangeFrom + } + hir::ExprRange::RangeFull => { + a = None; + b = None; + InstRange::RangeFull } - hir::ExprRange::RangeFull => (InstRange::RangeFull, 0), hir::ExprRange::RangeInclusive { start, end } => { - expr(cx, start, needs)?.apply(cx)?; - cx.scopes.alloc(start)?; - expr(cx, end, needs)?.apply(cx)?; - cx.scopes.alloc(end)?; - (InstRange::RangeInclusive, 2) + a = Some(start); + b = Some(end); + InstRange::RangeInclusive } hir::ExprRange::RangeToInclusive { end } => { - expr(cx, end, needs)?.apply(cx)?; - cx.scopes.alloc(end)?; - (InstRange::RangeToInclusive, 1) + a = Some(end); + b = None; + InstRange::RangeToInclusive } hir::ExprRange::RangeTo { end } => { - expr(cx, end, needs)?.apply(cx)?; - cx.scopes.alloc(end)?; - (InstRange::RangeTo, 1) + a = Some(end); + b = None; + InstRange::RangeTo } hir::ExprRange::Range { start, end } => { - expr(cx, start, needs)?.apply(cx)?; - cx.scopes.alloc(start)?; - expr(cx, end, needs)?.apply(cx)?; - cx.scopes.alloc(end)?; - (InstRange::Range, 2) + a = Some(start); + b = Some(end); + InstRange::Range } }; - if needs.value() { - cx.asm.push(Inst::Range { range }, span)?; + let a = a.map(slice::from_ref).unwrap_or_default(); + let b = b.map(slice::from_ref).unwrap_or_default(); + + if let Some(linear) = exprs_2(cx, span, a, b)?.into_converging() { + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push( + Inst::Range { + addr: linear.addr(), + range, + out, + }, + span, + )?; + } + + linear.free()?; } - cx.scopes.free(span, count)?; - cx.scopes.pop(guard, span)?; - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a return expression. #[instrument(span = span)] -fn expr_return<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_return<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: Option<&'hir hir::Expr<'hir>>, - span: &dyn Spanned, - _: Needs, + span: &'hir dyn Spanned, ) -> compile::Result> { - // NB: drop any loop temporaries. - for l in cx.loops.iter() { - if let Some(offset) = l.drop { - cx.asm.push(Inst::Drop { offset }, span)?; - } - } - if let Some(e) = hir { - return_(cx, span, e, expr)?; + converge!(return_(cx, span, e, expr)?); } else { cx.asm.push(Inst::ReturnUnit, span)?; } - Ok(Asm::top(span)) + Ok(Asm::diverge(span)) } /// Assemble a select expression. -#[instrument(span = span)] -fn expr_select<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_select_inner<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprSelect<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - cx.contexts.try_push(span.span())?; - - let len = hir.branches.len(); let mut default_branch = None; - let mut branches = Vec::new(); let end_label = cx.asm.new_label("select_end"); for branch in hir.branches { - match *branch { - hir::ExprSelectBranch::Pat(pat) => { - let label = cx.asm.new_label("select_branch"); - branches.try_push((label, pat))?; - } - hir::ExprSelectBranch::Default(def) => { - if default_branch.is_some() { - return Err(compile::Error::new(span, ErrorKind::SelectMultipleDefaults)); - } - - let label = cx.asm.new_label("select_default"); - default_branch = Some((def, label)); - } - } + let label = cx.asm.new_label("select_branch"); + cx.select_branches.try_push((label, branch))?; } - for (_, branch) in &branches { - expr(cx, &branch.expr, Needs::Value)?.apply(cx)?; + if let Some(def) = hir.default { + let label = cx.asm.new_label("select_default"); + default_branch = Some((def, label)); } - cx.asm.push(Inst::Select { len }, span)?; + let linear = converge!(exprs(cx, span, hir.exprs)?); + + let branch_addr = cx.scopes.alloc(span)?; + 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(), + branch: branch_addr.output(), + value: value_addr.output(), + }, + span, + )?; - for (branch, (label, _)) in branches.iter().enumerate() { - cx.asm.jump_if_branch(branch as i64, label, span)?; + for (branch, (label, _)) in cx.select_branches.iter().enumerate() { + cx.asm + .jump_if_branch(branch_addr.addr(), branch as i64, label, span)?; } + branch_addr.free()?; + if let Some((_, label)) = &default_branch { - cx.asm.push(Inst::Pop, span)?; 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 !needs.value() { - cx.asm.push(Inst::Pop, span)?; + cx.asm.jump(&end_label, span)?; } - cx.asm.jump(&end_label, span)?; + let mut branches = core::mem::take(&mut cx.select_branches); - for (label, branch) in branches { + for (label, branch) in branches.drain(..) { cx.asm.label(&label)?; - let expected = cx.scopes.child(&branch.body)?; + let scope = cx.scopes.child(&branch.body)?; - match branch.pat.kind { - hir::PatKind::Path(&hir::PatPathKind::Ident(name)) => { - cx.scopes.define(hir::Name::Str(name), &branch.pat)?; - } - hir::PatKind::Ignore => { - cx.asm.push(Inst::Pop, &branch.body)?; - } - _ => { - return Err(compile::Error::new( - branch.pat.span, - ErrorKind::UnsupportedSelectPattern, - )); - } + 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)?; } - // Set up a new scope with the binding. - expr(cx, &branch.body, needs)?.apply(cx)?; - cx.clean_last_scope(&branch.body, expected, needs)?; - 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)?.apply(cx)?; + expr(cx, branch, needs)?.ignore(); } cx.asm.label(&end_label)?; + // Drop futures we are currently using. + for addr in &linear { + cx.asm.push(Inst::Drop { addr: addr.addr() }, span)?; + } + + value_addr.free()?; + linear.free()?; + Ok(Asm::new(span, ())) +} + +#[instrument(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> { + cx.contexts.try_push(span.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::top(span)) + Ok(asm) } /// Assemble a try expression. #[instrument(span = span)] -fn expr_try<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_try<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::Expr<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let address = expr(cx, hir, Needs::Value)?.apply_targeted(cx)?; + let mut e = cx.scopes.defer(span); + converge!(expr(cx, hir, &mut e)?); cx.asm.push( Inst::Try { - address, - preserve: needs.value(), + addr: e.addr()?.addr(), + out: needs.alloc_output()?, }, span, )?; - if let InstAddress::Top = address { - cx.scopes.free(span, 1)?; - } - - // Why no needs.value() check here to declare another anonymous - // variable? Because when these assembling functions were initially - // implemented it was decided that the caller that indicates - // Needs::Value is responsible for declaring any anonymous variables. - // - // TODO: This should probably change! - - Ok(Asm::top(span)) + e.free()?; + Ok(Asm::new(span, ())) } /// Assemble a literal tuple. #[instrument(span = span)] -fn expr_tuple<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_tuple<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprSeq<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { macro_rules! tuple { - ($variant:ident, $($var:ident),*) => {{ - let guard = cx.scopes.child(span)?; + ($variant:ident $(, $var:ident, $expr:ident)* $(,)?) => {{ + $(let mut $var = cx.scopes.defer(span);)* - let mut it = hir.items.iter(); + let asm = expr_array(cx, span, [$(($expr, &mut $var)),*])?; - $( - let $var = it.next().ok_or_else(|| compile::Error::msg(span, "items ended unexpectedly"))?; - let $var = expr(cx, $var, Needs::Value)?.apply_targeted(cx)?; - )* + let [$($expr),*] = converge!(asm, free($($var),*)); cx.asm.push( Inst::$variant { - args: [$($var,)*], + args: [$($expr.addr(),)*], + out: needs.alloc_output()?, }, span, )?; - cx.scopes.pop(guard, span)?; + $($var.free()?;)* }}; } - if hir.items.is_empty() { - cx.asm.push(Inst::unit(), span)?; - } else { - match hir.items.len() { - 1 => tuple!(Tuple1, e1), - 2 => tuple!(Tuple2, e1, e2), - 3 => tuple!(Tuple3, e1, e2, e3), - 4 => tuple!(Tuple4, e1, e2, e3, e4), - _ => { - for e in hir.items { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(e)?; - } + 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, )?; - - cx.scopes.free(span, hir.items.len())?; } - } - } - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; + linear.free()?; + } } - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a unary expression. #[instrument(span = span)] -fn expr_unary<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_unary<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::ExprUnary<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - expr(cx, &hir.expr, Needs::Value)?.apply(cx)?; + converge!(expr(cx, &hir.expr, needs)?); + + let Some(addr) = needs.try_as_addr()? else { + return Ok(Asm::new(span, ())); + }; match hir.op { ast::UnOp::Not(..) => { - cx.asm.push(Inst::Not, span)?; + cx.asm.push( + Inst::Not { + addr: addr.addr(), + out: addr.output(), + }, + span, + )?; } ast::UnOp::Neg(..) => { - cx.asm.push(Inst::Neg, span)?; + cx.asm.push( + Inst::Neg { + addr: addr.addr(), + out: addr.output(), + }, + span, + )?; } op => { return Err(compile::Error::new( @@ -2324,203 +2898,194 @@ fn expr_unary<'hir>( } } - // NB: we put it here to preserve the call in case it has side effects. - // But if we don't need the value, then pop it from the stack. - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; - } - - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a literal vector. #[instrument(span = span)] -fn expr_vec<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_vec<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &hir::ExprSeq<'hir>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { + let mut linear = cx.scopes.linear(span, hir.items.len())?; let count = hir.items.len(); - for e in hir.items { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.scopes.alloc(e)?; + for (e, needs) in hir.items.iter().zip(&mut linear) { + converge!(expr(cx, e, needs)?, free(linear)); } - cx.asm.push(Inst::Vec { count }, span)?; - cx.scopes.free(span, hir.items.len())?; - - // Evaluate the expressions one by one, then pop them to cause any - // side effects (without creating an object). - if !needs.value() { - cx.q.diagnostics - .not_used(cx.source_id, span, cx.context())?; - cx.asm.push(Inst::Pop, span)?; + if let Some(out) = needs.try_alloc_addr()? { + cx.asm.push( + Inst::Vec { + addr: linear.addr(), + count, + out: out.output(), + }, + span, + )?; } - Ok(Asm::top(span)) + linear.free()?; + Ok(Asm::new(span, ())) } /// Assemble a while loop. #[instrument(span = span)] -fn expr_loop<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, - hir: &hir::ExprLoop<'hir>, - span: &dyn Spanned, - needs: Needs, +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> { 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"); - let var_count = cx.scopes.total(span)?; - - cx.loops.push(Loop { + cx.breaks.push(Break { label: hir.label, - continue_label: continue_label.try_clone()?, - continue_var_count: var_count, + continue_label: Some(continue_label.try_clone()?), break_label: break_label.try_clone()?, - break_var_count: var_count, - needs, + output: Some(needs.alloc_output()?), drop: None, })?; cx.asm.label(&continue_label)?; - let expected = if let Some(hir) = hir.condition { - let then_scope = condition(cx, hir, &then_label)?; - let expected = cx.scopes.push(then_scope)?; + let count = hir.condition.and_then(|c| c.count()).unwrap_or_default(); + let mut linear = cx.scopes.linear(span, count)?; - cx.asm.jump(&end_label, span)?; - cx.asm.label(&then_label)?; - Some(expected) + 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, Needs::None)?.apply(cx)?; + // Divergence should be ignored, since there are labels which might jump over it. + block(cx, &hir.body, &mut Any::ignore(span))?.ignore(); - if let Some(expected) = expected { - cx.clean_last_scope(span, expected, Needs::None)?; + if let Some(scope) = condition_scope { + cx.scopes.pop(span, scope)?; } cx.asm.jump(&continue_label, span)?; cx.asm.label(&end_label)?; - if needs.value() { - cx.asm.push(Inst::unit(), span)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), span)?; } // NB: breaks produce their own value / perform their own cleanup. cx.asm.label(&break_label)?; - cx.loops.pop(); - Ok(Asm::top(span)) + linear.free()?; + cx.breaks.pop(); + Ok(Asm::new(span, ())) } /// Assemble a `yield` expression. #[instrument(span = span)] -fn expr_yield<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn expr_yield<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: Option<&'hir hir::Expr<'hir>>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { + let out = needs.alloc_output()?; + if let Some(e) = hir { - expr(cx, e, Needs::Value)?.apply(cx)?; - cx.asm.push(Inst::Yield, span)?; - } else { - cx.asm.push(Inst::YieldUnit, span)?; - } + 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, + )?; - if !needs.value() { - cx.asm.push(Inst::Pop, span)?; + addr.free()?; + } else { + cx.asm.push(Inst::YieldUnit { out }, span)?; } - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a literal value. #[instrument(span = span)] -fn lit<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn lit<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: hir::Lit<'_>, - span: &dyn Spanned, - needs: Needs, + span: &'hir dyn Spanned, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { // Elide the entire literal if it's not needed. - if !needs.value() { + let Some(addr) = needs.try_alloc_addr()? else { cx.q.diagnostics .not_used(cx.source_id, span, cx.context())?; - return Ok(Asm::top(span)); - } + return Ok(Asm::new(span, ())); + }; + + let out = addr.output(); match hir { - hir::Lit::Bool(boolean) => { - cx.asm.push(Inst::bool(boolean), span)?; + hir::Lit::Bool(v) => { + cx.asm.push(Inst::bool(v, out), span)?; } - hir::Lit::Byte(byte) => { - cx.asm.push(Inst::byte(byte), span)?; + hir::Lit::Byte(v) => { + cx.asm.push(Inst::byte(v, out), span)?; } - hir::Lit::Char(char) => { - cx.asm.push(Inst::char(char), span)?; + hir::Lit::Char(v) => { + cx.asm.push(Inst::char(v, out), span)?; } - hir::Lit::Integer(integer) => { - cx.asm.push(Inst::integer(integer), span)?; + hir::Lit::Integer(v) => { + cx.asm.push(Inst::integer(v, out), span)?; } - hir::Lit::Float(float) => { - cx.asm.push(Inst::float(float), 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 }, span)?; + 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 }, span)?; + cx.asm.push(Inst::Bytes { slot, out }, span)?; } }; - Ok(Asm::top(span)) + Ok(Asm::new(span, ())) } /// Assemble a local expression. #[instrument(span = hir)] -fn local<'hir>( - cx: &mut Ctxt<'_, 'hir, '_>, +fn local<'a, 'hir>( + cx: &mut Ctxt<'a, 'hir, '_>, hir: &'hir hir::Local<'hir>, - needs: Needs, + needs: &mut dyn Needs<'a, 'hir>, ) -> compile::Result> { - let load = |cx: &mut Ctxt<'_, 'hir, '_>, needs: Needs| { - // NB: assignments "move" the value being assigned. - expr(cx, &hir.expr, needs)?.apply(cx)?; - Ok(()) - }; - - let false_label = cx.asm.new_label("let_panic"); - - if pat(cx, &hir.pat, &false_label, &load)? { - cx.q.diagnostics - .let_pattern_might_panic(cx.source_id, hir, cx.context())?; + let mut load = + |cx: &mut Ctxt<'a, 'hir, '_>, needs: &mut dyn Needs<'a, 'hir>| expr(cx, &hir.expr, needs); - let ok_label = cx.asm.new_label("let_ok"); - cx.asm.jump(&ok_label, hir)?; - cx.asm.label(&false_label)?; - cx.asm.push( - Inst::Panic { - reason: PanicReason::UnmatchedPattern, - }, - hir, - )?; - - cx.asm.label(&ok_label)?; - } + converge!(pattern_panic(cx, &hir.pat, |cx, false_label| { + pat_binding(cx, &hir.pat, false_label, &mut load) + })?); // If a value is needed for a let expression, it is evaluated as a unit. - if needs.value() { - cx.asm.push(Inst::unit(), hir)?; + if let Some(out) = needs.try_alloc_output()? { + cx.asm.push(Inst::unit(out), hir)?; } - Ok(Asm::top(hir)) + Ok(Asm::new(hir, ())) } diff --git a/crates/rune/src/compile/v1/breaks.rs b/crates/rune/src/compile/v1/breaks.rs new file mode 100644 index 000000000..3e2b0caa5 --- /dev/null +++ b/crates/rune/src/compile/v1/breaks.rs @@ -0,0 +1,96 @@ +use crate as rune; +use crate::alloc::prelude::*; +use crate::alloc::{self, Vec}; +use crate::ast::Spanned; +use crate::compile::{self, ErrorKind, WithSpan}; +use crate::runtime::{InstAddress, Label, Output}; + +/// Loops we are inside. +#[derive(TryClone)] +pub(crate) struct Break<'hir> { + /// The optional label of the start of the break. + pub(crate) label: Option<&'hir str>, + /// If the break supports breaking with a value, this would be where to + /// store it. + pub(crate) output: Option, + /// If the break supports continuing, this is the label to use. + pub(crate) continue_label: Option