Repository: microsoft/qsharp
Pull request #1375 (2024-04-10 19:18:59)

Using new QIR codegen for adaptive profile in Rust/Python
Support for new QIR codegen for adaptive profile in Python.

![image](https://github.com/microsoft/qsharp/assets/63540/5517f589-6da4-422d-bbdd-37594ea070d2)


github-actions[bot]: ## Benchmark for 424e9d3
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 333.0±2.26µs | 332.9±2.65µs | -0.03% |
| Array literal evaluation | 180.0±1.06µs | **178.7±9.18µs** | **-0.72%** |
| Array update evaluation | 416.5±1.65µs | 415.2±19.63µs | -0.31% |
| Core + Standard library compilation | 19.9±1.09ms | 19.8±1.15ms | -0.50% |
| Deutsch-Jozsa evaluation | 5.1±0.05ms | 5.1±0.05ms | 0.00% |
| Large file parity evaluation | 33.8±0.85ms | 33.8±0.69ms | 0.00% |
| Large input file compilation | 12.4±0.84ms | 12.1±0.76ms | -2.42% |
| Large input file compilation (interpreter) | 48.3±2.02ms | 47.7±1.90ms | -1.24% |
| Large nested iteration | 32.5±0.28ms | 32.6±0.19ms | +0.31% |
| Perform Runtime Capabilities Analysis (RCA) on Deutsch-Jozsa sample | 1577.3±182.55µs | **1501.1±55.33µs** | **-4.83%** |
| Perform Runtime Capabilities Analysis (RCA) on large file sample | 8.5±0.17ms | **7.9±0.15ms** | **-7.06%** |
| Perform Runtime Capabilities Analysis (RCA) on teleport sample | 1479.6±162.30µs | 1441.7±103.85µs | -2.56% |
| Perform Runtime Capabilities Analysis (RCA) on the core and std libraries | 22.9±0.32ms | **21.7±0.41ms** | **-5.24%** |
| Teleport evaluation | 82.1±3.67µs | 81.6±3.80µs | -0.61% |

  </details>
  

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 45bbaa08..fcee74d8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -928,6 +928,7 @@ dependencies = [
  "qsc_hir",
  "qsc_linter",
  "qsc_lowerer",
+ "qsc_partial_eval",
  "qsc_passes",
  "qsc_project",
  "qsc_rca",
diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml
index 74aa6f10..bb34ddb6 100644
--- a/compiler/qsc/Cargo.toml
+++ b/compiler/qsc/Cargo.toml
@@ -28,6 +28,7 @@ qsc_ast = { path = "../qsc_ast" }
 qsc_fir = { path = "../qsc_fir" }
 qsc_hir = { path = "../qsc_hir" }
 qsc_passes = { path = "../qsc_passes" }
+qsc_partial_eval = { path = "../qsc_partial_eval" }
 qsc_project = { path = "../qsc_project", features = ["fs"] }
 qsc_rca = { path = "../qsc_rca" }
 qsc_circuit = { path = "../qsc_circuit" }
diff --git a/compiler/qsc/src/codegen.rs b/compiler/qsc/src/codegen.rs
index 8dd19224..8270593f 100644
--- a/compiler/qsc/src/codegen.rs
+++ b/compiler/qsc/src/codegen.rs
@@ -1,9 +1,10 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use qsc_codegen::qir::hir_to_qir;
+use qsc_codegen::qir::fir_to_qir;
 use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap};
+use qsc_partial_eval::ProgramEntry;
 use qsc_passes::{PackageType, PassContext};
 
 use crate::compile;
@@ -35,17 +36,25 @@ pub fn get_qir(
     }
 
     let package_id = package_store.insert(unit);
-
-    let caps_results = PassContext::run_fir_passes_on_hir(&package_store, package_id, capabilities);
+    let (fir_store, fir_package_id) = qsc_passes::lower_hir_to_fir(&package_store, package_id);
+    let caps_results = PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities);
+    let package = fir_store.get(fir_package_id);
+    let entry = ProgramEntry {
+        exec_graph: package.entry_exec_graph.clone(),
+        expr: (
+            fir_package_id,
+            package
+                .entry
+                .expect("package must have an entry expression"),
+        )
+            .into(),
+    };
     // Ensure it compiles before trying to add it to the store.
     match caps_results {
-        Ok(compute_properties) => hir_to_qir(
-            &package_store,
-            package_id,
-            capabilities,
-            Some(compute_properties),
-        )
-        .map_err(|e| e.to_string()),
+        Ok(compute_properties) => {
+            fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry)
+                .map_err(|e| e.to_string())
+        }
         Err(_) => {
             // This should never happen, as the program should be checked for errors before trying to
             // generate code for it. But just in case, simply report the failure.
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index 452ee0c9..5225b2fe 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -24,6 +24,8 @@ pub use qsc_eval::{
     StepAction, StepResult,
 };
 use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir};
+use qsc_partial_eval::ProgramEntry;
+use qsc_rca::PackageStoreComputeProperties;
 
 use crate::{
     error::{self, WithStack},
@@ -38,7 +40,7 @@ use qsc_circuit::{
     operations::entry_expr_for_qubit_operation, Builder as CircuitBuilder, Circuit,
     Config as CircuitConfig,
 };
-use qsc_codegen::qir_base::BaseProfSim;
+use qsc_codegen::{qir::fir_to_qir, qir_base::BaseProfSim};
 use qsc_data_structures::{
     functors::FunctorApp,
     language_features::LanguageFeatures,
@@ -97,6 +99,9 @@ pub enum Error {
     #[diagnostic(code("Qsc.Interpret.NotAnOperation"))]
     #[diagnostic(help("provide the name of a callable or a lambda expression"))]
     NotAnOperation,
+    #[error("partial evaluation error")]
+    #[diagnostic(transparent)]
+    PartialEvaluation(#[from] WithSource<qsc_partial_eval::Error>),
 }
 
 /// A Q# interpreter.
@@ -300,7 +305,7 @@ impl Interpreter {
             .compile_fragments_fail_fast(&label, fragments)
             .map_err(into_errors)?;
 
-        let (_, graph) = self.lower(&increment)?;
+        let (graph, _) = self.lower(&increment)?;
 
         // Updating the compiler state with the new AST/HIR nodes
         // is not necessary for the interpreter to function, as all
@@ -345,17 +350,59 @@ impl Interpreter {
     /// Performs QIR codegen using the given entry expression on a new instance of the environment
     /// and simulator but using the current compilation.
     pub fn qirgen(&mut self, expr: &str) -> std::result::Result<String, Vec<Error>> {
-        if self.capabilities != RuntimeCapabilityFlags::empty() {
+        if self.capabilities == RuntimeCapabilityFlags::all() {
             return Err(vec![Error::UnsupportedRuntimeCapabilities]);
         }
+        if self.capabilities == RuntimeCapabilityFlags::empty() {
+            let mut sim = BaseProfSim::new();
+            let mut stdout = std::io::sink();
+            let mut out = GenericReceiver::new(&mut stdout);
 
-        let mut sim = BaseProfSim::new();
-        let mut stdout = std::io::sink();
-        let mut out = GenericReceiver::new(&mut stdout);
-
-        let val = self.run_with_sim(&mut sim, &mut out, expr)??;
+            let val = self.run_with_sim(&mut sim, &mut out, expr)??;
 
-        Ok(sim.finish(&val))
+            Ok(sim.finish(&val))
+        } else {
+            // Compile the expression. This operation will set the expression as
+            // the entry-point in the FIR store.
+            let (graph, compute_properties) = self.compile_entry_expr(expr)?;
+
+            let Some(compute_properties) = compute_properties else {
+                // This can only happen if capability analysis was not run. This would be a bug
+                // and we are in a bad state and can't proceed.
+                panic!(
+                    "internal error: compute properties not set after lowering entry expression"
+                );
+            };
+            let package = self.fir_store.get(self.package);
+            let entry = ProgramEntry {
+                exec_graph: graph.into(),
+                expr: (
+                    self.package,
+                    package
+                        .entry
+                        .expect("package must have an entry expression"),
+                )
+                    .into(),
+            };
+            // Generate QIR
+            fir_to_qir(
+                &self.fir_store,
+                self.capabilities,
+                Some(compute_properties),
+                &entry,
+            )
+            .map_err(|e| {
+                let source_package = self
+                    .compiler
+                    .package_store()
+                    .get(map_fir_package_to_hir(self.package))
+                    .expect("package should exist in the package store");
+                vec![Error::PartialEvaluation(WithSource::from_map(
+                    &source_package.sources,
+                    e,
+                ))]
+            })
+        }
     }
 
     /// Generates a circuit representation for the program.
@@ -403,7 +450,7 @@ impl Interpreter {
         receiver: &mut impl Receiver,
         expr: &str,
     ) -> std::result::Result<InterpretResult, Vec<Error>> {
-        let graph = self.compile_entry_expr(expr)?;
+        let (graph, _) = self.compile_entry_expr(expr)?;
 
         if self.quantum_seed.is_some() {
             sim.set_seed(self.quantum_seed);
@@ -424,7 +471,8 @@ impl Interpreter {
     fn compile_entry_expr(
         &mut self,
         expr: &str,
-    ) -> std::result::Result<Vec<ExecGraphNode>, Vec<Error>> {
+    ) -> std::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
+    {
         let increment = self
             .compiler
             .compile_entry_expr(expr)
@@ -432,7 +480,7 @@ impl Interpreter {
 
         // `lower` will update the entry expression in the FIR store,
         // and it will always return an empty list of statements.
-        let (_, graph) = self.lower(&increment)?;
+        let (graph, compute_properties) = self.lower(&increment)?;
 
         // The AST and HIR packages in `increment` only contain an entry
         // expression and no statements. The HIR *can* contain items if the entry
@@ -448,58 +496,52 @@ impl Interpreter {
         // here to keep the package stores consistent.
         self.compiler.update(increment);
 
-        Ok(graph)
+        Ok((graph, compute_properties))
     }
 
     fn lower(
         &mut self,
         unit_addition: &qsc_frontend::incremental::Increment,
-    ) -> core::result::Result<(Vec<StmtId>, Vec<ExecGraphNode>), Vec<Error>> {
+    ) -> core::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
+    {
         if self.capabilities != RuntimeCapabilityFlags::all() {
             return self.run_fir_passes(unit_addition);
         }
         let fir_package = self.fir_store.get_mut(self.package);
-        Ok((
-            self.lowerer
-                .lower_and_update_package(fir_package, &unit_addition.hir),
-            self.lowerer.take_exec_graph(),
-        ))
+        self.lowerer
+            .lower_and_update_package(fir_package, &unit_addition.hir);
+        Ok((self.lowerer.take_exec_graph(), None))
     }
 
     fn run_fir_passes(
         &mut self,
         unit: &qsc_frontend::incremental::Increment,
-    ) -> std::result::Result<(Vec<StmtId>, Vec<ExecGraphNode>), Vec<Error>> {
+    ) -> std::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
+    {
         let fir_package = self.fir_store.get_mut(self.package);
-        let stmts = self
-            .lowerer
+        self.lowerer
             .lower_and_update_package(fir_package, &unit.hir);
 
         let cap_results =
             PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities);
 
-        let Err(caps_errors) = cap_results else {
-            let graph = self.lowerer.take_exec_graph();
-            return Ok((stmts, graph));
-        };
-
-        // if there are errors, convert them to interpreter errors
-        // and don't update the lowerer or FIR store.
-        let mut errors = Vec::with_capacity(caps_errors.len());
-        let source_package = self
-            .compiler
-            .package_store()
-            .get(map_fir_package_to_hir(self.package))
-            .expect("package should exist in the package store");
-
-        for error in caps_errors {
-            errors.push(Error::Pass(WithSource::from_map(
-                &source_package.sources,
-                error,
-            )));
-        }
-
-        Err(errors)
+        let compute_properties = cap_results.map_err(|caps_errors| {
+            // if there are errors, convert them to interpreter errors
+            // and don't update the lowerer or FIR store.
+            let source_package = self
+                .compiler
+                .package_store()
+                .get(map_fir_package_to_hir(self.package))
+                .expect("package should exist in the package store");
+
+            caps_errors
+                .into_iter()
+                .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
+                .collect::<Vec<_>>()
+        })?;
+
+        let graph = self.lowerer.take_exec_graph();
+        Ok((graph, Some(compute_properties)))
     }
 
     fn next_line_label(&mut self) -> String {
diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs
index c20827ee..487883ee 100644
--- a/compiler/qsc/src/lib.rs
+++ b/compiler/qsc/src/lib.rs
@@ -37,7 +37,7 @@ pub mod project {
 
 pub use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
 
-pub use qsc_passes::{PackageType, PassContext};
+pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext};
 
 pub mod line_column {
     pub use qsc_data_structures::line_column::{Encoding, Position, Range};
diff --git a/compiler/qsc_codegen/src/qir.rs b/compiler/qsc_codegen/src/qir.rs
index 2e3f3fa9..0794f5e6 100644
--- a/compiler/qsc_codegen/src/qir.rs
+++ b/compiler/qsc_codegen/src/qir.rs
@@ -8,9 +8,8 @@ mod instruction_tests;
 mod tests;
 
 use qsc_frontend::compile::RuntimeCapabilityFlags;
-use qsc_hir::hir;
 use qsc_lowerer::map_hir_package_to_fir;
-use qsc_partial_eval::partially_evaluate;
+use qsc_partial_eval::{partially_evaluate, ProgramEntry};
 use qsc_rca::PackageStoreComputeProperties;
 use qsc_rir::{
     passes::{check_and_transform, defer_quantum_measurements},
@@ -30,22 +29,21 @@ fn lower_store(package_store: &qsc_frontend::compile::PackageStore) -> qsc_fir::
 /// converts the given sources to QIR using the given language features.
 pub fn hir_to_qir(
     package_store: &qsc_frontend::compile::PackageStore,
-    package_id: hir::PackageId,
     capabilities: RuntimeCapabilityFlags,
     compute_properties: Option<PackageStoreComputeProperties>,
+    entry: &ProgramEntry,
 ) -> Result<String, qsc_partial_eval::Error> {
     let fir_store = lower_store(package_store);
-    let fir_package_id = map_hir_package_to_fir(package_id);
-    fir_to_qir(&fir_store, fir_package_id, capabilities, compute_properties)
+    fir_to_qir(&fir_store, capabilities, compute_properties, entry)
 }
 
 pub fn fir_to_qir(
     fir_store: &qsc_fir::fir::PackageStore,
-    fir_package_id: qsc_fir::fir::PackageId,
     capabilities: RuntimeCapabilityFlags,
     compute_properties: Option<PackageStoreComputeProperties>,
+    entry: &ProgramEntry,
 ) -> Result<String, qsc_partial_eval::Error> {
-    let mut program = get_rir_from_compilation(fir_store, fir_package_id, compute_properties)?;
+    let mut program = get_rir_from_compilation(fir_store, compute_properties, entry)?;
     check_and_transform(&mut program);
     if capabilities.is_empty() {
         defer_quantum_measurements(&mut program);
@@ -55,15 +53,15 @@ pub fn fir_to_qir(
 
 fn get_rir_from_compilation(
     fir_store: &qsc_fir::fir::PackageStore,
-    fir_package_id: qsc_fir::fir::PackageId,
     compute_properties: Option<PackageStoreComputeProperties>,
+    entry: &ProgramEntry,
 ) -> Result<rir::Program, qsc_partial_eval::Error> {
     let compute_properties = compute_properties.unwrap_or_else(|| {
         let analyzer = qsc_rca::Analyzer::init(fir_store);
         analyzer.analyze_all()
     });
 
-    partially_evaluate(fir_package_id, fir_store, &compute_properties)
+    partially_evaluate(fir_store, &compute_properties, entry)
 }
 
 /// A trait for converting a type into QIR of type `T`.
diff --git a/compiler/qsc_lowerer/src/lib.rs b/compiler/qsc_lowerer/src/lib.rs
index 570ddde7..c38e9809 100644
--- a/compiler/qsc_lowerer/src/lib.rs
+++ b/compiler/qsc_lowerer/src/lib.rs
@@ -110,7 +110,7 @@ impl Lowerer {
         &mut self,
         fir_package: &mut fir::Package,
         hir_package: &hir::Package,
-    ) -> Vec<StmtId> {
+    ) {
         let items: IndexMap<LocalItemId, fir::Item> = hir_package
             .items
             .values()
@@ -118,11 +118,9 @@ impl Lowerer {
             .map(|i| (i.id, i))
             .collect();
 
-        let new_stmts = hir_package
-            .stmts
-            .iter()
-            .map(|s| self.lower_stmt(s))
-            .collect();
+        for stmt in &hir_package.stmts {
+            let _ = self.lower_stmt(stmt);
+        }
 
         let entry = hir_package.entry.as_ref().map(|e| self.lower_expr(e));
 
@@ -135,8 +133,6 @@ impl Lowerer {
         fir_package.entry = entry;
 
         qsc_fir::validate::validate(fir_package);
-
-        new_stmts
     }
 
     fn update_package(&mut self, package: &mut fir::Package) {
diff --git a/compiler/qsc_partial_eval/src/evaluation_context.rs b/compiler/qsc_partial_eval/src/evaluation_context.rs
index e6a97e1f..3da43218 100644
--- a/compiler/qsc_partial_eval/src/evaluation_context.rs
+++ b/compiler/qsc_partial_eval/src/evaluation_context.rs
@@ -14,8 +14,8 @@ pub struct EvaluationContext {
 }
 
 impl EvaluationContext {
-    pub fn new(entry_package_id: PackageId, initial_block: BlockId) -> Self {
-        let entry_callable_scope = Scope::new(entry_package_id, None, Vec::new());
+    pub fn new(package_id: PackageId, initial_block: BlockId) -> Self {
+        let entry_callable_scope = Scope::new(package_id, None, Vec::new());
         Self {
             current_block: initial_block,
             scopes: vec![entry_callable_scope],
diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs
index 18832e2e..3990eb7e 100644
--- a/compiler/qsc_partial_eval/src/lib.rs
+++ b/compiler/qsc_partial_eval/src/lib.rs
@@ -17,9 +17,9 @@ use qsc_eval::{
 };
 use qsc_fir::{
     fir::{
-        BinOp, Block, BlockId, CallableDecl, CallableImpl, ExecGraphNode, Expr, ExprId, ExprKind,
-        Global, Ident, PackageId, PackageStore, PackageStoreLookup, Pat, PatId, PatKind, Res,
-        SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StoreBlockId, StoreExprId, StoreItemId,
+        self, BinOp, Block, BlockId, CallableDecl, CallableImpl, ExecGraphNode, Expr, ExprId,
+        ExprKind, Global, Ident, PackageId, PackageStore, PackageStoreLookup, Pat, PatId, PatKind,
+        Res, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StoreBlockId, StoreExprId, StoreItemId,
         StorePatId, StoreStmtId,
     },
     ty::{Prim, Ty},
@@ -34,12 +34,17 @@ use rustc_hash::FxHashMap;
 use std::{collections::hash_map::Entry, rc::Rc, result::Result};
 use thiserror::Error;
 
+pub struct ProgramEntry {
+    pub exec_graph: Rc<[ExecGraphNode]>,
+    pub expr: fir::StoreExprId,
+}
+
 pub fn partially_evaluate(
-    package_id: PackageId,
     package_store: &PackageStore,
     compute_properties: &PackageStoreComputeProperties,
+    entry: &ProgramEntry,
 ) -> Result<Program, Error> {
-    let partial_evaluator = PartialEvaluator::new(package_id, package_store, compute_properties);
+    let partial_evaluator = PartialEvaluator::new(package_store, compute_properties, entry);
     partial_evaluator.eval()
 }
 
@@ -73,14 +78,15 @@ struct PartialEvaluator<'a> {
     callables_map: FxHashMap<Rc<str>, CallableId>,
     eval_context: EvaluationContext,
     program: Program,
+    entry: &'a ProgramEntry,
     errors: Vec<Error>,
 }
 
 impl<'a> PartialEvaluator<'a> {
     fn new(
-        entry_package_id: PackageId,
         package_store: &'a PackageStore,
         compute_properties: &'a PackageStoreComputeProperties,
+        entry: &'a ProgramEntry,
     ) -> Self {
         // Create the entry-point callable.
         let mut resource_manager = ResourceManager::default();
@@ -100,7 +106,7 @@ impl<'a> PartialEvaluator<'a> {
         program.entry = entry_point_id;
 
         // Initialize the evaluation context and create a new partial evaluator.
-        let context = EvaluationContext::new(entry_package_id, entry_block_id);
+        let context = EvaluationContext::new(entry.expr.package, entry_block_id);
         Self {
             package_store,
             compute_properties,
@@ -109,6 +115,7 @@ impl<'a> PartialEvaluator<'a> {
             backend: QuantumIntrinsicsChecker::default(),
             callables_map: FxHashMap::default(),
             program,
+            entry,
             errors: Vec::new(),
         }
     }
@@ -181,14 +188,8 @@ impl<'a> PartialEvaluator<'a> {
     }
 
     fn eval(mut self) -> Result<Program, Error> {
-        let current_package = self.get_current_package_id();
-        let entry_package = self.package_store.get(current_package);
-        let Some(entry_expr_id) = entry_package.entry else {
-            panic!("package does not have an entry expression");
-        };
-
         // Visit the entry point expression.
-        self.visit_expr(entry_expr_id);
+        self.visit_expr(self.entry.expr.expr);
 
         // Return the first error, if any.
         // We should eventually return all the errors but since that is an interface change, we will do that as its own
@@ -592,9 +593,7 @@ impl<'a> PartialEvaluator<'a> {
         if let Some(spec_decl) = self.get_current_scope_spec_decl() {
             &spec_decl.exec_graph
         } else {
-            let package_id = self.get_current_package_id();
-            let package = self.package_store.get(package_id);
-            &package.entry_exec_graph
+            &self.entry.exec_graph
         }
     }
 
diff --git a/compiler/qsc_passes/src/entry_point.rs b/compiler/qsc_passes/src/entry_point.rs
index 784964a5..d7cc7a8d 100644
--- a/compiler/qsc_passes/src/entry_point.rs
+++ b/compiler/qsc_passes/src/entry_point.rs
@@ -98,7 +98,7 @@ fn create_entry_from_callables(
                         };
                         let call = Expr {
                             id: assigner.next_node(),
-                            span: ep.span,
+                            span: ep.name.span,
                             ty: block.ty.clone(),
                             kind: ExprKind::Call(Box::new(callee), Box::new(arg)),
                         };
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index ae82a9ce..f66e91ab 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -54,6 +54,16 @@ pub enum PackageType {
     Lib,
 }
 
+#[must_use]
+pub fn lower_hir_to_fir(
+    package_store: &qsc_frontend::compile::PackageStore,
+    package_id: qsc_hir::hir::PackageId,
+) -> (fir::PackageStore, fir::PackageId) {
+    let fir_store = lower_store(package_store);
+    let fir_package_id = map_hir_package_to_fir(package_id);
+    (fir_store, fir_package_id)
+}
+
 pub struct PassContext {
     capabilities: RuntimeCapabilityFlags,
     borrow_check: borrowck::Checker,
@@ -120,16 +130,6 @@ impl PassContext {
             .collect()
     }
 
-    pub fn run_fir_passes_on_hir(
-        package_store: &qsc_frontend::compile::PackageStore,
-        package_id: qsc_hir::hir::PackageId,
-        capabilities: RuntimeCapabilityFlags,
-    ) -> Result<PackageStoreComputeProperties, Vec<Error>> {
-        let fir_store = lower_store(package_store);
-        let fir_package_id = map_hir_package_to_fir(package_id);
-        Self::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities)
-    }
-
     pub fn run_fir_passes_on_fir(
         fir_store: &qsc_fir::fir::PackageStore,
         package_id: qsc_fir::fir::PackageId,
diff --git a/katas/content/single_qubit_gates/global_phase_change/Placeholder.qs b/katas/content/single_qubit_gates/global_phase_change/Placeholder.qs
new file mode 100644
index 00000000..9035212a
--- /dev/null
+++ b/katas/content/single_qubit_gates/global_phase_change/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl {
+        // Implement your solution here...
+        
+    }
+}
+
diff --git a/katas/content/single_qubit_gates/global_phase_change/Solution.qs b/katas/content/single_qubit_gates/global_phase_change/Solution.qs
new file mode 100644
index 00000000..3e95f1e3
--- /dev/null
+++ b/katas/content/single_qubit_gates/global_phase_change/Solution.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl {
+        Z(q);
+        X(q);
+        Z(q);
+        X(q);
+    }
+}
diff --git a/katas/content/single_qubit_gates/global_phase_change/Verification.qs b/katas/content/single_qubit_gates/global_phase_change/Verification.qs
new file mode 100644
index 00000000..7b77833f
--- /dev/null
+++ b/katas/content/single_qubit_gates/global_phase_change/Verification.qs
@@ -0,0 +1,35 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Canon;
+    open Microsoft.Quantum.Intrinsic;
+
+
+    operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl {
+        Z(q);
+        X(q);
+        Z(q);
+        X(q);
+    }
+
+    operation CheckSolution() : Bool {
+        let solution = register => Kata.GlobalPhaseChange(register[0]);
+        let reference = register => GlobalPhaseChange(register[0]);
+        let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1);
+
+        // Output different feedback to the user depending on whether the solution was correct.
+        if isCorrect {
+            Message("Correct!");
+        } else {
+            Message("Incorrect.");
+            Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " +
+                "is expected to have.");
+            use initial = Qubit(); // |0〉
+            Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉
+            ShowQuantumStateComparison([initial], solution, reference);
+            Reset(initial);
+        }
+        isCorrect
+    }
+}
diff --git a/katas/content/single_qubit_gates/global_phase_change/index.md b/katas/content/single_qubit_gates/global_phase_change/index.md
new file mode 100644
index 00000000..40f0611f
--- /dev/null
+++ b/katas/content/single_qubit_gates/global_phase_change/index.md
@@ -0,0 +1,7 @@
+**Input:** A qubit in state $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$.
+
+**Goal**: Change the state of the qubit to $- \beta |0\rangle - \gamma |1\rangle$.
+
+> Note: this change on its own is not observable - there is no experiment you can do on a standalone qubit to figure out whether it acquired the global phase or not.
+> However, you can use a controlled version of this operation to observe the global phase it introduces.
+> This is used in later katas as part of more complicated tasks.
diff --git a/katas/content/single_qubit_gates/global_phase_change/solution.md b/katas/content/single_qubit_gates/global_phase_change/solution.md
new file mode 100644
index 00000000..0f60697c
--- /dev/null
+++ b/katas/content/single_qubit_gates/global_phase_change/solution.md
@@ -0,0 +1,30 @@
+A global phase is a phase factor that multiplies the entire quantum state. It is not observable when measuring the qubit's state, as the probabilities remain unchanged. However, it is significant when considering quantum state transformations.
+
+Our task is to implement an operation that transforms the given qubit state from $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$ to $- \beta |0\rangle - \gamma |1\rangle$.
+
+To do that, we utilize a sequence of gates. The Pauli Z gate followed by the Pauli X gate can be used to achieve this effect when applied in succession twice.
+
+1. **Apply the Pauli Z gate**: The Z gate multiplies the $|1\rangle$ state by $-1$ but does not change the $|0\rangle$ state, converting our state $\beta |0\rangle + \gamma |1\rangle$ to $\beta |0\rangle - \gamma |1\rangle$.
+
+   The matrix representation of the Z gate is:
+
+   $$
+   Z =
+   \begin{bmatrix}1 & 0 \\\ 0 & -1 \end{bmatrix}
+   $$
+
+2. **Apply the Pauli X gate**: The X gate flips the $|0\rangle$ and $|1\rangle$ basis states, converting our $\beta |0\rangle - \gamma |1\rangle$ state to $\beta |1\rangle - \gamma |0\rangle$.
+
+   The matrix representation of the X gate is:
+
+   $$
+   X =
+   \begin{bmatrix}0 & 1 \\\1 & 0\end{bmatrix}
+   $$
+
+3. **Repeat the Z and X gates**: Applying the Z gate again will multiply the $|1\rangle$ state (that used to be $|0\rangle$), converting our state $\beta |1\rangle - \gamma |0\rangle$ to $- \beta |1\rangle - \gamma |0\rangle$. Finally, the second X gate will restore the original basis states, but now with both amplitudes having acquired an additional phase of $-1$. This means our state has been multiplied by $-1$, achieving the required global phase change.
+
+@[solution]({
+"id": "single_qubit_gates__global_phase_change_solution",
+"codePath": "./Solution.qs"
+})
diff --git a/katas/content/single_qubit_gates/index.md b/katas/content/single_qubit_gates/index.md
index 264fad9d..52985959 100644
--- a/katas/content/single_qubit_gates/index.md
+++ b/katas/content/single_qubit_gates/index.md
@@ -488,6 +488,15 @@ The next two gates are known as phase shift gates. They apply a phase to the $|1
 > Notice that applying the $T$ gate twice is equivalent to applying the $S$ gate, and applying the $S$ gate twice is equivalent to applying the $Z$ gate:
 $$T^2 = S, S^2 = Z$$
 
+@[exercise]({
+    "id": "single_qubit_gates__phase_flip",
+    "title": "Relative Phase i",
+    "path": "./phase_flip/",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
+
 @[exercise]({
     "id": "single_qubit_gates__three_quarters_pi_phase",
     "title": "Three-Fourths Phase",
@@ -571,14 +580,33 @@ In addition, the rotation gates are very closely related to their respective Pau
 
 $$X = iR_x(\pi), Y = iR_y(\pi), Z = iR_z(\pi)$$
 
+@[exercise]({
+    "id": "single_qubit_gates__phase_change",
+    "title": "Complex Relative Phase",
+    "path": "./phase_change/",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
+
 @[exercise]({
     "id": "single_qubit_gates__amplitude_change",
-    "title": "Amplitude_Change",
+    "title": "Amplitude Change",
     "path": "./amplitude_change/",
     "qsDependencies": [
         "../KatasLibrary.qs"
     ]
 })
+
+@[exercise]({
+    "id": "single_qubit_gates__global_phase_change",
+    "title": "Global Phase -1",
+    "path": "./global_phase_change/",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
+
 @[exercise]({
     "id": "single_qubit_gates__prepare_rotated_state",
     "title": "Prepare Rotated State",
diff --git a/katas/content/single_qubit_gates/phase_change/Placeholder.qs b/katas/content/single_qubit_gates/phase_change/Placeholder.qs
new file mode 100644
index 00000000..add005a7
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_change/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj + Ctl {
+        // Implement your solution here...
+        
+    }
+}
+
diff --git a/katas/content/single_qubit_gates/phase_change/Solution.qs b/katas/content/single_qubit_gates/phase_change/Solution.qs
new file mode 100644
index 00000000..53baafa6
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_change/Solution.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj + Ctl {
+        R1(alpha, q); 
+    }
+}
+
diff --git a/katas/content/single_qubit_gates/phase_change/Verification.qs b/katas/content/single_qubit_gates/phase_change/Verification.qs
new file mode 100644
index 00000000..2c2375fd
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_change/Verification.qs
@@ -0,0 +1,32 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Convert;
+
+    operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj+Ctl {
+        R1(alpha, q); 
+    }
+
+    operation CheckSolution() : Bool {
+        for i in 0 .. 36 {
+            let alpha = ((2.0 * PI()) * IntAsDouble(i)) / 36.0;
+            let solution = register => Kata.PhaseChange(alpha, register[0]);
+            let reference = register => PhaseChange(alpha, register[0]);
+            let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1);
+            if not isCorrect {
+                Message("Incorrect.");
+                Message($"The solution was incorrect for the test case alpha = {alpha}.");
+                Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " +
+                "is expected to have.");
+                use initial = Qubit(); // |0〉
+                Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉
+                ShowQuantumStateComparison([initial], solution, reference);
+                Reset(initial);
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        true
+    }
+}
\ No newline at end of file
diff --git a/katas/content/single_qubit_gates/phase_change/index.md b/katas/content/single_qubit_gates/phase_change/index.md
new file mode 100644
index 00000000..2c827d4d
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_change/index.md
@@ -0,0 +1,10 @@
+**Inputs:**
+
+1. Angle $\alpha$, in radians, represented as Double.
+2. A qubit in state $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$.
+
+**Goal:** Change the state of the qubit as follows:
+
+- If the qubit is in state $|0\rangle$, don't change its state.
+- If the qubit is in state $|1\rangle$, change its state to $e^{i\alpha} |1\rangle$.
+- If the qubit is in superposition, change its state according to the effect on basis vectors: $\beta |0\rangle + {\color{red}{e^{i\alpha}}} \gamma |1\rangle$.
diff --git a/katas/content/single_qubit_gates/phase_change/solution.md b/katas/content/single_qubit_gates/phase_change/solution.md
new file mode 100644
index 00000000..394af69a
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_change/solution.md
@@ -0,0 +1,28 @@
+We know that:
+
+$$
+R1(\alpha)=
+ \begin{bmatrix}1 & 0\\\0 & \color{red}{e^{i\alpha}}\end{bmatrix}
+$$
+
+So we have:
+
+$$
+R1(\beta |0\rangle + \gamma |1\rangle) =
+ \begin{bmatrix}1 & 0 \\\0 & \color{red}{e^{i\alpha}} \end{bmatrix}
+ \begin{bmatrix}\beta\\\ \gamma\\\ \end{bmatrix}=
+\begin{bmatrix}1 \cdot \beta + 0 \cdot \gamma\\\ 0 \cdot \beta + {\color{red}{e^{i\alpha}}} \cdot \gamma\end{bmatrix}=
+ \begin{bmatrix}\beta\\\ {\color{red}{e^{i\alpha}}}\gamma\end{bmatrix}=
+ \beta |0\rangle + {\color{red}{e^{i\alpha}}} \gamma |1\rangle
+$$
+
+> Suppose now that $\alpha = \frac{\pi}{2}$.
+> Then $e^{i\alpha}= \cos\frac{\pi}{2} + i\sin\frac{\pi}{2}$.
+> And, since $\cos\frac{\pi}{2}= 0$ and $\sin\frac{\pi}{2} = 1$, then we have that $\cos\frac{\pi}{2} + i \sin\frac{\pi}{2} = i$, and  
+> $R1(\frac{\pi}{2}) = S$, which we used in the second solution to the task "Relative Phase i".
+
+@[solution]({
+    "id": "single_qubit_gates__phase_change_solution",
+    "codePath": "./Solution.qs"
+
+})
diff --git a/katas/content/single_qubit_gates/phase_flip/Placeholder.qs b/katas/content/single_qubit_gates/phase_flip/Placeholder.qs
new file mode 100644
index 00000000..09eb8f8c
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl {
+        // Implement your solution here...
+        
+    }
+}
+
diff --git a/katas/content/single_qubit_gates/phase_flip/SolutionA.qs b/katas/content/single_qubit_gates/phase_flip/SolutionA.qs
new file mode 100644
index 00000000..23b1f2cc
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/SolutionA.qs
@@ -0,0 +1,5 @@
+namespace Kata {
+    operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl {
+        S(q);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/single_qubit_gates/phase_flip/SolutionB.qs b/katas/content/single_qubit_gates/phase_flip/SolutionB.qs
new file mode 100644
index 00000000..d87e676c
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/SolutionB.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl {
+        open Microsoft.Quantum.Math;
+        R1(0.5 * PI(), q);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/single_qubit_gates/phase_flip/Verification.qs b/katas/content/single_qubit_gates/phase_flip/Verification.qs
new file mode 100644
index 00000000..b2cbc2eb
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/Verification.qs
@@ -0,0 +1,29 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Convert;
+
+    operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl {
+        S(q);
+    }
+
+    operation CheckSolution() : Bool {
+        let solution = register => Kata.PhaseFlip(register[0]);
+        let reference = register => PhaseFlip(register[0]);
+        let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1);
+
+        // Output different feedback to the user depending on whether the solution was correct.
+        if isCorrect {
+            Message("Correct!");
+        } else {
+            Message("Incorrect.");
+            Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " +
+                "is expected to have.");
+            use initial = Qubit(); // |0〉
+            Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉
+            ShowQuantumStateComparison([initial], solution, reference);
+            Reset(initial);
+        }
+        isCorrect
+    }
+}
diff --git a/katas/content/single_qubit_gates/phase_flip/index.md b/katas/content/single_qubit_gates/phase_flip/index.md
new file mode 100644
index 00000000..cc4ebd4b
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/index.md
@@ -0,0 +1,3 @@
+**Input:** A qubit in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$.
+
+**Goal:** Change the qubit state to $\alpha |0\rangle + {\color{red}i}\beta |1\rangle$ (add a relative phase $i$ to $|1\rangle$ component of the superposition).
diff --git a/katas/content/single_qubit_gates/phase_flip/solution.md b/katas/content/single_qubit_gates/phase_flip/solution.md
new file mode 100644
index 00000000..fe97ddd3
--- /dev/null
+++ b/katas/content/single_qubit_gates/phase_flip/solution.md
@@ -0,0 +1,32 @@
+#### Solution 1
+
+We can recognize that the S gate performs this particular relative phase addition to the $|1\rangle$ basis state. As a reminder,
+
+$$
+S =
+\begin{bmatrix}1 & 0\\\ 0 & i\end{bmatrix}
+$$
+
+Let's see the effect of this gate on the general superposition $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$.
+
+$$
+ \begin{bmatrix}1 & 0 \\\ 0 & i \end{bmatrix}
+ \begin{bmatrix}\alpha\\\ \beta\\\ \end{bmatrix}=
+\begin{bmatrix}1\cdot\alpha + 0\cdot\beta\\\ 0\cdot\alpha + i\cdot\beta \end{bmatrix}=
+ \begin{bmatrix}\alpha\\\ i\beta\\\ \end{bmatrix}
+$$
+
+It is therefore easy to see that when $|\psi\rangle = 0.6|0\rangle +  0.8|1\rangle, S|\psi\rangle =  0.6|0\rangle + 0.8i|1\rangle$.
+@[solution]({
+"id": "single_qubit_gates__phase_flip_solution_a",
+"codePath": "./SolutionA.qs"
+})
+
+#### Solution 2
+
+Alternatively, see the Phase Change task later in the kata for an explanation of using R1 gate to implement the same transformation.
+
+@[solution]({
+"id": "single_qubit_gates_phase_flip_solution_b",
+"codePath": "./SolutionB.qs"
+})
diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs
index 9312d38c..e721f911 100644
--- a/language_service/src/compilation.rs
+++ b/language_service/src/compilation.rs
@@ -270,9 +270,10 @@ fn run_fir_passes(
         return;
     }
 
-    let cap_results =
-        PassContext::run_fir_passes_on_hir(package_store, package_id, target_profile.into());
-    if let Err(caps_errors) = cap_results {
+    let (fir_store, fir_package_id) = qsc::lower_hir_to_fir(package_store, package_id);
+    let caps_results =
+        PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, target_profile.into());
+    if let Err(caps_errors) = caps_results {
         for err in caps_errors {
             let err = WithSource::from_map(&unit.sources, compile::ErrorKind::Pass(err));
             errors.push(err);
diff --git a/wasm/src/diagnostic.rs b/wasm/src/diagnostic.rs
index e334a40c..b29d3803 100644
--- a/wasm/src/diagnostic.rs
+++ b/wasm/src/diagnostic.rs
@@ -216,6 +216,7 @@ fn interpret_error_labels(err: &interpret::Error) -> Vec<Label> {
         interpret::Error::Compile(e) => error_labels(e),
         interpret::Error::Pass(e) => error_labels(e),
         interpret::Error::NoEntryPoint
+        | interpret::Error::PartialEvaluation(_)
         | interpret::Error::UnsupportedRuntimeCapabilities
         | interpret::Error::Circuit(_)
         | interpret::Error::NotAnOperation => Vec::new(),


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index dc1ab6df..f9fd21c3 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -712,6 +712,97 @@ mod given_interpreter {
             "#]].assert_eq(&res);
         }
 
+        #[test]
+        fn adaptive_qirgen() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                RuntimeCapabilityFlags::ForwardBranching,
+                LanguageFeatures::default(),
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                namespace Test {
+                    open Microsoft.Quantum.Math;
+                    open QIR.Intrinsic;
+                    @EntryPoint()
+                    operation Main() : Unit {
+                        use q = Qubit();
+                        let pi_over_2 = 4.0 / 2.0;
+                        __quantum__qis__rz__body(pi_over_2, q);
+                        mutable some_angle = ArcSin(0.0);
+                        __quantum__qis__rz__body(some_angle, q);
+                        set some_angle = ArcCos(-1.0) / PI();
+                        __quantum__qis__rz__body(some_angle, q);
+                    }
+                }"#
+                },
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter.qirgen("Test.Main()").expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                block_0:
+                  call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+                  ret void
+                }
+
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="0" "required_num_results"="0" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]]
+            .assert_eq(&res);
+        }
+
+        #[test]
+        fn adaptive_qirgen_fails_when_entry_expr_does_not_match_profile() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                RuntimeCapabilityFlags::ForwardBranching,
+                LanguageFeatures::default(),
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                use q = Qubit();
+                mutable x = 1;
+                "#
+                },
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter
+                .qirgen("if M(q) == One { set x = 2; }")
+                .expect_err("expected error");
+            is_error(
+                &res,
+                &expect![[r#"
+                    cannot use a dynamic integer value
+                       [<entry>] [set x = 2]
+                "#]],
+            );
+        }
+
         #[test]
         fn qirgen_entry_expr_in_block() {
             let mut interpreter = Interpreter::new(
diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs
index f0cde17b..4a4d55ef 100644
--- a/compiler/qsc_partial_eval/tests/test_utils.rs
+++ b/compiler/qsc_partial_eval/tests/test_utils.rs
@@ -3,10 +3,10 @@
 
 use qsc::{incremental::Compiler, PackageType};
 use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_fir::fir::{PackageId, PackageStore};
+use qsc_fir::fir::PackageStore;
 use qsc_frontend::compile::{PackageStore as HirPackageStore, RuntimeCapabilityFlags, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
-use qsc_partial_eval::partially_evaluate;
+use qsc_partial_eval::{partially_evaluate, ProgramEntry};
 use qsc_rca::{Analyzer, PackageStoreComputeProperties};
 use qsc_rir::rir::{BlockId, Callable, CallableId, Instruction, Program};
 
@@ -38,9 +38,9 @@ pub fn assert_callable(program: &Program, callable_id: CallableId, expected_call
 pub fn compile_and_partially_evaluate(source: &str) -> Program {
     let compilation_context = CompilationContext::new(source);
     let maybe_program = partially_evaluate(
-        compilation_context.package_id,
         &compilation_context.fir_store,
         &compilation_context.compute_properties,
+        &compilation_context.entry,
     );
     match maybe_program {
         Ok(program) => program,
@@ -51,7 +51,7 @@ pub fn compile_and_partially_evaluate(source: &str) -> Program {
 struct CompilationContext {
     fir_store: PackageStore,
     compute_properties: PackageStoreComputeProperties,
-    package_id: PackageId,
+    entry: ProgramEntry,
 }
 
 impl CompilationContext {
@@ -69,10 +69,22 @@ impl CompilationContext {
         let fir_store = lower_hir_package_store(compiler.package_store());
         let analyzer = Analyzer::init(&fir_store);
         let compute_properties = analyzer.analyze_all();
+        let package = fir_store.get(package_id);
+        let entry = ProgramEntry {
+            exec_graph: package.entry_exec_graph.clone(),
+            expr: (
+                package_id,
+                package
+                    .entry
+                    .expect("package must have an entry expression"),
+            )
+                .into(),
+        };
+
         Self {
             fir_store,
             compute_properties,
-            package_id,
+            entry,
         }
     }
 }
diff --git a/compiler/qsc_passes/src/entry_point/tests.rs b/compiler/qsc_passes/src/entry_point/tests.rs
index 08bb16b8..9daed0bf 100644
--- a/compiler/qsc_passes/src/entry_point/tests.rs
+++ b/compiler/qsc_passes/src/entry_point/tests.rs
@@ -44,7 +44,7 @@ fn test_entry_point_attr_to_expr() {
             }"},
         "",
         &expect![[r#"
-            Expr 12 [40-73] [Type Int]: Call:
+            Expr 12 [50-54] [Type Int]: Call:
                 Expr 11 [40-73] [Type Int]: Var: Item 1
                 Expr 10 [40-73] [Type Unit]: Unit"#]],
     );
diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py
index 66d322ec..e770450d 100644
--- a/pip/tests/test_interpreter.py
+++ b/pip/tests/test_interpreter.py
@@ -315,9 +315,7 @@ def test_adaptive_errors_are_raised_from_entry_expr() -> None:
     assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo)
 
 
-# This is temporary but asserts that the functionality is
-# Not yet implemented.
-def test_adaptive_qir_cannot_be_generated() -> None:
+def test_adaptive_qir_can_be_generated() -> None:
     adaptive_input = """
         namespace Test {
             open Microsoft.Quantum.Math;
@@ -336,10 +334,35 @@ def test_adaptive_qir_cannot_be_generated() -> None:
         """
     e = Interpreter(TargetProfile.Adaptive)
     e.interpret(adaptive_input)
-    with pytest.raises(Exception) as excinfo:
-        e.qir("Test.Main()")
+    qir = e.qir("Test.Main()")
+    assert qir == dedent(
+        """\
+        %Result = type opaque
+        %Qubit = type opaque
+
+        define void @ENTRYPOINT__main() #0 {
+        block_0:
+          call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+          call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+          call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*))
+          ret void
+        }
+
+        declare void @__quantum__qis__rz__body(double, %Qubit*)
+
+        attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="0" "required_num_results"="0" }
+        attributes #1 = { "irreversible" }
 
-    assert "UnsupportedRuntimeCapabilities" in str(excinfo)
+        ; module flags
+
+        !llvm.module.flags = !{!0, !1, !2, !3}
+
+        !0 = !{i32 1, !"qir_major_version", i32 1}
+        !1 = !{i32 7, !"qir_minor_version", i32 0}
+        !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+        !3 = !{i32 1, !"dynamic_result_management", i1 false}
+        """
+    )
 
 
 def test_operation_circuit() -> None: