From 8b3eff031e4e9a6adbc71efbff02eebff2d17fd7 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 9 May 2023 12:38:43 -0700 Subject: [PATCH 1/4] Add Samples-based evaluator benchmarks This change adds some benchmarking of the evaluator using the checked in Teleportation and Deutsch-Jozsa samples. --- compiler/qsc/Cargo.toml | 15 +++++++++++++++ compiler/qsc/benches/eval.rs | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 compiler/qsc/benches/eval.rs diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml index 71cddd0f88..5b76661fb3 100644 --- a/compiler/qsc/Cargo.toml +++ b/compiler/qsc/Cargo.toml @@ -26,6 +26,17 @@ thiserror = { workspace = true } criterion = { workspace = true, features = ["cargo_bench_support"] } indoc = { workspace = true } +[lib] +bench = false + +[[bin]] +name = "qsc" +bench = false + +[[bin]] +name = "qsi" +bench = false + [[bench]] name = "nqueens" harness = false @@ -33,3 +44,7 @@ harness = false [[bench]] name = "library" harness = false + +[[bench]] +name = "eval" +harness = false diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs new file mode 100644 index 0000000000..019db769ed --- /dev/null +++ b/compiler/qsc/benches/eval.rs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use criterion::{criterion_group, criterion_main, Criterion}; +use qsc::interpret::stateless; +use qsc_eval::output::GenericReceiver; +use qsc_frontend::compile::SourceMap; + +const TELEPORT: &str = include_str!("../../../samples/Teleportation.qs"); +const DEUTSCHJOZSA: &str = include_str!("../../../samples/DeutschJozsa.qs"); + +pub fn teleport(c: &mut Criterion) { + c.bench_function("Teleport evaluation", |b| { + let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None); + let evaluator = stateless::Context::new(true, sources).expect("code should compile"); + b.iter(move || { + let mut out = Vec::new(); + let mut rec = GenericReceiver::new(&mut out); + assert!(evaluator.eval(&mut rec).is_ok()); + }) + }); +} + +pub fn deutsch_jozsa(c: &mut Criterion) { + c.bench_function("Deutsch-Jozsa evaluation", |b| { + let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None); + let evaluator = stateless::Context::new(true, sources).expect("code should compile"); + b.iter(move || { + let mut out = Vec::new(); + let mut rec = GenericReceiver::new(&mut out); + assert!(evaluator.eval(&mut rec).is_ok()); + }) + }); +} + +criterion_group!(benches, teleport, deutsch_jozsa); +criterion_main!(benches); From f5414ddbdbdbf4e37228dbfdb1925e94980ea482 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 3 May 2023 09:35:44 -0700 Subject: [PATCH 2/4] Refactor evaluator for stackless execution --- compiler/qsc/src/compile.rs | 18 +- compiler/qsc/src/interpret/stateful/tests.rs | 17 + compiler/qsc_eval/src/intrinsic.rs | 223 +- compiler/qsc_eval/src/intrinsic/tests.rs | 10 +- compiler/qsc_eval/src/lib.rs | 2020 ++++++++--------- compiler/qsc_eval/src/tests.rs | 114 +- compiler/qsc_eval/src/val.rs | 217 +- compiler/qsc_frontend/src/typeck/rules.rs | 14 +- compiler/qsc_frontend/src/typeck/tests.rs | 42 + compiler/qsc_passes/src/lib.rs | 26 +- compiler/qsc_passes/src/loop_unification.rs | 21 +- .../qsc_passes/src/loop_unification/tests.rs | 13 +- .../src/replace_qubit_allocation.rs | 190 +- .../src/replace_qubit_allocation/tests.rs | 5 +- library/core/qir.qs | 3 + 15 files changed, 1485 insertions(+), 1448 deletions(-) diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs index 8ce19ea9bd..19165bc61e 100644 --- a/compiler/qsc/src/compile.rs +++ b/compiler/qsc/src/compile.rs @@ -4,8 +4,8 @@ use crate::error::WithSource; use miette::{Diagnostic, Report}; use qsc_frontend::compile::{CompileUnit, PackageStore, SourceMap}; -use qsc_hir::{global, hir::PackageId}; -use qsc_passes::run_default_passes; +use qsc_hir::hir::PackageId; +use qsc_passes::{run_core_passes, run_default_passes}; use thiserror::Error; #[derive(Clone, Debug, Diagnostic, Error)] @@ -45,18 +45,8 @@ pub fn compile( #[must_use] pub fn core() -> CompileUnit { let mut unit = qsc_frontend::compile::core(); - let table = global::iter_package(None, &unit.package).collect(); - let pass_errors = run_default_passes(&table, &mut unit); - if pass_errors.is_empty() { - unit - } else { - for error in pass_errors { - let report = Report::new(WithSource::from_map(&unit.sources, error, None)); - eprintln!("{report:?}"); - } - - panic!("could not compile core library") - } + run_core_passes(&mut unit); + unit } /// Compiles the standard library. diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/stateful/tests.rs index 9d167146a9..9366b52015 100644 --- a/compiler/qsc/src/interpret/stateful/tests.rs +++ b/compiler/qsc/src/interpret/stateful/tests.rs @@ -222,6 +222,23 @@ mod given_interpreter { ); is_unit_with_output(&result, &output, "before\nafter"); } + + #[test] + fn global_qubits() { + let mut interpreter = get_interpreter(); + let (result, output) = line(&mut interpreter, "open Microsoft.Quantum.Diagnostics;"); + is_only_value(&result, &output, &Value::unit()); + let (result, output) = line(&mut interpreter, "DumpMachine()"); + is_unit_with_output(&result, &output, "STATE:\n|0⟩: 1+0i"); + let (result, output) = line(&mut interpreter, "use (q0, qs) = (Qubit(), Qubit[3]);"); + is_only_value(&result, &output, &Value::unit()); + let (result, output) = line(&mut interpreter, "DumpMachine()"); + is_unit_with_output(&result, &output, "STATE:\n|0000⟩: 1+0i"); + let (result, output) = line(&mut interpreter, "X(q0); X(qs[1]);"); + is_only_value(&result, &output, &Value::unit()); + let (result, output) = line(&mut interpreter, "DumpMachine()"); + is_unit_with_output(&result, &output, "STATE:\n|0101⟩: 1+0i"); + } } #[cfg(test)] diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 802361dd68..621b2ab939 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -7,7 +7,7 @@ mod tests; use crate::{ output::Receiver, val::{Qubit, Value}, - Error, Reason, WithSpan, + Error, }; use num_bigint::BigInt; use qir_backend::{ @@ -24,7 +24,6 @@ use qir_backend::{ }; use qsc_data_structures::span::Span; use rand::Rng; -use std::ops::ControlFlow::{self, Break, Continue}; #[allow(clippy::too_many_lines)] pub(crate) fn invoke_intrinsic( @@ -33,206 +32,248 @@ pub(crate) fn invoke_intrinsic( args: Value, args_span: Span, out: &mut dyn Receiver, -) -> ControlFlow { +) -> Result { if name.starts_with("__quantum__qis__") { invoke_quantum_intrinsic(name, name_span, args, args_span) } else { match name { - "Length" => match args.try_into_array().with_span(args_span)?.len().try_into() { - Ok(len) => ControlFlow::Continue(Value::Int(len)), - Err(_) => ControlFlow::Break(Reason::Error(Error::ArrayTooLarge(args_span))), + "Length" => match args.unwrap_array().len().try_into() { + Ok(len) => Ok(Value::Int(len)), + Err(_) => Err(Error::ArrayTooLarge(args_span)), }, #[allow(clippy::cast_precision_loss)] "IntAsDouble" => { - let val: i64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val as f64)) + let val = args.unwrap_int(); + Ok(Value::Double(val as f64)) } "IntAsBigInt" => { - let val: i64 = args.try_into().with_span(args_span)?; - Continue(Value::BigInt(BigInt::from(val))) + let val = args.unwrap_int(); + Ok(Value::BigInt(BigInt::from(val))) } "DumpMachine" => { let (state, qubit_count) = capture_quantum_state(); match out.state(state, qubit_count) { - Ok(_) => Continue(Value::unit()), - Err(_) => Break(Reason::Error(Error::Output(name_span))), + Ok(_) => Ok(Value::unit()), + Err(_) => Err(Error::Output(name_span)), } } - "Message" => match out.message(&args.try_into_string().with_span(args_span)?) { - Ok(_) => Continue(Value::unit()), - Err(_) => Break(Reason::Error(Error::Output(name_span))), + "Message" => match out.message(&args.unwrap_string()) { + Ok(_) => Ok(Value::unit()), + Err(_) => Err(Error::Output(name_span)), }, - "CheckZero" => Continue(Value::Bool(qubit_is_zero( - args.try_into().with_span(args_span)?, - ))), + "CheckZero" => Ok(Value::Bool(qubit_is_zero(args.unwrap_qubit().0))), "ArcCos" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.acos())) + let val = args.unwrap_double(); + Ok(Value::Double(val.acos())) } "ArcSin" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.asin())) + let val = args.unwrap_double(); + Ok(Value::Double(val.asin())) } "ArcTan" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.atan())) + let val = args.unwrap_double(); + Ok(Value::Double(val.atan())) } - "ArcTan2" => match args.try_into_tuple().with_span(args_span)?.as_ref() { - [x, y] => { - let x: f64 = x.clone().try_into().with_span(args_span)?; - let y = y.clone().try_into().with_span(args_span)?; - Continue(Value::Double(x.atan2(y))) + "ArcTan2" => { + let tup = args.unwrap_tuple(); + match &*tup { + [x, y] => { + let x = x.clone().unwrap_double(); + let y = y.clone().unwrap_double(); + Ok(Value::Double(x.atan2(y))) + } + _ => Err(Error::TupleArity(2, tup.len(), args_span)), } - args => Break(Reason::Error(Error::TupleArity(2, args.len(), args_span))), - }, + } "Cos" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.cos())) + let val = args.unwrap_double(); + Ok(Value::Double(val.cos())) } "Cosh" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.cosh())) + let val = args.unwrap_double(); + Ok(Value::Double(val.cosh())) } "Sin" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.sin())) + let val = args.unwrap_double(); + Ok(Value::Double(val.sin())) } "Sinh" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.sinh())) + let val = args.unwrap_double(); + Ok(Value::Double(val.sinh())) } "Tan" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.tan())) + let val = args.unwrap_double(); + Ok(Value::Double(val.tan())) } "Tanh" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.tanh())) + let val = args.unwrap_double(); + Ok(Value::Double(val.tanh())) } "Sqrt" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.sqrt())) + let val = args.unwrap_double(); + Ok(Value::Double(val.sqrt())) } "Log" => { - let val: f64 = args.try_into().with_span(args_span)?; - Continue(Value::Double(val.ln())) + let val = args.unwrap_double(); + Ok(Value::Double(val.ln())) } - "DrawRandomInt" => match args.try_into_tuple().with_span(args_span)?.as_ref() { - [lo, hi] => invoke_draw_random_int(lo.clone(), hi.clone(), args_span), - args => Break(Reason::Error(Error::TupleArity(2, args.len(), args_span))), - }, + "DrawRandomInt" => { + let tup = args.unwrap_tuple(); + match &*tup { + [lo, hi] => invoke_draw_random_int(lo.clone(), hi.clone(), args_span), + _ => Err(Error::TupleArity(2, tup.len(), args_span)), + } + } "Truncate" => { - let val: f64 = args.try_into().with_span(args_span)?; + let val = args.unwrap_double(); #[allow(clippy::cast_possible_truncation)] - Continue(Value::Int(val as i64)) + Ok(Value::Int(val as i64)) } "__quantum__rt__qubit_allocate" => { let qubit = Qubit(__quantum__rt__qubit_allocate()); - Continue(Value::Qubit(qubit)) + Ok(Value::Qubit(qubit)) } "__quantum__rt__qubit_release" => { - __quantum__rt__qubit_release(args.try_into().with_span(args_span)?); - Continue(Value::unit()) + let qubit = args.unwrap_qubit().0; + if !qubit_is_zero(qubit) { + return Err(Error::ReleasedQubitNotZero(qubit as usize)); + } + __quantum__rt__qubit_release(qubit); + Ok(Value::unit()) } - _ => Break(Reason::Error(Error::UnknownIntrinsic(name_span))), + _ => Err(Error::UnknownIntrinsic(name_span)), } } } -fn invoke_draw_random_int(lo: Value, hi: Value, args_span: Span) -> ControlFlow { - let lo: i64 = lo.try_into().with_span(args_span)?; - let hi: i64 = hi.try_into().with_span(args_span)?; +fn invoke_draw_random_int(lo: Value, hi: Value, args_span: Span) -> Result { + let lo = lo.unwrap_int(); + let hi = hi.unwrap_int(); if lo > hi { - Break(Reason::Error(Error::EmptyRange(args_span))) + Err(Error::EmptyRange(args_span)) } else { - Continue(Value::Int(rand::thread_rng().gen_range(lo..=hi))) + Ok(Value::Int(rand::thread_rng().gen_range(lo..=hi))) } } +#[allow(clippy::too_many_lines)] fn invoke_quantum_intrinsic( name: &str, name_span: Span, args: Value, args_span: Span, -) -> ControlFlow { +) -> Result { macro_rules! match_intrinsic { - ($chosen_op:ident, $chosen_op_span:ident, $(($(1, $op1:ident),* $(2, $op2:ident),* $(3, $op3:ident),*)),* ) => { + ($chosen_op:ident, $chosen_op_span:ident, $(($(1, $op1:ident),* $(2, $op2:ident),* $(3, $op3:ident),* $(2.1, $op21:ident),* $(3.1, $op31:ident),*)),* ) => { match $chosen_op { $($(stringify!($op1) => { - $op1(args.try_into().with_span(args_span)?); - Continue(Value::unit()) + $op1(args.unwrap_qubit().0); + Ok(Value::unit()) })* $(stringify!($op2) => { - match args.try_into_tuple().with_span(args_span)?.as_ref() { + let tup = args.unwrap_tuple(); + match &*tup { [x, y] => { if x == y { - return Break(Reason::Error(Error::QubitUniqueness(args_span))); + return Err(Error::QubitUniqueness(args_span)); } $op2( - x.clone().try_into().with_span(args_span)?, - y.clone().try_into().with_span(args_span)?, + x.clone().unwrap_qubit().0, + y.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) + } + _ => Err(Error::TupleArity(2, tup.len(), args_span)) + } + })* + $(stringify!($op21) => { + let tup = args.unwrap_tuple(); + match &*tup { + [x, y] => { + $op21( + x.clone().unwrap_double(), + y.clone().unwrap_qubit().0, ); - Continue(Value::unit()) + Ok(Value::unit()) } - args => Break(Reason::Error(Error::TupleArity(2, args.len(), args_span))) + _ => Err(Error::TupleArity(2, tup.len(), args_span)) } })* $(stringify!($op3) => { - match args.try_into_tuple().with_span(args_span)?.as_ref() { + let tup = args.unwrap_tuple(); + match &*tup { [x, y, z] => { if x == y || y == z || x == z { - return Break(Reason::Error(Error::QubitUniqueness(args_span))); + return Err(Error::QubitUniqueness(args_span)); } $op3( - x.clone().try_into().with_span(args_span)?, - y.clone().try_into().with_span(args_span)?, - z.clone().try_into().with_span(args_span)?, + x.clone().unwrap_qubit().0, + y.clone().unwrap_qubit().0, + z.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) + } + _ => Err(Error::TupleArity(3, tup.len(), args_span)) + } + })* + $(stringify!($op31) => { + let tup = args.unwrap_tuple(); + match &*tup { + [x, y, z] => { + if y == z { + return Err(Error::QubitUniqueness(args_span)); + } + $op31( + x.clone().unwrap_double(), + y.clone().unwrap_qubit().0, + z.clone().unwrap_qubit().0, ); - Continue(Value::unit()) + Ok(Value::unit()) } - args => Break(Reason::Error(Error::TupleArity(3, args.len(), args_span))) + _ => Err(Error::TupleArity(3, tup.len(), args_span)) } - })*)* + })* + )* "__quantum__qis__m__body" => { - let res = __quantum__qis__m__body(args.try_into().with_span(args_span)?); - Continue(Value::Result(__quantum__rt__result_equal( + let res = __quantum__qis__m__body(args.unwrap_qubit().0); + Ok(Value::Result(__quantum__rt__result_equal( res, __quantum__rt__result_get_one(), ))) } "__quantum__qis__mresetz__body" => { - let res = __quantum__qis__mresetz__body(args.try_into().with_span(args_span)?); - Continue(Value::Result(__quantum__rt__result_equal( + let res = __quantum__qis__mresetz__body(args.unwrap_qubit().0); + Ok(Value::Result(__quantum__rt__result_equal( res, __quantum__rt__result_get_one(), ))) } - _ => Break(Reason::Error(Error::UnknownIntrinsic($chosen_op_span))), + _ => Err(Error::UnknownIntrinsic($chosen_op_span)), } }; } @@ -244,12 +285,12 @@ fn invoke_quantum_intrinsic( (2, __quantum__qis__cx__body), (2, __quantum__qis__cy__body), (2, __quantum__qis__cz__body), - (2, __quantum__qis__rx__body), - (3, __quantum__qis__rxx__body), - (2, __quantum__qis__ry__body), - (3, __quantum__qis__ryy__body), - (2, __quantum__qis__rz__body), - (3, __quantum__qis__rzz__body), + (2.1, __quantum__qis__rx__body), + (3.1, __quantum__qis__rxx__body), + (2.1, __quantum__qis__ry__body), + (3.1, __quantum__qis__ryy__body), + (2.1, __quantum__qis__rz__body), + (3.1, __quantum__qis__rzz__body), (1, __quantum__qis__h__body), (1, __quantum__qis__s__body), (1, __quantum__qis__s__adj), diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 72db7a04a9..4530f93ba0 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -7,7 +7,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use num_bigint::BigInt; use qsc_frontend::compile::{self, compile, PackageStore, SourceMap}; -use qsc_passes::run_default_passes; +use qsc_passes::{run_core_passes, run_default_passes}; use crate::{ eval_expr, @@ -18,7 +18,9 @@ use crate::{ }; fn check_intrinsic(file: &str, expr: &str, out: &mut dyn Receiver) -> Result { - let mut store = PackageStore::new(compile::core()); + let mut core = compile::core(); + run_core_passes(&mut core); + let mut store = PackageStore::new(core); let mut std = compile::std(&store); assert!(std.errors.is_empty()); assert!(run_default_passes(store.core(), &mut std).is_empty()); @@ -879,10 +881,6 @@ fn qubit_release_non_zero_failure() { &expect![[r#" ReleasedQubitNotZero( 0, - Span { - lo: 14, - hi: 21, - }, ) "#]], ); diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 7530205920..cd63ceffba 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -11,35 +11,29 @@ mod intrinsic; pub mod output; pub mod val; -use crate::val::{ConversionError, FunctorApp, Value}; +use crate::val::{FunctorApp, Value}; use debug::{CallStack, Frame}; use intrinsic::invoke_intrinsic; use miette::Diagnostic; use num_bigint::BigInt; use output::Receiver; -use qir_backend::{ - __quantum__rt__initialize, __quantum__rt__qubit_allocate, __quantum__rt__qubit_release, - qubit_is_zero, -}; +use qir_backend::__quantum__rt__initialize; use qsc_data_structures::span::Span; use qsc_hir::hir::{ self, BinOp, Block, CallableBody, CallableDecl, Expr, ExprKind, Field, Functor, Lit, - Mutability, NodeId, PackageId, Pat, PatKind, PrimField, QubitInit, QubitInitKind, Res, Spec, - SpecBody, SpecGen, Stmt, StmtKind, StringComponent, TernOp, UnOp, + Mutability, NodeId, PackageId, Pat, PatKind, PrimField, Res, Spec, SpecBody, SpecGen, Stmt, + StmtKind, StringComponent, TernOp, UnOp, }; use std::{ collections::{hash_map::Entry, HashMap}, + convert::AsRef, fmt::Write, - mem::take, - ops::{ - ControlFlow::{self, Break, Continue}, - Neg, - }, + ops::Neg, ptr::null_mut, rc::Rc, }; use thiserror::Error; -use val::{GlobalId, Qubit}; +use val::GlobalId; #[derive(Clone, Debug, Diagnostic, Error)] pub enum Error { @@ -92,14 +86,7 @@ pub enum Error { RangeStepZero(#[label("invalid range")] Span), #[error("Qubit{0} released while not in |0⟩ state")] - ReleasedQubitNotZero(usize, #[label] Span), - - #[error("mismatched types")] - Type( - &'static str, - &'static str, - #[label("expected {0}, found {1}")] Span, - ), + ReleasedQubitNotZero(usize), #[error("mismatched tuples")] TupleArity( @@ -126,27 +113,38 @@ pub enum Error { UserFail(String, #[label("explicit fail")] Span), } -#[derive(Debug)] -enum Reason { - Error(Error), - Return(Value), +/// Evaluates the given statement with the given context. +/// # Errors +/// Returns the first error encountered during execution. +pub fn eval_stmt<'a>( + stmt: &'a Stmt, + globals: &'a impl GlobalLookup<'a>, + package: PackageId, + env: &'a mut Env, + out: &'a mut dyn Receiver, +) -> Result { + let mut state = State::new(globals, package, env, out); + state.push_stmt(stmt); + state.eval() } -trait WithSpan { - type Output; - - fn with_span(self, span: Span) -> Self::Output; +/// Evaluates the given expression with the given context. +/// # Errors +/// Returns the first error encountered during execution. +pub fn eval_expr<'a>( + expr: &'a Expr, + globals: &'a impl GlobalLookup<'a>, + package: PackageId, + env: &'a mut Env, + out: &'a mut dyn Receiver, +) -> Result { + let mut state = State::new(globals, package, env, out); + state.push_expr(expr); + state.eval() } -impl WithSpan for Result { - type Output = ControlFlow; - - fn with_span(self, span: Span) -> Self::Output { - match self { - Ok(c) => Continue(c), - Err(e) => Break(Reason::Error(Error::Type(e.expected, e.actual, span))), - } - } +pub fn init() { + __quantum__rt__initialize(null_mut()); } trait AsIndex { @@ -156,12 +154,12 @@ trait AsIndex { } impl AsIndex for i64 { - type Output = ControlFlow; + type Output = Result; - fn as_index(&self, span: Span) -> ControlFlow { + fn as_index(&self, span: Span) -> Self::Output { match (*self).try_into() { - Ok(index) => Continue(index), - Err(_) => Break(Reason::Error(Error::IndexVal(*self, span))), + Ok(index) => Ok(index), + Err(_) => Err(Error::IndexVal(*self, span)), } } } @@ -223,60 +221,6 @@ impl<'a, F: Fn(GlobalId) -> Option>> GlobalLookup<'a> for F { } } -/// Evaluates the given statement with the given context. -/// # Errors -/// Returns the first error encountered during execution. -pub fn eval_stmt<'a>( - stmt: &Stmt, - globals: &'a impl GlobalLookup<'a>, - package: PackageId, - env: &mut Env, - out: &'a mut dyn Receiver, -) -> Result { - let mut eval = Evaluator { - globals, - package, - env: take(env), - call_stack: CallStack::default(), - out: Some(out), - }; - let res = match eval.eval_stmt(stmt) { - Continue(res) | Break(Reason::Return(res)) => Ok(res), - Break(Reason::Error(error)) => Err((error, eval.call_stack.clone())), - }; - *env = take(&mut eval.env); - res -} - -/// Evaluates the given expression with the given context. -/// # Errors -/// Returns the first error encountered during execution. -pub fn eval_expr<'a>( - expr: &Expr, - globals: &'a impl GlobalLookup<'a>, - package: PackageId, - env: &mut Env, - out: &'a mut dyn Receiver, -) -> Result { - let mut eval = Evaluator { - globals, - package, - env: take(env), - call_stack: CallStack::default(), - out: Some(out), - }; - let res = match eval.eval_expr(expr) { - Continue(res) | Break(Reason::Return(res)) => Ok(res), - Break(Reason::Error(error)) => Err((error, eval.call_stack.clone())), - }; - *env = take(&mut eval.env); - res -} - -pub fn init() { - __quantum__rt__initialize(null_mut()); -} - #[derive(Default)] pub struct Env(Vec); @@ -299,7 +243,6 @@ impl Env { #[derive(Default)] struct Scope { bindings: HashMap, - qubits: Vec<(Qubit, Span)>, } impl Env { @@ -309,751 +252,881 @@ impl Env { } } -struct Evaluator<'a, G> { - globals: &'a G, +enum Cont<'a> { + Action(Action<'a>), + Expr(&'a Expr), + Frame(usize), + Scope, + Stmt(&'a Stmt), +} + +#[derive(Copy, Clone)] +enum Action<'a> { + Array(usize), + ArrayRepeat(Span), + Assign(&'a Expr), + Bind(&'a Pat, Mutability), + BinOp(BinOp, Span, Option<&'a Expr>), + Call(Span, Span), + Consume, + Fail(Span), + Field(&'a Field), + If(&'a Block, Option<&'a Expr>), + Index(Span), + Range(bool, bool, bool), + Return, + StringConcat(usize), + StringLit(&'a Rc), + TernOp(TernOp, &'a Expr, &'a Expr), + Tuple(usize), + UnOp(UnOp), + UpdateField(&'a Field), + While(&'a Expr, &'a Block), +} + +pub(crate) struct State<'a, G> { + stack: Vec>, + vals: Vec, package: PackageId, - env: Env, + globals: &'a G, + env: &'a mut Env, + out: &'a mut dyn Receiver, call_stack: CallStack, - out: Option<&'a mut dyn Receiver>, } -impl<'a, G: GlobalLookup<'a>> Evaluator<'a, G> { - #[allow(clippy::too_many_lines)] - fn eval_expr(&mut self, expr: &Expr) -> ControlFlow { - match &expr.kind { - ExprKind::Array(arr) => { - let mut val_arr = vec![]; - for expr in arr { - val_arr.push(self.eval_expr(expr)?); +impl<'a, G: GlobalLookup<'a>> State<'a, G> { + fn new( + globals: &'a G, + package: PackageId, + env: &'a mut Env, + out: &'a mut dyn Receiver, + ) -> Self { + Self { + stack: Vec::new(), + vals: Vec::new(), + package, + globals, + env, + out, + call_stack: CallStack::default(), + } + } + + fn pop_cont(&mut self) -> Option> { + self.stack.pop() + } + + fn push_action(&mut self, action: Action<'a>) { + self.stack.push(Cont::Action(action)); + } + + fn push_expr(&mut self, expr: &'a Expr) { + self.stack.push(Cont::Expr(expr)); + } + + fn push_frame(&mut self, span: Option, id: GlobalId, functor: FunctorApp) { + self.call_stack.push_frame(Frame { + span, + id, + caller: self.package, + functor, + }); + self.stack.push(Cont::Frame(self.vals.len())); + self.package = id.package; + } + + fn leave_frame(&mut self, len: usize) { + let frame = self + .call_stack + .pop_frame() + .expect("frame should be present"); + self.package = frame.caller; + let frame_val = self.pop_val(); + self.vals.drain(len..); + self.push_val(frame_val); + } + + fn push_scope(&mut self) { + self.env.0.push(Scope::default()); + self.stack.push(Cont::Scope); + } + + fn leave_scope(&mut self) { + self.env + .0 + .pop() + .expect("scope should be entered first before leaving"); + } + + fn push_stmt(&mut self, stmt: &'a Stmt) { + self.stack.push(Cont::Stmt(stmt)); + } + + fn push_block(&mut self, block: &'a Block) { + self.push_scope(); + for stmt in block.stmts.iter().rev() { + self.push_stmt(stmt); + self.push_action(Action::Consume); + } + if block.stmts.is_empty() { + self.push_val(Value::unit()); + } else { + self.pop_cont(); + } + } + + fn pop_val(&mut self) -> Value { + self.vals.pop().expect("value should be present") + } + + fn pop_vals(&mut self, len: usize) -> Vec { + self.vals.drain(self.vals.len() - len..).collect() + } + + fn push_val(&mut self, val: Value) { + self.vals.push(val); + } + + pub(crate) fn eval(&mut self) -> Result { + while let Some(cont) = self.pop_cont() { + let res = match cont { + Cont::Action(action) => self.cont_action(action), + Cont::Expr(expr) => self.cont_expr(expr), + Cont::Frame(len) => { + self.leave_frame(len); + Ok(()) } - Continue(Value::Array(val_arr.into())) - } - ExprKind::ArrayRepeat(item, size) => { - let item_val = self.eval_expr(item)?; - let size_val: i64 = self.eval_expr(size)?.try_into().with_span(size.span)?; - let s = match size_val.try_into() { - Ok(i) => Continue(i), - Err(_) => Break(Reason::Error(Error::Count(size_val, size.span))), - }?; - Continue(Value::Array(vec![item_val; s].into())) - } - ExprKind::Assign(lhs, rhs) => { - let val = self.eval_expr(rhs)?; - self.update_binding(lhs, val) - } - ExprKind::AssignOp(op, lhs, rhs) => { - let update = self.eval_binop(*op, lhs, rhs)?; - self.update_binding(lhs, update) - } - ExprKind::AssignField(record, field, replace) => { - let update = self.eval_update_field(record, field, replace)?; - self.update_binding(record, update) - } - ExprKind::AssignIndex(array, index, replace) => { - let update = self.eval_update_index(array, index, replace)?; - self.update_binding(array, update) - } - ExprKind::BinOp(op, lhs, rhs) => self.eval_binop(*op, lhs, rhs), - ExprKind::Block(block) => self.eval_block(block), - ExprKind::Call(call, args) => self.eval_call(call, args), - ExprKind::Fail(msg) => { - let msg = self.eval_expr(msg)?.try_into_string().with_span(msg.span)?; - Break(Reason::Error(Error::UserFail(msg.to_string(), expr.span))) - } - ExprKind::Field(record, field) => self.eval_field(record, field), - ExprKind::For(pat, expr, block) => self.eval_for_loop(pat, expr, block), - ExprKind::If(cond, then, els) => { - if self.eval_expr(cond)?.try_into().with_span(cond.span)? { - self.eval_block(then) - } else if let Some(els) = els { - self.eval_expr(els) - } else { - Continue(Value::unit()) + Cont::Scope => { + self.leave_scope(); + Ok(()) } - } - ExprKind::Index(arr, index_expr) => { - let arr = self.eval_expr(arr)?.try_into_array().with_span(arr.span)?; - let index_val = self.eval_expr(index_expr)?; - match &index_val { - Value::Int(index) => index_array(&arr, *index, index_expr.span), - &Value::Range(start, step, end) => { - slice_array(&arr, start, step, end, index_expr.span) - } - _ => Break(Reason::Error(Error::Type( - "Int or Range", - index_val.type_name(), - index_expr.span, - ))), + Cont::Stmt(stmt) => { + self.cont_stmt(stmt); + Ok(()) } + }; + if let Err(e) = res { + return Err((e, self.call_stack.clone())); } - ExprKind::Lit(lit) => Continue(lit_to_val(lit)), - ExprKind::Range(start, step, end) => self.eval_range(start, step, end), - ExprKind::Repeat(repeat, cond, fixup) => self.eval_repeat_loop(repeat, cond, fixup), - ExprKind::Return(expr) => Break(Reason::Return(self.eval_expr(expr)?)), - ExprKind::String(components) => self.eval_string(components), - ExprKind::TernOp(ternop, lhs, mid, rhs) => match *ternop { - TernOp::Cond => self.eval_cond(lhs, mid, rhs), - TernOp::UpdateIndex => self.eval_update_index(lhs, mid, rhs), - }, - ExprKind::Tuple(tup) => { - let mut val_tup = vec![]; - for expr in tup { - val_tup.push(self.eval_expr(expr)?); - } - Continue(Value::Tuple(val_tup.into())) + } + + Ok(self.pop_val()) + } + + fn cont_expr(&mut self, expr: &'a Expr) -> Result<(), Error> { + match &expr.kind { + ExprKind::Array(arr) => self.cont_arr(arr), + ExprKind::ArrayRepeat(item, size) => self.cont_arr_repeat(item, size), + ExprKind::Assign(lhs, rhs) => self.cont_assign(lhs, rhs), + ExprKind::AssignOp(op, lhs, rhs) => self.cont_assign_op(*op, lhs, rhs), + ExprKind::AssignField(record, field, replace) => { + self.cont_assign_field(record, field, replace); } - ExprKind::UnOp(op, rhs) => self.eval_unop(expr, *op, rhs), - ExprKind::UpdateField(record, field, replace) => { - self.eval_update_field(record, field, replace) + ExprKind::AssignIndex(lhs, mid, rhs) => self.cont_assign_index(lhs, mid, rhs), + ExprKind::BinOp(op, lhs, rhs) => self.cont_binop(*op, rhs, lhs), + ExprKind::Block(block) => self.push_block(block), + ExprKind::Call(callee_expr, args_expr) => self.cont_call(callee_expr, args_expr), + ExprKind::Conjugate(..) => panic!("conjugate should be eliminated by passes"), + ExprKind::Err => panic!("error expr should not be present"), + ExprKind::Fail(fail_expr) => self.cont_fail(expr.span, fail_expr), + ExprKind::Field(expr, field) => self.cont_field(expr, field), + ExprKind::For(..) => panic!("for-loop should be eliminated by passes"), + ExprKind::Hole => panic!("hole expr should be disallowed by passes"), + ExprKind::If(cond_expr, then_block, else_expr) => { + self.cont_if(cond_expr, then_block, else_expr.as_ref().map(AsRef::as_ref)); } - &ExprKind::Var(res) => match self.resolve_binding(res, expr.span) { - Ok(val) => Continue(val), - Err(e) => Break(Reason::Error(e)), - }, - ExprKind::While(cond, block) => { - while self.eval_expr(cond)?.try_into().with_span(cond.span)? { - self.eval_block(block)?; - } - Continue(Value::unit()) + ExprKind::Index(arr, index) => self.cont_index(arr, index), + ExprKind::Lambda(..) => panic!("lambda expr should be disallowed by passes"), + ExprKind::Lit(lit) => self.push_val(lit_to_val(lit)), + ExprKind::Range(start, step, end) => self.cont_range(start, step, end), + ExprKind::Repeat(..) => panic!("repeat-loop should be eliminated by passes"), + ExprKind::Return(expr) => self.cont_ret(expr), + ExprKind::String(components) => self.cont_string(components), + ExprKind::TernOp(op, lhs, mid, rhs) => self.cont_ternop(*op, lhs, mid, rhs), + ExprKind::Tuple(tup) => self.cont_tup(tup), + ExprKind::UnOp(op, expr) => self.cont_unop(*op, expr), + ExprKind::UpdateField(record, field, replace) => { + self.cont_update_field(record, field, replace); } - ExprKind::Conjugate(..) => { - Break(Reason::Error(Error::Unimplemented("conjugate", expr.span))) + ExprKind::Var(res) => { + let val = resolve_binding(self.env, self.package, *res, expr.span)?; + self.push_val(val); } - ExprKind::Err => Break(Reason::Error(Error::Unimplemented("error", expr.span))), - ExprKind::Hole => Break(Reason::Error(Error::Unimplemented("hole", expr.span))), - ExprKind::Lambda(..) => Break(Reason::Error(Error::Unimplemented("lambda", expr.span))), + ExprKind::While(cond_expr, block) => self.cont_while(cond_expr, block), + } + Ok(()) + } + + fn cont_tup(&mut self, tup: &'a Vec) { + self.push_action(Action::Tuple(tup.len())); + for tup_expr in tup.iter().rev() { + self.push_expr(tup_expr); } } - fn eval_range( + fn cont_arr(&mut self, arr: &'a Vec) { + self.push_action(Action::Array(arr.len())); + for entry in arr.iter().rev() { + self.push_expr(entry); + } + } + + fn cont_arr_repeat(&mut self, item: &'a Expr, size: &'a Expr) { + self.push_action(Action::ArrayRepeat(size.span)); + self.push_expr(size); + self.push_expr(item); + } + + fn cont_ret(&mut self, expr: &'a Expr) { + self.push_action(Action::Return); + self.push_expr(expr); + } + + fn cont_if(&mut self, cond_expr: &'a Expr, then_block: &'a Block, else_expr: Option<&'a Expr>) { + self.push_action(Action::If(then_block, else_expr)); + self.push_expr(cond_expr); + } + + fn cont_fail(&mut self, span: Span, fail_expr: &'a Expr) { + self.push_action(Action::Fail(span)); + self.push_expr(fail_expr); + } + + fn cont_assign(&mut self, lhs: &'a Expr, rhs: &'a Expr) { + self.push_action(Action::Assign(lhs)); + self.push_expr(rhs); + self.push_val(Value::unit()); + } + + fn cont_assign_op(&mut self, op: BinOp, lhs: &'a Expr, rhs: &'a Expr) { + self.push_action(Action::Assign(lhs)); + self.cont_binop(op, rhs, lhs); + self.push_val(Value::unit()); + } + + fn cont_assign_field(&mut self, record: &'a Expr, field: &'a Field, replace: &'a Expr) { + self.push_action(Action::Assign(record)); + self.cont_update_field(record, field, replace); + self.push_val(Value::unit()); + } + + fn cont_assign_index(&mut self, lhs: &'a Expr, mid: &'a Expr, rhs: &'a Expr) { + self.push_action(Action::Assign(lhs)); + self.cont_ternop(TernOp::UpdateIndex, lhs, mid, rhs); + self.push_val(Value::unit()); + } + + fn cont_field(&mut self, expr: &'a Expr, field: &'a Field) { + self.push_action(Action::Field(field)); + self.push_expr(expr); + } + + fn cont_index(&mut self, arr: &'a Expr, index: &'a Expr) { + self.push_action(Action::Index(index.span)); + self.push_expr(index); + self.push_expr(arr); + } + + fn cont_range( &mut self, - start: &Option>, - step: &Option>, - end: &Option>, - ) -> ControlFlow { - let mut to_opt_i64 = |e: &Option>| match e { - Some(expr) => Continue(Some(self.eval_expr(expr)?.try_into().with_span(expr.span)?)), - None => Continue(None), - }; - Continue(Value::Range( - to_opt_i64(start)?, - to_opt_i64(step)?.unwrap_or(val::DEFAULT_RANGE_STEP), - to_opt_i64(end)?, - )) + start: &'a Option>, + step: &'a Option>, + end: &'a Option>, + ) { + self.push_action(Action::Range( + start.is_some(), + step.is_some(), + end.is_some(), + )); + if let Some(end) = end { + self.push_expr(end); + } + if let Some(step) = step { + self.push_expr(step); + } + if let Some(start) = start { + self.push_expr(start); + } } - fn eval_string(&mut self, components: &[StringComponent]) -> ControlFlow { + fn cont_string(&mut self, components: &'a [StringComponent]) { if let [StringComponent::Lit(str)] = components { - return Continue(Value::String(Rc::clone(str))); + self.push_val(Value::String(Rc::clone(str))); + return; } - let mut string = String::new(); - for component in components { + self.push_action(Action::StringConcat(components.len())); + for component in components.iter().rev() { match component { - StringComponent::Expr(expr) => { - let value = self.eval_expr(expr)?; - write!(string, "{value}").expect("string should be writable"); - } - StringComponent::Lit(lit) => string += lit, + StringComponent::Expr(expr) => self.push_expr(expr), + StringComponent::Lit(lit) => self.push_action(Action::StringLit(lit)), } } + } - Continue(Value::String(string.into())) + fn cont_while(&mut self, cond_expr: &'a Expr, block: &'a Block) { + self.push_action(Action::While(cond_expr, block)); + self.push_expr(cond_expr); } - fn eval_block(&mut self, block: &Block) -> ControlFlow { - self.enter_scope(); - let result = if let Some((last, most)) = block.stmts.split_last() { - for stmt in most { - self.eval_stmt(stmt)?; - } - self.eval_stmt(last) - } else { - Continue(Value::unit()) - }; - self.leave_scope()?; - result + fn cont_call(&mut self, callee: &'a Expr, args: &'a Expr) { + self.push_action(Action::Call(callee.span, args.span)); + self.push_expr(args); + self.push_expr(callee); } - fn eval_stmt(&mut self, stmt: &Stmt) -> ControlFlow { - match &stmt.kind { - StmtKind::Item(_) => Continue(Value::unit()), - StmtKind::Expr(expr) => self.eval_expr(expr), - StmtKind::Local(mutability, pat, expr) => { - let val = self.eval_expr(expr)?; - self.bind_value(pat, val, expr.span, *mutability)?; - Continue(Value::unit()) - } - StmtKind::Semi(expr) => { - self.eval_expr(expr)?; - Continue(Value::unit()) + fn cont_binop(&mut self, op: BinOp, rhs: &'a Expr, lhs: &'a Expr) { + match op { + BinOp::Add + | BinOp::AndB + | BinOp::Div + | BinOp::Eq + | BinOp::Exp + | BinOp::Gt + | BinOp::Gte + | BinOp::Lt + | BinOp::Lte + | BinOp::Mod + | BinOp::Mul + | BinOp::Neq + | BinOp::OrB + | BinOp::Shl + | BinOp::Shr + | BinOp::Sub + | BinOp::XorB => { + self.push_action(Action::BinOp(op, rhs.span, None)); + self.push_expr(rhs); + self.push_expr(lhs); } - StmtKind::Qubit(_, pat, qubit_init, block) => { - let (qubit_val, qubits) = self.eval_qubit_init(qubit_init)?; - if let Some(block) = block { - self.enter_scope(); - self.track_qubits(qubits); - self.bind_value(pat, qubit_val, stmt.span, Mutability::Immutable)?; - self.eval_block(block)?; - self.leave_scope()?; - } else { - self.track_qubits(qubits); - self.bind_value(pat, qubit_val, stmt.span, Mutability::Immutable)?; - } - Continue(Value::unit()) + BinOp::AndL | BinOp::OrL => { + self.push_action(Action::BinOp(op, rhs.span, Some(rhs))); + self.push_expr(lhs); } } } - fn eval_for_loop( - &mut self, - pat: &Pat, - expr: &Expr, - block: &Block, - ) -> ControlFlow { - match self.eval_expr(expr)? { - Value::Array(arr) => self.iterate_for_loop(pat, arr.iter().cloned(), expr.span, block), - Value::Range(start, step, end) => { - let start = - start.map_or(Break(Reason::Error(Error::OpenEnded(expr.span))), Continue)?; - let end = - end.map_or(Break(Reason::Error(Error::OpenEnded(expr.span))), Continue)?; - let range = Range::new(start, step, end); - self.iterate_for_loop(pat, range.map(Value::Int), expr.span, block) + fn cont_ternop(&mut self, op: TernOp, lhs: &'a Expr, mid: &'a Expr, rhs: &'a Expr) { + match op { + TernOp::Cond => { + self.push_action(Action::TernOp(op, mid, rhs)); + self.push_expr(lhs); + } + TernOp::UpdateIndex => { + self.push_action(Action::TernOp(op, mid, rhs)); + self.push_expr(lhs); + self.push_expr(rhs); + self.push_expr(mid); } - value => Break(Reason::Error(Error::NotIterable( - value.type_name(), - expr.span, - ))), } } - fn iterate_for_loop( - &mut self, - pat: &Pat, - values: impl Iterator, - span: Span, - block: &Block, - ) -> ControlFlow { - for value in values { - self.enter_scope(); - self.bind_value(pat, value, span, Mutability::Immutable); - self.eval_block(block)?; - self.leave_scope()?; - } + fn cont_unop(&mut self, op: UnOp, expr: &'a Expr) { + self.push_action(Action::UnOp(op)); + self.push_expr(expr); + } - Continue(Value::unit()) + fn cont_update_field(&mut self, record: &'a Expr, field: &'a Field, replace: &'a Expr) { + self.push_action(Action::UpdateField(field)); + self.push_expr(replace); + self.push_expr(record); } - fn eval_repeat_loop( - &mut self, - repeat: &Block, - cond: &Expr, - fixup: &Option, - ) -> ControlFlow { - self.enter_scope(); - - for stmt in &repeat.stmts { - self.eval_stmt(stmt)?; - } - while !self.eval_expr(cond)?.try_into().with_span(cond.span)? { - if let Some(block) = fixup.as_ref() { - self.eval_block(block)?; + fn cont_stmt(&mut self, stmt: &'a Stmt) { + match &stmt.kind { + StmtKind::Expr(expr) => self.push_expr(expr), + StmtKind::Item(..) => self.push_val(Value::unit()), + StmtKind::Local(mutability, pat, expr) => { + self.push_action(Action::Bind(pat, *mutability)); + self.push_expr(expr); + self.push_val(Value::unit()); } - - self.leave_scope()?; - self.enter_scope(); - - for stmt in &repeat.stmts { - self.eval_stmt(stmt)?; + StmtKind::Qubit(..) => panic!("qubit use-stmt should be eliminated by passes"), + StmtKind::Semi(expr) => { + self.push_action(Action::Consume); + self.push_expr(expr); + self.push_val(Value::unit()); } } - - self.leave_scope()?; - Continue(Value::unit()) } - fn eval_qubit_init( - &mut self, - qubit_init: &QubitInit, - ) -> ControlFlow)> { - match &qubit_init.kind { - QubitInitKind::Array(count) => { - let count_val: i64 = self.eval_expr(count)?.try_into().with_span(count.span)?; - let count: usize = match count_val.try_into() { - Ok(i) => Continue(i), - Err(_) => Break(Reason::Error(Error::Count(count_val, count.span))), - }?; - let mut arr = vec![]; - arr.resize_with(count, || { - (Qubit(__quantum__rt__qubit_allocate()), qubit_init.span) - }); - - Continue(( - Value::Array(arr.iter().copied().map(|q| Value::Qubit(q.0)).collect()), - arr, - )) + fn cont_action(&mut self, action: Action<'a>) -> Result<(), Error> { + match action { + Action::Array(len) => self.eval_arr(len), + Action::ArrayRepeat(span) => self.eval_arr_repeat(span)?, + Action::Assign(lhs) => self.eval_assign(lhs)?, + Action::BinOp(op, span, rhs) => self.eval_binop(op, span, rhs)?, + Action::Bind(pat, mutability) => self.eval_bind(pat, mutability)?, + Action::Call(callee_span, args_span) => self.eval_call(callee_span, args_span)?, + Action::Consume => { + self.pop_val(); } - QubitInitKind::Single => { - let qubit = Qubit(__quantum__rt__qubit_allocate()); - Continue((Value::Qubit(qubit), vec![(qubit, qubit_init.span)])) + Action::Fail(span) => { + return Err(Error::UserFail( + self.pop_val().unwrap_string().to_string(), + span, + )); } - QubitInitKind::Tuple(tup) => { - let mut tup_vec = vec![]; - let mut qubit_vec = vec![]; - for init in tup { - let (t, mut v) = self.eval_qubit_init(init)?; - tup_vec.push(t); - qubit_vec.append(&mut v); - } - Continue((Value::Tuple(tup_vec.into()), qubit_vec)) + Action::Field(field) => self.eval_field(field), + Action::If(then_block, else_expr) => self.eval_if(then_block, else_expr), + Action::Index(span) => self.eval_index(span)?, + Action::Range(has_start, has_step, has_end) => { + self.eval_range(has_start, has_step, has_end); } - } + Action::Return => self.eval_ret(), + Action::StringConcat(len) => self.eval_string_concat(len), + Action::StringLit(str) => self.push_val(Value::String(Rc::clone(str))), + Action::TernOp(op, mid, rhs) => self.eval_ternop(op, mid, rhs)?, + Action::Tuple(len) => self.eval_tup(len), + Action::UnOp(op) => self.eval_unop(op), + Action::UpdateField(field) => self.eval_update_field(field), + Action::While(cond_expr, block) => self.eval_while(cond_expr, block), + } + Ok(()) } - fn eval_call(&mut self, callee: &Expr, args: &Expr) -> ControlFlow { - let callee_val = self.eval_expr(callee)?; - let (callee_id, functor) = value_to_call_id(&callee_val, callee.span)?; - let spec = spec_from_functor_app(functor); - let args_val = self.eval_expr(args)?; - let decl = match self.globals.get(callee_id) { - Some(Global::Callable(decl)) => Continue(decl), - Some(Global::Udt) => return Continue(args_val), - None => Break(Reason::Error(Error::Unbound(callee.span))), + fn eval_arr(&mut self, len: usize) { + let arr = self.pop_vals(len); + self.push_val(Value::Array(arr.into())); + } + + fn eval_arr_repeat(&mut self, span: Span) -> Result<(), Error> { + let size_val = self.pop_val().unwrap_int(); + let item_val = self.pop_val(); + let s = match size_val.try_into() { + Ok(i) => Ok(i), + Err(_) => Err(Error::Count(size_val, span)), }?; + self.push_val(Value::Array(vec![item_val; s].into())); + Ok(()) + } - self.push_frame(Frame { - id: callee_id, - span: Some(callee.span), - caller: self.package, - functor, - }); + fn eval_assign(&mut self, lhs: &'a Expr) -> Result<(), Error> { + let rhs = self.pop_val(); + update_binding(self.env, lhs, rhs) + } - let mut new_self = Self { - globals: self.globals, - package: callee_id.package, - env: Env::default(), - out: self.out.take(), - call_stack: CallStack::default(), - }; - let call_res = new_self.eval_call_spec( - decl, - spec, - args_val, - args.span, - callee.span, - functor.controlled, - ); - self.out = new_self.out.take(); - - match call_res { - Break(Reason::Return(val)) => { - self.pop_frame(); - Continue(val) - } - Break(Reason::Error(_)) => { - for frame in new_self.call_stack.clone().into_frames() { - self.call_stack.push_frame(frame); - } + fn eval_bind(&mut self, pat: &'a Pat, mutability: Mutability) -> Result<(), Error> { + let val = self.pop_val(); + bind_value(self.env, pat, val, mutability) + } - call_res + fn eval_binop(&mut self, op: BinOp, span: Span, rhs: Option<&'a Expr>) -> Result<(), Error> { + match op { + BinOp::Add => self.eval_binop_simple(eval_binop_add), + BinOp::AndB => self.eval_binop_simple(eval_binop_andb), + BinOp::AndL => { + if self.pop_val().unwrap_bool() { + self.push_expr(rhs.expect("rhs should be provided with binop andl")); + } else { + self.push_val(Value::Bool(false)); + } + } + BinOp::Div => self.eval_binop_with_error(span, eval_binop_div)?, + BinOp::Eq => { + let rhs_val = self.pop_val(); + let lhs_val = self.pop_val(); + self.push_val(Value::Bool(lhs_val == rhs_val)); + } + BinOp::Exp => self.eval_binop_with_error(span, eval_binop_exp)?, + BinOp::Gt => self.eval_binop_simple(eval_binop_gt), + BinOp::Gte => self.eval_binop_simple(eval_binop_gte), + BinOp::Lt => self.eval_binop_simple(eval_binop_lt), + BinOp::Lte => self.eval_binop_simple(eval_binop_lte), + BinOp::Mod => self.eval_binop_simple(eval_binop_mod), + BinOp::Mul => self.eval_binop_simple(eval_binop_mul), + BinOp::Neq => { + let rhs_val = self.pop_val(); + let lhs_val = self.pop_val(); + self.push_val(Value::Bool(lhs_val != rhs_val)); } - Continue(_) => { - self.pop_frame(); - call_res + BinOp::OrB => self.eval_binop_simple(eval_binop_orb), + BinOp::OrL => { + if self.pop_val().unwrap_bool() { + self.push_val(Value::Bool(true)); + } else { + self.push_expr(rhs.expect("rhs should be provided with binop andl")); + } } + BinOp::Shl => self.eval_binop_simple(eval_binop_shl), + BinOp::Shr => self.eval_binop_simple(eval_binop_shr), + BinOp::Sub => self.eval_binop_simple(eval_binop_sub), + BinOp::XorB => self.eval_binop_simple(eval_binop_xorb), } + Ok(()) + } + + fn eval_binop_simple(&mut self, binop_func: impl FnOnce(Value, Value) -> Value) { + let rhs_val = self.pop_val(); + let lhs_val = self.pop_val(); + self.push_val(binop_func(lhs_val, rhs_val)); } - fn eval_call_spec( + fn eval_binop_with_error( &mut self, - decl: &CallableDecl, - spec: Spec, - args_val: Value, - args_span: Span, - call_span: Span, - ctl_count: u8, - ) -> ControlFlow { - self.enter_scope(); - let res = match (&decl.body, spec) { + span: Span, + binop_func: impl FnOnce(Value, Value, Span) -> Result, + ) -> Result<(), Error> { + let rhs_val = self.pop_val(); + let lhs_val = self.pop_val(); + self.push_val(binop_func(lhs_val, rhs_val, span)?); + Ok(()) + } + + fn eval_call(&mut self, callee_span: Span, args_span: Span) -> Result<(), Error> { + let args_val = self.pop_val(); + let call_val = self.pop_val(); + let (call, functor) = value_to_call_id(&call_val); + let decl = match self.globals.get(call) { + Some(Global::Callable(decl)) => Ok(decl), + Some(Global::Udt) => { + self.push_val(args_val); + return Ok(()); + } + None => Err(Error::Unbound(callee_span)), + }?; + let spec = spec_from_functor_app(functor); + + self.push_frame(Some(callee_span), call, functor); + + self.push_scope(); + + match (&decl.body, spec) { (CallableBody::Block(body_block), Spec::Body) => { - self.bind_value(&decl.input, args_val, args_span, Mutability::Immutable)?; - self.eval_block(body_block) + bind_value(self.env, &decl.input, args_val, Mutability::Immutable)?; + self.push_block(body_block); + Ok(()) } (CallableBody::Specs(spec_decls), spec) => { let spec_decl = spec_decls .iter() .find(|spec_decl| spec_decl.spec == spec) .map_or_else( - || Break(Reason::Error(Error::MissingSpec(spec, call_span))), - |spec_decl| Continue(&spec_decl.body), + || Err(Error::MissingSpec(spec, callee_span)), + |spec_decl| Ok(&spec_decl.body), )?; match spec_decl { SpecBody::Impl(pat, body_block) => { - self.bind_args_for_spec(&decl.input, pat, args_val, args_span, ctl_count)?; - self.eval_block(body_block) + bind_args_for_spec( + self.env, + &decl.input, + pat, + args_val, + args_span, + functor.controlled, + )?; + self.push_block(body_block); + Ok(()) + } + SpecBody::Gen(SpecGen::Intrinsic) => { + let val = invoke_intrinsic( + &decl.name.name, + callee_span, + args_val, + args_span, + self.out, + )?; + self.push_val(val); + Ok(()) } - SpecBody::Gen(SpecGen::Intrinsic) => invoke_intrinsic( - &decl.name.name, - call_span, - args_val, - args_span, - self.out - .as_deref_mut() - .expect("output receiver should be set"), - ), - SpecBody::Gen(_) => Break(Reason::Error(Error::MissingSpec(spec, call_span))), + SpecBody::Gen(_) => Err(Error::MissingSpec(spec, callee_span)), } } - _ => Break(Reason::Error(Error::MissingSpec(spec, call_span))), - }; - self.leave_scope()?; - res + _ => Err(Error::MissingSpec(spec, callee_span)), + } } - fn bind_args_for_spec( - &mut self, - decl_pat: &Pat, - spec_pat: &Pat, - args_val: Value, - args_span: Span, - ctl_count: u8, - ) -> ControlFlow { - match &spec_pat.kind { - PatKind::Bind(_) | PatKind::Discard => { - panic!("spec pattern should be elided or elided tuple, found bind/discard") - } - PatKind::Elided => { - self.bind_value(decl_pat, args_val, args_span, Mutability::Immutable) + fn eval_field(&mut self, field: &'a Field) { + let record = self.pop_val(); + let val = match (record, field) { + (Value::Range(Some(start), _, _), Field::Prim(PrimField::Start)) => Value::Int(start), + (Value::Range(_, step, _), Field::Prim(PrimField::Step)) => Value::Int(step), + (Value::Range(_, _, Some(end)), Field::Prim(PrimField::End)) => Value::Int(end), + (record, Field::Path(path)) => { + follow_field_path(record, &path.indices).expect("field path should be valid") } - PatKind::Tuple(pats) => { - assert_eq!(pats.len(), 2, "spec pattern tuple should have 2 elements"); - assert!( - ctl_count > 0, - "spec pattern tuple used without controlled functor" - ); - - let mut tup = args_val; - let mut ctls = vec![]; - for _ in 0..ctl_count { - let tup_nesting = tup.try_into_tuple().with_span(args_span)?; - if tup_nesting.len() != 2 { - return Break(Reason::Error(Error::TupleArity( - 2, - tup_nesting.len(), - args_span, - ))); - } - - let c = tup_nesting[0].clone(); - let rest = tup_nesting[1].clone(); - ctls.extend_from_slice(c.try_into_array().with_span(args_span)?.as_ref()); - tup = rest; - } + _ => panic!("invalid field access"), + }; + self.push_val(val); + } - self.bind_value( - &pats[0], - Value::Array(ctls.into()), - args_span, - Mutability::Immutable, - )?; - self.bind_value(decl_pat, tup, args_span, Mutability::Immutable) - } + fn eval_if(&mut self, then_block: &'a Block, else_expr: Option<&'a Expr>) { + if self.pop_val().unwrap_bool() { + self.push_block(then_block); + } else if let Some(else_expr) = else_expr { + self.push_expr(else_expr); + } else { + self.push_val(Value::unit()); } } - fn eval_unop(&mut self, expr: &Expr, op: UnOp, rhs: &Expr) -> ControlFlow { - let val = self.eval_expr(rhs)?; - match op { - UnOp::Neg => match val { - Value::BigInt(v) => Continue(Value::BigInt(v.neg())), - Value::Double(v) => Continue(Value::Double(v.neg())), - Value::Int(v) => Continue(Value::Int(v.wrapping_neg())), - _ => Break(Reason::Error(Error::Type( - "Int, BigInt, or Double", - val.type_name(), - rhs.span, - ))), - }, - UnOp::Pos => match val { - Value::BigInt(_) | Value::Int(_) | Value::Double(_) => Continue(val), - _ => Break(Reason::Error(Error::Type( - "Int, BigInt, or Double", - val.type_name(), - rhs.span, - ))), - }, - UnOp::NotL => match val { - Value::Bool(b) => Continue(Value::Bool(!b)), - _ => Break(Reason::Error(Error::Type( - "Bool", - val.type_name(), - rhs.span, - ))), - }, - UnOp::NotB => match val { - Value::Int(v) => Continue(Value::Int(!v)), - Value::BigInt(v) => Continue(Value::BigInt(!v)), - _ => Break(Reason::Error(Error::Type( - "Int or BigInt", - val.type_name(), - rhs.span, - ))), - }, - UnOp::Functor(functor) => match val { - Value::Closure => Break(Reason::Error(Error::Unimplemented("closure", expr.span))), - Value::Global(id, app) => { - Continue(Value::Global(id, update_functor_app(functor, app))) - } - _ => Break(Reason::Error(Error::Type( - "Callable", - val.type_name(), - rhs.span, - ))), - }, - UnOp::Unwrap => Continue(val), + fn eval_index(&mut self, span: Span) -> Result<(), Error> { + let index_val = self.pop_val(); + let arr = self.pop_val().unwrap_array(); + match &index_val { + Value::Int(i) => self.push_val(index_array(&arr, *i, span)?), + &Value::Range(start, step, end) => { + self.push_val(slice_array(&arr, start, step, end, span)?); + } + _ => panic!("array should only be indexed by Int or Range"), } + Ok(()) } - fn eval_binop(&mut self, op: BinOp, lhs: &Expr, rhs: &Expr) -> ControlFlow { - let lhs_val = self.eval_expr(lhs)?; - match op { - BinOp::Add => eval_binop_add(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::AndB => eval_binop_andb(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::AndL => self.eval_binop_andl(lhs_val.try_into().with_span(lhs.span)?, rhs), - BinOp::Div => eval_binop_div(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Eq => eval_binop_eq(&lhs_val, lhs.span, &self.eval_expr(rhs)?, rhs.span), - BinOp::Exp => eval_binop_exp(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Gt => eval_binop_gt(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Gte => eval_binop_gte(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Lt => eval_binop_lt(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Lte => eval_binop_lte(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Mod => eval_binop_mod(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Mul => eval_binop_mul(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Neq => eval_binop_neq(&lhs_val, lhs.span, &self.eval_expr(rhs)?, rhs.span), - BinOp::OrB => eval_binop_orb(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::OrL => self.eval_binop_orl(lhs_val.try_into().with_span(lhs.span)?, rhs), - BinOp::Shl => eval_binop_shl(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Shr => eval_binop_shr(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::Sub => eval_binop_sub(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - BinOp::XorB => eval_binop_xorb(lhs_val, lhs.span, self.eval_expr(rhs)?, rhs.span), - } - } - - fn eval_binop_andl(&mut self, lhs: bool, rhs: &Expr) -> ControlFlow { - Continue(Value::Bool( - lhs && self.eval_expr(rhs)?.try_into().with_span(rhs.span)?, - )) - } - - fn eval_binop_orl(&mut self, lhs: bool, rhs: &Expr) -> ControlFlow { - Continue(Value::Bool( - lhs || self.eval_expr(rhs)?.try_into().with_span(rhs.span)?, - )) - } - - fn eval_cond(&mut self, lhs: &Expr, mid: &Expr, rhs: &Expr) -> ControlFlow { - if self.eval_expr(lhs)?.try_into().with_span(lhs.span)? { - self.eval_expr(mid) + fn eval_range(&mut self, has_start: bool, has_step: bool, has_end: bool) { + let end = if has_end { + Some(self.pop_val().unwrap_int()) } else { - self.eval_expr(rhs) - } + None + }; + let step = if has_step { + self.pop_val().unwrap_int() + } else { + val::DEFAULT_RANGE_STEP + }; + let start = if has_start { + Some(self.pop_val().unwrap_int()) + } else { + None + }; + self.push_val(Value::Range(start, step, end)); } - fn eval_update_index( - &mut self, - lhs: &Expr, - mid: &Expr, - rhs: &Expr, - ) -> ControlFlow { - let values = self.eval_expr(lhs)?.try_into_array().with_span(lhs.span)?; - let index: i64 = self.eval_expr(mid)?.try_into().with_span(mid.span)?; - if index < 0 { - return Break(Reason::Error(Error::Negative(index, mid.span))); - } - - let mut values: Vec<_> = values.iter().cloned().collect(); - match values.get_mut(index.as_index(mid.span)?) { - Some(value) => { - *value = self.eval_expr(rhs)?; - Continue(Value::Array(values.into())) + fn eval_ret(&mut self) { + while let Some(cont) = self.pop_cont() { + match cont { + Cont::Frame(len) => { + self.leave_frame(len); + break; + } + Cont::Scope => self.leave_scope(), + _ => {} } - None => Break(Reason::Error(Error::OutOfRange(index, mid.span))), } } - fn eval_field(&mut self, record: &Expr, field: &Field) -> ControlFlow { - match (self.eval_expr(record)?, field) { - (Value::Range(Some(start), _, _), Field::Prim(PrimField::Start)) => { - Continue(Value::Int(start)) - } - (Value::Range(_, step, _), Field::Prim(PrimField::Step)) => Continue(Value::Int(step)), - (Value::Range(_, _, Some(end)), Field::Prim(PrimField::End)) => { - Continue(Value::Int(end)) - } - (record, Field::Path(path)) => Continue( - follow_field_path(record, &path.indices).expect("field path should be valid"), - ), - _ => panic!("invalid field access"), + fn eval_string_concat(&mut self, len: usize) { + let mut string = String::new(); + for component in self.pop_vals(len) { + write!(string, "{component}").expect("string should be writable"); } + self.push_val(Value::String(string.into())); } - fn eval_update_field( - &mut self, - record: &Expr, - field: &Field, - value: &Expr, - ) -> ControlFlow { - let record = self.eval_expr(record)?; - let value_span = value.span; - let value = self.eval_expr(value)?; - match (record, field) { - (Value::Range(_, step, end), Field::Prim(PrimField::Start)) => Continue(Value::Range( - Some(value.try_into().with_span(value_span)?), - step, - end, - )), - (Value::Range(start, _, end), Field::Prim(PrimField::Step)) => Continue(Value::Range( - start, - value.try_into().with_span(value_span)?, - end, - )), - (Value::Range(start, step, _), Field::Prim(PrimField::End)) => Continue(Value::Range( - start, - step, - Some(value.try_into().with_span(value_span)?), - )), - (record, Field::Path(path)) => Continue( - update_field_path(&record, &path.indices, &value) - .expect("field path should be valid"), - ), - _ => panic!("invalid field access"), + fn eval_ternop(&mut self, op: TernOp, mid: &'a Expr, rhs: &'a Expr) -> Result<(), Error> { + match op { + TernOp::Cond => { + if self.pop_val().unwrap_bool() { + self.push_expr(mid); + } else { + self.push_expr(rhs); + } + } + TernOp::UpdateIndex => { + let values = self.pop_val().unwrap_array(); + let update = self.pop_val(); + let index = self.pop_val().unwrap_int(); + if index < 0 { + return Err(Error::Negative(index, mid.span)); + } + let i = index.as_index(mid.span)?; + let mut values = values.iter().cloned().collect::>(); + match values.get_mut(i) { + Some(value) => { + *value = update; + } + None => return Err(Error::OutOfRange(index, mid.span)), + } + self.push_val(Value::Array(values.into())); + } } + Ok(()) } - fn enter_scope(&mut self) { - self.env.0.push(Scope::default()); + fn eval_tup(&mut self, len: usize) { + let tup = self.pop_vals(len); + self.push_val(Value::Tuple(tup.into())); } - fn track_qubits(&mut self, mut qubits: Vec<(Qubit, Span)>) { - self.env - .0 - .last_mut() - .expect("scope should have been entered to track qubits") - .qubits - .append(&mut qubits); + fn eval_unop(&mut self, op: UnOp) { + let val = self.pop_val(); + match op { + UnOp::Functor(functor) => match val { + Value::Closure => panic!("closure should be disallowed by passes"), + Value::Global(id, app) => { + self.push_val(Value::Global(id, update_functor_app(functor, app))); + } + _ => panic!("value should be callable"), + }, + UnOp::Neg => match val { + Value::BigInt(v) => self.push_val(Value::BigInt(v.neg())), + Value::Double(v) => self.push_val(Value::Double(v.neg())), + Value::Int(v) => self.push_val(Value::Int(v.wrapping_neg())), + _ => panic!("value should be number"), + }, + UnOp::NotB => match val { + Value::Int(v) => self.push_val(Value::Int(!v)), + Value::BigInt(v) => self.push_val(Value::BigInt(!v)), + _ => panic!("value should be Int or BigInt"), + }, + UnOp::NotL => match val { + Value::Bool(b) => self.push_val(Value::Bool(!b)), + _ => panic!("value should be bool"), + }, + UnOp::Pos => match val { + Value::BigInt(_) | Value::Int(_) | Value::Double(_) => self.push_val(val), + _ => panic!("value should be number"), + }, + UnOp::Unwrap => self.push_val(val), + } } - fn leave_scope(&mut self) -> ControlFlow { - for (qubit, span) in self - .env - .0 - .pop() - .expect("scope should be entered first before leaving") - .qubits - { - if !qubit_is_zero(qubit.0) { - return ControlFlow::Break(Reason::Error(Error::ReleasedQubitNotZero( - qubit.0 as usize, - span, - ))); + fn eval_update_field(&mut self, field: &'a Field) { + let value = self.pop_val(); + let record = self.pop_val(); + let update = match (record, field) { + (Value::Range(_, step, end), Field::Prim(PrimField::Start)) => { + Value::Range(Some(value.unwrap_int()), step, end) + } + (Value::Range(start, _, end), Field::Prim(PrimField::Step)) => { + Value::Range(start, value.unwrap_int(), end) + } + (Value::Range(start, step, _), Field::Prim(PrimField::End)) => { + Value::Range(start, step, Some(value.unwrap_int())) } - __quantum__rt__qubit_release(qubit.0); + (record, Field::Path(path)) => update_field_path(&record, &path.indices, &value) + .expect("field path should be valid"), + _ => panic!("invalid field access"), + }; + self.push_val(update); + } + + fn eval_while(&mut self, cond_expr: &'a Expr, block: &'a Block) { + if self.pop_val().unwrap_bool() { + self.cont_while(cond_expr, block); + self.push_action(Action::Consume); + self.push_val(Value::unit()); + self.push_block(block); + } else { + self.push_val(Value::unit()); } - ControlFlow::Continue(()) } +} - fn bind_value( - &mut self, - pat: &Pat, - value: Value, - span: Span, - mutability: Mutability, - ) -> ControlFlow { - match &pat.kind { - PatKind::Bind(variable) => { - let scope = self.env.0.last_mut().expect("binding should have a scope"); - match scope.bindings.entry(variable.id) { - Entry::Vacant(entry) => entry.insert(Variable { value, mutability }), - Entry::Occupied(_) => panic!("duplicate binding"), - }; - Continue(()) - } - PatKind::Discard => Continue(()), - PatKind::Elided => panic!("elision used in binding"), - PatKind::Tuple(tup) => { - let val_tup = value.try_into_tuple().with_span(span)?; - if val_tup.len() == tup.len() { - for (pat, val) in tup.iter().zip(val_tup.iter()) { - self.bind_value(pat, val.clone(), span, mutability)?; - } - Continue(()) - } else { - Break(Reason::Error(Error::TupleArity( - tup.len(), - val_tup.len(), - pat.span, - ))) +fn bind_value(env: &mut Env, pat: &Pat, val: Value, mutability: Mutability) -> Result<(), Error> { + match &pat.kind { + PatKind::Bind(variable) => { + let scope = env.0.last_mut().expect("binding should have a scope"); + match scope.bindings.entry(variable.id) { + Entry::Vacant(entry) => entry.insert(Variable { + value: val, + mutability, + }), + Entry::Occupied(_) => panic!("duplicate binding"), + }; + Ok(()) + } + PatKind::Discard => Ok(()), + PatKind::Elided => panic!("elision used in binding"), + PatKind::Tuple(tup) => { + let val_tup = val.unwrap_tuple(); + if val_tup.len() == tup.len() { + for (pat, val) in tup.iter().zip(val_tup.iter()) { + bind_value(env, pat, val.clone(), mutability)?; } + Ok(()) + } else { + Err(Error::TupleArity(tup.len(), val_tup.len(), pat.span)) } } } +} - fn resolve_binding(&mut self, res: Res, span: Span) -> Result { - Ok(match res { - Res::Err => panic!("resolution error"), - Res::Item(item) => Value::Global( - GlobalId { - package: item.package.unwrap_or(self.package), - item: item.item, - }, - FunctorApp::default(), - ), - Res::Local(node) => self - .env - .get(node) - .ok_or(Error::Unbound(span))? - .value - .clone(), - }) - } - - #[allow(clippy::similar_names)] - fn update_binding(&mut self, lhs: &Expr, rhs: Value) -> ControlFlow { - match (&lhs.kind, rhs) { - (ExprKind::Hole, _) => Continue(Value::unit()), - (&ExprKind::Var(Res::Local(node)), rhs) => match self.env.get_mut(node) { - Some(var) if var.is_mutable() => { - var.value = rhs; - Continue(Value::unit()) - } - Some(_) => Break(Reason::Error(Error::Mutability(lhs.span))), - None => Break(Reason::Error(Error::Unbound(lhs.span))), +fn resolve_binding( + env: &mut Env, + package: PackageId, + res: Res, + span: Span, +) -> Result { + Ok(match res { + Res::Err => panic!("resolution error"), + Res::Item(item) => Value::Global( + GlobalId { + package: item.package.unwrap_or(package), + item: item.item, }, - (ExprKind::Tuple(var_tup), Value::Tuple(tup)) => { - if var_tup.len() == tup.len() { - for (expr, val) in var_tup.iter().zip(tup.iter()) { - self.update_binding(expr, val.clone())?; - } - Continue(Value::unit()) - } else { - Break(Reason::Error(Error::TupleArity( - var_tup.len(), - tup.len(), - lhs.span, - ))) + FunctorApp::default(), + ), + Res::Local(node) => env.get(node).ok_or(Error::Unbound(span))?.value.clone(), + }) +} + +#[allow(clippy::similar_names)] +fn update_binding(env: &mut Env, lhs: &Expr, rhs: Value) -> Result<(), Error> { + match (&lhs.kind, rhs) { + (ExprKind::Hole, _) => {} + (&ExprKind::Var(Res::Local(node)), rhs) => match env.get_mut(node) { + Some(var) if var.is_mutable() => { + var.value = rhs; + } + Some(_) => return Err(Error::Mutability(lhs.span)), + None => return Err(Error::Unbound(lhs.span)), + }, + (ExprKind::Tuple(var_tup), Value::Tuple(tup)) => { + if var_tup.len() == tup.len() { + for (expr, val) in var_tup.iter().zip(tup.iter()) { + update_binding(env, expr, val.clone())?; } + } else { + return Err(Error::TupleArity(var_tup.len(), tup.len(), lhs.span)); } - _ => Break(Reason::Error(Error::Unassignable(lhs.span))), } + _ => return Err(Error::Unassignable(lhs.span)), } + Ok(()) +} - fn push_frame(&mut self, frame: Frame) { - self.call_stack.push_frame(frame); - } +fn bind_args_for_spec( + env: &mut Env, + decl_pat: &Pat, + spec_pat: &Pat, + args_val: Value, + args_span: Span, + ctl_count: u8, +) -> Result<(), Error> { + match &spec_pat.kind { + PatKind::Bind(_) | PatKind::Discard => { + panic!("spec pattern should be elided or elided tuple, found bind/discard") + } + PatKind::Elided => bind_value(env, decl_pat, args_val, Mutability::Immutable), + PatKind::Tuple(pats) => { + assert_eq!(pats.len(), 2, "spec pattern tuple should have 2 elements"); + assert!( + ctl_count > 0, + "spec pattern tuple used without controlled functor" + ); + + let mut tup = args_val; + let mut ctls = vec![]; + for _ in 0..ctl_count { + let tup_nesting = tup.unwrap_tuple(); + if tup_nesting.len() != 2 { + return Err(Error::TupleArity(2, tup_nesting.len(), args_span)); + } + + let c = tup_nesting[0].clone(); + let rest = tup_nesting[1].clone(); + ctls.extend_from_slice(&c.unwrap_array()); + tup = rest; + } - fn pop_frame(&mut self) { - self.call_stack.pop_frame(); + bind_value( + env, + &pats[0], + Value::Array(ctls.into()), + Mutability::Immutable, + )?; + bind_value(env, decl_pat, tup, Mutability::Immutable) + } } } @@ -1066,15 +1139,11 @@ fn spec_from_functor_app(functor: FunctorApp) -> Spec { } } -fn value_to_call_id(val: &Value, span: Span) -> ControlFlow { +fn value_to_call_id(val: &Value) -> (GlobalId, FunctorApp) { match val { - Value::Closure => Break(Reason::Error(Error::Unimplemented("closure", span))), - Value::Global(global, functor) => Continue((*global, *functor)), - _ => Break(Reason::Error(Error::Type( - "Callable", - val.type_name(), - span, - ))), + Value::Closure => panic!("closure not supported"), + Value::Global(global, functor) => (*global, *functor), + _ => panic!("value is not call id"), } } @@ -1090,10 +1159,11 @@ fn lit_to_val(lit: &Lit) -> Value { } } -fn index_array(arr: &[Value], index: i64, span: Span) -> ControlFlow { - match arr.get(index.as_index(span)?) { - Some(v) => Continue(v.clone()), - None => Break(Reason::Error(Error::OutOfRange(index, span))), +fn index_array(arr: &[Value], index: i64, span: Span) -> Result { + let i = index.as_index(span)?; + match arr.get(i) { + Some(v) => Ok(v.clone()), + None => Err(Error::OutOfRange(index, span)), } } @@ -1103,13 +1173,13 @@ fn slice_array( step: i64, end: Option, span: Span, -) -> ControlFlow { +) -> Result { if step == 0 { - Break(Reason::Error(Error::RangeStepZero(span))) + Err(Error::RangeStepZero(span)) } else { let len: i64 = match arr.len().try_into() { - Ok(len) => Continue(len), - Err(_) => Break(Reason::Error(Error::ArrayTooLarge(span))), + Ok(len) => Ok(len), + Err(_) => Err(Error::ArrayTooLarge(span)), }?; let (start, end) = if step > 0 { (start.unwrap_or(0), end.unwrap_or(len - 1)) @@ -1123,502 +1193,320 @@ fn slice_array( slice.push(index_array(arr, i, span)?); } - Continue(Value::Array(slice.into())) + Ok(Value::Array(slice.into())) } } -fn update_functor_app(functor: Functor, app: FunctorApp) -> FunctorApp { - match functor { - Functor::Adj => FunctorApp { - adjoint: !app.adjoint, - controlled: app.controlled, - }, - Functor::Ctl => FunctorApp { - adjoint: app.adjoint, - controlled: app.controlled + 1, - }, - } -} - -fn eval_binop_add( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_add(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::Array(arr) => { - let rhs_arr = rhs_val.try_into_array().with_span(rhs_span)?; + let rhs_arr = rhs_val.unwrap_array(); let items: Vec<_> = arr.iter().cloned().chain(rhs_arr.iter().cloned()).collect(); - Continue(Value::Array(items.into())) + Value::Array(items.into()) } Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val + rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val + rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Double(val + rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Double(val + rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val + rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Int(val + rhs) } Value::String(val) => { - let rhs = rhs_val.try_into_string().with_span(rhs_span)?; - Continue(Value::String((val.to_string() + &rhs).into())) + let rhs = rhs_val.unwrap_string(); + Value::String((val.to_string() + &rhs).into()) } - _ => Break(Reason::Error(Error::Type( - "Array, BigInt, Double, Int, or String", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value is not addable"), } } -fn eval_binop_andb( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_andb(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val & rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val & rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val & rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Int(val & rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value type does not support andb"), } } -fn eval_binop_div( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_div(lhs_val: Value, rhs_val: Value, rhs_span: Span) -> Result { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_big_int(); if rhs == BigInt::from(0) { - Break(Reason::Error(Error::DivZero(rhs_span))) + Err(Error::DivZero(rhs_span)) } else { - Continue(Value::BigInt(val / rhs)) + Ok(Value::BigInt(val / rhs)) } } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_int(); if rhs == 0 { - Break(Reason::Error(Error::DivZero(rhs_span))) + Err(Error::DivZero(rhs_span)) } else { - Continue(Value::Int(val / rhs)) + Ok(Value::Int(val / rhs)) } } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_double(); if rhs == 0.0 { - Break(Reason::Error(Error::DivZero(rhs_span))) + Err(Error::DivZero(rhs_span)) } else { - Continue(Value::Double(val / rhs)) + Ok(Value::Double(val / rhs)) } } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), - } -} - -fn supports_eq(val: &Value, val_span: Span) -> ControlFlow { - match val { - Value::Closure | Value::Global(..) => { - Break(Reason::Error(Error::Equality(val.type_name(), val_span))) - } - _ => Continue(()), - } -} - -fn eval_binop_eq( - lhs_val: &Value, - lhs_span: Span, - rhs_val: &Value, - rhs_span: Span, -) -> ControlFlow { - supports_eq(lhs_val, lhs_span)?; - if lhs_val.type_name() == rhs_val.type_name() { - Continue(Value::Bool(lhs_val == rhs_val)) - } else { - Break(Reason::Error(Error::Type( - lhs_val.type_name(), - rhs_val.type_name(), - rhs_span, - ))) + _ => panic!("value should support div"), } } -fn eval_binop_exp( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_exp(lhs_val: Value, rhs_val: Value, rhs_span: Span) -> Result { match lhs_val { Value::BigInt(val) => { - let rhs_val: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs_val = rhs_val.unwrap_int(); if rhs_val < 0 { - Break(Reason::Error(Error::Negative(rhs_val, rhs_span))) + Err(Error::Negative(rhs_val, rhs_span)) } else { let rhs_val: u32 = match rhs_val.try_into() { - Ok(v) => Continue(v), - Err(_) => Break(Reason::Error(Error::IntTooLarge(rhs_val, rhs_span))), + Ok(v) => Ok(v), + Err(_) => Err(Error::IntTooLarge(rhs_val, rhs_span)), }?; - Continue(Value::BigInt(val.pow(rhs_val))) + Ok(Value::BigInt(val.pow(rhs_val))) } } - Value::Double(val) => Continue(Value::Double( - val.powf(rhs_val.try_into().with_span(rhs_span)?), - )), + Value::Double(val) => Ok(Value::Double(val.powf(rhs_val.unwrap_double()))), Value::Int(val) => { - let rhs_val: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs_val = rhs_val.unwrap_int(); if rhs_val < 0 { - Break(Reason::Error(Error::Negative(rhs_val, rhs_span))) + Err(Error::Negative(rhs_val, rhs_span)) } else { let rhs_val: u32 = match rhs_val.try_into() { - Ok(v) => Continue(v), - Err(_) => Break(Reason::Error(Error::IntTooLarge(rhs_val, rhs_span))), + Ok(v) => Ok(v), + Err(_) => Err(Error::IntTooLarge(rhs_val, rhs_span)), }?; - Continue(Value::Int(val.pow(rhs_val))) + Ok(Value::Int(val.pow(rhs_val))) } } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value should support exp"), } } -fn eval_binop_gt( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_gt(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val > rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::Bool(val > rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val > rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Bool(val > rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val > rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Bool(val > rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value doesn't support binop gt"), } } -fn eval_binop_gte( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_gte(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val >= rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::Bool(val >= rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val >= rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Bool(val >= rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val >= rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Bool(val >= rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value doesn't support binop gte"), } } -fn eval_binop_lt( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_lt(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val < rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::Bool(val < rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val < rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Bool(val < rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val < rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Bool(val < rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value doesn't support binop lt"), } } -fn eval_binop_lte( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_lte(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val <= rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::Bool(val <= rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val <= rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Bool(val <= rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Bool(val <= rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Bool(val <= rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value doesn't support binop lte"), } } -fn eval_binop_mod( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_mod(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val % rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val % rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val % rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Int(val % rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Double(val % rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Double(val % rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value should support mod"), } } -fn eval_binop_mul( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_mul(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val * rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val * rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val * rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Int(val * rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Double(val * rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Double(val * rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), - } -} - -fn eval_binop_neq( - lhs_val: &Value, - lhs_span: Span, - rhs_val: &Value, - rhs_span: Span, -) -> ControlFlow { - supports_eq(lhs_val, lhs_span)?; - if lhs_val.type_name() == rhs_val.type_name() { - Continue(Value::Bool(lhs_val != rhs_val)) - } else { - Break(Reason::Error(Error::Type( - lhs_val.type_name(), - rhs_val.type_name(), - rhs_span, - ))) + _ => panic!("value should support mul"), } } -fn eval_binop_orb( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_orb(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val | rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val | rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val | rhs)) + let rhs = rhs_val.unwrap_int(); + Value::Int(val | rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value type does not support orb"), } } -fn eval_binop_shl( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_shl(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_int(); if rhs > 0 { - Continue(Value::BigInt(val << rhs)) + Value::BigInt(val << rhs) } else { - Continue(Value::BigInt(val >> rhs.abs())) + Value::BigInt(val >> rhs.abs()) } } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_int(); if rhs > 0 { - Continue(Value::Int(val << rhs)) + Value::Int(val << rhs) } else { - Continue(Value::Int(val >> rhs.abs())) + Value::Int(val >> rhs.abs()) } } - _ => Break(Reason::Error(Error::Type( - "BigInt or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value should support shl"), } } -fn eval_binop_shr( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_shr(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_int(); if rhs > 0 { - Continue(Value::BigInt(val >> rhs)) + Value::BigInt(val >> rhs) } else { - Continue(Value::BigInt(val << rhs.abs())) + Value::BigInt(val << rhs.abs()) } } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; + let rhs = rhs_val.unwrap_int(); if rhs > 0 { - Continue(Value::Int(val >> rhs)) + Value::Int(val >> rhs) } else { - Continue(Value::Int(val << rhs.abs())) + Value::Int(val << rhs.abs()) } } - _ => Break(Reason::Error(Error::Type( - "BigInt or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value should support shr"), } } -fn eval_binop_sub( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_sub(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val - rhs)) - } - Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val - rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val - rhs) } Value::Double(val) => { - let rhs: f64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Double(val - rhs)) + let rhs = rhs_val.unwrap_double(); + Value::Double(val - rhs) + } + Value::Int(val) => { + let rhs = rhs_val.unwrap_int(); + Value::Int(val - rhs) } - _ => Break(Reason::Error(Error::Type( - "BigInt, Double, or Int", - lhs_val.type_name(), - lhs_span, - ))), + _ => panic!("value is not addable"), } } -fn eval_binop_xorb( - lhs_val: Value, - lhs_span: Span, - rhs_val: Value, - rhs_span: Span, -) -> ControlFlow { +fn eval_binop_xorb(lhs_val: Value, rhs_val: Value) -> Value { match lhs_val { Value::BigInt(val) => { - let rhs: BigInt = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::BigInt(val ^ rhs)) + let rhs = rhs_val.unwrap_big_int(); + Value::BigInt(val ^ rhs) } Value::Int(val) => { - let rhs: i64 = rhs_val.try_into().with_span(rhs_span)?; - Continue(Value::Int(val ^ rhs)) - } - _ => Break(Reason::Error(Error::Type( - "BigInt or Int", - lhs_val.type_name(), - lhs_span, - ))), + let rhs = rhs_val.unwrap_int(); + Value::Int(val ^ rhs) + } + _ => panic!("value type does not support xorb"), + } +} + +fn update_functor_app(functor: Functor, app: FunctorApp) -> FunctorApp { + match functor { + Functor::Adj => FunctorApp { + adjoint: !app.adjoint, + controlled: app.controlled, + }, + Functor::Ctl => FunctorApp { + adjoint: app.adjoint, + controlled: app.controlled + 1, + }, } } diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 48d03fe44a..a83bbe30af 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -6,10 +6,12 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_frontend::compile::{self, compile, PackageStore, SourceMap}; use qsc_hir::hir::ItemKind; -use qsc_passes::run_default_passes; +use qsc_passes::{run_core_passes, run_default_passes}; fn check_expr(file: &str, expr: &str, expect: &Expect) { - let mut store = PackageStore::new(compile::core()); + let mut core = compile::core(); + run_core_passes(&mut core); + let mut store = PackageStore::new(core); let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into())); let mut unit = compile(&store, &[], sources); assert!(unit.errors.is_empty(), "{:?}", unit.errors); @@ -69,6 +71,11 @@ fn block_expr() { ); } +#[test] +fn block_empty_is_unit_expr() { + check_expr("", "{}", &expect!["()"]); +} + #[test] fn block_shadowing_expr() { check_expr( @@ -307,15 +314,39 @@ fn block_qubit_use_array_invalid_count_expr() { }"}, &expect![[r#" ( - Count( - -3, + UserFail( + "Cannot allocate qubit array with a negative length", Span { - lo: 20, - hi: 22, + lo: 758, + hi: 815, }, ), CallStack { - frames: [], + frames: [ + Frame { + span: Some( + Span { + lo: 10, + hi: 11, + }, + ), + id: GlobalId { + package: PackageId( + 0, + ), + item: LocalItemId( + 5, + ), + }, + caller: PackageId( + 1, + ), + functor: FunctorApp { + adjoint: false, + controlled: 0, + }, + }, + ], }, ) "#]], @@ -406,29 +437,6 @@ fn binop_andb_int() { check_expr("", "28 &&& 54", &expect!["20"]); } -#[test] -fn binop_andb_invalid() { - check_expr( - "", - "2.8 &&& 5.4", - &expect![[r#" - ( - Type( - "BigInt or Int", - "Double", - Span { - lo: 0, - hi: 3, - }, - ), - CallStack { - frames: [], - }, - ) - "#]], - ); -} - #[test] fn binop_andl() { check_expr("", "true and true", &expect!["true"]); @@ -1050,29 +1058,6 @@ fn binop_orb_int() { check_expr("", "28 ||| 54", &expect!["62"]); } -#[test] -fn binop_orb_invalid() { - check_expr( - "", - "2.8 ||| 5.4", - &expect![[r#" - ( - Type( - "BigInt or Int", - "Double", - Span { - lo: 0, - hi: 3, - }, - ), - CallStack { - frames: [], - }, - ) - "#]], - ); -} - #[test] fn binop_orl() { check_expr("", "true or true", &expect!["true"]); @@ -1163,29 +1148,6 @@ fn binop_xorb_int() { check_expr("", "28 ^^^ 54", &expect!["42"]); } -#[test] -fn binop_xorb_invalid() { - check_expr( - "", - "2.8 ^^^ 5.4", - &expect![[r#" - ( - Type( - "BigInt or Int", - "Double", - Span { - lo: 0, - hi: 3, - }, - ), - CallStack { - frames: [], - }, - ) - "#]], - ); -} - #[test] fn assignop_add_expr() { check_expr( diff --git a/compiler/qsc_eval/src/val.rs b/compiler/qsc_eval/src/val.rs index b5c87b8de8..cc953428ce 100644 --- a/compiler/qsc_eval/src/val.rs +++ b/compiler/qsc_eval/src/val.rs @@ -123,129 +123,142 @@ impl Display for Value { } } -pub struct ConversionError { - pub expected: &'static str, - pub actual: &'static str, -} - -impl TryFrom for i64 { - type Error = ConversionError; - - fn try_from(value: Value) -> Result { - if let Value::Int(v) = value { - Ok(v) - } else { - Err(ConversionError { - expected: "Int", - actual: value.type_name(), - }) - } +impl Value { + #[must_use] + pub fn unit() -> Self { + Self::Tuple([].as_slice().into()) } -} -impl TryFrom for BigInt { - type Error = ConversionError; + /// Convert the [Value] into an array of [Value] + /// # Panics + /// This will panic if the [Value] is not a [`Value::Array`]. + #[must_use] + pub fn unwrap_array(self) -> Rc<[Self]> { + let Value::Array(v) = self else { + panic!("value should be Array, got {}", self.type_name()); + }; + v + } - fn try_from(value: Value) -> Result { - if let Value::BigInt(v) = value { - Ok(v) - } else { - Err(ConversionError { - expected: "BigInt", - actual: value.type_name(), - }) - } + /// Convert the [Value] into a `BigInt` + /// # Panics + /// This will panic if the [Value] is not a [`Value::BigInt`]. + #[must_use] + pub fn unwrap_big_int(self) -> BigInt { + let Value::BigInt(v) = self else { + panic!("value should be BigInt, got {}", self.type_name()); + }; + v } -} -impl TryFrom for bool { - type Error = ConversionError; + /// Convert the [Value] into a bool + /// # Panics + /// This will panic if the [Value] is not a [`Value::Bool`]. + #[must_use] + pub fn unwrap_bool(self) -> bool { + let Value::Bool(v) = self else { + panic!("value should be Bool, got {}", self.type_name()); + }; + v + } - fn try_from(value: Value) -> Result { - if let Value::Bool(v) = value { - Ok(v) - } else { - Err(ConversionError { - expected: "Bool", - actual: value.type_name(), - }) - } + /// Convert the [Value] into a double + /// # Panics + /// This will panic if the [Value] is not a [`Value::Double`]. + #[must_use] + pub fn unwrap_double(self) -> f64 { + let Value::Double(v) = self else { + panic!("value should be Double, got {}", self.type_name()); + }; + v } -} -impl TryFrom for *mut c_void { - type Error = ConversionError; + /// Convert the [Value] into a global tuple + /// # Panics + /// This will panic if the [Value] is not a [`Value::Global`]. + #[must_use] + pub fn unwrap_global(self) -> (GlobalId, FunctorApp) { + let Value::Global(id, functor) = self else { + panic!("value should be Global, got {}", self.type_name()); + }; + (id, functor) + } - fn try_from(value: Value) -> Result { - if let Value::Qubit(q) = value { - Ok(q.0) - } else { - Err(ConversionError { - expected: "Qubit", - actual: value.type_name(), - }) - } + /// Convert the [Value] into an integer + /// # Panics + /// This will panic if the [Value] is not a [`Value::Int`]. + #[must_use] + pub fn unwrap_int(self) -> i64 { + let Value::Int(v) = self else { + panic!("value should be Int, got {}", self.type_name()); + }; + v } -} -impl TryFrom for f64 { - type Error = ConversionError; + /// Convert the [Value] into a Pauli + /// # Panics + /// This will panic if the [Value] is not a [`Value::Pauli`]. + #[must_use] + pub fn unwrap_pauli(self) -> Pauli { + let Value::Pauli(v) = self else { + panic!("value should be Pauli, got {}", self.type_name()); + }; + v + } - fn try_from(value: Value) -> Result { - if let Value::Double(v) = value { - Ok(v) - } else { - Err(ConversionError { - expected: "Double", - actual: value.type_name(), - }) - } + /// Convert the [Value] into a qubit + /// # Panics + /// This will panic if the [Value] is not a [`Value::Qubit`]. + #[must_use] + pub fn unwrap_qubit(self) -> Qubit { + let Value::Qubit(v) = self else { + panic!("value should be Qubit, got {}", self.type_name()); + }; + v } -} -impl Value { + /// Convert the [Value] into a range tuple + /// # Panics + /// This will panic if the [Value] is not a [`Value::Range`]. #[must_use] - pub fn unit() -> Self { - Self::Tuple([].as_slice().into()) + pub fn unwrap_range(self) -> (Option, i64, Option) { + let Value::Range(start, step, end) = self else { + panic!("value should be Range, got {}", self.type_name()); + }; + (start, step, end) } - /// Convert the [Value] into an array of [Value] - /// # Errors - /// This will return an error if the [Value] is not a [`Value::Array`]. - pub fn try_into_array(self) -> Result, ConversionError> { - if let Value::Array(v) = self { - Ok(v) - } else { - Err(ConversionError { - expected: "Array", - actual: self.type_name(), - }) - } + /// Convert the [Value] into a measurement result + /// # Panics + /// This will panic if the [Value] is not a [`Value::Result`]. + #[must_use] + pub fn unwrap_result(self) -> bool { + let Value::Result(v) = self else { + panic!("value should be Result, got {}", self.type_name()); + }; + v } - pub(super) fn try_into_string(self) -> Result, ConversionError> { - if let Value::String(s) = self { - Ok(s) - } else { - Err(ConversionError { - expected: "String", - actual: self.type_name(), - }) - } + /// Convert the [Value] into a string + /// # Panics + /// This will panic if the [Value] is not a [`Value::String`]. + #[must_use] + pub fn unwrap_string(self) -> Rc { + let Value::String(v) = self else { + panic!("value should be String, got {}", self.type_name()); + }; + v } - /// Convert the [Value] into an tuple of [Value] - /// # Errors - /// This will return an error if the [Value] is not a [`Value::Tuple`]. - pub fn try_into_tuple(self) -> Result, ConversionError> { - if let Value::Tuple(v) = self { - Ok(v) - } else { - Err(ConversionError { - expected: "Tuple", - actual: self.type_name(), - }) - } + /// Convert the [Value] into an array of [Value] + /// # Panics + /// This will panic if the [Value] is not a [`Value::Tuple`]. + #[must_use] + pub fn unwrap_tuple(self) -> Rc<[Self]> { + let Value::Tuple(v) = self else { + panic!("value should be Tuple, got {}", self.type_name()); + }; + v } #[must_use] diff --git a/compiler/qsc_frontend/src/typeck/rules.rs b/compiler/qsc_frontend/src/typeck/rules.rs index d9dbd285d4..2aeefc9aa0 100644 --- a/compiler/qsc_frontend/src/typeck/rules.rs +++ b/compiler/qsc_frontend/src/typeck/rules.rs @@ -489,13 +489,13 @@ impl<'a> Context<'a> { self.inferrer.class(lhs_span, Class::Num(lhs.ty)); converge(Ty::Prim(PrimTy::Bool)) } - BinOp::AndB - | BinOp::Div - | BinOp::Mod - | BinOp::Mul - | BinOp::OrB - | BinOp::Sub - | BinOp::XorB => { + BinOp::AndB | BinOp::OrB | BinOp::XorB => { + self.inferrer.eq(span, lhs.ty.clone(), rhs.ty); + self.inferrer + .class(lhs_span, Class::Integral(lhs.ty.clone())); + lhs + } + BinOp::Div | BinOp::Mod | BinOp::Mul | BinOp::Sub => { self.inferrer.eq(span, lhs.ty.clone(), rhs.ty); self.inferrer.class(lhs_span, Class::Num(lhs.ty.clone())); lhs diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs index aa036d2aa5..61f7c8fd01 100644 --- a/compiler/qsc_frontend/src/typeck/tests.rs +++ b/compiler/qsc_frontend/src/typeck/tests.rs @@ -376,6 +376,20 @@ fn binop_add_mismatch() { ); } +#[test] +fn binop_andb_invalid() { + check( + "", + "2.8 &&& 5.4", + &expect![[r##" + #0 0-11 "2.8 &&& 5.4" : Double + #1 0-3 "2.8" : Double + #2 8-11 "5.4" : Double + Error(Type(Error(MissingClass(Integral(Prim(Double)), Span { lo: 0, hi: 3 })))) + "##]], + ); +} + #[test] fn binop_andb_mismatch() { check( @@ -523,6 +537,20 @@ fn binop_neq_tuple_arity_mismatch() { ); } +#[test] +fn binop_orb_invalid() { + check( + "", + "2.8 ||| 5.4", + &expect![[r##" + #0 0-11 "2.8 ||| 5.4" : Double + #1 0-3 "2.8" : Double + #2 8-11 "5.4" : Double + Error(Type(Error(MissingClass(Integral(Prim(Double)), Span { lo: 0, hi: 3 })))) + "##]], + ); +} + #[test] fn binop_orb_mismatch() { check( @@ -537,6 +565,20 @@ fn binop_orb_mismatch() { ); } +#[test] +fn binop_xorb_invalid() { + check( + "", + "2.8 ^^^ 5.4", + &expect![[r##" + #0 0-11 "2.8 ^^^ 5.4" : Double + #1 0-3 "2.8" : Double + #2 8-11 "5.4" : Double + Error(Type(Error(MissingClass(Integral(Prim(Double)), Span { lo: 0, hi: 3 })))) + "##]], + ); +} + #[test] fn binop_xorb_mismatch() { check( diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs index e6f7722dcf..7fdfb8eeda 100644 --- a/compiler/qsc_passes/src/lib.rs +++ b/compiler/qsc_passes/src/lib.rs @@ -12,13 +12,16 @@ pub mod loop_unification; pub mod replace_qubit_allocation; pub mod spec_gen; +use loop_unification::LoopUni; use miette::Diagnostic; use qsc_frontend::{compile::CompileUnit, incremental::Fragment}; use qsc_hir::{ assigner::Assigner, - global::Table, + global::{self, Table}, hir::{Item, ItemKind}, + mut_visit::MutVisitor, }; +use replace_qubit_allocation::ReplaceQubitAllocation; use thiserror::Error; #[derive(Clone, Debug, Diagnostic, Error)] @@ -35,6 +38,13 @@ pub fn run_default_passes(core: &Table, unit: &mut CompileUnit) -> Vec { let spec_errors = spec_gen::generate_specs(core, unit); let conjugate_errors = conjugate_invert::invert_conjugate_exprs(core, unit); + LoopUni { + core, + assigner: &mut unit.assigner, + } + .visit_package(&mut unit.package); + ReplaceQubitAllocation::new(core, &mut unit.assigner).visit_package(&mut unit.package); + spec_errors .into_iter() .map(Error::SpecGen) @@ -42,6 +52,16 @@ pub fn run_default_passes(core: &Table, unit: &mut CompileUnit) -> Vec { .collect() } +pub fn run_core_passes(core: &mut CompileUnit) { + let table = global::iter_package(None, &core.package).collect(); + LoopUni { + core: &table, + assigner: &mut core.assigner, + } + .visit_package(&mut core.package); + ReplaceQubitAllocation::new(&table, &mut core.assigner).visit_package(&mut core.package); +} + pub fn run_default_passes_for_fragment( core: &Table, assigner: &mut Assigner, @@ -56,6 +76,8 @@ pub fn run_default_passes_for_fragment( .into_iter() .map(Error::ConjInvert), ); + LoopUni { core, assigner }.visit_stmt(stmt); + ReplaceQubitAllocation::new(core, assigner).visit_stmt(stmt); } Fragment::Item(Item { kind: ItemKind::Callable(decl), @@ -71,6 +93,8 @@ pub fn run_default_passes_for_fragment( .into_iter() .map(Error::ConjInvert), ); + LoopUni { core, assigner }.visit_callable_decl(decl); + ReplaceQubitAllocation::new(core, assigner).visit_callable_decl(decl); } Fragment::Item(_) | Fragment::Error(_) => {} } diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs index bf4f726ad4..9eb461f310 100644 --- a/compiler/qsc_passes/src/loop_unification.rs +++ b/compiler/qsc_passes/src/loop_unification.rs @@ -5,7 +5,6 @@ use core::panic; use std::{mem::take, rc::Rc}; use qsc_data_structures::span::Span; -use qsc_frontend::compile::CompileUnit; use qsc_hir::{ assigner::Assigner, global::Table, @@ -16,26 +15,14 @@ use qsc_hir::{ mut_visit::{walk_expr, MutVisitor}, }; -use crate::{ - common::{create_gen_core_ref, IdentTemplate}, - Error, -}; +use crate::common::{create_gen_core_ref, IdentTemplate}; #[cfg(test)] mod tests; -pub fn loop_unification(core: &Table, unit: &mut CompileUnit) -> Vec { - let mut pass = LoopUni { - core, - assigner: &mut unit.assigner, - }; - pass.visit_package(&mut unit.package); - vec![] -} - -struct LoopUni<'a> { - core: &'a Table, - assigner: &'a mut Assigner, +pub(crate) struct LoopUni<'a> { + pub(crate) core: &'a Table, + pub(crate) assigner: &'a mut Assigner, } impl LoopUni<'_> { diff --git a/compiler/qsc_passes/src/loop_unification/tests.rs b/compiler/qsc_passes/src/loop_unification/tests.rs index 44ae306088..47c2affd9c 100644 --- a/compiler/qsc_passes/src/loop_unification/tests.rs +++ b/compiler/qsc_passes/src/loop_unification/tests.rs @@ -6,20 +6,21 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_frontend::compile::{self, compile, PackageStore, SourceMap}; +use qsc_hir::mut_visit::MutVisitor; -use crate::loop_unification::loop_unification; +use crate::loop_unification::LoopUni; fn check(file: &str, expect: &Expect) { let store = PackageStore::new(compile::core()); let sources = SourceMap::new([("test".into(), file.into())], None); let mut unit = compile(&store, &[], sources); assert!(unit.errors.is_empty(), "{:?}", unit.errors); - let errors = loop_unification(store.core(), &mut unit); - if errors.is_empty() { - expect.assert_eq(&unit.package.to_string()); - } else { - expect.assert_debug_eq(&errors); + LoopUni { + core: store.core(), + assigner: &mut unit.assigner, } + .visit_package(&mut unit.package); + expect.assert_eq(&unit.package.to_string()); } #[test] diff --git a/compiler/qsc_passes/src/replace_qubit_allocation.rs b/compiler/qsc_passes/src/replace_qubit_allocation.rs index 95401a0cd3..782fab5daa 100644 --- a/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -5,7 +5,6 @@ mod tests; use qsc_data_structures::span::Span; -use qsc_frontend::compile::CompileUnit; use qsc_hir::{ assigner::Assigner, global::Table, @@ -19,38 +18,36 @@ use std::{mem::take, rc::Rc}; use crate::common::{create_gen_core_ref, IdentTemplate}; -pub fn replace_qubit_allocation(unit: &mut CompileUnit, core_table: &Table) { - let mut pass = ReplaceQubitAllocation { - assigner: &mut unit.assigner, - core_table, - qubits_curr_callable: Vec::new(), - qubits_curr_block: Vec::new(), - prefix_qubits: Vec::new(), - }; - pass.visit_package(&mut unit.package); -} - struct QubitIdent { id: IdentTemplate, is_array: bool, } -struct ReplaceQubitAllocation<'a> { +pub(crate) struct ReplaceQubitAllocation<'a> { assigner: &'a mut Assigner, - core_table: &'a Table, + core: &'a Table, qubits_curr_callable: Vec>, qubits_curr_block: Vec, prefix_qubits: Vec, } -impl ReplaceQubitAllocation<'_> { - fn process_qubit_stmt( +impl<'a> ReplaceQubitAllocation<'a> { + pub(crate) fn new(core: &'a Table, assigner: &'a mut Assigner) -> Self { + Self { + assigner, + core, + qubits_curr_callable: Vec::new(), + qubits_curr_block: Vec::new(), + prefix_qubits: Vec::new(), + } + } + + fn generate_qubit_alloc_stmts( &mut self, stmt_span: Span, pat: Pat, mut init: QubitInit, - block: Option, - ) -> Vec { + ) -> (Vec, Vec) { fn is_non_tuple(init: &mut QubitInit) -> (bool, Option) { match &mut init.kind { QubitInitKind::Array(e) => (true, Some(take(e))), @@ -108,26 +105,47 @@ impl ReplaceQubitAllocation<'_> { }); } - if let Some(mut block) = block { - self.prefix_qubits = new_ids; - block.stmts.splice(0..0, new_stmts); - self.visit_block(&mut block); - vec![Stmt { - id: NodeId::default(), - span: stmt_span, - kind: StmtKind::Expr(Expr { - id: NodeId::default(), - span: stmt_span, - ty: block.ty.clone(), - kind: ExprKind::Block(block), - }), - }] + (new_ids, new_stmts) + } + + fn process_qubit_stmt( + &mut self, + stmt_span: Span, + pat: Pat, + init: QubitInit, + block: Option, + ) -> Vec { + let (new_ids, new_stmts) = self.generate_qubit_alloc_stmts(stmt_span, pat, init); + if let Some(block) = block { + vec![self.generate_block_stmt(stmt_span, new_ids, block, new_stmts)] } else { self.qubits_curr_block.extend(new_ids); new_stmts } } + fn generate_block_stmt( + &mut self, + stmt_span: Span, + new_ids: Vec, + mut block: Block, + new_stmts: Vec, + ) -> Stmt { + self.prefix_qubits = new_ids; + block.stmts.splice(0..0, new_stmts); + self.visit_block(&mut block); + Stmt { + id: NodeId::default(), + span: stmt_span, + kind: StmtKind::Expr(Expr { + id: NodeId::default(), + span: stmt_span, + ty: block.ty.clone(), + kind: ExprKind::Block(block), + }), + } + } + fn process_qubit_init( &mut self, init: QubitInit, @@ -214,7 +232,7 @@ impl ReplaceQubitAllocation<'_> { create_general_alloc_stmt( ident, create_gen_core_ref( - self.core_table, + self.core, "QIR.Runtime", "__quantum__rt__qubit_allocate", ident.span, @@ -226,12 +244,7 @@ impl ReplaceQubitAllocation<'_> { fn create_array_alloc_stmt(&self, ident: &IdentTemplate, array_size: Expr) -> Stmt { create_general_alloc_stmt( ident, - create_gen_core_ref( - self.core_table, - "QIR.Runtime", - "AllocateQubitArray", - ident.span, - ), + create_gen_core_ref(self.core, "QIR.Runtime", "AllocateQubitArray", ident.span), Some(array_size), ) } @@ -239,7 +252,7 @@ impl ReplaceQubitAllocation<'_> { fn create_dealloc_stmt(&self, ident: &IdentTemplate) -> Stmt { create_general_dealloc_stmt( create_gen_core_ref( - self.core_table, + self.core, "QIR.Runtime", "__quantum__rt__qubit_release", ident.span, @@ -250,12 +263,7 @@ impl ReplaceQubitAllocation<'_> { fn create_array_dealloc_stmt(&self, ident: &IdentTemplate) -> Stmt { create_general_dealloc_stmt( - create_gen_core_ref( - self.core_table, - "QIR.Runtime", - "ReleaseQubitArray", - ident.span, - ), + create_gen_core_ref(self.core, "QIR.Runtime", "ReleaseQubitArray", ident.span), ident, ) } @@ -348,6 +356,64 @@ impl MutVisitor for ReplaceQubitAllocation<'_> { _ => walk_expr(self, expr), } } + + fn visit_stmt(&mut self, stmt: &mut Stmt) { + // This function is not called by visit_block above, so the only time it will be used is for + // top-level statement fragments. Given that, the qubits allocated will always be live for + // the entirety of a global scope, so only qubit allocations need to be generated. + match stmt.kind.clone() { + StmtKind::Qubit(_, pat, qubit_init, None) => { + stmt.kind = create_qubit_global_alloc(self.core, pat, qubit_init); + } + StmtKind::Qubit(_, pat, qubit_init, Some(block)) => { + let (new_ids, new_stmts) = + self.generate_qubit_alloc_stmts(stmt.span, pat, qubit_init); + *stmt = self.generate_block_stmt(stmt.span, new_ids, block, new_stmts); + } + kind => { + stmt.kind = kind; + walk_stmt(self, stmt); + } + } + } +} + +fn create_qubit_global_alloc(core: &Table, pat: Pat, qubit_init: QubitInit) -> StmtKind { + fn qubit_alloc_expr(core: &Table, qubit_init: QubitInit) -> Expr { + match qubit_init.kind { + QubitInitKind::Array(mut expr) => create_qubit_alloc_call_expr( + qubit_init.span, + create_gen_core_ref(core, "QIR.Runtime", "AllocateQubitArray", qubit_init.span), + Some(take(&mut expr)), + ), + QubitInitKind::Single => create_qubit_alloc_call_expr( + qubit_init.span, + create_gen_core_ref( + core, + "QIR.Runtime", + "__quantum__rt__qubit_allocate", + qubit_init.span, + ), + None, + ), + QubitInitKind::Tuple(tup) => Expr { + id: NodeId::default(), + span: qubit_init.span, + ty: qubit_init.ty, + kind: ExprKind::Tuple( + tup.into_iter() + .map(|init| qubit_alloc_expr(core, init)) + .collect(), + ), + }, + } + } + + StmtKind::Local( + Mutability::Immutable, + pat, + qubit_alloc_expr(core, qubit_init), + ) } fn create_general_alloc_stmt( @@ -357,23 +423,27 @@ fn create_general_alloc_stmt( ) -> Stmt { ident.gen_id_init( Mutability::Immutable, - Expr { - id: NodeId::default(), - span: ident.span, - ty: Ty::Prim(PrimTy::Qubit), - kind: ExprKind::Call( - Box::new(call_expr), - Box::new(array_size.unwrap_or(Expr { - id: NodeId::default(), - span: ident.span, - ty: Ty::UNIT, - kind: ExprKind::Tuple(vec![]), - })), - ), - }, + create_qubit_alloc_call_expr(ident.span, call_expr, array_size), ) } +fn create_qubit_alloc_call_expr(span: Span, call_expr: Expr, array_size: Option) -> Expr { + Expr { + id: NodeId::default(), + span, + ty: Ty::Prim(PrimTy::Qubit), + kind: ExprKind::Call( + Box::new(call_expr), + Box::new(array_size.unwrap_or(Expr { + id: NodeId::default(), + span, + ty: Ty::UNIT, + kind: ExprKind::Tuple(vec![]), + })), + ), + } +} + fn create_general_dealloc_stmt(call_expr: Expr, ident: &IdentTemplate) -> Stmt { Stmt { id: NodeId::default(), diff --git a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs index b7f8b5c9e6..a3cea66bfc 100644 --- a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs +++ b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs @@ -1,17 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::replace_qubit_allocation::replace_qubit_allocation; +use crate::replace_qubit_allocation::ReplaceQubitAllocation; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_frontend::compile::{self, compile, PackageStore, SourceMap}; +use qsc_hir::mut_visit::MutVisitor; fn check(file: &str, expect: &Expect) { let store = PackageStore::new(compile::core()); let sources = SourceMap::new([("test".into(), file.into())], None); let mut unit = compile(&store, &[], sources); assert!(unit.errors.is_empty(), "{:?}", unit.errors); - replace_qubit_allocation(&mut unit, store.core()); + ReplaceQubitAllocation::new(store.core(), &mut unit.assigner).visit_package(&mut unit.package); expect.assert_eq(&unit.package.to_string()); } diff --git a/library/core/qir.qs b/library/core/qir.qs index 2d0dbbdeef..6e20041b57 100644 --- a/library/core/qir.qs +++ b/library/core/qir.qs @@ -11,6 +11,9 @@ namespace QIR.Runtime { } operation AllocateQubitArray(size: Int) : Qubit[] { + if size < 0 { + fail "Cannot allocate qubit array with a negative length"; + } mutable qs = []; for _ in 0..size-1 { set qs += [__quantum__rt__qubit_allocate()]; From dffae0c617595b2fda2a246933bd48c1c1d1c44d Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 17 May 2023 11:14:15 -0700 Subject: [PATCH 3/4] Removed unused or unneeded errors --- compiler/qsc_eval/src/intrinsic.rs | 116 +++++++++++++---------------- compiler/qsc_eval/src/lib.rs | 74 +++++------------- 2 files changed, 69 insertions(+), 121 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 621b2ab939..02a68a95ef 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -84,15 +84,12 @@ pub(crate) fn invoke_intrinsic( } "ArcTan2" => { - let tup = args.unwrap_tuple(); - match &*tup { - [x, y] => { - let x = x.clone().unwrap_double(); - let y = y.clone().unwrap_double(); - Ok(Value::Double(x.atan2(y))) - } - _ => Err(Error::TupleArity(2, tup.len(), args_span)), - } + let [x, y] = &*args.unwrap_tuple() else { + panic!("args should be tuple of arity 2"); + }; + let x = x.clone().unwrap_double(); + let y = y.clone().unwrap_double(); + Ok(Value::Double(x.atan2(y))) } "Cos" => { @@ -136,11 +133,10 @@ pub(crate) fn invoke_intrinsic( } "DrawRandomInt" => { - let tup = args.unwrap_tuple(); - match &*tup { - [lo, hi] => invoke_draw_random_int(lo.clone(), hi.clone(), args_span), - _ => Err(Error::TupleArity(2, tup.len(), args_span)), - } + let [lo, hi] = &*args.unwrap_tuple() else { + panic!("args should be a tuple of arity 2"); + }; + invoke_draw_random_int(lo.clone(), hi.clone(), args_span) } "Truncate" => { @@ -193,67 +189,55 @@ fn invoke_quantum_intrinsic( Ok(Value::unit()) })* $(stringify!($op2) => { - let tup = args.unwrap_tuple(); - match &*tup { - [x, y] => { - if x == y { - return Err(Error::QubitUniqueness(args_span)); - } - $op2( - x.clone().unwrap_qubit().0, - y.clone().unwrap_qubit().0, - ); - Ok(Value::unit()) - } - _ => Err(Error::TupleArity(2, tup.len(), args_span)) + let [x, y] = &*args.unwrap_tuple() else { + panic!("args should be tuple of arity 2"); + }; + if x == y { + return Err(Error::QubitUniqueness(args_span)); } + $op2( + x.clone().unwrap_qubit().0, + y.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) })* $(stringify!($op21) => { - let tup = args.unwrap_tuple(); - match &*tup { - [x, y] => { - $op21( - x.clone().unwrap_double(), - y.clone().unwrap_qubit().0, - ); - Ok(Value::unit()) - } - _ => Err(Error::TupleArity(2, tup.len(), args_span)) - } + let [x, y] = &*args.unwrap_tuple() else { + panic!("args should be tuple of arity 2"); + }; + $op21( + x.clone().unwrap_double(), + y.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) })* $(stringify!($op3) => { - let tup = args.unwrap_tuple(); - match &*tup { - [x, y, z] => { - if x == y || y == z || x == z { - return Err(Error::QubitUniqueness(args_span)); - } - $op3( - x.clone().unwrap_qubit().0, - y.clone().unwrap_qubit().0, - z.clone().unwrap_qubit().0, - ); - Ok(Value::unit()) - } - _ => Err(Error::TupleArity(3, tup.len(), args_span)) + let [x, y, z] = &*args.unwrap_tuple() else { + panic!("args should be tuple of arity 3"); + }; + if x == y || y == z || x == z { + return Err(Error::QubitUniqueness(args_span)); } + $op3( + x.clone().unwrap_qubit().0, + y.clone().unwrap_qubit().0, + z.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) })* $(stringify!($op31) => { - let tup = args.unwrap_tuple(); - match &*tup { - [x, y, z] => { - if y == z { - return Err(Error::QubitUniqueness(args_span)); - } - $op31( - x.clone().unwrap_double(), - y.clone().unwrap_qubit().0, - z.clone().unwrap_qubit().0, - ); - Ok(Value::unit()) - } - _ => Err(Error::TupleArity(3, tup.len(), args_span)) + let [x, y, z] = &*args.unwrap_tuple() else { + panic!("args should be tuple of arity 3"); + }; + if y == z { + return Err(Error::QubitUniqueness(args_span)); } + $op31( + x.clone().unwrap_double(), + y.clone().unwrap_qubit().0, + z.clone().unwrap_qubit().0, + ); + Ok(Value::unit()) })* )* diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index cd63ceffba..47829c2d64 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -49,9 +49,6 @@ pub enum Error { #[error("empty range")] EmptyRange(#[label("the range cannot be empty")] Span), - #[error("{0} type does not support equality comparison")] - Equality(&'static str, #[label("does not support comparison")] Span), - #[error("value cannot be used as an index: {0}")] IndexVal(i64, #[label("invalid index")] Span), @@ -64,18 +61,12 @@ pub enum Error { #[error("reassigning immutable variable")] Mutability(#[label("variable declared as immutable")] Span), - #[error("iterable ranges cannot be open-ended")] - OpenEnded(#[label("open-ended range used as iterator")] Span), - #[error("index out of range: {0}")] OutOfRange(i64, #[label("out of range")] Span), #[error("negative integers cannot be used here: {0}")] Negative(i64, #[label("invalid negative integer")] Span), - #[error("type {0} is not iterable")] - NotIterable(&'static str, #[label("not iterable")] Span), - #[error("output failure")] Output(#[label("failed to generate output")] Span), @@ -88,13 +79,6 @@ pub enum Error { #[error("Qubit{0} released while not in |0⟩ state")] ReleasedQubitNotZero(usize), - #[error("mismatched tuples")] - TupleArity( - usize, - usize, - #[label("expected {0}-tuple, found {1}-tuple")] Span, - ), - #[error("invalid left-hand side of assignment")] #[diagnostic(help("the left-hand side must be a variable or tuple of variables"))] Unassignable(#[label("not assignable")] Span), @@ -102,10 +86,6 @@ pub enum Error { #[error("symbol is not bound")] Unbound(#[label] Span), - #[error("{0} support is not implemented")] - #[diagnostic(help("this language feature is not yet supported"))] - Unimplemented(&'static str, #[label("cannot evaluate this")] Span), - #[error("unknown intrinsic")] UnknownIntrinsic(#[label("callable has no implementation")] Span), @@ -655,7 +635,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> { Action::ArrayRepeat(span) => self.eval_arr_repeat(span)?, Action::Assign(lhs) => self.eval_assign(lhs)?, Action::BinOp(op, span, rhs) => self.eval_binop(op, span, rhs)?, - Action::Bind(pat, mutability) => self.eval_bind(pat, mutability)?, + Action::Bind(pat, mutability) => self.eval_bind(pat, mutability), Action::Call(callee_span, args_span) => self.eval_call(callee_span, args_span)?, Action::Consume => { self.pop_val(); @@ -705,9 +685,9 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> { update_binding(self.env, lhs, rhs) } - fn eval_bind(&mut self, pat: &'a Pat, mutability: Mutability) -> Result<(), Error> { + fn eval_bind(&mut self, pat: &'a Pat, mutability: Mutability) { let val = self.pop_val(); - bind_value(self.env, pat, val, mutability) + bind_value(self.env, pat, val, mutability); } fn eval_binop(&mut self, op: BinOp, span: Span, rhs: Option<&'a Expr>) -> Result<(), Error> { @@ -792,7 +772,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> { match (&decl.body, spec) { (CallableBody::Block(body_block), Spec::Body) => { - bind_value(self.env, &decl.input, args_val, Mutability::Immutable)?; + bind_value(self.env, &decl.input, args_val, Mutability::Immutable); self.push_block(body_block); Ok(()) } @@ -811,9 +791,8 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> { &decl.input, pat, args_val, - args_span, functor.controlled, - )?; + ); self.push_block(body_block); Ok(()) } @@ -1012,7 +991,7 @@ impl<'a, G: GlobalLookup<'a>> State<'a, G> { } } -fn bind_value(env: &mut Env, pat: &Pat, val: Value, mutability: Mutability) -> Result<(), Error> { +fn bind_value(env: &mut Env, pat: &Pat, val: Value, mutability: Mutability) { match &pat.kind { PatKind::Bind(variable) => { let scope = env.0.last_mut().expect("binding should have a scope"); @@ -1023,19 +1002,13 @@ fn bind_value(env: &mut Env, pat: &Pat, val: Value, mutability: Mutability) -> R }), Entry::Occupied(_) => panic!("duplicate binding"), }; - Ok(()) } - PatKind::Discard => Ok(()), + PatKind::Discard => {} PatKind::Elided => panic!("elision used in binding"), PatKind::Tuple(tup) => { let val_tup = val.unwrap_tuple(); - if val_tup.len() == tup.len() { - for (pat, val) in tup.iter().zip(val_tup.iter()) { - bind_value(env, pat, val.clone(), mutability)?; - } - Ok(()) - } else { - Err(Error::TupleArity(tup.len(), val_tup.len(), pat.span)) + for (pat, val) in tup.iter().zip(val_tup.iter()) { + bind_value(env, pat, val.clone(), mutability); } } } @@ -1072,12 +1045,8 @@ fn update_binding(env: &mut Env, lhs: &Expr, rhs: Value) -> Result<(), Error> { None => return Err(Error::Unbound(lhs.span)), }, (ExprKind::Tuple(var_tup), Value::Tuple(tup)) => { - if var_tup.len() == tup.len() { - for (expr, val) in var_tup.iter().zip(tup.iter()) { - update_binding(env, expr, val.clone())?; - } - } else { - return Err(Error::TupleArity(var_tup.len(), tup.len(), lhs.span)); + for (expr, val) in var_tup.iter().zip(tup.iter()) { + update_binding(env, expr, val.clone())?; } } _ => return Err(Error::Unassignable(lhs.span)), @@ -1090,9 +1059,8 @@ fn bind_args_for_spec( decl_pat: &Pat, spec_pat: &Pat, args_val: Value, - args_span: Span, ctl_count: u8, -) -> Result<(), Error> { +) { match &spec_pat.kind { PatKind::Bind(_) | PatKind::Discard => { panic!("spec pattern should be elided or elided tuple, found bind/discard") @@ -1108,15 +1076,11 @@ fn bind_args_for_spec( let mut tup = args_val; let mut ctls = vec![]; for _ in 0..ctl_count { - let tup_nesting = tup.unwrap_tuple(); - if tup_nesting.len() != 2 { - return Err(Error::TupleArity(2, tup_nesting.len(), args_span)); - } - - let c = tup_nesting[0].clone(); - let rest = tup_nesting[1].clone(); - ctls.extend_from_slice(&c.unwrap_array()); - tup = rest; + let [c, rest] = &*tup.unwrap_tuple() else { + panic!("tuple should be arity 2"); + }; + ctls.extend_from_slice(&c.clone().unwrap_array()); + tup = rest.clone(); } bind_value( @@ -1124,8 +1088,8 @@ fn bind_args_for_spec( &pats[0], Value::Array(ctls.into()), Mutability::Immutable, - )?; - bind_value(env, decl_pat, tup, Mutability::Immutable) + ); + bind_value(env, decl_pat, tup, Mutability::Immutable); } } } From e22df4910d80888e2474a78bebb3d660c90055cc Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 17 May 2023 11:59:24 -0700 Subject: [PATCH 4/4] More CR feedback --- compiler/qsc_eval/src/intrinsic.rs | 42 +++++++++++++++--------------- compiler/qsc_eval/src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 02a68a95ef..3c27ca7b8d 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -182,7 +182,7 @@ fn invoke_quantum_intrinsic( args_span: Span, ) -> Result { macro_rules! match_intrinsic { - ($chosen_op:ident, $chosen_op_span:ident, $(($(1, $op1:ident),* $(2, $op2:ident),* $(3, $op3:ident),* $(2.1, $op21:ident),* $(3.1, $op31:ident),*)),* ) => { + ($chosen_op:ident, $chosen_op_span:ident, $(($("Qubit", $op1:ident),* $("Qubit, Qubit", $op2:ident),* $("Qubit, Qubit, Qubit", $op3:ident),* $("Double, Qubit", $op21:ident),* $("Double, Qubit, Qubit", $op31:ident),*)),* ) => { match $chosen_op { $($(stringify!($op1) => { $op1(args.unwrap_qubit().0); @@ -265,25 +265,25 @@ fn invoke_quantum_intrinsic( match_intrinsic!( name, name_span, - (3, __quantum__qis__ccx__body), - (2, __quantum__qis__cx__body), - (2, __quantum__qis__cy__body), - (2, __quantum__qis__cz__body), - (2.1, __quantum__qis__rx__body), - (3.1, __quantum__qis__rxx__body), - (2.1, __quantum__qis__ry__body), - (3.1, __quantum__qis__ryy__body), - (2.1, __quantum__qis__rz__body), - (3.1, __quantum__qis__rzz__body), - (1, __quantum__qis__h__body), - (1, __quantum__qis__s__body), - (1, __quantum__qis__s__adj), - (1, __quantum__qis__t__body), - (1, __quantum__qis__t__adj), - (1, __quantum__qis__x__body), - (1, __quantum__qis__y__body), - (1, __quantum__qis__z__body), - (2, __quantum__qis__swap__body), - (1, __quantum__qis__reset__body) + ("Qubit, Qubit, Qubit", __quantum__qis__ccx__body), + ("Qubit, Qubit", __quantum__qis__cx__body), + ("Qubit, Qubit", __quantum__qis__cy__body), + ("Qubit, Qubit", __quantum__qis__cz__body), + ("Double, Qubit", __quantum__qis__rx__body), + ("Double, Qubit, Qubit", __quantum__qis__rxx__body), + ("Double, Qubit", __quantum__qis__ry__body), + ("Double, Qubit, Qubit", __quantum__qis__ryy__body), + ("Double, Qubit", __quantum__qis__rz__body), + ("Double, Qubit, Qubit", __quantum__qis__rzz__body), + ("Qubit", __quantum__qis__h__body), + ("Qubit", __quantum__qis__s__body), + ("Qubit", __quantum__qis__s__adj), + ("Qubit", __quantum__qis__t__body), + ("Qubit", __quantum__qis__t__adj), + ("Qubit", __quantum__qis__x__body), + ("Qubit", __quantum__qis__y__body), + ("Qubit", __quantum__qis__z__body), + ("Qubit, Qubit", __quantum__qis__swap__body), + ("Qubit", __quantum__qis__reset__body) ) } diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 47829c2d64..59be10d968 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -1443,7 +1443,7 @@ fn eval_binop_sub(lhs_val: Value, rhs_val: Value) -> Value { let rhs = rhs_val.unwrap_int(); Value::Int(val - rhs) } - _ => panic!("value is not addable"), + _ => panic!("value is not subtractable"), } }