Repository: microsoft/qsharp
Pull request #1418 (2024-04-23 21:52:52)

Add tasks 2.2, 2.3 to Superposition Kata
This resolves part of https://github.com/microsoft/qsharp/issues/1185 .


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 32e5a336..b2864bcb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1015,6 +1015,7 @@ version = "0.0.0"
 dependencies = [
  "expect-test",
  "qsc_ast",
+ "qsc_data_structures",
  "qsc_frontend",
  "qsc_hir",
  "regex-lite",
@@ -1068,7 +1069,6 @@ dependencies = [
 name = "qsc_frontend"
 version = "0.0.0"
 dependencies = [
- "bitflags 2.4.2",
  "expect-test",
  "indoc",
  "library",
diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs
index df4b5492..bc4dceb3 100644
--- a/compiler/qsc/benches/eval.rs
+++ b/compiler/qsc/benches/eval.rs
@@ -5,10 +5,10 @@ allocator::assign_global!();
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use indoc::indoc;
-use qsc::{interpret::Interpreter, PackageType};
+use qsc::{interpret::Interpreter, PackageType, TargetCapabilityFlags};
 use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_eval::output::GenericReceiver;
-use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::SourceMap;
 
 const TELEPORT: &str = include_str!("../../../samples/algorithms/Teleportation.qs");
 const DEUTSCHJOZSA: &str = include_str!("../../../samples/algorithms/DeutschJozsa.qs");
diff --git a/compiler/qsc/benches/large.rs b/compiler/qsc/benches/large.rs
index 5025108d..b89a20ee 100644
--- a/compiler/qsc/benches/large.rs
+++ b/compiler/qsc/benches/large.rs
@@ -4,9 +4,12 @@
 allocator::assign_global!();
 
 use criterion::{criterion_group, criterion_main, Criterion};
-use qsc::compile::{self, compile};
+use qsc::{
+    compile::{self, compile},
+    TargetCapabilityFlags,
+};
 use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore, SourceMap};
 use qsc_passes::PackageType;
 
 const INPUT: &str = include_str!("./large.qs");
diff --git a/compiler/qsc/benches/library.rs b/compiler/qsc/benches/library.rs
index 3decd16b..e1b80b00 100644
--- a/compiler/qsc/benches/library.rs
+++ b/compiler/qsc/benches/library.rs
@@ -4,8 +4,8 @@
 allocator::assign_global!();
 
 use criterion::{criterion_group, criterion_main, Criterion};
-use qsc::compile;
-use qsc_frontend::compile::{PackageStore, TargetCapabilityFlags};
+use qsc::{compile, TargetCapabilityFlags};
+use qsc_frontend::compile::PackageStore;
 
 pub fn library(c: &mut Criterion) {
     c.bench_function("Core + Standard library compilation", |b| {
diff --git a/compiler/qsc/benches/rca.rs b/compiler/qsc/benches/rca.rs
index c9005f9a..0cbbfd70 100644
--- a/compiler/qsc/benches/rca.rs
+++ b/compiler/qsc/benches/rca.rs
@@ -3,9 +3,9 @@
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use qsc::incremental::Compiler;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_fir::fir::PackageStore;
-use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
 use qsc_passes::PackageType;
 use qsc_rca::{Analyzer, PackageStoreComputeProperties};
diff --git a/compiler/qsc/src/bin/qsc.rs b/compiler/qsc/src/bin/qsc.rs
index 91f3f8b1..83bd128a 100644
--- a/compiler/qsc/src/bin/qsc.rs
+++ b/compiler/qsc/src/bin/qsc.rs
@@ -8,9 +8,9 @@ use log::info;
 use miette::{Context, IntoDiagnostic, Report};
 use qsc::compile::compile;
 use qsc_codegen::qir_base;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_frontend::{
-    compile::{PackageStore, SourceContents, SourceMap, SourceName, TargetCapabilityFlags},
+    compile::{PackageStore, SourceContents, SourceMap, SourceName},
     error::WithSource,
 };
 use qsc_hir::hir::{Package, PackageId};
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index c987012e..1876787c 100644
--- a/compiler/qsc/src/bin/qsi.rs
+++ b/compiler/qsc/src/bin/qsi.rs
@@ -8,13 +8,13 @@ use miette::{Context, IntoDiagnostic, Report, Result};
 use num_bigint::BigUint;
 use num_complex::Complex64;
 use qsc::interpret::{self, InterpretResult, Interpreter};
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_eval::{
     output::{self, Receiver},
     state::format_state_id,
     val::Value,
 };
-use qsc_frontend::compile::{SourceContents, SourceMap, SourceName, TargetCapabilityFlags};
+use qsc_frontend::compile::{SourceContents, SourceMap, SourceName};
 use qsc_passes::PackageType;
 use qsc_project::{FileSystem, Manifest, StdFs};
 use std::{
diff --git a/compiler/qsc/src/codegen.rs b/compiler/qsc/src/codegen.rs
index d0d8b692..80b893b2 100644
--- a/compiler/qsc/src/codegen.rs
+++ b/compiler/qsc/src/codegen.rs
@@ -1,11 +1,15 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+#[cfg(test)]
+mod tests;
+
 use qsc_codegen::qir::fir_to_qir;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore, SourceMap};
 use qsc_partial_eval::ProgramEntry;
 use qsc_passes::{PackageType, PassContext};
+use qsc_rca::Analyzer;
 
 use crate::compile;
 
@@ -30,14 +34,19 @@ pub fn get_qir(
 
     // Ensure it compiles before trying to add it to the store.
     if !errors.is_empty() {
-        // 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.
-        return Err("Failed to generate QIR".to_string());
+        // This will happen when QIR generation is attempted on a program that has errors.
+        // This can happen in the playground.
+        let mut error_message =
+            String::from("Failed to generate QIR. Could not compile sources.:\n");
+        for error in errors {
+            error_message.push_str(&format!("{error}\n"));
+        }
+
+        return Err(error_message);
     }
 
     let package_id = package_store.insert(unit);
     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(),
@@ -49,16 +58,21 @@ pub fn get_qir(
         )
             .into(),
     };
-    // Ensure it compiles before trying to add it to the store.
-    match caps_results {
-        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.
-            Err("Failed to generate QIR".to_string())
-        }
-    }
+
+    let compute_properties = if capabilities == TargetCapabilityFlags::empty() {
+        // baseprofchk already handled compliance, run the analyzer to get the compute properties.
+        let analyzer = Analyzer::init(&fir_store);
+        Ok(analyzer.analyze_all())
+    } else {
+        PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities)
+    };
+
+    let Ok(compute_properties) = compute_properties else {
+        // 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.
+        return Err("Failed to generate QIR. Could not generate compute properties.".to_string());
+    };
+
+    fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry)
+        .map_err(|e| e.to_string())
 }
diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs
index 593ce375..85d9958e 100644
--- a/compiler/qsc/src/compile.rs
+++ b/compiler/qsc/src/compile.rs
@@ -2,9 +2,9 @@
 // Licensed under the MIT License.
 
 use miette::{Diagnostic, Report};
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_frontend::{
-    compile::{CompileUnit, PackageStore, SourceMap, TargetCapabilityFlags},
+    compile::{CompileUnit, PackageStore, SourceMap},
     error::WithSource,
 };
 use qsc_hir::hir::PackageId;
diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs
index a4473ce6..536affb1 100644
--- a/compiler/qsc/src/incremental.rs
+++ b/compiler/qsc/src/incremental.rs
@@ -3,10 +3,12 @@
 
 use crate::compile::{self, compile, core, std};
 use miette::Diagnostic;
+
 use qsc_ast::ast;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+
 use qsc_frontend::{
-    compile::{OpenPackageStore, PackageStore, SourceMap, TargetCapabilityFlags},
+    compile::{OpenPackageStore, PackageStore, SourceMap},
     error::WithSource,
     incremental::Increment,
 };
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index b1f9dbce..ffbca883 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -46,6 +46,7 @@ use qsc_data_structures::{
     language_features::LanguageFeatures,
     line_column::{Encoding, Range},
     span::Span,
+    target::TargetCapabilityFlags,
 };
 use qsc_eval::{
     backend::{Backend, Chain as BackendChain, SparseSim},
@@ -58,7 +59,7 @@ use qsc_fir::{
     visit::{self, Visitor},
 };
 use qsc_frontend::{
-    compile::{CompileUnit, PackageStore, Source, SourceMap, TargetCapabilityFlags},
+    compile::{CompileUnit, PackageStore, Source, SourceMap},
     error::WithSource,
     incremental::Increment,
 };
diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs
index 7d48b480..4712f815 100644
--- a/compiler/qsc/src/lib.rs
+++ b/compiler/qsc/src/lib.rs
@@ -11,9 +11,7 @@ pub mod target;
 
 pub use qsc_formatter::formatter;
 
-pub use qsc_frontend::compile::{
-    CompileUnit, PackageStore, SourceContents, SourceMap, SourceName, TargetCapabilityFlags,
-};
+pub use qsc_frontend::compile::{CompileUnit, PackageStore, SourceContents, SourceMap, SourceName};
 
 pub mod resolve {
     pub use qsc_frontend::resolve::{Local, LocalKind, Locals, Res};
@@ -35,7 +33,9 @@ pub mod project {
     pub use qsc_project::{DirEntry, EntryType, FileSystem, Manifest, ManifestDescriptor};
 }
 
-pub use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
+pub use qsc_data_structures::{
+    language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
+};
 
 pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext};
 
diff --git a/compiler/qsc/src/location.rs b/compiler/qsc/src/location.rs
index 851c679f..dd3bde2b 100644
--- a/compiler/qsc/src/location.rs
+++ b/compiler/qsc/src/location.rs
@@ -62,8 +62,9 @@ mod tests {
     use expect_test::expect;
     use qsc_data_structures::{
         language_features::LanguageFeatures, line_column::Encoding, span::Span,
+        target::TargetCapabilityFlags,
     };
-    use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags};
+    use qsc_frontend::compile::{PackageStore, SourceMap};
     use qsc_hir::hir::PackageId;
     use qsc_passes::PackageType;
 
diff --git a/compiler/qsc/src/target.rs b/compiler/qsc/src/target.rs
index f34de255..b1cd27c0 100644
--- a/compiler/qsc/src/target.rs
+++ b/compiler/qsc/src/target.rs
@@ -3,7 +3,7 @@
 
 use std::str::FromStr;
 
-use qsc_frontend::compile::TargetCapabilityFlags;
+use qsc_data_structures::target::TargetCapabilityFlags;
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum Profile {
diff --git a/compiler/qsc_codegen/src/qir.rs b/compiler/qsc_codegen/src/qir.rs
index 03085b0b..568e7e73 100644
--- a/compiler/qsc_codegen/src/qir.rs
+++ b/compiler/qsc_codegen/src/qir.rs
@@ -7,12 +7,12 @@ mod instruction_tests;
 #[cfg(test)]
 mod tests;
 
-use qsc_frontend::compile::TargetCapabilityFlags;
+use qsc_data_structures::target::TargetCapabilityFlags;
 use qsc_lowerer::map_hir_package_to_fir;
 use qsc_partial_eval::{partially_evaluate, ProgramEntry};
 use qsc_rca::PackageStoreComputeProperties;
 use qsc_rir::{
-    passes::{check_and_transform, defer_quantum_measurements},
+    passes::check_and_transform,
     rir::{self, ConditionCode},
     utils::get_all_block_successors,
 };
@@ -43,11 +43,8 @@ pub fn fir_to_qir(
     compute_properties: Option<PackageStoreComputeProperties>,
     entry: &ProgramEntry,
 ) -> Result<String, qsc_partial_eval::Error> {
-    let mut program = get_rir_from_compilation(fir_store, compute_properties, entry)?;
+    let mut program = get_rir_from_compilation(fir_store, compute_properties, entry, capabilities)?;
     check_and_transform(&mut program);
-    if capabilities.is_empty() {
-        defer_quantum_measurements(&mut program);
-    }
     Ok(ToQir::<String>::to_qir(&program, &program))
 }
 
@@ -55,13 +52,14 @@ fn get_rir_from_compilation(
     fir_store: &qsc_fir::fir::PackageStore,
     compute_properties: Option<PackageStoreComputeProperties>,
     entry: &ProgramEntry,
+    capabilities: TargetCapabilityFlags,
 ) -> 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_store, &compute_properties, entry)
+    partially_evaluate(fir_store, &compute_properties, entry, capabilities)
 }
 
 /// A trait for converting a type into QIR of type `T`.
@@ -546,9 +544,92 @@ impl ToQir<String> for rir::Program {
         } else {
             "adaptive_profile"
         };
-        format!(
+        let body = format!(
             include_str!("./qir/template.ll"),
             callables, profile, self.num_qubits, self.num_results
-        )
+        );
+        let flags = get_module_metadata(self);
+        body + "\n" + &flags
+    }
+}
+
+/// Create the module metadata for the given program.
+/// creating the `llvm.module.flags` and its associated values.
+fn get_module_metadata(program: &rir::Program) -> String {
+    let mut flags = String::new();
+
+    // push the default attrs, we don't have any config values
+    // for now that would change any of them.
+    flags.push_str(
+        r#"
+!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}
+"#,
+    );
+
+    let mut index = 4;
+
+    // If we are not in the base profile, we need to add the capabilities
+    // associated with the adaptive profile.
+    if !program.config.is_base() {
+        // loop through the capabilities and add them to the metadata
+        // for values that we can generate.
+        for cap in program.config.capabilities.iter() {
+            let name = match cap {
+                TargetCapabilityFlags::QubitReset => "qubit_resetting",
+                TargetCapabilityFlags::IntegerComputations => "classical_ints",
+                TargetCapabilityFlags::FloatingPointComputations => "classical_floats",
+                TargetCapabilityFlags::BackwardsBranching => "backwards_branching",
+                _ => continue,
+            };
+            flags.push_str(&format!(
+                "!{} = !{{i32 {}, !\"{}\", i1 {}}}\n",
+                index, 1, name, true
+            ));
+            index += 1;
+        }
+
+        // loop through the capabilities that are missing and add them to the metadata
+        // as not supported.
+        let missing = TargetCapabilityFlags::all().difference(program.config.capabilities);
+        for cap in missing.iter() {
+            let name = match cap {
+                TargetCapabilityFlags::QubitReset => "qubit_resetting",
+                TargetCapabilityFlags::IntegerComputations => "classical_ints",
+                TargetCapabilityFlags::FloatingPointComputations => "classical_floats",
+                TargetCapabilityFlags::BackwardsBranching => "backwards_branching",
+                _ => continue,
+            };
+            flags.push_str(&format!(
+                "!{} = !{{i32 {}, !\"{}\", i1 {}}}\n",
+                index, 1, name, false
+            ));
+            index += 1;
+        }
+
+        // Add the remaining extension capabilities as not supported.
+        // We can't generate these values yet so we just add them as false.
+        let unmapped_capabilities = [
+            "classical_fixed_points",
+            "user_functions",
+            "multiple_target_branching",
+        ];
+        for capability in unmapped_capabilities {
+            flags.push_str(&format!(
+                "!{} = !{{i32 {}, !\"{}\", i1 {}}}\n",
+                index, 1, capability, false
+            ));
+            index += 1;
+        }
+    }
+
+    let mut metadata_def = String::new();
+    metadata_def.push_str("!llvm.module.flags = !{");
+    for i in 0..index - 1 {
+        metadata_def.push_str(&format!("!{i}, "));
     }
+    metadata_def.push_str(&format!("!{}}}\n", index - 1));
+    metadata_def + &flags
 }
diff --git a/compiler/qsc_codegen/src/qir/template.ll b/compiler/qsc_codegen/src/qir/template.ll
index fae49bd7..b58560a7 100644
--- a/compiler/qsc_codegen/src/qir/template.ll
+++ b/compiler/qsc_codegen/src/qir/template.ll
@@ -7,10 +7,3 @@ attributes #0 = {{ "entry_point" "output_labeling_schema" "qir_profiles"="{}" "r
 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}}
diff --git a/compiler/qsc_data_structures/src/lib.rs b/compiler/qsc_data_structures/src/lib.rs
index 53a850c6..68fc178c 100644
--- a/compiler/qsc_data_structures/src/lib.rs
+++ b/compiler/qsc_data_structures/src/lib.rs
@@ -7,3 +7,4 @@ pub mod index_map;
 pub mod language_features;
 pub mod line_column;
 pub mod span;
+pub mod target;
diff --git a/compiler/qsc_data_structures/src/target.rs b/compiler/qsc_data_structures/src/target.rs
new file mode 100644
index 00000000..f9030ba2
--- /dev/null
+++ b/compiler/qsc_data_structures/src/target.rs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use bitflags::bitflags;
+
+bitflags! {
+    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+    pub struct TargetCapabilityFlags: u32 {
+        const Adaptive = 0b0000_0001;
+        const IntegerComputations = 0b0000_0010;
+        const FloatingPointComputations = 0b0000_0100;
+        const BackwardsBranching = 0b0000_1000;
+        const HigherLevelConstructs = 0b0001_0000;
+        const QubitReset = 0b0010_0000;
+    }
+}
+
+impl std::str::FromStr for TargetCapabilityFlags {
+    type Err = ();
+
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        match value {
+            "Base" => Ok(TargetCapabilityFlags::empty()),
+            "Adaptive" => Ok(TargetCapabilityFlags::Adaptive),
+            "IntegerComputations" => Ok(TargetCapabilityFlags::IntegerComputations),
+            "FloatingPointComputations" => Ok(TargetCapabilityFlags::FloatingPointComputations),
+            "BackwardsBranching" => Ok(TargetCapabilityFlags::BackwardsBranching),
+            "HigherLevelConstructs" => Ok(TargetCapabilityFlags::HigherLevelConstructs),
+            "QubitReset" => Ok(TargetCapabilityFlags::QubitReset),
+            "Unrestricted" => Ok(TargetCapabilityFlags::all()),
+            _ => Err(()),
+        }
+    }
+}
+
+impl Default for TargetCapabilityFlags {
+    fn default() -> Self {
+        TargetCapabilityFlags::empty()
+    }
+}
diff --git a/compiler/qsc_doc_gen/Cargo.toml b/compiler/qsc_doc_gen/Cargo.toml
index 7249c2e6..ddc9e37e 100644
--- a/compiler/qsc_doc_gen/Cargo.toml
+++ b/compiler/qsc_doc_gen/Cargo.toml
@@ -13,6 +13,7 @@ expect-test = { workspace = true }
 
 [dependencies]
 regex-lite = { workspace = true }
+qsc_data_structures = { path = "../qsc_data_structures" }
 qsc_frontend = { path = "../qsc_frontend" }
 qsc_ast = { path = "../qsc_ast" }
 qsc_hir = { path = "../qsc_hir" }
diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs
index e03b76b4..311c2c70 100644
--- a/compiler/qsc_doc_gen/src/generate_docs.rs
+++ b/compiler/qsc_doc_gen/src/generate_docs.rs
@@ -7,7 +7,8 @@ mod tests;
 use crate::display::{increase_header_level, parse_doc_for_summary};
 use crate::display::{CodeDisplay, Lookup};
 use qsc_ast::ast;
-use qsc_frontend::compile::{self, PackageStore, TargetCapabilityFlags};
+use qsc_data_structures::target::TargetCapabilityFlags;
+use qsc_frontend::compile::{self, PackageStore};
 use qsc_frontend::resolve;
 use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Visibility};
 use qsc_hir::{hir, ty};
diff --git a/compiler/qsc_eval/src/state.rs b/compiler/qsc_eval/src/state.rs
index 1988dfcb..6befc095 100644
--- a/compiler/qsc_eval/src/state.rs
+++ b/compiler/qsc_eval/src/state.rs
@@ -483,7 +483,10 @@ fn write_latex_for_real_number(latex: &mut String, number: &RealNumber, render_o
 /// 1 is only rendered if ``render_one`` is true.
 fn write_latex_for_decimal_number(latex: &mut String, number: &DecimalNumber, render_one: bool) {
     if render_one || is_significant(number.value - 1.0) {
-        write!(latex, "{}", number.value).expect("Expected to write decimal value.");
+        // Using round() instead of neater string formatting({:.4})
+        // because we do not want trailing zeros (we need 0.5 and not 0.5000)
+        write!(latex, "{}", (number.value * 10000.0).round() / 10000.0)
+            .expect("Expected to write decimal value.");
     }
 }
 
diff --git a/compiler/qsc_frontend/Cargo.toml b/compiler/qsc_frontend/Cargo.toml
index 0ee1895d..a59b4663 100644
--- a/compiler/qsc_frontend/Cargo.toml
+++ b/compiler/qsc_frontend/Cargo.toml
@@ -9,7 +9,6 @@ edition.workspace = true
 license.workspace = true
 
 [dependencies]
-bitflags = { workspace = true }
 miette = { workspace = true }
 qsc_data_structures = { path = "../qsc_data_structures" }
 qsc_ast = { path = "../qsc_ast" }
diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs
index 0f89a4f5..86e816a4 100644
--- a/compiler/qsc_frontend/src/compile.rs
+++ b/compiler/qsc_frontend/src/compile.rs
@@ -12,7 +12,7 @@ use crate::{
     resolve::{self, Locals, Names, Resolver},
     typeck::{self, Checker, Table},
 };
-use bitflags::bitflags;
+
 use miette::{Diagnostic, Report};
 use preprocess::TrackedName;
 use qsc_ast::{
@@ -26,6 +26,7 @@ use qsc_data_structures::{
     index_map::{self, IndexMap},
     language_features::LanguageFeatures,
     span::Span,
+    target::TargetCapabilityFlags,
 };
 use qsc_hir::{
     assigner::Assigner as HirAssigner,
@@ -34,39 +35,9 @@ use qsc_hir::{
     validate::Validator as HirValidator,
     visit::Visitor as _,
 };
-use std::{fmt::Debug, str::FromStr, sync::Arc};
+use std::{fmt::Debug, sync::Arc};
 use thiserror::Error;
 
-bitflags! {
-    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-    pub struct TargetCapabilityFlags: u32 {
-        const Adaptive = 0b0000_0001;
-        const IntegerComputations = 0b0000_0010;
-        const FloatingPointComputations = 0b0000_0100;
-        const BackwardsBranching = 0b0000_1000;
-        const HigherLevelConstructs = 0b0001_0000;
-        const QubitReset = 0b0010_0000;
-    }
-}
-
-impl FromStr for TargetCapabilityFlags {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "Base" => Ok(TargetCapabilityFlags::empty()),
-            "Adaptive" => Ok(TargetCapabilityFlags::Adaptive),
-            "IntegerComputations" => Ok(TargetCapabilityFlags::IntegerComputations),
-            "FloatingPointComputations" => Ok(TargetCapabilityFlags::FloatingPointComputations),
-            "BackwardsBranching" => Ok(TargetCapabilityFlags::BackwardsBranching),
-            "HigherLevelConstructs" => Ok(TargetCapabilityFlags::HigherLevelConstructs),
-            "QubitReset" => Ok(TargetCapabilityFlags::QubitReset),
-            "Unrestricted" => Ok(TargetCapabilityFlags::all()),
-            _ => Err(()),
-        }
-    }
-}
-
 #[derive(Debug, Default)]
 pub struct CompileUnit {
     pub package: hir::Package,
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 7cd4eb62..75a3d8f4 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -5,10 +5,7 @@
 mod tests;
 
 use crate::{
-    compile::{
-        self, preprocess, AstPackage, CompileUnit, Offsetter, PackageStore, SourceMap,
-        TargetCapabilityFlags,
-    },
+    compile::{self, preprocess, AstPackage, CompileUnit, Offsetter, PackageStore, SourceMap},
     error::WithSource,
     lower::Lowerer,
     resolve::{self, Resolver},
@@ -21,7 +18,7 @@ use qsc_ast::{
     validate::Validator as AstValidator,
     visit::Visitor as AstVisitor,
 };
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_hir::{
     assigner::Assigner as HirAssigner,
     hir::{self, PackageId},
diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs
index 9d58f804..a5ecfa45 100644
--- a/compiler/qsc_frontend/src/lower.rs
+++ b/compiler/qsc_frontend/src/lower.rs
@@ -6,13 +6,12 @@ mod tests;
 
 use crate::{
     closure::{self, Lambda, PartialApp},
-    compile::TargetCapabilityFlags,
     resolve::{self, Names},
     typeck::{self, convert},
 };
 use miette::Diagnostic;
 use qsc_ast::ast;
-use qsc_data_structures::{index_map::IndexMap, span::Span};
+use qsc_data_structures::{index_map::IndexMap, span::Span, target::TargetCapabilityFlags};
 use qsc_hir::{
     assigner::Assigner,
     hir::{self, LocalItemId},
diff --git a/compiler/qsc_linter/src/linter.rs b/compiler/qsc_linter/src/linter.rs
index b7901f0b..9e9d2af9 100644
--- a/compiler/qsc_linter/src/linter.rs
+++ b/compiler/qsc_linter/src/linter.rs
@@ -9,7 +9,7 @@ use crate::lints::{ast::AstLint, hir::HirLint};
 use miette::{Diagnostic, LabeledSpan};
 use qsc_data_structures::span::Span;
 use qsc_frontend::compile::CompileUnit;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use std::fmt::Display;
 
 /// The entry point to the linter. It takes a [`qsc_frontend::compile::CompileUnit`]
@@ -73,7 +73,7 @@ impl Diagnostic for Lint {
 
 /// A lint level. This defines if a lint will be treated as a warning or an error,
 /// and if the lint level can be overriden by the user.
-#[derive(Debug, Clone, Copy, Deserialize)]
+#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub enum LintLevel {
     /// The lint is effectively disabled.
@@ -101,7 +101,7 @@ impl Display for LintLevel {
 }
 
 /// End-user configuration for each lint level.
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct LintConfig {
     #[serde(rename = "lint")]
     /// Represents the lint name.
@@ -111,7 +111,7 @@ pub struct LintConfig {
 }
 
 /// Represents a lint name.
-#[derive(Debug, Clone, Copy, Deserialize)]
+#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
 #[serde(untagged)]
 pub enum LintKind {
     /// AST lint name.
diff --git a/compiler/qsc_linter/src/linter/ast.rs b/compiler/qsc_linter/src/linter/ast.rs
index a5dad4fa..d62cd008 100644
--- a/compiler/qsc_linter/src/linter/ast.rs
+++ b/compiler/qsc_linter/src/linter/ast.rs
@@ -131,9 +131,9 @@ macro_rules! declare_ast_lints {
 
     // Declare the `AstLint` enum.
     (@CONFIG_ENUM $($lint_name:ident),*) => {
-        use serde::Deserialize;
+        use serde::{Deserialize, Serialize};
 
-        #[derive(Debug, Clone, Copy, Deserialize)]
+        #[derive(Debug, Clone, Copy, Deserialize, Serialize)]
         #[serde(rename_all = "camelCase")]
         pub enum AstLint {
             $($lint_name),*
diff --git a/compiler/qsc_linter/src/linter/hir.rs b/compiler/qsc_linter/src/linter/hir.rs
index e6e2d1e0..ee64c05b 100644
--- a/compiler/qsc_linter/src/linter/hir.rs
+++ b/compiler/qsc_linter/src/linter/hir.rs
@@ -119,9 +119,9 @@ macro_rules! declare_hir_lints {
 
     // Declare the `HirLint` enum.
     (@CONFIG_ENUM $($lint_name:ident),*) => {
-        use serde::Deserialize;
+        use serde::{Deserialize, Serialize};
 
-        #[derive(Debug, Clone, Copy, Deserialize)]
+        #[derive(Debug, Clone, Copy, Deserialize, Serialize)]
         #[serde(rename_all = "camelCase")]
         pub enum HirLint {
             $($lint_name),*
diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs
index 5c0ec744..1cc325d5 100644
--- a/compiler/qsc_partial_eval/src/lib.rs
+++ b/compiler/qsc_partial_eval/src/lib.rs
@@ -7,8 +7,8 @@ mod management;
 use evaluation_context::{Arg, BlockNode, EvaluationContext, Scope};
 use management::{QuantumIntrinsicsChecker, ResourceManager};
 use miette::Diagnostic;
-use qsc_data_structures::functors::FunctorApp;
 use qsc_data_structures::span::Span;
+use qsc_data_structures::{functors::FunctorApp, target::TargetCapabilityFlags};
 use qsc_eval::{
     self, exec_graph_section,
     output::GenericReceiver,
@@ -46,8 +46,10 @@ pub fn partially_evaluate(
     package_store: &PackageStore,
     compute_properties: &PackageStoreComputeProperties,
     entry: &ProgramEntry,
+    capabilities: TargetCapabilityFlags,
 ) -> Result<Program, Error> {
-    let partial_evaluator = PartialEvaluator::new(package_store, compute_properties, entry);
+    let partial_evaluator =
+        PartialEvaluator::new(package_store, compute_properties, entry, capabilities);
     partial_evaluator.eval()
 }
 
@@ -123,10 +125,12 @@ impl<'a> PartialEvaluator<'a> {
         package_store: &'a PackageStore,
         compute_properties: &'a PackageStoreComputeProperties,
         entry: &'a ProgramEntry,
+        capabilities: TargetCapabilityFlags,
     ) -> Self {
         // Create the entry-point callable.
         let mut resource_manager = ResourceManager::default();
         let mut program = Program::new();
+        program.config.capabilities = capabilities;
         let entry_block_id = resource_manager.next_block();
         program.blocks.insert(entry_block_id, rir::Block::default());
         let entry_point_id = resource_manager.next_callable();
diff --git a/compiler/qsc_passes/src/capabilitiesck.rs b/compiler/qsc_passes/src/capabilitiesck.rs
index a7e5d648..406501b6 100644
--- a/compiler/qsc_passes/src/capabilitiesck.rs
+++ b/compiler/qsc_passes/src/capabilitiesck.rs
@@ -14,7 +14,7 @@ mod tests_adaptive_plus_integers;
 pub mod tests_common;
 
 use miette::Diagnostic;
-use qsc_data_structures::span::Span;
+use qsc_data_structures::{span::Span, target::TargetCapabilityFlags};
 
 use qsc_fir::{
     fir::{
@@ -25,7 +25,7 @@ use qsc_fir::{
     ty::FunctorSetValue,
     visit::Visitor,
 };
-use qsc_frontend::compile::TargetCapabilityFlags;
+
 use qsc_lowerer::map_hir_package_to_fir;
 use qsc_rca::{
     Analyzer, ComputeKind, ItemComputeProperties, PackageComputeProperties,
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index 9116ee60..45ff8988 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -20,8 +20,9 @@ use capabilitiesck::{check_supported_capabilities, lower_store, run_rca_pass};
 use entry_point::generate_entry_expr;
 use loop_unification::LoopUni;
 use miette::Diagnostic;
+use qsc_data_structures::target::TargetCapabilityFlags;
 use qsc_fir::fir;
-use qsc_frontend::compile::{CompileUnit, TargetCapabilityFlags};
+use qsc_frontend::compile::CompileUnit;
 use qsc_hir::{
     assigner::Assigner,
     global::{self, Table},
diff --git a/compiler/qsc_project/src/manifest.rs b/compiler/qsc_project/src/manifest.rs
index 4f39462a..8341d04a 100644
--- a/compiler/qsc_project/src/manifest.rs
+++ b/compiler/qsc_project/src/manifest.rs
@@ -10,13 +10,13 @@ use std::{
 };
 
 pub use qsc_linter::LintConfig;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use std::{path::PathBuf, sync::Arc};
 
 pub const MANIFEST_FILE_NAME: &str = "qsharp.json";
 
 /// A Q# manifest, used to describe project metadata.
-#[derive(Deserialize, Debug, Default, Clone)]
+#[derive(Deserialize, Serialize, Debug, Default, Clone)]
 #[serde(rename_all = "camelCase")]
 pub struct Manifest {
     pub author: Option<String>,
diff --git a/compiler/qsc_rca/src/lib.rs b/compiler/qsc_rca/src/lib.rs
index 7a7544ea..5ee665a8 100644
--- a/compiler/qsc_rca/src/lib.rs
+++ b/compiler/qsc_rca/src/lib.rs
@@ -18,7 +18,10 @@ mod scaffolding;
 use crate::common::set_indentation;
 use bitflags::bitflags;
 use indenter::indented;
-use qsc_data_structures::index_map::{IndexMap, Iter};
+use qsc_data_structures::{
+    index_map::{IndexMap, Iter},
+    target::TargetCapabilityFlags,
+};
 use qsc_fir::{
     fir::{
         BlockId, ExprId, LocalItemId, PackageId, StmtId, StoreBlockId, StoreExprId, StoreItemId,
@@ -26,7 +29,7 @@ use qsc_fir::{
     },
     ty::Ty,
 };
-use qsc_frontend::compile::TargetCapabilityFlags;
+
 use std::{
     cmp::Ord,
     fmt::{self, Debug, Display, Formatter, Write},
diff --git a/compiler/qsc_rir/src/builder.rs b/compiler/qsc_rir/src/builder.rs
index 135f74c7..aa67cc07 100644
--- a/compiler/qsc_rir/src/builder.rs
+++ b/compiler/qsc_rir/src/builder.rs
@@ -1,6 +1,8 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+use qsc_data_structures::target::TargetCapabilityFlags;
+
 use crate::rir::{
     Block, BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Program, Ty,
     Variable, VariableId,
@@ -260,8 +262,6 @@ pub fn bell_program() -> Program {
     );
     program.num_qubits = 2;
     program.num_results = 2;
-    program.config.defer_measurements = true;
-    program.config.remap_qubits_on_reuse = true;
     program
 }
 
@@ -269,6 +269,7 @@ pub fn bell_program() -> Program {
 #[must_use]
 pub fn teleport_program() -> Program {
     let mut program = Program::default();
+    program.config.capabilities = TargetCapabilityFlags::Adaptive;
     program.callables.insert(CallableId(0), h_decl());
     program.callables.insert(CallableId(1), z_decl());
     program.callables.insert(CallableId(2), x_decl());
diff --git a/compiler/qsc_rir/src/passes.rs b/compiler/qsc_rir/src/passes.rs
index d6cd3076..412a772e 100644
--- a/compiler/qsc_rir/src/passes.rs
+++ b/compiler/qsc_rir/src/passes.rs
@@ -5,6 +5,7 @@ mod build_dominator_graph;
 mod defer_meas;
 mod reindex_qubits;
 mod remap_block_ids;
+mod simplify_control_flow;
 mod ssa_check;
 mod ssa_transform;
 mod type_check;
@@ -12,8 +13,10 @@ mod unreachable_code_check;
 
 use build_dominator_graph::build_dominator_graph;
 use defer_meas::defer_measurements;
+use qsc_data_structures::target::TargetCapabilityFlags;
 use reindex_qubits::reindex_qubits;
 use remap_block_ids::remap_block_ids;
+use simplify_control_flow::simplify_control_flow;
 use ssa_check::check_ssa_form;
 use ssa_transform::transform_to_ssa;
 pub use type_check::check_types;
@@ -28,6 +31,7 @@ use crate::{rir::Program, utils::build_predecessors_map};
 /// - Transforming the program to SSA form
 /// - Checking that the program is in SSA form
 pub fn check_and_transform(program: &mut Program) {
+    simplify_control_flow(program);
     check_unreachable_code(program);
     check_types(program);
     remap_block_ids(program);
@@ -37,12 +41,18 @@ pub fn check_and_transform(program: &mut Program) {
     check_ssa_form(program, &preds, &doms);
     check_unreachable_code(program);
     check_types(program);
-}
 
-/// Run the RIR passes that are necessary for targets with no mid-program measurement.
-/// This requires that qubits are not reused after measurement or reset, so qubit ids must be reindexed.
-/// This also requires that the program is a single block and will panic otherwise.
-pub fn defer_quantum_measurements(program: &mut Program) {
-    reindex_qubits(program);
-    defer_measurements(program);
+    // Run the RIR passes that are necessary for targets with no mid-program measurement.
+    // This requires that qubits are not reused after measurement or reset, so qubit ids must be reindexed.
+    // This also requires that the program is a single block and will panic otherwise.
+    if !program
+        .config
+        .capabilities
+        .contains(TargetCapabilityFlags::QubitReset)
+    {
+        reindex_qubits(program);
+    }
+    if program.config.capabilities == TargetCapabilityFlags::empty() {
+        defer_measurements(program);
+    }
 }
diff --git a/compiler/qsc_rir/src/passes/simplify_control_flow.rs b/compiler/qsc_rir/src/passes/simplify_control_flow.rs
new file mode 100644
index 00000000..f5e5f47c
--- /dev/null
+++ b/compiler/qsc_rir/src/passes/simplify_control_flow.rs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#[cfg(test)]
+mod tests;
+
+use rustc_hash::FxHashMap;
+
+use crate::{
+    rir::{Instruction, Program},
+    utils::build_predecessors_map,
+};
+
+/// Simplify control flow of the program.
+/// For now, this only removes single redundant blocks where the block is the only successor of its only predecessor.
+pub fn simplify_control_flow(program: &mut Program) {
+    // Use a map to track which blocks have been merged into their predecessors. This helps
+    // find the ultimate target block for later merges.
+    let mut merge_map = FxHashMap::default();
+
+    // For each block, check if it has a single predecessor and that predecessor ends with a jump to the current block.
+    // If so, the block is redundant and can be merged.
+    let preds_map = build_predecessors_map(program);
+    for (block_id, preds) in preds_map.iter() {
+        if preds.len() == 1
+            && program.get_block(preds[0]).0.last() == Some(&Instruction::Jump(block_id))
+        {
+            merge_map.insert(block_id, preds[0]);
+
+            // The block to merge into may itself have been merged into another block.
+            // Find the ultimate target block by iterating through the merge map until we find a predecessor
+            // block that is missing from the map indicating it hasn't been merged.
+            let mut target_block_id = preds[0];
+            while let Some(mapped_block) = merge_map.get(&target_block_id) {
+                target_block_id = *mapped_block;
+            }
+
+            // Clone the instructions for the current block. This is done via clone in part to satisfy the
+            // borrow checker, and in part so the check for last instruction above doesn't need to traverse
+            // to the ultimate target block via the merge map.
+            let mut instrs = program.get_block_mut(block_id).0.clone();
+            let target_block = program.get_block_mut(target_block_id);
+
+            // Remove the existing terminator instruction and append the instructions copied from the current block.
+            target_block.0.pop();
+            target_block.0.append(&mut instrs);
+        }
+    }
+
+    // Since each key in the merge map is a block that has been merged into another block, the keys can be used
+    // as the list of blocks to remove from the program.
+    for block_id in merge_map.keys() {
+        program.blocks.remove(*block_id);
+    }
+}
diff --git a/compiler/qsc_rir/src/rir.rs b/compiler/qsc_rir/src/rir.rs
index 8d0687fe..6415350d 100644
--- a/compiler/qsc_rir/src/rir.rs
+++ b/compiler/qsc_rir/src/rir.rs
@@ -2,7 +2,7 @@
 // Licensed under the MIT License.
 
 use indenter::{indented, Indented};
-use qsc_data_structures::index_map::IndexMap;
+use qsc_data_structures::{index_map::IndexMap, target::TargetCapabilityFlags};
 use std::fmt::{self, Display, Formatter, Write};
 
 /// The root of the RIR.
@@ -56,12 +56,16 @@ impl Program {
     pub fn get_block(&self, id: BlockId) -> &Block {
         self.blocks.get(id).expect("block should be present")
     }
+
+    #[must_use]
+    pub fn get_block_mut(&mut self, id: BlockId) -> &mut Block {
+        self.blocks.get_mut(id).expect("block should be present")
+    }
 }
 
 #[derive(Default)]
 pub struct Config {
-    pub remap_qubits_on_reuse: bool,
-    pub defer_measurements: bool,
+    pub capabilities: TargetCapabilityFlags,
 }
 
 impl Display for Config {
@@ -69,12 +73,11 @@ impl Display for Config {
         let mut indent = set_indentation(indented(f), 0);
         write!(indent, "Config:",)?;
         indent = set_indentation(indent, 1);
-        write!(
-            indent,
-            "\nremap_qubits_on_reuse: {}",
-            self.remap_qubits_on_reuse
-        )?;
-        write!(indent, "\ndefer_measurements: {}", self.defer_measurements)?;
+        if self.capabilities.is_empty() {
+            write!(indent, "\ncapabilities: Base")?;
+        } else {
+            write!(indent, "\ncapabilities: {:?}", self.capabilities)?;
+        }
         Ok(())
     }
 }
@@ -82,7 +85,7 @@ impl Display for Config {
 impl Config {
     #[must_use]
     pub fn is_base(&self) -> bool {
-        self.remap_qubits_on_reuse || self.defer_measurements
+        self.capabilities == TargetCapabilityFlags::empty()
     }
 }
 
diff --git a/katas/content/superdense_coding/entangled_pair/Placeholder.qs b/katas/content/superdense_coding/entangled_pair/Placeholder.qs
new file mode 100644
index 00000000..43078301
--- /dev/null
+++ b/katas/content/superdense_coding/entangled_pair/Placeholder.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj {
+        // Implement your solution here...
+
+    }
+}
diff --git a/katas/content/superdense_coding/entangled_pair/Solution.qs b/katas/content/superdense_coding/entangled_pair/Solution.qs
new file mode 100644
index 00000000..46710f3b
--- /dev/null
+++ b/katas/content/superdense_coding/entangled_pair/Solution.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj {
+        H(qs[0]);
+        CNOT(qs[0], qs[1]);
+    }
+}
diff --git a/katas/content/superdense_coding/entangled_pair/Verification.qs b/katas/content/superdense_coding/entangled_pair/Verification.qs
new file mode 100644
index 00000000..4a803b06
--- /dev/null
+++ b/katas/content/superdense_coding/entangled_pair/Verification.qs
@@ -0,0 +1,18 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+
+    operation CreateEntangledPair(qs : Qubit[]) : Unit is Adj + Ctl {
+        H(qs[0]);
+        CNOT(qs[0], qs[1]);
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+
+        return CheckOperationsEquivalenceOnZeroStateWithFeedback(
+            Kata.CreateEntangledPair,
+            CreateEntangledPair,
+            2
+        );
+    }
+}
diff --git a/katas/content/superdense_coding/entangled_pair/index.md b/katas/content/superdense_coding/entangled_pair/index.md
new file mode 100644
index 00000000..fe6a2fdb
--- /dev/null
+++ b/katas/content/superdense_coding/entangled_pair/index.md
@@ -0,0 +1,3 @@
+**Input:** Two qubits in the $|00\rangle$ state (stored in an array of length 2).
+
+**Goal:**  Prepare a Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} (|00\rangle + |11\rangle)$ on these qubits.
diff --git a/katas/content/superdense_coding/entangled_pair/solution.md b/katas/content/superdense_coding/entangled_pair/solution.md
new file mode 100644
index 00000000..ee4f6eb2
--- /dev/null
+++ b/katas/content/superdense_coding/entangled_pair/solution.md
@@ -0,0 +1,11 @@
+Recall that this state is the Bell state that we have already seen in Multi-Qubit Gates and Superposition katas.
+
+The solution takes two steps:
+
+1. Apply Hadamard gate on first qubit to get the state: $\big(\frac{1}{\sqrt{2}}|0\rangle + \frac{1}{\sqrt{2}}|1\rangle\big) \otimes |0\rangle$.
+2. Apply a $CNOT$ gate with the first qubit as the control and the second qubit as the target.
+
+@[solution]({
+    "id": "superdense_coding__create_entangled_pair_solution",
+    "codePath": "./Solution.qs"
+})
diff --git a/katas/content/superdense_coding/index.md b/katas/content/superdense_coding/index.md
new file mode 100644
index 00000000..bf23fda7
--- /dev/null
+++ b/katas/content/superdense_coding/index.md
@@ -0,0 +1,27 @@
+# Superdense Coding Kata
+
+@[section]({
+    "id": "./superdense_coding__overview",
+    "title": "Overview"
+})
+
+Superdense coding protocol allows us to transmit two bits of classical information by sending just one qubit using previously shared quantum entanglement. We are asssuming that Alice is the sender and Bob is the receiver.
+
+- A good description can be found in [the Wikipedia article](https://en.wikipedia.org/wiki/Superdense_coding).
+- A great interactive demonstration can be found [on the Wolfram Demonstrations Project](https://demonstrations.wolfram.com/SuperdenseCoding/).
+
+We split the superdense coding protocol into several steps:
+
+- Preparation (creating the entangled pair of qubits that are sent to Alice and Bob).
+- Encoding the message (Alice's task): Encoding the classical bits of the message into the state of Alice's qubit which then is sent to Bob.
+- Decoding the message (Bob's task): Using Bob's original qubit and the qubit he received from Alice to decode the classical message sent.
+- Finally, we compose those steps into the complete superdense coding protocol.
+
+@[exercise]({
+    "id": "./superdense_coding__entangled_pair",
+    "title": "Entangled Pair",
+    "path": "entangled_pair",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
diff --git a/katas/content/superposition/controlled_rotation/Placeholder.qs b/katas/content/superposition/controlled_rotation/Placeholder.qs
new file mode 100644
index 00000000..74747bed
--- /dev/null
+++ b/katas/content/superposition/controlled_rotation/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation ControlledRotation (qs : Qubit[]) : Unit is Adj + Ctl {
+        // Implement your solution here...
+
+    }  
+}
+
diff --git a/katas/content/superposition/controlled_rotation/Solution.qs b/katas/content/superposition/controlled_rotation/Solution.qs
new file mode 100644
index 00000000..f70008ed
--- /dev/null
+++ b/katas/content/superposition/controlled_rotation/Solution.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation ControlledRotation (qs : Qubit[]) : Unit is Adj + Ctl {
+        H(qs[0]);
+        Controlled H ([qs[0]], qs[1]);
+    }
+}
diff --git a/katas/content/superposition/controlled_rotation/Verification.qs b/katas/content/superposition/controlled_rotation/Verification.qs
new file mode 100644
index 00000000..37b90a5f
--- /dev/null
+++ b/katas/content/superposition/controlled_rotation/Verification.qs
@@ -0,0 +1,16 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+
+    operation ControlledRotation_Reference (qs : Qubit[]) : Unit is Adj + Ctl {
+        H(qs[0]);
+        Controlled H ([qs[0]], qs[1]);
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckOperationsEquivalenceOnZeroStateWithFeedback(
+            Kata.ControlledRotation,
+            ControlledRotation_Reference,
+            2)
+    }
+}
diff --git a/katas/content/superposition/controlled_rotation/index.md b/katas/content/superposition/controlled_rotation/index.md
new file mode 100644
index 00000000..1139e04f
--- /dev/null
+++ b/katas/content/superposition/controlled_rotation/index.md
@@ -0,0 +1,3 @@
+**Input:** Two qubits in the $|00\rangle$ state.
+
+**Goal**: Change the state of the qubits to $\frac{1}{\sqrt{2}}|00\rangle+\frac{1}{2}|10\rangle+\frac{1}{2}|11\rangle$.
\ No newline at end of file
diff --git a/katas/content/superposition/controlled_rotation/solution.md b/katas/content/superposition/controlled_rotation/solution.md
new file mode 100644
index 00000000..9485e16b
--- /dev/null
+++ b/katas/content/superposition/controlled_rotation/solution.md
@@ -0,0 +1,23 @@
+We will start by putting the first qubit in the state $\alpha|0\rangle + \beta|1\rangle$, where $\alpha$ and $\beta$ are the square roots of relative weights of all basis states which start with 0 and with 1, respectively.  
+
+In this case, our state can be represented as $\frac{1}{\sqrt{2}} \big( |0\rangle \otimes |0\rangle + |1\rangle \otimes \frac{1}{\sqrt2}(|0\rangle+|1\rangle) \big)$.
+
+We see that the relative weights of $|0\rangle$ and $|1\rangle$ states of the first qubit are both $\frac12$ (the squares of their amplitudes in the decomposition above). This means that we can do the first step by applying the $H$ gate to the first qubit, which gives the $\frac{1}{\sqrt{2}}|00\rangle + \frac{1}{\sqrt{2}}|10\rangle$ state. In matrix form this will look as follows:  
+
+$$ H \otimes I = \frac{1}{\sqrt{2}} \begin{bmatrix}1 & 0 & 1 & 0 \\\ 0 & 1 & 0 & 1 \\\ 1 & 0 & -1 & 0 \\\ 0 & 1 & 0 & -1 \end{bmatrix} \cdot
+\begin{bmatrix}1 \\\ 0 \\\0 \\\ 0 \end{bmatrix} = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\\ 0 \\\ 1 \\\0 \end{bmatrix} = \frac{1}{\sqrt{2}} \big( |0\rangle + |1\rangle \big) \otimes |0\rangle$$
+
+Now the first term of the state $\frac{1}{\sqrt2}|00\rangle$ matches that of the goal state, and we need to convert the second term $\frac{1}{\sqrt2}|10\rangle$ to $|1\rangle \otimes \frac{1}{\sqrt2}(|0\rangle+|1\rangle)$.
+
+To do this, we use the controlled $H$ gate. The matrix representation of the controlled $H$ gate is similar to the $CNOT$ gate, however the bottom right block of the matrix is not an $X$ gate but the $H$ gate:
+
+$$\text{Controlled } H = \begin{bmatrix}1 & 0 & 0 & 0 \\\ 0 & 1 & 0 & 0 \\\ 0 & 0 & \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \\\ 0 & 0 & \frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}} \end{bmatrix}$$
+
+When this is applied to our current state, we get our goal state:
+
+$$\text{Controlled } H \cdot \frac{1}{\sqrt{2}} \begin{bmatrix} 1 \\\ 0 \\\ 1 \\\0 \end{bmatrix} = \begin{bmatrix}\frac{1}{\sqrt{2}} \\\ 0 \\\ \frac{1}{2} \\\ \frac{1}{2} \end{bmatrix} = \frac{1}{\sqrt{2}}|00\rangle+\frac{1}{2}|10\rangle+\frac{1}{2}|11\rangle  $$
+
+@[solution]({
+    "id": "superposition__controlled_rotation_solution",
+    "codePath": "./Solution.qs"
+})
diff --git a/katas/content/superposition/index.md b/katas/content/superposition/index.md
index 9104292a..e267fd58 100644
--- a/katas/content/superposition/index.md
+++ b/katas/content/superposition/index.md
@@ -168,6 +168,25 @@ This kata is designed to get you familiar with the concept of superposition and
     ]
 })
 
+@[exercise]({
+    "id": "superposition__controlled_rotation",
+    "title": "Controlled Rotation",
+    "path": "./controlled_rotation/",
+    "qsDependencies": [
+        "../KatasLibrary.qs",
+        "./Common.qs"
+    ]
+})
+
+@[exercise]({
+    "id": "superposition__three_states_two_qubits",
+    "title": "Three States Two Qubits",
+    "path": "./three_states_two_qubits/",
+    "qsDependencies": [
+        "../KatasLibrary.qs",
+        "./Common.qs"
+    ]
+})
 
 @[section]({
     "id": "superposition__conclusion",
diff --git a/katas/content/superposition/three_states_two_qubits/Placeholder.qs b/katas/content/superposition/three_states_two_qubits/Placeholder.qs
new file mode 100644
index 00000000..3c05d85e
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation ThreeStates_TwoQubits (qs : Qubit[]) : Unit is Adj + Ctl {
+        // Implement your solution here...
+
+    }  
+}
+
diff --git a/katas/content/superposition/three_states_two_qubits/SolutionA.qs b/katas/content/superposition/three_states_two_qubits/SolutionA.qs
new file mode 100644
index 00000000..e4ac9736
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/SolutionA.qs
@@ -0,0 +1,15 @@
+namespace Kata {
+    operation ThreeStates_TwoQubits (qs : Qubit[]) : Unit {
+        use anc = Qubit();
+        mutable res = Zero;
+        repeat {
+            ApplyToEach(H, qs);
+            Controlled X(qs, anc);
+            set res = MResetZ(anc);
+        } 
+        until res == Zero
+        fixup {
+            ResetAll(qs);
+        }
+    }
+}
diff --git a/katas/content/superposition/three_states_two_qubits/SolutionB.qs b/katas/content/superposition/three_states_two_qubits/SolutionB.qs
new file mode 100644
index 00000000..07de3220
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/SolutionB.qs
@@ -0,0 +1,9 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+
+    operation ThreeStates_TwoQubits (qs : Qubit[]) : Unit is Adj + Ctl {
+        let theta = ArcSin(1.0 / Sqrt(3.0));
+        Ry(2.0 * theta, qs[0]);
+        ApplyControlledOnInt(0, H,[qs[0]], qs[1]);
+    }
+}
diff --git a/katas/content/superposition/three_states_two_qubits/Verification.qs b/katas/content/superposition/three_states_two_qubits/Verification.qs
new file mode 100644
index 00000000..7c6aae4d
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/Verification.qs
@@ -0,0 +1,19 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Math;
+
+    // Reference solution that does not use measurements (to be adjointable)
+    operation ThreeStates_TwoQubits_Reference (qs : Qubit[]) : Unit is Adj + Ctl {
+        let theta = ArcSin(1.0 / Sqrt(3.0));
+        Ry(2.0 * theta, qs[0]);
+        ApplyControlledOnInt(0, H,[qs[0]], qs[1]);
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckOperationsEquivalenceOnZeroStateWithFeedback(
+            Kata.ThreeStates_TwoQubits,
+            ThreeStates_TwoQubits_Reference,
+            2)
+    }
+}
diff --git a/katas/content/superposition/three_states_two_qubits/index.md b/katas/content/superposition/three_states_two_qubits/index.md
new file mode 100644
index 00000000..f7d2145c
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/index.md
@@ -0,0 +1,3 @@
+**Input:** Two qubits in the $|00\rangle$ state.
+
+**Goal:**  Change the state of the qubits to $\frac{1}{\sqrt{3}} \big(|00\rangle + |01\rangle + |10\rangle\big)$.
\ No newline at end of file
diff --git a/katas/content/superposition/three_states_two_qubits/solution.md b/katas/content/superposition/three_states_two_qubits/solution.md
new file mode 100644
index 00000000..a5806625
--- /dev/null
+++ b/katas/content/superposition/three_states_two_qubits/solution.md
@@ -0,0 +1,65 @@
+There are multiple ways to prepare this state; the first solution described will focus on preparing this state without using arbitrary rotation gates. 
+
+Initially we will prepare an equal superposition of all basis states on the two starting qubits by applying the $H$ gate to each of them: 
+$$\frac{1}{2} \big(|00\rangle + |01\rangle + |10\rangle + |11\rangle\big)$$
+
+This state is a superposition of the state we want to prepare, and the $|11\rangle$ state that we would like to discard. 
+We can do exactly that by performing the right measurement on the system. To do this, we allocate an extra qubit (sometimes referred to as an *ancilla* qubit). With this extra qubit the new state becomes: 
+$$\frac{1}{2} \big(|00\textbf{0}\rangle + |01\textbf{0}\rangle + |10\textbf{0}\rangle + |11\textbf{0}\rangle\big)$$
+
+Now, we want to separate the first three basis states from the last one and to store this separation in the extra qubit. 
+For example, we can keep the state of the extra qubit $|0\rangle$ for the basis states that we want to keep, and switch it to $|1\rangle$ for the basis states that we would like to discard. 
+A $CCNOT$ gate can be used to accomplish this, with the first two qubits used as control qubits and the extra qubit as target. 
+When the gate is applied, the state of the extra qubit will only change to $|1\rangle$ if both control qubits are in the $|11\rangle$ state, which marks exactly the state that we want to discard:
+
+$$CCNOT\frac{1}{2} \big(|00\textbf{0}\rangle + |01\textbf{0}\rangle + |10\textbf{0}\rangle + |11\textbf{0}\rangle\big) = 
+\frac{1}{2}\big(|00\rangle + |01\rangle + |10\rangle \big) \otimes |\textbf{0}\rangle + \frac{1}{2}|11\rangle \otimes |\textbf{1}\rangle $$
+
+Finally we measure just the extra qubit; this causes a partial collapse of the system to the state defined by the measurement result:
+* If the result is $|0\rangle$, the system collapses to a state that is a linear combination of basis states which had the extra qubit in state $|0\rangle$, i.e., the two qubits end up in the target state $\frac{1}{\sqrt3}\big(|00\rangle + |01\rangle + |10\rangle\big)$. 
+* If the result is $|1\rangle$, the system collapses to a state $|11\rangle$, so our goal is not achieved. The good thing is, this only happens in 25% of the cases, and we can just reset our qubits to the $|00\rangle$ state and try again.
+
+> Q# has a built-in [repeat-until-success (RUS) loop](https://learn.microsoft.com/azure/quantum/user-guide/language/expressions/conditionalloops#repeat-expression), which comes in handy in this case. 
+> * We will describe the main operations (applying $H$ and $CCNOT$ gates and the measurement) in the `repeat` part of the loop, which specifies its body.  
+> * `until` section specifies the condition which will break the loop. In this case the result of the measurement needs to be `Zero` to indicate our success.  
+> * Finally, the `fixup` section allows us to clean up the results of the loop body execution before trying again if the success criteria is not met. In this case we reset the first two qubits back to the $|00\rangle$ state.
+
+This technique is sometimes called post-selection.
+
+@[solution]({
+    "id": "superposition__three_states_two_qubits_solution_a",
+    "codePath": "./SolutionA.qs"
+})
+
+Alternatively, this state can be prepared using arbitrary rotations (the $R_y$ gate). 
+
+To start, we will try to find a decomposition of the target state that makes it easier to see how to prepare the state.  
+Knowing that $|+\rangle = \frac{1}{\sqrt{2}}\big(|0\rangle+|1\rangle\big)$, we can represent the state as follows:
+
+$$ \frac{1}{\sqrt{3}} \big(|00\rangle + |01\rangle + |10\rangle\big) = \frac{\sqrt{2}}{\sqrt{3}}|0\rangle \otimes |+\rangle + \frac{1}{\sqrt{3}}|1\rangle \otimes |0\rangle $$ 
+
+To prepare this state, we first want to prepare the first qubit in the state $ \frac{\sqrt{2}}{\sqrt{3}}|0\rangle + \frac{1}{\sqrt{3}}|1\rangle $, while leaving the second qubit unchanged. 
+To do this, we can use a rotation gate $R_y$ (see Single Qubit Gates kata) which will perform the following transformation:
+$$ R_y|0\rangle = \cos\frac{\theta}{2}|0\rangle + \sin\frac{\theta}{2}|1\rangle $$
+We need to find a value of $\theta$ which satisfies both: 
+$$\cos\frac{\theta}{2} = \frac{\sqrt{2}}{\sqrt{3}} \text{ and } \sin\frac{\theta}{2} = \frac{1}{\sqrt{3}}$$
+
+Solving the last equation for $\theta$ gives us $\frac{\theta}{2} = \arcsin\frac{1}{\sqrt{3}}$, or $\theta = 2 \arcsin\frac{1}{\sqrt{3}}$.
+
+When we apply this to our first qubit, we will get our desired intermediary state:
+$$ R_y(2 \arcsin\frac{1}{\sqrt{3}})|0\rangle \otimes |0\rangle = 
+\left(\frac{\sqrt{2}}{\sqrt{3}}|0\rangle + \frac{1}{\sqrt{3}}|1\rangle \right) \otimes |0\rangle = 
+\frac{\sqrt{2}}{\sqrt{3}}|0\rangle \otimes |0\rangle + \frac{1}{\sqrt{3}}|1\rangle \otimes |0\rangle$$
+
+Now, the second term of this state already matches our final goal, so we need to adjust the first term: 
+prepare the $|+\rangle$ state on the second qubit only if the first qubit is in the $|0\rangle$ state. 
+To do this, we apply a conditional $H$ gate to the second qubit, if the first qubit is in the $|0\rangle$ state, this will give our desired state:
+$$ \frac{\sqrt{2}}{\sqrt{3}}|0\rangle \otimes |+\rangle + \frac{1}{\sqrt{3}}|1\rangle \otimes |0\rangle$$
+
+> In Q# we can apply a conditional gate with arbitrary controls using the [`ApplyControlledOnInt` operation](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonint). 
+> In this case we want the $H$ gate to be applied if the control qubit is in the $|0\rangle$ state, so we will use `ApplyControlledOnInt(0, H)` gate.
+
+@[solution]({
+    "id": "superposition__three_states_two_qubits_solution_b",
+    "codePath": "./SolutionB.qs"
+})
diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs
index e721f911..fb2e4284 100644
--- a/language_service/src/compilation.rs
+++ b/language_service/src/compilation.rs
@@ -72,6 +72,21 @@ impl Compilation {
             language_features,
         );
 
+        // Compute new lints and append them to the errors Vec.
+        // Lints are only computed if the erros vector is empty. For performance
+        // reasons we don't want to waste time running lints every few keystrokes,
+        // if the user is in the middle of typing a statement, for example.
+        if errors.is_empty() {
+            let lints = qsc::linter::run_lints(&unit, Some(lints_config));
+            let lints: Vec<_> = lints
+                .into_iter()
+                .map(|lint| {
+                    WithSource::from_map(&unit.sources, qsc::compile::ErrorKind::Lint(lint))
+                })
+                .collect();
+            errors.extend(lints);
+        }
+
         let package_id = package_store.insert(unit);
         let unit = package_store
             .get(package_id)
@@ -106,6 +121,7 @@ impl Compilation {
         cells: I,
         target_profile: Profile,
         language_features: LanguageFeatures,
+        lints_config: &[LintConfig],
     ) -> Self
     where
         I: Iterator<Item = (Arc<str>, Arc<str>)>,
@@ -138,6 +154,21 @@ impl Compilation {
             .get(package_id)
             .expect("expected to find user package");
 
+        // Compute new lints and append them to the errors Vec.
+        // Lints are only computed if the erros vector is empty. For performance
+        // reasons we don't want to waste time running lints every few keystrokes,
+        // if the user is in the middle of typing a statement, for example.
+        if errors.is_empty() {
+            let lints = qsc::linter::run_lints(unit, Some(lints_config));
+            let lints: Vec<_> = lints
+                .into_iter()
+                .map(|lint| {
+                    WithSource::from_map(&unit.sources, qsc::compile::ErrorKind::Lint(lint))
+                })
+                .collect();
+            errors.extend(lints);
+        }
+
         run_fir_passes(
             &mut errors,
             target_profile,
@@ -234,7 +265,7 @@ impl Compilation {
                 lints_config,
             ),
             CompilationKind::Notebook => {
-                Self::new_notebook(sources, target_profile, language_features)
+                Self::new_notebook(sources, target_profile, language_features, lints_config)
             }
         };
         self.package_store = new.package_store;
diff --git a/language_service/src/protocol.rs b/language_service/src/protocol.rs
index a6826c41..2ec8c979 100644
--- a/language_service/src/protocol.rs
+++ b/language_service/src/protocol.rs
@@ -3,6 +3,7 @@
 
 use qsc::line_column::Range;
 use qsc::{compile::Error, target::Profile, LanguageFeatures, PackageType};
+use qsc_project::Manifest;
 
 /// A change to the workspace configuration
 #[derive(Clone, Debug, Default, Copy)]
@@ -122,6 +123,7 @@ pub struct ParameterInformation {
 pub struct NotebookMetadata {
     pub target_profile: Option<Profile>,
     pub language_features: LanguageFeatures,
+    pub manifest: Option<Manifest>,
 }
 
 #[derive(Debug)]
diff --git a/language_service/src/state.rs b/language_service/src/state.rs
index 2bb93feb..fa40339f 100644
--- a/language_service/src/state.rs
+++ b/language_service/src/state.rs
@@ -367,7 +367,7 @@ impl<'a> CompilationStateUpdater<'a> {
                 target_profile: notebook_metadata.target_profile,
                 package_type: None,
                 language_features: Some(notebook_metadata.language_features),
-                lints_config: None,
+                lints_config: notebook_metadata.manifest.map(|manifest| manifest.lints),
             };
             let configuration = merge_configurations(&notebook_configuration, &configuration);
 
@@ -387,6 +387,10 @@ impl<'a> CompilationStateUpdater<'a> {
                 }),
                 configuration.target_profile,
                 notebook_configuration.language_features.unwrap_or_default(),
+                notebook_configuration
+                    .lints_config
+                    .as_ref()
+                    .unwrap_or(&vec![]),
             );
 
             state.compilations.insert(
diff --git a/npm/qsharp/generate_katas_content.js b/npm/qsharp/generate_katas_content.js
index f43dadea..342c08ba 100644
--- a/npm/qsharp/generate_katas_content.js
+++ b/npm/qsharp/generate_katas_content.js
@@ -1,6 +1,9 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+/// <reference lib="es2022"/>
+// @ts-check
+
 /**
  * Katas Taxonomy
  *
@@ -12,8 +15,6 @@
  * Each Kata is organized in a directory where an index.md file provides a description on how the kata must be composed.
  */
 
-// @ts-check
-
 import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
 import { basename, dirname, join, relative, sep } from "node:path";
 import { fileURLToPath } from "node:url";
@@ -185,7 +186,7 @@ function preProcessSegments(segments, baseFolderPath) {
 
 function parseMarkdown(markdown) {
   const segments = [];
-  const macroRegex = /@\[(?<type>\w+)\]\((?<json>\{.*?\})\)\r?\n/gs;
+  const macroRegex = /@\[(?<type>\w+)\]\((?<json>\{.*?\})\)((\r?\n)|$)/gs;
   let latestProcessedIndex = 0;
   while (latestProcessedIndex < markdown.length) {
     const match = macroRegex.exec(markdown);
diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts
index 2fea46a3..05a91b69 100644
--- a/npm/qsharp/src/compiler/compiler.ts
+++ b/npm/qsharp/src/compiler/compiler.ts
@@ -31,9 +31,17 @@ type Wasm = typeof import("../../lib/web/qsc_wasm.js");
 export interface ICompiler {
   checkCode(code: string): Promise<VSDiagnostic[]>;
 
-  getAst(code: string, languageFeatures?: string[]): Promise<string>;
+  getAst(
+    code: string,
+    languageFeatures?: string[],
+    profile?: TargetProfile,
+  ): Promise<string>;
 
-  getHir(code: string, languageFeatures?: string[]): Promise<string>;
+  getHir(
+    code: string,
+    languageFeatures?: string[],
+    profile?: TargetProfile,
+  ): Promise<string>;
 
   /** @deprecated -- switch to using `ProgramConfig`-based overload. Instead of passing
    * sources and language features separately, pass an object with named properties. This change was made
@@ -197,12 +205,28 @@ export class Compiler implements ICompiler {
     return this.wasm.get_estimates(sources, params, languageFeatures);
   }
 
-  async getAst(code: string, languageFeatures: string[]): Promise<string> {
-    return this.wasm.get_ast(code, languageFeatures);
+  async getAst(
+    code: string,
+    languageFeatures?: string[],
+    profile?: TargetProfile,
+  ): Promise<string> {
+    return this.wasm.get_ast(
+      code,
+      languageFeatures ?? [],
+      profile ?? "quantinuum",
+    );
   }
 
-  async getHir(code: string, languageFeatures: string[]): Promise<string> {
-    return this.wasm.get_hir(code, languageFeatures);
+  async getHir(
+    code: string,
+    languageFeatures: string[],
+    profile: TargetProfile,
+  ): Promise<string> {
+    return this.wasm.get_hir(
+      code,
+      languageFeatures ?? [],
+      profile ?? "quantinuum",
+    );
   }
 
   async run(
diff --git a/npm/qsharp/ux/qsharp-ux.css b/npm/qsharp/ux/qsharp-ux.css
index 03cf5811..f7351115 100644
--- a/npm/qsharp/ux/qsharp-ux.css
+++ b/npm/qsharp/ux/qsharp-ux.css
@@ -275,6 +275,14 @@ html {
   white-space: pre;
 }
 
+.qir-output {
+  height: 40vh;
+  min-height: 400px;
+  width: 100%;
+  resize: none;
+  white-space: pre;
+}
+
 /* Results output */
 
 .output-header {
diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py
index 48eb06e9..f0218288 100644
--- a/pip/qsharp/_qsharp.py
+++ b/pip/qsharp/_qsharp.py
@@ -23,7 +23,12 @@ class Config:
     Configuration hints for the language service.
     """
 
-    def __init__(self, target_profile: TargetProfile, language_features: List[str]):
+    def __init__(
+        self,
+        target_profile: TargetProfile,
+        language_features: Optional[List[str]],
+        manifest: Optional[str],
+    ):
         if target_profile == TargetProfile.Quantinuum:
             self._config = {"targetProfile": "quantinuum"}
             warn("The Quantinuum target profile is a preview feature.")
@@ -34,6 +39,7 @@ class Config:
             self._config = {"targetProfile": "unrestricted"}
 
         self._config["languageFeatures"] = language_features
+        self._config["manifest"] = manifest
 
     def __repr__(self) -> str:
         return "Q# initialized with configuration: " + str(self._config)
@@ -55,7 +61,7 @@ def init(
     *,
     target_profile: TargetProfile = TargetProfile.Unrestricted,
     project_root: Optional[str] = None,
-    language_features: List[str] = [],
+    language_features: Optional[List[str]] = None,
 ) -> Config:
     """
     Initializes the Q# interpreter.
@@ -71,6 +77,7 @@ def init(
 
     global _interpreter
 
+    manifest_contents = None
     manifest_descriptor = None
     if project_root is not None:
         qsharp_json = join(project_root, "qsharp.json")
@@ -83,26 +90,13 @@ def init(
         manifest_descriptor["manifest_dir"] = project_root
 
         try:
-            (_, file_contents) = read_file(qsharp_json)
+            (_, manifest_contents) = read_file(qsharp_json)
+            manifest_descriptor["manifest"] = manifest_contents
         except Exception as e:
             raise QSharpError(
                 f"Error reading {qsharp_json}. qsharp.json should exist at the project root and be a valid JSON file."
             ) from e
 
-        try:
-            manifest_descriptor["manifest"] = json.loads(file_contents)
-        except Exception as e:
-            raise QSharpError(
-                f"Error parsing {qsharp_json}. qsharp.json should exist at the project root and be a valid JSON file."
-            ) from e
-
-    # if no features were passed in as an argument, use the features from the manifest.
-    # this way we prefer the features from the argument over those from the manifest.
-    if language_features == [] and manifest_descriptor != None:
-        language_features = (
-            manifest_descriptor["manifest"].get("languageFeatures") or []
-        )
-
     _interpreter = Interpreter(
         target_profile,
         language_features,
@@ -113,7 +107,7 @@ def init(
 
     # Return the configuration information to provide a hint to the
     # language service through the cell output.
-    return Config(target_profile, language_features)
+    return Config(target_profile, language_features, manifest_contents)
 
 
 def get_interpreter() -> Interpreter:
diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs
index 25037f7b..a8c3ef3f 100644
--- a/pip/src/interpreter.rs
+++ b/pip/src/interpreter.rs
@@ -81,22 +81,19 @@ impl FromPyObject<'_> for PyManifestDescriptor {
         let manifest_dir = get_dict_opt_string(dict, "manifest_dir")?.ok_or(
             PyException::new_err("missing key `manifest_dir` in manifest descriptor"),
         )?;
-        let manifest = dict
-            .get_item("manifest")?
-            .ok_or(PyException::new_err(
-                "missing key `manifest` in manifest descriptor",
-            ))?
-            .downcast::<PyDict>()?;
 
-        let language_features = get_dict_opt_list_string(manifest, "features")?;
+        let manifest = get_dict_opt_string(dict, "manifest")?.ok_or(PyException::new_err(
+            "missing key `manifest` in manifest descriptor",
+        ))?;
+
+        let manifest = serde_json::from_str::<Manifest>(&manifest).map_err(|_| {
+            PyErr::new::<PyException, _>(format!(
+                "Error parsing {manifest_dir}/qsharp.json . Manifest should be a valid JSON file."
+            ))
+        })?;
 
         Ok(Self(ManifestDescriptor {
-            manifest: Manifest {
-                author: get_dict_opt_string(manifest, "author")?,
-                license: get_dict_opt_string(manifest, "license")?,
-                language_features,
-                lints: vec![],
-            },
+            manifest,
             manifest_dir: manifest_dir.into(),
         }))
     }
@@ -121,7 +118,15 @@ impl Interpreter {
             TargetProfile::Base => Profile::Base,
             TargetProfile::Unrestricted => Profile::Unrestricted,
         };
-        let language_features = language_features.unwrap_or_default();
+        // If no features were passed in as an argument, use the features from the manifest.
+        // this way we prefer the features from the argument over those from the manifest.
+        let language_features: Vec<String> = match (language_features, &manifest_descriptor) {
+            (Some(language_features), _) => language_features,
+            (_, Some(manifest_descriptor)) => {
+                manifest_descriptor.0.manifest.language_features.clone()
+            }
+            (None, None) => vec![],
+        };
 
         let sources = if let Some(manifest_descriptor) = manifest_descriptor {
             let project = file_system(
@@ -607,21 +612,3 @@ fn get_dict_opt_string(dict: &PyDict, key: &str) -> PyResult<Option<String>> {
         None => None,
     })
 }
-fn get_dict_opt_list_string(dict: &PyDict, key: &str) -> PyResult<Vec<String>> {
-    let value = dict.get_item(key)?;
-    let list: &PyList = match value {
-        Some(item) => item.downcast::<PyList>()?,
-        None => return Ok(vec![]),
-    };
-    match list
-        .iter()
-        .map(|item| {
-            item.downcast::<PyString>()
-                .map(|s| s.to_string_lossy().into())
-        })
-        .collect::<std::result::Result<Vec<String>, _>>()
-    {
-        Ok(list) => Ok(list),
-        Err(e) => Err(e.into()),
-    }
-}
diff --git a/playground/src/editor.tsx b/playground/src/editor.tsx
index 8acc8cca..a0b116d6 100644
--- a/playground/src/editor.tsx
+++ b/playground/src/editor.tsx
@@ -14,6 +14,8 @@ import {
   QscEventTarget,
   VSDiagnostic,
   log,
+  ProgramConfig,
+  TargetProfile,
 } from "qsharp-lang";
 import { codeToCompressedBase64, lsRangeToMonacoRange } from "./utils.js";
 import { ActiveTab } from "./main.js";
@@ -56,9 +58,17 @@ function VSDiagsToMarkers(errors: VSDiagnostic[]): monaco.editor.IMarkerData[] {
   });
 }
 
+// get the language service profile from the URL
+// default to unrestricted if not specified
+export function getProfile(): TargetProfile {
+  return (new URLSearchParams(window.location.search).get("profile") ??
+    "unrestricted") as TargetProfile;
+}
+
 export function Editor(props: {
   code: string;
   compiler: ICompilerWorker;
+  compiler_worker_factory: () => ICompilerWorker;
   compilerState: CompilerState;
   defaultShots: number;
   evtTarget: QscEventTarget;
@@ -67,8 +77,10 @@ export function Editor(props: {
   shotError?: VSDiagnostic;
   showExpr: boolean;
   showShots: boolean;
+  profile: TargetProfile;
   setAst: (ast: string) => void;
   setHir: (hir: string) => void;
+  setQir: (qir: string) => void;
   activeTab: ActiveTab;
   languageService: ILanguageServiceWorker;
 }) {
@@ -80,7 +92,7 @@ export function Editor(props: {
   const irRef = useRef(async () => {
     return;
   });
-
+  const [profile, setProfile] = useState(props.profile);
   const [shotCount, setShotCount] = useState(props.defaultShots);
   const [runExpr, setRunExpr] = useState("");
   const [errors, setErrors] = useState<{ location: string; msg: string[] }[]>(
@@ -116,11 +128,52 @@ export function Editor(props: {
     const code = editor.current?.getValue();
     if (code == null) return;
 
+    const config = {
+      sources: [["code", code]],
+      languageFeatures: ["preview-qir-gen"],
+      profile: profile,
+    } as ProgramConfig;
+
     if (props.activeTab === "ast-tab") {
-      props.setAst(await props.compiler.getAst(code, []));
+      props.setAst(
+        await props.compiler.getAst(
+          code,
+          config.languageFeatures ?? [],
+          config.profile,
+        ),
+      );
     }
     if (props.activeTab === "hir-tab") {
-      props.setHir(await props.compiler.getHir(code, []));
+      props.setHir(
+        await props.compiler.getHir(
+          code,
+          config.languageFeatures ?? [],
+          config.profile,
+        ),
+      );
+    }
+    const codeGenTimeout = 1000; // ms
+    if (props.activeTab === "qir-tab") {
+      let timedOut = false;
+      const compiler = props.compiler_worker_factory();
+      const compilerTimeout = setTimeout(() => {
+        log.info("Compiler timeout. Terminating worker.");
+        timedOut = true;
+        compiler.terminate();
+      }, codeGenTimeout);
+      try {
+        const qir = await compiler.getQir(config);
+        clearTimeout(compilerTimeout);
+        props.setQir(qir);
+      } catch (e: any) {
+        if (timedOut) {
+          props.setQir("timed out");
+        } else {
+          props.setQir(e.toString());
+        }
+      } finally {
+        compiler.terminate();
+      }
     }
   };
 
@@ -128,11 +181,18 @@ export function Editor(props: {
     const code = editor.current?.getValue();
     if (code == null) return;
     props.evtTarget.clearResults();
+    const config = {
+      sources: [["code", code]],
+      languageFeatures: ["preview-qir-gen"],
+      profile: profile,
+    } as ProgramConfig;
 
     try {
       if (props.kataExercise) {
         // This is for a kata exercise. Provide the sources that implement the solution verification.
         const sources = await getExerciseSources(props.kataExercise);
+        // check uses the unrestricted profile and doesn't do code gen,
+        // so we just pass the sources
         await props.compiler.checkExerciseSolution(
           code,
           sources,
@@ -140,14 +200,7 @@ export function Editor(props: {
         );
       } else {
         performance.mark("compiler-run-start");
-        await props.compiler.run(
-          {
-            sources: [["code", code]],
-          },
-          runExpr,
-          shotCount,
-          props.evtTarget,
-        );
+        await props.compiler.run(config, runExpr, shotCount, props.evtTarget);
         const runTimer = performance.measure(
           "compiler-run",
           "compiler-run-start",
@@ -221,6 +274,7 @@ export function Editor(props: {
 
   useEffect(() => {
     props.languageService.updateConfiguration({
+      targetProfile: profile,
       packageType: props.kataExercise ? "lib" : "exe",
     });
 
@@ -259,6 +313,16 @@ export function Editor(props: {
     irRef.current();
   }, [props.activeTab]);
 
+  useEffect(() => {
+    // Whenever the selected profile changes, update the language service configuration
+    // and run the tabs again.
+    props.languageService.updateConfiguration({
+      targetProfile: profile,
+      packageType: props.kataExercise ? "lib" : "exe",
+    });
+    irRef.current();
+  }, [profile]);
+
   // On reset, reload the initial code
   function onReset() {
     const theEditor = editor.current;
@@ -276,11 +340,10 @@ export function Editor(props: {
     try {
       const encodedCode = await codeToCompressedBase64(code);
       const escapedCode = encodeURIComponent(encodedCode);
-
       // Get current URL without query parameters to use as the base URL
       const newUrl = `${
         window.location.href.split("?")[0]
-      }?code=${escapedCode}`;
+      }?code=${escapedCode}&profile=${profile}`;
 
       // Copy link to clipboard and update url without reloading the page
       navigator.clipboard.writeText(newUrl);
@@ -310,6 +373,11 @@ export function Editor(props: {
     setRunExpr(target.value);
   }
 
+  function profileChanged(e: Event) {
+    const target = e.target as HTMLInputElement;
+    setProfile(target.value as TargetProfile);
+  }
+
   return (
     <div class="editor-column">
       <div style="display: flex; justify-content: space-between; align-items: center;">
@@ -356,6 +424,14 @@ export function Editor(props: {
       </div>
       <div class="code-editor" ref={editorDiv}></div>
       <div class="button-row">
+        <>
+          <span>Profile</span>
+          <select value={profile} onChange={profileChanged}>
+            <option value="unrestricted">Unrestricted</option>
+            <option value="quantinuum">Quantinuum</option>
+            <option value="base">Base</option>
+          </select>
+        </>
         {props.showExpr ? (
           <>
             <span>Start</span>
diff --git a/playground/src/kata.tsx b/playground/src/kata.tsx
index c9a61fa1..2b04c8cb 100644
--- a/playground/src/kata.tsx
+++ b/playground/src/kata.tsx
@@ -13,7 +13,7 @@ import {
   Question,
   VSDiagnostic,
 } from "qsharp-lang";
-import { Editor } from "./editor.js";
+import { Editor, getProfile } from "./editor.js";
 import { OutputTabs } from "./tabs.js";
 
 function ExplainedSolutionAsHtml(solution: ExplainedSolution): string {
@@ -76,6 +76,7 @@ function QuestionAsHtml(question: Question): string {
 export function Kata(props: {
   kata: Kata;
   compiler: ICompilerWorker;
+  compiler_worker_factory: () => ICompilerWorker;
   compilerState: CompilerState;
   onRestartCompiler: () => void;
   languageService: ILanguageServiceWorker;
@@ -166,12 +167,15 @@ export function Kata(props: {
                   evtTarget={itemEvtHandlers[idx]}
                   compiler={props.compiler}
                   compilerState={props.compilerState}
+                  compiler_worker_factory={props.compiler_worker_factory}
                   onRestartCompiler={props.onRestartCompiler}
                   code={section.placeholderCode}
                   kataExercise={section}
                   key={section.id}
+                  profile={getProfile()}
                   setAst={() => ({})}
                   setHir={() => ({})}
+                  setQir={() => ({})}
                   activeTab="results-tab"
                   languageService={props.languageService}
                 ></Editor>
@@ -183,6 +187,7 @@ export function Kata(props: {
                   onShotError={(diag?: VSDiagnostic) => setShotError(diag)}
                   ast=""
                   hir=""
+                  qir=""
                   activeTab="results-tab"
                   setActiveTab={() => undefined}
                 ></OutputTabs>
diff --git a/playground/src/main.tsx b/playground/src/main.tsx
index 36f47626..f357a8a0 100644
--- a/playground/src/main.tsx
+++ b/playground/src/main.tsx
@@ -22,7 +22,7 @@ import {
 } from "qsharp-lang";
 
 import { Nav } from "./nav.js";
-import { Editor } from "./editor.js";
+import { Editor, getProfile } from "./editor.js";
 import { OutputTabs } from "./tabs.js";
 import { useEffect, useState } from "preact/hooks";
 import { Kata as Katas } from "./kata.js";
@@ -37,7 +37,7 @@ import {
   monacoPositionToLsPosition,
 } from "./utils.js";
 
-export type ActiveTab = "results-tab" | "hir-tab" | "ast-tab" | "logs-tab";
+export type ActiveTab = "results-tab" | "ast-tab" | "hir-tab" | "qir-tab";
 
 const basePath = (window as any).qscBasePath || "";
 const monacoPath = basePath + "libs/monaco/vs";
@@ -70,6 +70,12 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
   const [compiler, setCompiler] = useState(() =>
     createCompiler(setCompilerState),
   );
+
+  const [compiler_worker_factory] = useState(() => {
+    const compiler_worker_factory = () => getCompilerWorker(compilerWorkerPath);
+    return compiler_worker_factory;
+  });
+
   const [evtTarget] = useState(() => new QscEventTarget(true));
 
   const [languageService] = useState(() => {
@@ -87,6 +93,7 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
 
   const [ast, setAst] = useState<string>("");
   const [hir, setHir] = useState<string>("");
+  const [qir, setQir] = useState<string>("");
   const [activeTab, setActiveTab] = useState<ActiveTab>("results-tab");
 
   const onRestartCompiler = () => {
@@ -147,6 +154,7 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
           <Editor
             code={sampleCode}
             compiler={compiler}
+            compiler_worker_factory={compiler_worker_factory}
             compilerState={compilerState}
             onRestartCompiler={onRestartCompiler}
             evtTarget={evtTarget}
@@ -154,8 +162,10 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
             showShots={true}
             showExpr={true}
             shotError={shotError}
+            profile={getProfile()}
             setAst={setAst}
             setHir={setHir}
+            setQir={setQir}
             activeTab={activeTab}
             languageService={languageService}
           ></Editor>
@@ -165,6 +175,7 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
             onShotError={(diag?: VSDiagnostic) => setShotError(diag)}
             ast={ast}
             hir={hir}
+            qir={qir}
             activeTab={activeTab}
             setActiveTab={setActiveTab}
           ></OutputTabs>
@@ -174,6 +185,7 @@ function App(props: { katas: Kata[]; linkedCode?: string }) {
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
           kata={activeKata!}
           compiler={compiler}
+          compiler_worker_factory={compiler_worker_factory}
           compilerState={compilerState}
           onRestartCompiler={onRestartCompiler}
           languageService={languageService}
diff --git a/playground/src/tabs.tsx b/playground/src/tabs.tsx
index 69467d4d..cab4c1e3 100644
--- a/playground/src/tabs.tsx
+++ b/playground/src/tabs.tsx
@@ -7,9 +7,9 @@ import { ActiveTab } from "./main.js";
 
 const tabArray: Array<[ActiveTab, string]> = [
   ["results-tab", "RESULTS"],
-  ["hir-tab", "HIR"],
   ["ast-tab", "AST"],
-  ["logs-tab", "LOGS"],
+  ["hir-tab", "HIR"],
+  ["qir-tab", "QIR"],
 ];
 
 function AstTab(props: { ast: string; activeTab: ActiveTab }) {
@@ -28,6 +28,14 @@ function HirTab(props: { hir: string; activeTab: ActiveTab }) {
   ) : null;
 }
 
+function QirTab(props: { qir: string; activeTab: ActiveTab }) {
+  return props.activeTab === "qir-tab" ? (
+    <textarea readonly class="qir-output">
+      {props.qir}
+    </textarea>
+  ) : null;
+}
+
 export function OutputTabs(props: {
   evtTarget: QscEventTarget;
   showPanel: boolean;
@@ -35,6 +43,7 @@ export function OutputTabs(props: {
   kataMode?: boolean;
   ast: string;
   hir: string;
+  qir: string;
   activeTab: ActiveTab;
   setActiveTab: (tab: ActiveTab) => void;
 }) {
@@ -53,8 +62,9 @@ export function OutputTabs(props: {
         </div>
       ) : null}
       <ResultsTab {...props} />
-      <HirTab {...props} />
       <AstTab {...props} />
+      <HirTab {...props} />
+      <QirTab {...props} />
     </div>
   );
 }
diff --git a/wasm/src/language_service.rs b/wasm/src/language_service.rs
index 01e53e07..2db291ca 100644
--- a/wasm/src/language_service.rs
+++ b/wasm/src/language_service.rs
@@ -12,6 +12,7 @@ use crate::{
     serializable_type,
 };
 use qsc::{self, line_column::Encoding, target::Profile, LanguageFeatures, PackageType};
+use qsc_project::Manifest;
 use qsls::protocol::DiagnosticUpdate;
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
@@ -118,15 +119,26 @@ impl LanguageService {
     ) {
         let cells: Vec<Cell> = cells.into_iter().map(std::convert::Into::into).collect();
         let notebook_metadata: NotebookMetadata = notebook_metadata.into();
+        let manifest: Option<Manifest> = notebook_metadata
+            .manifest
+            .and_then(|manifest| serde_json::from_str(&manifest).ok());
+
+        // If no features were passed in as an argument, use the features from the manifest.
+        // this way we prefer the features from the argument over those from the manifest.
+        let language_features: Vec<String> = match (notebook_metadata.languageFeatures, &manifest) {
+            (Some(language_features), _) => language_features,
+            (_, Some(manifest)) => manifest.language_features.clone(),
+            (None, None) => vec![],
+        };
+
         self.0.update_notebook_document(
             notebook_uri,
             qsls::protocol::NotebookMetadata {
                 target_profile: notebook_metadata
                     .targetProfile
                     .map(|s| Profile::from_str(&s).expect("invalid target profile")),
-                language_features: LanguageFeatures::from_iter(
-                    notebook_metadata.languageFeatures.unwrap_or_default(),
-                ),
+                language_features: LanguageFeatures::from_iter(language_features),
+                manifest,
             },
             cells
                 .iter()
@@ -486,11 +498,13 @@ serializable_type! {
     NotebookMetadata,
     {
         pub targetProfile: Option<String>,
-        pub languageFeatures: Option<Vec<String>>
+        pub languageFeatures: Option<Vec<String>>,
+        pub manifest: Option<String>,
     },
     r#"export interface INotebookMetadata {
         targetProfile?: "base" | "quantinuum" | "unrestricted";
         languageFeatures?: "v2-preview-syntax"[];
+        manifest?: string;
     }"#,
     INotebookMetadata
 }
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index 351b00a9..ac08b39f 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -81,6 +81,9 @@ pub fn get_qir(
     let sources = get_source_map(sources, &None);
     let profile =
         Profile::from_str(profile).map_err(|()| format!("Invalid target profile {profile}"))?;
+    if profile == Profile::Unrestricted {
+        return Err("Invalid target profile for QIR generation".to_string());
+    }
     if language_features.contains(LanguageFeatures::PreviewQirGen) {
         qsc::codegen::get_qir(sources, language_features, profile.into())
     } else {
@@ -203,35 +206,46 @@ pub fn get_library_source_content(name: &str) -> Option<String> {
 }
 
 #[wasm_bindgen]
-#[must_use]
-pub fn get_ast(code: &str, language_features: Vec<String>) -> String {
+pub fn get_ast(
+    code: &str,
+    language_features: Vec<String>,
+    profile: &str,
+) -> Result<String, String> {
     let language_features = LanguageFeatures::from_iter(language_features);
     let sources = SourceMap::new([("code".into(), code.into())], None);
+    let profile =
+        Profile::from_str(profile).map_err(|()| format!("Invalid target profile {profile}"))?;
     let package = STORE_CORE_STD.with(|(store, std)| {
         let (unit, _) = compile::compile(
             store,
             &[*std],
             sources,
             PackageType::Exe,
-            Profile::Unrestricted.into(),
+            profile.into(),
             language_features,
         );
         unit.ast.package
     });
-    format!("{package}")
+    Ok(format!("{package}"))
 }
 
 #[wasm_bindgen]
-pub fn get_hir(code: &str, language_features: Vec<String>) -> Result<String, String> {
+pub fn get_hir(
+    code: &str,
+    language_features: Vec<String>,
+    profile: &str,
+) -> Result<String, String> {
     let language_features = LanguageFeatures::from_iter(language_features);
     let sources = SourceMap::new([("code".into(), code.into())], None);
+    let profile =
+        Profile::from_str(profile).map_err(|()| format!("Invalid target profile {profile}"))?;
     let package = STORE_CORE_STD.with(|(store, std)| {
         let (unit, _) = compile::compile(
             store,
             &[*std],
             sources,
             PackageType::Exe,
-            Profile::Unrestricted.into(),
+            profile.into(),
             language_features,
         );
         unit.package


Test patch:
diff --git a/compiler/qsc/src/bin/memtest.rs b/compiler/qsc/src/bin/memtest.rs
index 05cb61bf..d7471b07 100644
--- a/compiler/qsc/src/bin/memtest.rs
+++ b/compiler/qsc/src/bin/memtest.rs
@@ -4,7 +4,8 @@
 //! Records the memory usage of the compiler.
 
 use qsc::{compile, CompileUnit};
-use qsc_frontend::compile::{PackageStore, TargetCapabilityFlags};
+use qsc_data_structures::target::TargetCapabilityFlags;
+use qsc_frontend::compile::PackageStore;
 use std::{
     alloc::{GlobalAlloc, Layout, System},
     sync::atomic::{AtomicU64, Ordering},
diff --git a/compiler/qsc/src/codegen/tests.rs b/compiler/qsc/src/codegen/tests.rs
new file mode 100644
index 00000000..1c23e2f0
--- /dev/null
+++ b/compiler/qsc/src/codegen/tests.rs
@@ -0,0 +1,661 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use expect_test::expect;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::SourceMap;
+
+use crate::codegen::get_qir;
+
+#[test]
+fn code_with_errors_returns_errors() {
+    let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : Unit {
+                use q = Qubit()
+                let pi_over_two = 4.0 / 2.0;
+            }
+        }";
+    let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+    let language_features = LanguageFeatures::default();
+    let capabilities = TargetCapabilityFlags::empty();
+
+    expect![[r#"
+        Err(
+            "Failed to generate QIR. Could not compile sources.:\nsyntax error\n",
+        )
+    "#]]
+    .assert_debug_eq(&get_qir(sources, language_features, capabilities));
+}
+
+mod base_profile {
+    use expect_test::expect;
+    use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+    use qsc_frontend::compile::SourceMap;
+
+    use crate::codegen::get_qir;
+
+    #[test]
+    fn simple() {
+        let source = "namespace Test {
+            open Microsoft.Quantum.Math;
+            open QIR.Intrinsic;
+            @EntryPoint()
+            operation Main() : Result {
+                use q = Qubit();
+                let pi_over_two = 4.0 / 2.0;
+                __quantum__qis__rz__body(pi_over_two, 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);
+                __quantum__qis__mresetz__body(q)
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::empty();
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        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*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__rz__body(double, %Qubit*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="1" "required_num_results"="1" }
+            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(&qir);
+    }
+
+    #[test]
+    fn qubit_reuse_triggers_reindexing() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : (Result, Result) {
+                use q = Qubit();
+                (MResetZ(q), MResetZ(q))
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::empty();
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="2" }
+            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(&qir);
+    }
+
+    #[test]
+    fn qubit_measurements_get_deferred() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : Result[] {
+                use (q0, q1) = (Qubit(), Qubit());
+                X(q0);
+                let r0 = MResetZ(q0);
+                X(q1);
+                let r1 = MResetZ(q1);
+                [r0, r1]
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::empty();
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__array_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__x__body(%Qubit*)
+
+            declare void @__quantum__rt__array_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="2" }
+            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(&qir);
+    }
+}
+
+mod adaptive_profile {
+    use expect_test::expect;
+    use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+    use qsc_frontend::compile::SourceMap;
+
+    use crate::codegen::get_qir;
+
+    #[test]
+    fn simple() {
+        let source = "namespace Test {
+            open Microsoft.Quantum.Math;
+            open QIR.Intrinsic;
+            @EntryPoint()
+            operation Main() : Result {
+                use q = Qubit();
+                let pi_over_two = 4.0 / 2.0;
+                __quantum__qis__rz__body(pi_over_two, 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);
+                __quantum__qis__mresetz__body(q)
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        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*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__rz__body(double, %Qubit*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 false}
+            !5 = !{i32 1, !"classical_floats", i1 false}
+            !6 = !{i32 1, !"backwards_branching", i1 false}
+            !7 = !{i32 1, !"qubit_resetting", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]]
+        .assert_eq(&qir);
+    }
+
+    #[test]
+    fn qubit_reuse_triggers_reindexing() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : (Result, Result) {
+                use q = Qubit();
+                (MResetZ(q), MResetZ(q))
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 false}
+            !5 = !{i32 1, !"classical_floats", i1 false}
+            !6 = !{i32 1, !"backwards_branching", i1 false}
+            !7 = !{i32 1, !"qubit_resetting", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]].assert_eq(&qir);
+    }
+
+    #[test]
+    fn qubit_measurements_not_deferred() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : Result[] {
+                use (q0, q1) = (Qubit(), Qubit());
+                X(q0);
+                let r0 = MResetZ(q0);
+                X(q1);
+                let r1 = MResetZ(q1);
+                [r0, r1]
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
+              call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__array_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__x__body(%Qubit*)
+
+            declare void @__quantum__rt__array_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 false}
+            !5 = !{i32 1, !"classical_floats", i1 false}
+            !6 = !{i32 1, !"backwards_branching", i1 false}
+            !7 = !{i32 1, !"qubit_resetting", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]].assert_eq(&qir);
+    }
+}
+
+mod quantinuum_profile {
+    use expect_test::expect;
+    use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+    use qsc_frontend::compile::SourceMap;
+
+    use crate::codegen::get_qir;
+
+    #[test]
+    fn simple() {
+        let source = "namespace Test {
+            open Microsoft.Quantum.Math;
+            open QIR.Intrinsic;
+            @EntryPoint()
+            operation Main() : Result {
+                use q = Qubit();
+                let pi_over_two = 4.0 / 2.0;
+                __quantum__qis__rz__body(pi_over_two, 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);
+                __quantum__qis__mresetz__body(q)
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive
+            | TargetCapabilityFlags::QubitReset
+            | TargetCapabilityFlags::IntegerComputations;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        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*))
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__rz__body(double, %Qubit*)
+
+            declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 true}
+            !5 = !{i32 1, !"qubit_resetting", i1 true}
+            !6 = !{i32 1, !"classical_floats", i1 false}
+            !7 = !{i32 1, !"backwards_branching", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]]
+        .assert_eq(&qir);
+    }
+
+    #[test]
+    fn qubit_reuse_allowed() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : (Result, Result) {
+                use q = Qubit();
+                (MResetZ(q), MResetZ(q))
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive
+            | TargetCapabilityFlags::QubitReset
+            | TargetCapabilityFlags::IntegerComputations;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
+
+            declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="2" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 true}
+            !5 = !{i32 1, !"qubit_resetting", i1 true}
+            !6 = !{i32 1, !"classical_floats", i1 false}
+            !7 = !{i32 1, !"backwards_branching", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]].assert_eq(&qir);
+    }
+
+    #[test]
+    fn qubit_measurements_not_deferred() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : Result[] {
+                use (q0, q1) = (Qubit(), Qubit());
+                X(q0);
+                let r0 = MResetZ(q0);
+                X(q1);
+                let r1 = MResetZ(q1);
+                [r0, r1]
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive
+            | TargetCapabilityFlags::QubitReset
+            | TargetCapabilityFlags::IntegerComputations;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*))
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
+              call void @__quantum__rt__array_record_output(i64 2, i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+              call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__x__body(%Qubit*)
+
+            declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
+
+            declare void @__quantum__rt__array_record_output(i64, i8*)
+
+            declare void @__quantum__rt__result_record_output(%Result*, i8*)
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 true}
+            !5 = !{i32 1, !"qubit_resetting", i1 true}
+            !6 = !{i32 1, !"classical_floats", i1 false}
+            !7 = !{i32 1, !"backwards_branching", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]].assert_eq(&qir);
+    }
+
+    #[test]
+    fn dynamic_integer_with_branch_and_phi_supported() {
+        let source = "namespace Test {
+            @EntryPoint()
+            operation Main() : Int {
+                use q = Qubit();
+                H(q);
+                MResetZ(q) == Zero ? 0 | 1
+            }
+        }";
+        let sources = SourceMap::new([("test.qs".into(), source.into())], None);
+        let language_features = LanguageFeatures::default();
+        let capabilities = TargetCapabilityFlags::Adaptive
+            | TargetCapabilityFlags::QubitReset
+            | TargetCapabilityFlags::IntegerComputations;
+
+        let qir =
+            get_qir(sources, language_features, capabilities).expect("Failed to generate QIR");
+        expect![[r#"
+            %Result = type opaque
+            %Qubit = type opaque
+
+            define void @ENTRYPOINT__main() #0 {
+            block_0:
+              call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+              call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
+              %var_0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
+              %var_1 = icmp eq i1 %var_0, false
+              br i1 %var_1, label %block_1, label %block_2
+            block_1:
+              br label %block_3
+            block_2:
+              br label %block_3
+            block_3:
+              %var_2 = phi i64 [0, %block_1], [1, %block_2]
+              call void @__quantum__rt__integer_record_output(i64 %var_2, i8* null)
+              ret void
+            }
+
+            declare void @__quantum__qis__h__body(%Qubit*)
+
+            declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
+
+            declare i1 @__quantum__qis__read_result__body(%Result*)
+
+            declare void @__quantum__rt__integer_record_output(i64, i8*)
+
+            attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
+            attributes #1 = { "irreversible" }
+
+            ; module flags
+
+            !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+            !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}
+            !4 = !{i32 1, !"classical_ints", i1 true}
+            !5 = !{i32 1, !"qubit_resetting", i1 true}
+            !6 = !{i32 1, !"classical_floats", i1 false}
+            !7 = !{i32 1, !"backwards_branching", i1 false}
+            !8 = !{i32 1, !"classical_fixed_points", i1 false}
+            !9 = !{i32 1, !"user_functions", i1 false}
+            !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        "#]].assert_eq(&qir);
+    }
+}
diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs
index 3de86b72..c8bba1ef 100644
--- a/compiler/qsc/src/interpret/debug/tests.rs
+++ b/compiler/qsc/src/interpret/debug/tests.rs
@@ -5,9 +5,9 @@
 
 use indoc::indoc;
 use miette::Result;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_eval::{output::CursorReceiver, val::Value};
-use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::SourceMap;
 use qsc_passes::PackageType;
 use std::io::Cursor;
 
diff --git a/compiler/qsc/src/interpret/debugger_tests.rs b/compiler/qsc/src/interpret/debugger_tests.rs
index d3e73953..93f236a3 100644
--- a/compiler/qsc/src/interpret/debugger_tests.rs
+++ b/compiler/qsc/src/interpret/debugger_tests.rs
@@ -8,7 +8,7 @@ use crate::line_column::Encoding;
 use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_eval::{output::CursorReceiver, StepAction, StepResult};
 use qsc_fir::fir::StmtId;
-use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::SourceMap;
 use std::io::Cursor;
 
 fn get_breakpoint_ids(debugger: &Debugger, path: &str) -> Vec<StmtId> {
@@ -123,6 +123,8 @@ mod given_debugger {
         }"#;
     #[cfg(test)]
     mod step {
+        use qsc_data_structures::target::TargetCapabilityFlags;
+
         use super::*;
 
         #[test]
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 85cf65e7..796083a8 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -7,9 +7,9 @@ mod given_interpreter {
     use crate::interpret::{Error, InterpretResult, Interpreter};
     use expect_test::Expect;
     use miette::Diagnostic;
-    use qsc_data_structures::language_features::LanguageFeatures;
+    use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
     use qsc_eval::{output::CursorReceiver, val::Value};
-    use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+    use qsc_frontend::compile::SourceMap;
     use qsc_passes::PackageType;
     use std::{fmt::Write, io::Cursor, iter, str::from_utf8};
 
@@ -53,7 +53,6 @@ mod given_interpreter {
     mod without_sources {
         use expect_test::expect;
         use indoc::indoc;
-        use qsc_frontend::compile::TargetCapabilityFlags;
 
         use super::*;
 
@@ -727,7 +726,9 @@ mod given_interpreter {
                 true,
                 SourceMap::default(),
                 PackageType::Lib,
-                TargetCapabilityFlags::Adaptive,
+                TargetCapabilityFlags::Adaptive
+                    | TargetCapabilityFlags::QubitReset
+                    | TargetCapabilityFlags::IntegerComputations,
                 LanguageFeatures::default(),
             )
             .expect("interpreter should be created");
@@ -778,12 +779,19 @@ mod given_interpreter {
 
                 ; module flags
 
-                !llvm.module.flags = !{!0, !1, !2, !3}
+                !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
 
                 !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}
+                !4 = !{i32 1, !"classical_ints", i1 true}
+                !5 = !{i32 1, !"qubit_resetting", i1 true}
+                !6 = !{i32 1, !"classical_floats", i1 false}
+                !7 = !{i32 1, !"backwards_branching", i1 false}
+                !8 = !{i32 1, !"classical_fixed_points", i1 false}
+                !9 = !{i32 1, !"user_functions", i1 false}
+                !10 = !{i32 1, !"multiple_target_branching", i1 false}
             "#]]
             .assert_eq(&res);
         }
@@ -794,7 +802,7 @@ mod given_interpreter {
                 true,
                 SourceMap::default(),
                 PackageType::Lib,
-                TargetCapabilityFlags::Adaptive,
+                TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::QubitReset,
                 LanguageFeatures::default(),
             )
             .expect("interpreter should be created");
@@ -848,12 +856,19 @@ mod given_interpreter {
 
                 ; module flags
 
-                !llvm.module.flags = !{!0, !1, !2, !3}
+                !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
 
                 !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}
+                !4 = !{i32 1, !"qubit_resetting", i1 true}
+                !5 = !{i32 1, !"classical_ints", i1 false}
+                !6 = !{i32 1, !"classical_floats", i1 false}
+                !7 = !{i32 1, !"backwards_branching", i1 false}
+                !8 = !{i32 1, !"classical_fixed_points", i1 false}
+                !9 = !{i32 1, !"user_functions", i1 false}
+                !10 = !{i32 1, !"multiple_target_branching", i1 false}
             "#]]
             .assert_eq(&res);
         }
@@ -1442,9 +1457,10 @@ mod given_interpreter {
         use crate::line_column::Encoding;
         use expect_test::expect;
         use indoc::indoc;
+
         use qsc_ast::ast::{Expr, ExprKind, NodeId, Package, Path, Stmt, StmtKind, TopLevelNode};
         use qsc_data_structures::span::Span;
-        use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+        use qsc_frontend::compile::SourceMap;
         use qsc_passes::PackageType;
 
         #[test]
diff --git a/compiler/qsc_circuit/src/operations/tests.rs b/compiler/qsc_circuit/src/operations/tests.rs
index 94254aba..dcbd4604 100644
--- a/compiler/qsc_circuit/src/operations/tests.rs
+++ b/compiler/qsc_circuit/src/operations/tests.rs
@@ -3,8 +3,10 @@
 
 use super::*;
 use expect_test::expect;
-use qsc_data_structures::{functors::FunctorApp, language_features::LanguageFeatures};
-use qsc_frontend::compile::{compile, core, std, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{
+    functors::FunctorApp, language_features::LanguageFeatures, target::TargetCapabilityFlags,
+};
+use qsc_frontend::compile::{compile, core, std, PackageStore, SourceMap};
 use qsc_hir::hir::{Item, ItemKind};
 
 fn compile_one_operation(code: &str) -> (Item, String) {
diff --git a/compiler/qsc_codegen/src/qir/tests.rs b/compiler/qsc_codegen/src/qir/tests.rs
index ddc887b9..80c8a0f5 100644
--- a/compiler/qsc_codegen/src/qir/tests.rs
+++ b/compiler/qsc_codegen/src/qir/tests.rs
@@ -219,11 +219,18 @@ fn teleport_program() {
 
         ; module flags
 
-        !llvm.module.flags = !{!0, !1, !2, !3}
+        !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
 
         !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}
+        !4 = !{i32 1, !"classical_ints", i1 false}
+        !5 = !{i32 1, !"classical_floats", i1 false}
+        !6 = !{i32 1, !"backwards_branching", i1 false}
+        !7 = !{i32 1, !"qubit_resetting", i1 false}
+        !8 = !{i32 1, !"classical_fixed_points", i1 false}
+        !9 = !{i32 1, !"user_functions", i1 false}
+        !10 = !{i32 1, !"multiple_target_branching", i1 false}
     "#]].assert_eq(&program.to_qir(&program));
 }
diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs
index 4002d546..9d3b2ef9 100644
--- a/compiler/qsc_codegen/src/qir_base/tests.rs
+++ b/compiler/qsc_codegen/src/qir_base/tests.rs
@@ -8,8 +8,8 @@ use std::sync::Arc;
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_passes::{run_core_passes, run_default_passes, PackageType};
 
 use crate::qir_base::generate_qir;
diff --git a/compiler/qsc_codegen/src/qsharp/test_utils.rs b/compiler/qsc_codegen/src/qsharp/test_utils.rs
index 2a8f8f29..e13d728e 100644
--- a/compiler/qsc_codegen/src/qsharp/test_utils.rs
+++ b/compiler/qsc_codegen/src/qsharp/test_utils.rs
@@ -8,8 +8,10 @@ use std::sync::Arc;
 
 use expect_test::Expect;
 use qsc_ast::{ast::Package, mut_visit::MutVisitor};
-use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{
+    language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
+};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::hir::PackageId;
 use qsc_passes::{run_core_passes, run_default_passes, PackageType};
 
diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs
index 30a3184a..7925858d 100644
--- a/compiler/qsc_eval/src/intrinsic/tests.rs
+++ b/compiler/qsc_eval/src/intrinsic/tests.rs
@@ -17,8 +17,9 @@ use expect_test::{expect, Expect};
 use indoc::indoc;
 use num_bigint::BigInt;
 use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::target::TargetCapabilityFlags;
 use qsc_fir::fir;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_lowerer::map_hir_package_to_fir;
 use qsc_passes::{run_core_passes, run_default_passes, PackageType};
 
diff --git a/compiler/qsc_eval/src/state/tests.rs b/compiler/qsc_eval/src/state/tests.rs
index e69718c6..199f22d1 100644
--- a/compiler/qsc_eval/src/state/tests.rs
+++ b/compiler/qsc_eval/src/state/tests.rs
@@ -647,7 +647,7 @@ fn check_get_latex_for_real() {
     );
     assert_latex_for_real(
         &expect!([r#"
-            "0.00025"
+            "0.0003"
         "#]),
         1.0 / 4000.0,
         false,
diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs
index 1d74c7ba..5263f05b 100644
--- a/compiler/qsc_eval/src/tests.rs
+++ b/compiler/qsc_eval/src/tests.rs
@@ -14,10 +14,10 @@ use crate::{
 };
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_fir::fir::{self, ExecGraphNode, StmtId};
 use qsc_fir::fir::{PackageId, PackageStoreLookup};
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_lowerer::map_hir_package_to_fir;
 use qsc_passes::{run_core_passes, run_default_passes, PackageType};
 
diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs
index a055b2a3..ac077e60 100644
--- a/compiler/qsc_frontend/src/incremental/tests.rs
+++ b/compiler/qsc_frontend/src/incremental/tests.rs
@@ -3,13 +3,13 @@
 
 use super::{Compiler, Increment};
 use crate::{
-    compile::{self, CompileUnit, PackageStore, TargetCapabilityFlags},
+    compile::{self, CompileUnit, PackageStore},
     incremental::Error,
 };
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use miette::Diagnostic;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use std::fmt::Write;
 
 #[allow(clippy::too_many_lines)]
diff --git a/compiler/qsc_frontend/src/lower/tests.rs b/compiler/qsc_frontend/src/lower/tests.rs
index 0f63a88b..8f0a9b65 100644
--- a/compiler/qsc_frontend/src/lower/tests.rs
+++ b/compiler/qsc_frontend/src/lower/tests.rs
@@ -3,10 +3,10 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use crate::compile::{self, compile, PackageStore, SourceMap};
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 
 fn check_hir(input: &str, expect: &Expect) {
     let sources = SourceMap::new([("test".into(), input.into())], None);
diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs
index 0969ddd5..34222770 100644
--- a/compiler/qsc_frontend/src/resolve/tests.rs
+++ b/compiler/qsc_frontend/src/resolve/tests.rs
@@ -6,7 +6,6 @@
 use super::{Error, Locals, Names, Res};
 use crate::{
     compile,
-    compile::TargetCapabilityFlags,
     resolve::{LocalKind, Resolver},
 };
 use expect_test::{expect, Expect};
@@ -17,7 +16,9 @@ use qsc_ast::{
     mut_visit::MutVisitor,
     visit::{self, Visitor},
 };
-use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
+use qsc_data_structures::{
+    language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
+};
 use qsc_hir::assigner::Assigner as HirAssigner;
 use std::fmt::Write;
 
diff --git a/compiler/qsc_linter/src/tests.rs b/compiler/qsc_linter/src/tests.rs
index 70c0c6ce..fe436d92 100644
--- a/compiler/qsc_linter/src/tests.rs
+++ b/compiler/qsc_linter/src/tests.rs
@@ -6,8 +6,8 @@ use crate::{
     Lint, LintConfig, LintLevel,
 };
 use expect_test::{expect, Expect};
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, CompileUnit, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, CompileUnit, PackageStore, SourceMap};
 use qsc_passes::PackageType;
 
 #[test]
diff --git a/compiler/qsc_partial_eval/tests/output_recording.rs b/compiler/qsc_partial_eval/tests/output_recording.rs
index 3c9cd3c6..15677e26 100644
--- a/compiler/qsc_partial_eval/tests/output_recording.rs
+++ b/compiler/qsc_partial_eval/tests/output_recording.rs
@@ -83,8 +83,7 @@ fn output_recording_for_tuple_of_different_types() {
                     Call id(5), args( Variable(1, Boolean), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -170,8 +169,7 @@ fn output_recording_for_nested_tuples() {
                     Call id(5), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -265,8 +263,7 @@ fn output_recording_for_tuple_of_arrays() {
                     Call id(6), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -360,8 +357,7 @@ fn output_recording_for_array_of_tuples() {
                     Call id(6), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -403,8 +399,7 @@ fn output_recording_for_literal_bool() {
                     Call id(1), args( Bool(true), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -446,8 +441,7 @@ fn output_recording_for_literal_int() {
                     Call id(1), args( Integer(42), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -518,8 +512,7 @@ fn output_recording_for_mix_of_literal_and_variable() {
                     Call id(4), args( Bool(true), Pointer, )
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs
index 2552d808..8d558be4 100644
--- a/compiler/qsc_partial_eval/tests/test_utils.rs
+++ b/compiler/qsc_partial_eval/tests/test_utils.rs
@@ -3,9 +3,9 @@
 
 use expect_test::Expect;
 use qsc::{incremental::Compiler, PackageType};
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_fir::fir::PackageStore;
-use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
 use qsc_partial_eval::{partially_evaluate, ProgramEntry};
 use qsc_rca::{Analyzer, PackageStoreComputeProperties};
@@ -38,6 +38,7 @@ pub fn compile_and_partially_evaluate(source: &str) -> Program {
         &compilation_context.fir_store,
         &compilation_context.compute_properties,
         &compilation_context.entry,
+        TargetCapabilityFlags::empty(),
     );
     match maybe_program {
         Ok(program) => program,
diff --git a/compiler/qsc_passes/src/baseprofck/tests.rs b/compiler/qsc_passes/src/baseprofck/tests.rs
index 9f87f983..2e6f202a 100644
--- a/compiler/qsc_passes/src/baseprofck/tests.rs
+++ b/compiler/qsc_passes/src/baseprofck/tests.rs
@@ -5,8 +5,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 
 use crate::baseprofck::check_base_profile_compliance;
 
diff --git a/compiler/qsc_passes/src/borrowck/tests.rs b/compiler/qsc_passes/src/borrowck/tests.rs
index c6c146f7..f09c5d5e 100644
--- a/compiler/qsc_passes/src/borrowck/tests.rs
+++ b/compiler/qsc_passes/src/borrowck/tests.rs
@@ -5,8 +5,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::visit::Visitor;
 
 use crate::borrowck::Checker;
diff --git a/compiler/qsc_passes/src/callable_limits/tests.rs b/compiler/qsc_passes/src/callable_limits/tests.rs
index 5bd2a0cd..7c09837d 100644
--- a/compiler/qsc_passes/src/callable_limits/tests.rs
+++ b/compiler/qsc_passes/src/callable_limits/tests.rs
@@ -5,8 +5,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::visit::Visitor;
 
 use crate::callable_limits::CallableLimits;
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
index 1f162693..69193cc9 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
@@ -15,7 +15,7 @@ use super::tests_common::{
     USE_DYNAMIC_QUBIT, USE_DYNAMIC_RANGE, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT,
 };
 use expect_test::{expect, Expect};
-use qsc_frontend::compile::TargetCapabilityFlags;
+use qsc_data_structures::target::TargetCapabilityFlags;
 
 fn check_profile(source: &str, expect: &Expect) {
     check(source, expect, TargetCapabilityFlags::Adaptive);
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs
index c38f8d39..0ee2903d 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs
@@ -17,7 +17,7 @@ use super::tests_common::{
     USE_DYNAMIC_QUBIT, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT,
 };
 use expect_test::{expect, Expect};
-use qsc_frontend::compile::TargetCapabilityFlags;
+use qsc_data_structures::target::TargetCapabilityFlags;
 
 fn check_profile(source: &str, expect: &Expect) {
     check(
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
index 8410b7e4..56457eef 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
@@ -15,7 +15,7 @@ use super::tests_common::{
     USE_DYNAMIC_QUBIT, USE_DYNAMIC_RANGE, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT,
 };
 use expect_test::{expect, Expect};
-use qsc_frontend::compile::TargetCapabilityFlags;
+use qsc_data_structures::target::TargetCapabilityFlags;
 
 fn check_profile(source: &str, expect: &Expect) {
     check(source, expect, TargetCapabilityFlags::empty());
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
index b0b6fd9d..48f0ac34 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
@@ -7,9 +7,9 @@ use expect_test::Expect;
 
 use crate::capabilitiesck::check_supported_capabilities;
 use qsc::{incremental::Compiler, PackageType};
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_fir::fir::{Package, PackageId, PackageStore};
-use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
 use qsc_rca::{Analyzer, PackageComputeProperties, PackageStoreComputeProperties};
 
diff --git a/compiler/qsc_passes/src/conjugate_invert/tests.rs b/compiler/qsc_passes/src/conjugate_invert/tests.rs
index 34c8898b..b94e7eb5 100644
--- a/compiler/qsc_passes/src/conjugate_invert/tests.rs
+++ b/compiler/qsc_passes/src/conjugate_invert/tests.rs
@@ -6,8 +6,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::{validate::Validator, visit::Visitor};
 
 use crate::conjugate_invert::invert_conjugate_exprs;
diff --git a/compiler/qsc_passes/src/entry_point/tests.rs b/compiler/qsc_passes/src/entry_point/tests.rs
index 4639d431..b28a8eac 100644
--- a/compiler/qsc_passes/src/entry_point/tests.rs
+++ b/compiler/qsc_passes/src/entry_point/tests.rs
@@ -6,8 +6,8 @@
 use crate::entry_point::generate_entry_expr;
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 
 fn check(file: &str, expr: &str, expect: &Expect) {
     let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into()));
diff --git a/compiler/qsc_passes/src/logic_sep/tests.rs b/compiler/qsc_passes/src/logic_sep/tests.rs
index 5a99cd52..58077d72 100644
--- a/compiler/qsc_passes/src/logic_sep/tests.rs
+++ b/compiler/qsc_passes/src/logic_sep/tests.rs
@@ -5,8 +5,10 @@
 #![allow(clippy::needless_raw_string_hashes)]
 
 use expect_test::{expect, Expect};
-use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{
+    language_features::LanguageFeatures, span::Span, target::TargetCapabilityFlags,
+};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::{
     hir::{ExprKind, NodeId, Stmt},
     visit::{walk_stmt, Visitor},
diff --git a/compiler/qsc_passes/src/loop_unification/tests.rs b/compiler/qsc_passes/src/loop_unification/tests.rs
index 9ccf984c..f38145f8 100644
--- a/compiler/qsc_passes/src/loop_unification/tests.rs
+++ b/compiler/qsc_passes/src/loop_unification/tests.rs
@@ -5,8 +5,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::{mut_visit::MutVisitor, validate::Validator, visit::Visitor};
 
 use crate::loop_unification::LoopUni;
diff --git a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
index 42fa1be3..b9c4d140 100644
--- a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
+++ b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
@@ -4,8 +4,8 @@
 use crate::replace_qubit_allocation::ReplaceQubitAllocation;
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::{mut_visit::MutVisitor, validate::Validator, visit::Visitor};
 
 fn check(file: &str, expect: &Expect) {
diff --git a/compiler/qsc_passes/src/spec_gen/tests.rs b/compiler/qsc_passes/src/spec_gen/tests.rs
index 3743801e..a791a8da 100644
--- a/compiler/qsc_passes/src/spec_gen/tests.rs
+++ b/compiler/qsc_passes/src/spec_gen/tests.rs
@@ -6,8 +6,8 @@
 
 use expect_test::{expect, Expect};
 use indoc::indoc;
-use qsc_data_structures::language_features::LanguageFeatures;
-use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_hir::{validate::Validator, visit::Visitor};
 
 use crate::spec_gen::generate_specs;
diff --git a/compiler/qsc_rca/tests/test_utils.rs b/compiler/qsc_rca/tests/test_utils.rs
index a0b2db0b..b129af0e 100644
--- a/compiler/qsc_rca/tests/test_utils.rs
+++ b/compiler/qsc_rca/tests/test_utils.rs
@@ -3,9 +3,9 @@
 
 use expect_test::Expect;
 use qsc::incremental::Compiler;
-use qsc_data_structures::language_features::LanguageFeatures;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
 use qsc_fir::fir::{ItemKind, LocalItemId, Package, PackageStore, StoreItemId};
-use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags};
+use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
 use qsc_passes::PackageType;
 use qsc_rca::{Analyzer, ComputePropertiesLookup, PackageStoreComputeProperties};
@@ -19,12 +19,12 @@ pub struct CompilationContext {
 
 impl CompilationContext {
     #[must_use]
-    pub fn new(runtime_capabilities: TargetCapabilityFlags) -> Self {
+    pub fn new(capabilities: TargetCapabilityFlags) -> Self {
         let compiler = Compiler::new(
             true,
             SourceMap::default(),
             PackageType::Lib,
-            runtime_capabilities,
+            capabilities,
             LanguageFeatures::default(),
         )
         .expect("should be able to create a new compiler");
diff --git a/compiler/qsc_rir/src/passes/remap_block_ids/tests.rs b/compiler/qsc_rir/src/passes/remap_block_ids/tests.rs
index 7e16ca1f..2345e2cc 100644
--- a/compiler/qsc_rir/src/passes/remap_block_ids/tests.rs
+++ b/compiler/qsc_rir/src/passes/remap_block_ids/tests.rs
@@ -54,8 +54,7 @@ fn remap_block_ids_no_changes() {
                 Block 2: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -79,8 +78,7 @@ fn remap_block_ids_no_changes() {
                 Block 2: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -128,8 +126,7 @@ fn remap_block_ids_out_of_order_no_branches() {
                 Block 7: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -153,8 +150,7 @@ fn remap_block_ids_out_of_order_no_branches() {
                 Block 2: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -215,8 +211,7 @@ fn remap_block_ids_out_of_order_with_one_branch() {
                 Block 3: Block:
                     Jump(1)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -243,8 +238,7 @@ fn remap_block_ids_out_of_order_with_one_branch() {
                 Block 3: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -300,8 +294,7 @@ fn remap_block_ids_simple_loop() {
                 Block 6: Block:
                     Jump(4)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -326,8 +319,7 @@ fn remap_block_ids_simple_loop() {
                 Block 2: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -370,8 +362,7 @@ fn remap_block_ids_infinite_loop() {
                 Block 4: Block:
                     Jump(0)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -394,8 +385,7 @@ fn remap_block_ids_infinite_loop() {
                 Block 1: Block:
                     Jump(0)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -459,8 +449,7 @@ fn remap_block_ids_nested_branching_loops() {
                 Block 6: Block:
                     Branch Variable(1, Boolean), 4, 2
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -485,8 +474,7 @@ fn remap_block_ids_nested_branching_loops() {
                 Block 2: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -589,8 +577,7 @@ fn remap_block_ids_ensures_acyclic_program_gets_topological_ordering() {
                 Block 8: Block:
                     Jump(7)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -627,8 +614,7 @@ fn remap_block_ids_ensures_acyclic_program_gets_topological_ordering() {
                 Block 8: Block:
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
diff --git a/compiler/qsc_rir/src/passes/simplify_control_flow/tests.rs b/compiler/qsc_rir/src/passes/simplify_control_flow/tests.rs
new file mode 100644
index 00000000..3f584100
--- /dev/null
+++ b/compiler/qsc_rir/src/passes/simplify_control_flow/tests.rs
@@ -0,0 +1,474 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#![allow(clippy::too_many_lines, clippy::needless_raw_string_hashes)]
+
+use expect_test::expect;
+
+use crate::{
+    builder::{bell_program, teleport_program},
+    rir::{Block, BlockId, Instruction, Literal, Operand, Program, Ty, Variable, VariableId},
+};
+
+use super::simplify_control_flow;
+
+#[test]
+fn simplify_control_flow_leaves_program_with_single_block_unchanged() {
+    let mut program = bell_program();
+    let program_before = program.to_string();
+    simplify_control_flow(&mut program);
+    assert_eq!(program.to_string(), program_before);
+}
+
+#[test]
+fn simplify_control_flow_leaves_program_with_branching_and_no_extra_blocks_unchanged() {
+    let mut program = teleport_program();
+    let program_before = program.to_string();
+    simplify_control_flow(&mut program);
+    assert_eq!(program.to_string(), program_before);
+}
+
+#[test]
+fn simplify_control_flow_removes_single_redundant_block() {
+    let mut program = Program::new();
+    program.blocks.insert(
+        BlockId(0),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(1)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(1),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Return,
+        ]),
+    );
+
+    // Before
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Jump(1)
+                Block 1: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+
+    // After
+    simplify_control_flow(&mut program);
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Variable(1, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+}
+
+#[test]
+fn simplify_control_flow_removes_multiple_redundant_blocks() {
+    let mut program = Program::new();
+    program.blocks.insert(
+        BlockId(0),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(1)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(1),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(2)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(2),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(2),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Return,
+        ]),
+    );
+
+    // Before
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Jump(1)
+                Block 1: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Jump(2)
+                Block 2: Block:
+                    Variable(2, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+
+    // After
+    simplify_control_flow(&mut program);
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Variable(1, Boolean) = Store Bool(true)
+                    Variable(2, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+}
+
+#[test]
+fn simplify_control_flow_removes_redundant_blocks_across_branches() {
+    let mut program = Program::new();
+    program.blocks.insert(
+        BlockId(0),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Branch(
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+                BlockId(1),
+                BlockId(6),
+            ),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(1),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(2)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(2),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(2),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(4)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(4),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(3),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(6)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(6),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(4),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(7)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(7),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(5),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Return,
+        ]),
+    );
+
+    // Before
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Branch Variable(0, Boolean), 1, 6
+                Block 1: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Jump(2)
+                Block 2: Block:
+                    Variable(2, Boolean) = Store Bool(true)
+                    Jump(4)
+                Block 4: Block:
+                    Variable(3, Boolean) = Store Bool(true)
+                    Jump(6)
+                Block 6: Block:
+                    Variable(4, Boolean) = Store Bool(true)
+                    Jump(7)
+                Block 7: Block:
+                    Variable(5, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+
+    // After
+    simplify_control_flow(&mut program);
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Branch Variable(0, Boolean), 1, 6
+                Block 1: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Variable(2, Boolean) = Store Bool(true)
+                    Variable(3, Boolean) = Store Bool(true)
+                    Jump(6)
+                Block 6: Block:
+                    Variable(4, Boolean) = Store Bool(true)
+                    Variable(5, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+}
+
+#[test]
+fn simplify_control_flow_removes_redundant_blocks_across_out_of_order_branches() {
+    let mut program = Program::new();
+    // 0 -> 3
+    // 1 -> 0
+    // 6 -> 2
+    // 2 -> 1
+    // 4 -> 4
+    // 7 -> 5
+    program.blocks.insert(
+        BlockId(3),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Branch(
+                Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                },
+                BlockId(0),
+                BlockId(2),
+            ),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(0),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(1)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(1),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(2),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(4)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(4),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(3),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(2)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(2),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(4),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Jump(BlockId(5)),
+        ]),
+    );
+    program.blocks.insert(
+        BlockId(5),
+        Block(vec![
+            Instruction::Store(
+                Operand::Literal(Literal::Bool(true)),
+                Variable {
+                    variable_id: VariableId(5),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Return,
+        ]),
+    );
+
+    // Before
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Jump(1)
+                Block 1: Block:
+                    Variable(2, Boolean) = Store Bool(true)
+                    Jump(4)
+                Block 2: Block:
+                    Variable(4, Boolean) = Store Bool(true)
+                    Jump(5)
+                Block 3: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Branch Variable(0, Boolean), 0, 2
+                Block 4: Block:
+                    Variable(3, Boolean) = Store Bool(true)
+                    Jump(2)
+                Block 5: Block:
+                    Variable(5, Boolean) = Store Bool(true)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+
+    // After
+    simplify_control_flow(&mut program);
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+            blocks:
+                Block 0: Block:
+                    Variable(1, Boolean) = Store Bool(true)
+                    Variable(2, Boolean) = Store Bool(true)
+                    Variable(3, Boolean) = Store Bool(true)
+                    Jump(2)
+                Block 2: Block:
+                    Variable(4, Boolean) = Store Bool(true)
+                    Variable(5, Boolean) = Store Bool(true)
+                    Return
+                Block 3: Block:
+                    Variable(0, Boolean) = Store Bool(true)
+                    Branch Variable(0, Boolean), 0, 2
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+}
diff --git a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
index da9d6406..d5f54232 100644
--- a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
+++ b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
@@ -4,6 +4,7 @@
 #![allow(clippy::too_many_lines, clippy::needless_raw_string_hashes)]
 
 use expect_test::expect;
+use qsc_data_structures::target::TargetCapabilityFlags;
 
 use crate::{
     builder::{bell_program, new_program, teleport_program},
@@ -14,19 +15,15 @@ use crate::{
     },
 };
 fn transform_program(program: &mut Program) {
-    // When this configuration is replaced by target capabilities, set them to "all" here.
-    program.config.defer_measurements = false;
-    program.config.remap_qubits_on_reuse = false;
+    program.config.capabilities = TargetCapabilityFlags::all();
     check_and_transform(program);
 }
 
 #[test]
 fn ssa_transform_leaves_program_without_store_instruction_unchanged() {
     let mut program = bell_program();
-    program.config.defer_measurements = false;
-    program.config.remap_qubits_on_reuse = false;
+    program.config.capabilities = TargetCapabilityFlags::all();
     let program_string_orignal = program.to_string();
-
     transform_program(&mut program);
 
     assert_eq!(program_string_orignal, program.to_string());
@@ -35,8 +32,8 @@ fn ssa_transform_leaves_program_without_store_instruction_unchanged() {
 #[test]
 fn ssa_transform_leaves_branching_program_without_store_instruction_unchanged() {
     let mut program = teleport_program();
+    program.config.capabilities = TargetCapabilityFlags::all();
     let program_string_orignal = program.to_string();
-
     transform_program(&mut program);
 
     assert_eq!(program_string_orignal, program.to_string());
@@ -115,8 +112,7 @@ fn ssa_transform_removes_store_in_single_block_program() {
                     Variable(2, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -145,8 +141,7 @@ fn ssa_transform_removes_store_in_single_block_program() {
                     Variable(2, Boolean) = LogicalNot Variable(0, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -269,8 +264,7 @@ fn ssa_transform_removes_multiple_stores_in_single_block_program() {
                     Variable(4, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -301,8 +295,7 @@ fn ssa_transform_removes_multiple_stores_in_single_block_program() {
                     Variable(4, Boolean) = LogicalNot Variable(3, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -434,8 +427,7 @@ fn ssa_transform_store_dominating_usage_propagates_to_successor_blocks() {
                     Variable(4, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -472,8 +464,7 @@ fn ssa_transform_store_dominating_usage_propagates_to_successor_blocks() {
                     Variable(4, Boolean) = LogicalNot Variable(0, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -578,8 +569,7 @@ fn ssa_transform_store_dominating_usage_propagates_to_successor_blocks_without_i
                     Variable(4, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -614,8 +604,7 @@ fn ssa_transform_store_dominating_usage_propagates_to_successor_blocks_without_i
                     Variable(4, Boolean) = LogicalNot Variable(0, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -769,8 +758,7 @@ fn ssa_transform_inserts_phi_for_store_not_dominating_usage() {
                     Variable(4, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -808,8 +796,7 @@ fn ssa_transform_inserts_phi_for_store_not_dominating_usage() {
                     Variable(4, Boolean) = LogicalNot Variable(5, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
@@ -937,8 +924,7 @@ fn ssa_transform_inserts_phi_for_store_not_dominating_usage_in_one_branch() {
                     Variable(4, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -975,8 +961,7 @@ fn ssa_transform_inserts_phi_for_store_not_dominating_usage_in_one_branch() {
                     Variable(4, Boolean) = LogicalNot Variable(5, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
@@ -1188,8 +1173,7 @@ fn ssa_transform_inserts_phi_for_node_with_many_predecessors() {
                     Variable(5, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -1236,8 +1220,7 @@ fn ssa_transform_inserts_phi_for_node_with_many_predecessors() {
                     Variable(5, Boolean) = LogicalNot Variable(6, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
@@ -1412,8 +1395,7 @@ fn ssa_transform_inserts_phi_for_multiple_stored_values() {
                     Variable(6, Boolean) = LogicalNot Variable(2, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -1453,8 +1435,7 @@ fn ssa_transform_inserts_phi_for_multiple_stored_values() {
                     Variable(6, Boolean) = LogicalNot Variable(8, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
@@ -1734,8 +1715,7 @@ fn ssa_transform_inserts_phi_nodes_in_successive_blocks_for_chained_branches() {
                     Variable(8, Boolean) = LogicalNot Variable(1, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -1767,27 +1747,24 @@ fn ssa_transform_inserts_phi_nodes_in_successive_blocks_for_chained_branches() {
                     Branch Variable(2, Boolean), 3, 4
                 Block 2: Block:
                     Variable(3, Boolean) = LogicalNot Variable(0, Boolean)
-                    Jump(5)
+                    Variable(6, Boolean) = LogicalNot Variable(3, Boolean)
+                    Jump(6)
                 Block 3: Block:
                     Variable(4, Boolean) = LogicalNot Variable(2, Boolean)
-                    Jump(6)
+                    Jump(5)
                 Block 4: Block:
                     Variable(5, Boolean) = LogicalNot Variable(2, Boolean)
-                    Jump(6)
+                    Jump(5)
                 Block 5: Block:
-                    Variable(6, Boolean) = LogicalNot Variable(3, Boolean)
-                    Jump(7)
-                Block 6: Block:
                     Variable(9, Boolean) = Phi ( [Variable(4, Boolean), 3], [Variable(5, Boolean), 4], )
                     Variable(7, Boolean) = LogicalNot Variable(9, Boolean)
-                    Jump(7)
-                Block 7: Block:
-                    Variable(10, Boolean) = Phi ( [Variable(6, Boolean), 5], [Variable(7, Boolean), 6], )
+                    Jump(6)
+                Block 6: Block:
+                    Variable(10, Boolean) = Phi ( [Variable(6, Boolean), 2], [Variable(7, Boolean), 5], )
                     Variable(8, Boolean) = LogicalNot Variable(10, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
@@ -2026,8 +2003,7 @@ fn ssa_transform_inerts_phi_nodes_for_early_return_graph_pattern() {
                     Variable(7, Boolean) = LogicalNot Variable(1, Boolean)
                     Jump(3)
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: Base
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -2075,8 +2051,7 @@ fn ssa_transform_inerts_phi_nodes_for_early_return_graph_pattern() {
                     Variable(4, Boolean) = LogicalNot Variable(9, Boolean)
                     Return
             config: Config:
-                remap_qubits_on_reuse: false
-                defer_measurements: false
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
diff --git a/language_service/src/state/tests.rs b/language_service/src/state/tests.rs
index 483617f2..ca000697 100644
--- a/language_service/src/state/tests.rs
+++ b/language_service/src/state/tests.rs
@@ -644,6 +644,68 @@ fn notebook_document_errors() {
     );
 }
 
+#[test]
+fn notebook_document_lints() {
+    let errors = RefCell::new(Vec::new());
+    let mut updater = new_updater(&errors);
+
+    updater.update_notebook_document(
+        "notebook.ipynb",
+        &NotebookMetadata::default(),
+        [
+            ("cell1", 1, "operation Foo() : Unit { let x = 4;;;; }"),
+            ("cell2", 1, "operation Bar() : Unit { let y = 5 / 0; }"),
+        ]
+        .into_iter(),
+    );
+
+    expect_errors(
+        &errors,
+        &expect![[r#"
+            [
+                (
+                    "cell1",
+                    Some(
+                        1,
+                    ),
+                    [
+                        Lint(
+                            Lint {
+                                span: Span {
+                                    lo: 35,
+                                    hi: 38,
+                                },
+                                level: Warn,
+                                message: "redundant semicolons",
+                                help: "remove the redundant semicolons",
+                            },
+                        ),
+                    ],
+                ),
+                (
+                    "cell2",
+                    Some(
+                        1,
+                    ),
+                    [
+                        Lint(
+                            Lint {
+                                span: Span {
+                                    lo: 74,
+                                    hi: 79,
+                                },
+                                level: Warn,
+                                message: "attempt to divide by zero",
+                                help: "division by zero is not allowed",
+                            },
+                        ),
+                    ],
+                ),
+            ]
+        "#]],
+    );
+}
+
 #[test]
 fn notebook_update_remove_cell_clears_errors() {
     let errors = RefCell::new(Vec::new());
diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py
index bab27dcf..52cb3351 100644
--- a/pip/tests/test_interpreter.py
+++ b/pip/tests/test_interpreter.py
@@ -315,7 +315,7 @@ def test_adaptive_errors_are_raised_from_entry_expr() -> None:
     assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo)
 
 
-def test_adaptive_qir_can_be_generated() -> None:
+def test_quantinuum_qir_can_be_generated() -> None:
     adaptive_input = """
         namespace Test {
             open Microsoft.Quantum.Math;
@@ -362,6 +362,87 @@ def test_adaptive_qir_can_be_generated() -> None:
 
         ; module flags
 
+        !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10}
+
+        !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}
+        !4 = !{i32 1, !"classical_ints", i1 true}
+        !5 = !{i32 1, !"qubit_resetting", i1 true}
+        !6 = !{i32 1, !"classical_floats", i1 false}
+        !7 = !{i32 1, !"backwards_branching", i1 false}
+        !8 = !{i32 1, !"classical_fixed_points", i1 false}
+        !9 = !{i32 1, !"user_functions", i1 false}
+        !10 = !{i32 1, !"multiple_target_branching", i1 false}
+        """
+    )
+
+
+def test_base_qir_can_be_generated() -> None:
+    base_input = """
+        namespace Test {
+            open Microsoft.Quantum.Math;
+            open QIR.Intrinsic;
+            @EntryPoint()
+            operation Main() : Result {
+                use q = Qubit();
+                let pi_over_two = 4.0 / 2.0;
+                __quantum__qis__rz__body(pi_over_two, 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);
+                __quantum__qis__mresetz__body(q)
+            }
+        }
+        """
+    e = Interpreter(TargetProfile.Base)
+    e.interpret(base_input)
+    qir = e.qir("Test.Main()")
+    assert qir == dedent(
+        """\
+        %Result = type opaque
+        %Qubit = type opaque
+
+        define void @ENTRYPOINT__main() #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*))
+          call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+          call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+          ret void
+        }
+
+        declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+        declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+        declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+        declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+        declare void @__quantum__qis__rx__body(double, %Qubit*)
+        declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+        declare void @__quantum__qis__ry__body(double, %Qubit*)
+        declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+        declare void @__quantum__qis__rz__body(double, %Qubit*)
+        declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+        declare void @__quantum__qis__h__body(%Qubit*)
+        declare void @__quantum__qis__s__body(%Qubit*)
+        declare void @__quantum__qis__s__adj(%Qubit*)
+        declare void @__quantum__qis__t__body(%Qubit*)
+        declare void @__quantum__qis__t__adj(%Qubit*)
+        declare void @__quantum__qis__x__body(%Qubit*)
+        declare void @__quantum__qis__y__body(%Qubit*)
+        declare void @__quantum__qis__z__body(%Qubit*)
+        declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+        declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1
+        declare void @__quantum__rt__result_record_output(%Result*, i8*)
+        declare void @__quantum__rt__array_record_output(i64, i8*)
+        declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+        attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="1" "required_num_results"="1" }
+        attributes #1 = { "irreversible" }
+
+        ; module flags
+
         !llvm.module.flags = !{!0, !1, !2, !3}
 
         !0 = !{i32 1, !"qir_major_version", i32 1}
diff --git a/pip/tests/test_project.py b/pip/tests/test_project.py
index 6f98db70..e248ecfe 100644
--- a/pip/tests/test_project.py
+++ b/pip/tests/test_project.py
@@ -33,7 +33,7 @@ def test_project_compile_error(qsharp) -> None:
 def test_project_bad_qsharp_json(qsharp) -> None:
     with pytest.raises(Exception) as excinfo:
         qsharp.init(project_root="/bad_qsharp_json")
-    assert str(excinfo.value).startswith("Error parsing /bad_qsharp_json/qsharp.json.")
+    assert str(excinfo.value).startswith("Error parsing /bad_qsharp_json/qsharp.json")
 
 
 def test_project_unreadable_qsharp_json(qsharp) -> None: