Repository: microsoft/qsharp
Pull request #1408 (2024-04-22 03:26:21)

Superdense Coding Kata: Add EntangledPair task
Related issue: https://github.com/microsoft/qsharp/issues/1185


Patch:
diff --git a/.ado/build.yml b/.ado/build.yml
index f948e1bb..a1391f7a 100644
--- a/.ado/build.yml
+++ b/.ado/build.yml
@@ -16,7 +16,7 @@ schedules:
 
 jobs:
 - job: "qsharp"
-  pool: 
+  pool:
     vmImage: 'ubuntu-latest'
   steps:
   - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
diff --git a/.ado/codeql.yml b/.ado/codeql.yml
new file mode 100644
index 00000000..38ae0359
--- /dev/null
+++ b/.ado/codeql.yml
@@ -0,0 +1,64 @@
+name: qsharp-codeql
+
+trigger: none
+pr: none
+
+schedules:
+- cron: "0 9 * * Sat"
+  displayName: 'Build for CodeQL'
+  branches:
+    include:
+    - main
+  always: true
+
+variables:
+  CARGO_TERM_COLOR: always
+  RUST_TOOLCHAIN_VERSION: "1.77"
+
+jobs:
+- job: "qsharp"
+  pool:
+    vmImage: 'ubuntu-latest'
+  timeoutInMinutes: 120
+  steps:
+  - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
+    displayName: 'Component detection'
+
+  - task: RustInstaller@1
+    inputs:
+      rustVersion: ms-$(RUST_TOOLCHAIN_VERSION)
+      additionalTargets: wasm32-unknown-unknown
+      cratesIoFeedOverride: $(cratesIoFeedOverride)
+      toolchainFeed: $(toolchainFeed)
+    displayName: Install Rust toolchain
+
+  - task: UsePythonVersion@0
+    inputs:
+      versionSpec: '3.11'
+
+  - task: CodeQL3000Init@0
+    displayName: CodeQL Initialize
+
+  - script: |
+      python ./prereqs.py --install
+    displayName: Install Prereqs
+
+  - script: |
+      python build.py --no-check --no-test
+    displayName: Build
+
+  - script: |
+      which ar
+      which cc
+      cc --version
+    displayName: Display compile environment setup
+
+  - script: |
+      cd allocator/mimalloc-sys
+      mkdir -p target/codeql/out/
+      cc -O3 -ffunction-sections -fdata-sections -fPIC -gdwarf-2 -fno-omit-frame-pointer -I mimalloc/include -I mimalloc/src -Wall -Wextra -DMI_DEBUG=0 -o target/codeql/out/mimalloc-static.o -c mimalloc/src/static.c
+      ar cq target/codeql/out/libmimalloc.a target/codeql/out/mimalloc-static.o
+    displayName: Compile mimalloc for CodeQL
+
+  - task: CodeQL3000Finalize@0
+    displayName: CodeQL Finalize
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/CodeQL.yml b/CodeQL.yml
new file mode 100644
index 00000000..0dde940b
--- /dev/null
+++ b/CodeQL.yml
@@ -0,0 +1,11 @@
+path_classifiers:
+  library:
+    - allocator/mimalloc-sys/mimalloc
+    - "**/node_modules/**"
+  test:
+    - test
+    - "**/*.test.ts"
+  generated:
+    - target
+    - "**/out/**"
+    - "**/libs/**"
diff --git a/allocator/mimalloc-sys/build.rs b/allocator/mimalloc-sys/build.rs
index b3be712a..cd776994 100644
--- a/allocator/mimalloc-sys/build.rs
+++ b/allocator/mimalloc-sys/build.rs
@@ -48,6 +48,12 @@ fn compile_mimalloc() {
     // turning on optimizations doesn't seem to make a difference
     //build.opt_level(3);
 
+    // log the command that will be run
+    build.cargo_debug(true);
+
+    // turn off warnings from the mimalloc code
+    build.cargo_warnings(false);
+
     build.compile("mimalloc");
 }
 
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 d423bd55..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;
@@ -33,6 +33,27 @@ pub enum ErrorKind {
     Lint(#[from] qsc_linter::Lint),
 }
 
+#[must_use]
+#[allow(clippy::module_name_repetitions)]
+pub fn compile_ast(
+    store: &PackageStore,
+    dependencies: &[PackageId],
+    ast_package: qsc_ast::ast::Package,
+    sources: SourceMap,
+    package_type: PackageType,
+    capabilities: TargetCapabilityFlags,
+) -> (CompileUnit, Vec<Error>) {
+    let unit = qsc_frontend::compile::compile_ast(
+        store,
+        dependencies,
+        ast_package,
+        sources,
+        capabilities,
+        vec![],
+    );
+    process_compile_unit(store, package_type, capabilities, unit)
+}
+
 #[must_use]
 pub fn compile(
     store: &PackageStore,
@@ -42,13 +63,24 @@ pub fn compile(
     capabilities: TargetCapabilityFlags,
     language_features: LanguageFeatures,
 ) -> (CompileUnit, Vec<Error>) {
-    let mut unit = qsc_frontend::compile::compile(
+    let unit = qsc_frontend::compile::compile(
         store,
         dependencies,
         sources,
         capabilities,
         language_features,
     );
+    process_compile_unit(store, package_type, capabilities, unit)
+}
+
+#[must_use]
+#[allow(clippy::module_name_repetitions)]
+fn process_compile_unit(
+    store: &PackageStore,
+    package_type: PackageType,
+    capabilities: TargetCapabilityFlags,
+    mut unit: CompileUnit,
+) -> (CompileUnit, Vec<Error>) {
     let mut errors = Vec::new();
     for error in unit.errors.drain(..) {
         errors.push(WithSource::from_map(&unit.sources, error.into()));
diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs
index d2120809..536affb1 100644
--- a/compiler/qsc/src/incremental.rs
+++ b/compiler/qsc/src/incremental.rs
@@ -3,9 +3,12 @@
 
 use crate::compile::{self, compile, core, std};
 use miette::Diagnostic;
-use qsc_data_structures::language_features::LanguageFeatures;
+
+use qsc_ast::ast;
+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,
 };
@@ -80,6 +83,24 @@ impl Compiler {
         })
     }
 
+    pub fn from(
+        store: PackageStore,
+        source_package_id: PackageId,
+        capabilities: TargetCapabilityFlags,
+        language_features: LanguageFeatures,
+    ) -> Result<Self, Errors> {
+        let frontend =
+            qsc_frontend::incremental::Compiler::new(&store, [], capabilities, language_features);
+        let store = store.open();
+
+        Ok(Self {
+            store,
+            source_package_id,
+            frontend,
+            passes: PassContext::new(capabilities),
+        })
+    }
+
     /// Compiles Q# fragments. Fragments are Q# code that can contain
     /// top-level statements as well as namespaces. A notebook cell
     /// or an interpreter entry is an example of fragments.
@@ -99,6 +120,26 @@ impl Compiler {
         self.compile_fragments(source_name, source_contents, fail_on_error)
     }
 
+    /// Compiles Q# ast fragments. Fragments are Q# code that can contain
+    /// top-level statements as well as namespaces. A notebook cell
+    /// or an interpreter entry is an example of fragments.
+    ///
+    /// This method returns the AST and HIR packages that were created as a result of
+    /// the compilation, however it does *not* update the current compilation.
+    ///
+    /// The caller can use the returned packages to perform passes,
+    /// get information about the newly added items, or do other modifications.
+    /// It is then the caller's responsibility to merge
+    /// these packages into the current `CompileUnit` using the `update()` method.
+    pub fn compile_ast_fragments_fail_fast(
+        &mut self,
+        source_name: &str,
+        source_contents: &str,
+        package: ast::Package,
+    ) -> Result<Increment, Errors> {
+        self.compile_ast_fragments(source_name, source_contents, package, fail_on_error)
+    }
+
     /// Compiles Q# fragments. See [`compile_fragments_fail_fast`] for more details.
     ///
     /// This method calls an accumulator function with any errors returned
@@ -140,6 +181,52 @@ impl Compiler {
         Ok(increment)
     }
 
+    /// Compiles Q# ast fragments. See [`compile_ast_fragments_fail_fast`] for more details.
+    ///
+    /// This method calls an accumulator function with any errors returned
+    /// from each of the stages (parsing, lowering).
+    /// If the accumulator succeeds, compilation continues.
+    /// If the accumulator returns an error, compilation stops and the
+    /// error is returned to the caller.
+    pub fn compile_ast_fragments<F>(
+        &mut self,
+        source_name: &str,
+        source_contents: &str,
+        package: ast::Package,
+        mut accumulate_errors: F,
+    ) -> Result<Increment, Errors>
+    where
+        F: FnMut(Errors) -> Result<(), Errors>,
+    {
+        let (core, unit) = self.store.get_open_mut();
+
+        let mut errors = false;
+        let mut increment = self.frontend.compile_ast_fragments(
+            unit,
+            source_name,
+            source_contents,
+            package,
+            |e| {
+                errors = errors || !e.is_empty();
+                accumulate_errors(into_errors(e))
+            },
+        )?;
+
+        // Even if we don't fail fast, skip passes if there were compilation errors.
+        if !errors {
+            let pass_errors = self.passes.run_default_passes(
+                &mut increment.hir,
+                &mut unit.assigner,
+                core,
+                PackageType::Lib,
+            );
+
+            accumulate_errors(into_errors_with_source(pass_errors, &unit.sources))?;
+        }
+
+        Ok(increment)
+    }
+
     /// Compiles an entry expression.
     ///
     /// This method returns the AST and HIR packages that were created as a result of
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index a9d1af79..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,8 +59,9 @@ use qsc_fir::{
     visit::{self, Visitor},
 };
 use qsc_frontend::{
-    compile::{CompileUnit, PackageStore, Source, SourceMap, TargetCapabilityFlags},
+    compile::{CompileUnit, PackageStore, Source, SourceMap},
     error::WithSource,
+    incremental::Increment,
 };
 use qsc_passes::{PackageType, PassContext};
 use rustc_hash::FxHashSet;
@@ -218,6 +220,42 @@ impl Interpreter {
         })
     }
 
+    pub fn from(
+        store: PackageStore,
+        source_package_id: qsc_hir::hir::PackageId,
+        capabilities: TargetCapabilityFlags,
+        language_features: LanguageFeatures,
+    ) -> std::result::Result<Self, Vec<Error>> {
+        let compiler = Compiler::from(store, source_package_id, capabilities, language_features)
+            .map_err(into_errors)?;
+
+        let mut fir_store = fir::PackageStore::new();
+        for (id, unit) in compiler.package_store() {
+            let mut lowerer = qsc_lowerer::Lowerer::new();
+            fir_store.insert(
+                map_hir_package_to_fir(id),
+                lowerer.lower_package(&unit.package),
+            );
+        }
+
+        let source_package_id = compiler.source_package_id();
+        let package_id = compiler.package_id();
+
+        Ok(Self {
+            compiler,
+            lines: 0,
+            capabilities,
+            fir_store,
+            lowerer: qsc_lowerer::Lowerer::new(),
+            env: Env::default(),
+            sim: sim_circuit_backend(),
+            quantum_seed: None,
+            classical_seed: None,
+            package: map_hir_package_to_fir(package_id),
+            source_package: map_hir_package_to_fir(source_package_id),
+        })
+    }
+
     pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
         self.quantum_seed = seed;
         self.sim.set_seed(seed);
@@ -293,6 +331,36 @@ impl Interpreter {
             .compile_fragments_fail_fast(&label, fragments)
             .map_err(into_errors)?;
 
+        self.eval_increment(receiver, increment)
+    }
+
+    /// It is assumed that if there were any parse errors on the fragments, the caller would have
+    /// already handled them. This function is intended to be used in cases where the caller wants
+    /// to handle the parse errors themselves.
+    ///  # Errors
+    /// If the compilation of the fragments fails, an error is returned.
+    /// If there is a runtime error when interpreting the fragments, an error is returned.
+    pub fn eval_ast_fragments(
+        &mut self,
+        receiver: &mut impl Receiver,
+        fragments: &str,
+        package: qsc_ast::ast::Package,
+    ) -> InterpretResult {
+        let label = self.next_line_label();
+
+        let increment = self
+            .compiler
+            .compile_ast_fragments_fail_fast(&label, fragments, package)
+            .map_err(into_errors)?;
+
+        self.eval_increment(receiver, increment)
+    }
+
+    fn eval_increment(
+        &mut self,
+        receiver: &mut impl Receiver,
+        increment: Increment,
+    ) -> InterpretResult {
         let (graph, _) = self.lower(&increment)?;
 
         // Updating the compiler state with the new AST/HIR nodes
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_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 6aca4b3a..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,
@@ -314,6 +285,7 @@ impl MutVisitor for Offsetter {
     }
 }
 
+#[must_use]
 pub fn compile(
     store: &PackageStore,
     dependencies: &[PackageId],
@@ -321,8 +293,27 @@ pub fn compile(
     capabilities: TargetCapabilityFlags,
     language_features: LanguageFeatures,
 ) -> CompileUnit {
-    let (mut ast_package, parse_errors) = parse_all(&sources, language_features);
+    let (ast_package, parse_errors) = parse_all(&sources, language_features);
+
+    compile_ast(
+        store,
+        dependencies,
+        ast_package,
+        sources,
+        capabilities,
+        parse_errors,
+    )
+}
 
+#[allow(clippy::module_name_repetitions)]
+pub fn compile_ast(
+    store: &PackageStore,
+    dependencies: &[PackageId],
+    mut ast_package: ast::Package,
+    sources: SourceMap,
+    capabilities: TargetCapabilityFlags,
+    parse_errors: Vec<qsc_parse::Error>,
+) -> CompileUnit {
     let mut cond_compile = preprocess::Conditional::new(capabilities);
     cond_compile.visit_package(&mut ast_package);
     let dropped_names = cond_compile.into_names();
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 22cfd099..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},
@@ -110,18 +107,70 @@ impl Compiler {
         unit: &mut CompileUnit,
         source_name: &str,
         source_contents: &str,
-        mut accumulate_errors: F,
+        accumulate_errors: F,
     ) -> Result<Increment, E>
     where
         F: FnMut(Vec<Error>) -> Result<(), E>,
     {
-        let (mut ast, parse_errors) = Self::parse_fragments(
+        let (ast, parse_errors) = Self::parse_fragments(
             &mut unit.sources,
             source_name,
             source_contents,
             self.language_features,
         );
 
+        self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors)
+    }
+
+    /// Compiles Q# AST fragments.
+    ///
+    /// Uses the assigners and other mutable state from the passed in
+    /// `CompileUnit` to guarantee uniqueness, however does not
+    /// update the `CompileUnit` with the resulting AST and HIR packages.
+    ///
+    /// The caller can use the returned packages to perform passes,
+    /// get information about the newly added items, or do other modifications.
+    /// It is then the caller's responsibility to merge
+    /// these packages into the current `CompileUnit`.
+    ///
+    /// This method calls an accumulator function with any errors returned
+    /// from each of the stages instead of failing.
+    /// If the accumulator succeeds, compilation continues.
+    /// If the accumulator returns an error, compilation stops and the
+    /// error is returned to the caller.
+    pub fn compile_ast_fragments<F, E>(
+        &mut self,
+        unit: &mut CompileUnit,
+        source_name: &str,
+        source_contents: &str,
+        package: ast::Package,
+        accumulate_errors: F,
+    ) -> Result<Increment, E>
+    where
+        F: FnMut(Vec<Error>) -> Result<(), E>,
+    {
+        // Update the AST with source information offset from the current source map.
+        let (ast, parse_errors) = Self::offset_ast_fragments(
+            &mut unit.sources,
+            source_name,
+            source_contents,
+            package,
+            vec![],
+        );
+
+        self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors)
+    }
+
+    fn compile_fragments_internal<F, E>(
+        &mut self,
+        unit: &mut CompileUnit,
+        mut ast: ast::Package,
+        parse_errors: Vec<Error>,
+        mut accumulate_errors: F,
+    ) -> Result<Increment, E>
+    where
+        F: FnMut(Vec<Error>) -> Result<(), E>,
+    {
         accumulate_errors(parse_errors)?;
 
         let (hir, errors) = self.resolve_check_lower(unit, &mut ast);
@@ -280,7 +329,6 @@ impl Compiler {
         features: LanguageFeatures,
     ) -> (ast::Package, Vec<Error>) {
         let offset = sources.push(source_name.into(), source_contents.into());
-
         let (mut top_level_nodes, errors) = qsc_parse::top_level_nodes(source_contents, features);
         let mut offsetter = Offsetter(offset);
         for node in &mut top_level_nodes {
@@ -289,12 +337,32 @@ impl Compiler {
                 ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt),
             }
         }
-
         let package = ast::Package {
             id: ast::NodeId::default(),
             nodes: top_level_nodes.into_boxed_slice(),
             entry: None,
         };
+        (package, with_source(errors, sources, offset))
+    }
+
+    /// offset all top level nodes based on the source input
+    /// and return the updated package and errors
+    fn offset_ast_fragments(
+        sources: &mut SourceMap,
+        source_name: &str,
+        source_contents: &str,
+        mut package: ast::Package,
+        errors: Vec<qsc_parse::Error>,
+    ) -> (ast::Package, Vec<Error>) {
+        let offset = sources.push(source_name.into(), source_contents.into());
+
+        let mut offsetter = Offsetter(offset);
+        for node in package.nodes.iter_mut() {
+            match node {
+                ast::TopLevelNode::Namespace(ns) => offsetter.visit_namespace(ns),
+                ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt),
+            }
+        }
 
         (package, with_source(errors, sources, offset))
     }
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/multi_qubit_gates/entangle_qubits/Placeholder.qs b/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs
new file mode 100644
index 00000000..f5ee355d
--- /dev/null
+++ b/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl {
+        // Implement your solution here...
+
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs b/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs
new file mode 100644
index 00000000..3a2b89e2
--- /dev/null
+++ b/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs
@@ -0,0 +1,5 @@
+namespace Kata {
+    operation EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl {
+        CNOT(qs[0], qs[1]);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs b/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs
new file mode 100644
index 00000000..822d40de
--- /dev/null
+++ b/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs
@@ -0,0 +1,52 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Diagnostics;
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Convert;
+
+    operation  EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl {
+        CNOT(qs[0], qs[1]);
+    }
+
+    operation CheckOperationsEquivalenceOnInitialStateStrict(
+        initialState : Qubit[] => Unit is Adj,
+        op : (Qubit[] => Unit is Adj + Ctl),
+        reference : (Qubit[] => Unit is Adj + Ctl),
+        inputSize : Int
+    ) : Bool {
+        use (control, target) = (Qubit(), Qubit[inputSize]);
+        within {
+            H(control);
+            initialState(target);
+        }
+        apply {
+            Controlled op([control], target);
+            Adjoint Controlled reference([control], target);
+        }
+        let isCorrect = CheckAllZero([control] + target);
+        ResetAll([control] + target);
+        isCorrect
+    }
+    
+    operation CheckSolution() : Bool {
+        let range = 10;
+        for i in 0 .. range - 1 {
+            let angle = 2.0 * PI() * IntAsDouble(i) / IntAsDouble(range);
+            let initialState = qs => Ry(2.0 * angle, qs[0]);
+            let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict(
+                initialState,
+                Kata.EntangleQubits, 
+                EntangleQubits, 
+                2);
+            if not isCorrect {
+                Message("Incorrect");
+                Message($"Test fails for alpha = {Cos(angle)}, beta = {Sin(angle)}.");
+                ShowQuantumStateComparison(2, initialState, Kata.EntangleQubits, EntangleQubits);
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        true
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/entangle_qubits/index.md b/katas/content/multi_qubit_gates/entangle_qubits/index.md
new file mode 100644
index 00000000..07ce9227
--- /dev/null
+++ b/katas/content/multi_qubit_gates/entangle_qubits/index.md
@@ -0,0 +1,5 @@
+**Input:** Two unentangled qubits (stored in an array of length 2).
+The first qubit will be in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$, the second - in state $|0\rangle$
+(this can be written as two-qubit state $\big(\alpha |0\rangle + \beta |1\rangle \big) \otimes |0\rangle = \alpha |00\rangle + \beta |10\rangle$).
+
+**Goal:** Change the two-qubit state to $\alpha |00\rangle + \beta |11\rangle$.
diff --git a/katas/content/multi_qubit_gates/entangle_qubits/solution.md b/katas/content/multi_qubit_gates/entangle_qubits/solution.md
new file mode 100644
index 00000000..7326c091
--- /dev/null
+++ b/katas/content/multi_qubit_gates/entangle_qubits/solution.md
@@ -0,0 +1,11 @@
+Let's denote the first qubit in state $\alpha |0\rangle + \beta |1\rangle$ as A and the second qubit in state $|0\rangle$ as B.
+
+Compare our input state $\alpha |0_A0_B\rangle + \beta |1_A0_B\rangle$ with the goal state $\alpha |0_A0_B\rangle + \beta |1_A1_B\rangle$. 
+We want to pass our input qubit through a gate or gates (to be decided) that do the following. If qubit A is in the $|0\rangle$ state, then we want to leave qubit B alone (the first term of the superposition). 
+However, if A is in the $|1\rangle$ state, we want to flip qubit B from $|0\rangle$ into $|1\rangle$ state. In other words, the state of B is to be made contingent upon the state of A. 
+This is exactly the effect of the $CNOT$ gate. Depending upon the state of the **control** qubit (A in our case), the value of the controlled or **target** qubit (B in our case) is inverted or unchanged. Thus, we get the goal state $\alpha |00\rangle + \beta |11\rangle$.  
+
+@[solution]({
+    "id": "multi_qubit_gates__entangle_qubits_solution",
+    "codePath": "./Solution.qs"
+})
diff --git a/katas/content/multi_qubit_gates/index.md b/katas/content/multi_qubit_gates/index.md
index c5009d5e..9fa27369 100644
--- a/katas/content/multi_qubit_gates/index.md
+++ b/katas/content/multi_qubit_gates/index.md
@@ -121,6 +121,16 @@ $$\alpha|00\rangle + \beta|11\rangle$$
 
 The $CNOT$ gate is self-adjoint: applying it for the second time reverses its effect.
 
+
+@[exercise]({
+    "id": "multi_qubit_gates__entangle_qubits",
+    "title": "Entangle Qubits",
+    "path": "./entangle_qubits/",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
+
 @[exercise]({
     "id": "multi_qubit_gates__preparing_bell_state",
     "title": "Preparing a Bell State",
@@ -130,6 +140,62 @@ The $CNOT$ gate is self-adjoint: applying it for the second time reverses its ef
     ]
 })
 
+@[section]({
+    "id": "multi_qubit_gates__cz_gate",
+    "title": "CZ Gate"
+})
+
+
+The $CZ$ ("controlled-Z") gate is a two-qubit gate, with one qubit referred to as the **control** qubit, and the other as the **target** qubit. Interestingly, for the $CZ$ gate it doesn't matter which qubit is control and which is target - the effect of the gate is the same either way!
+
+The $CZ$ gate acts as a conditional gate: if the control qubit is in state $|1\rangle$, it applies the $Z$ gate to the target qubit, otherwise it does nothing.
+
+<table>
+    <tr>
+        <th>Gate</th>
+        <th>Matrix</th>
+        <th>Applying to $|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle + \delta|11\rangle$</th>
+        <th>Applying to basis states</th>
+    </tr>
+    <tr>
+        <td>$CZ$</td>
+        <td>
+            $$\begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & -1
+            \end{bmatrix}$$
+        </td>
+        <td>$CZ|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle - \delta|11\rangle$</td>
+        <td>
+            $$CZ|00\rangle = |00\rangle$$
+            $$CZ|01\rangle = |01\rangle$$
+            $$CZ|10\rangle = |10\rangle$$
+            $$CZ|11\rangle = -|11\rangle$$
+        </td>
+    </tr>
+</table>
+
+The $CZ$ gate is particularly useful for creating and manipulating entangled states where the phase of the quantum state is crucial. Consider the following separable state:
+
+$$\big(\alpha|0\rangle + \beta|1\rangle\big) \otimes \big(\gamma|0\rangle + \delta|1\rangle\big) = \alpha\gamma|00\rangle + \alpha\delta|01\rangle + \beta\gamma|10\rangle + \beta\delta|11\rangle$$
+
+If we apply the $CZ$ gate to it, with the first qubit as the control and the second as the target (or vice versa), we get the following state, which can no longer be separated:
+
+$$\alpha\gamma|00\rangle + \alpha\delta|01\rangle + \beta\gamma|10\rangle - \beta\delta|11\rangle$$
+
+The $CZ$ gate is also self-adjoint: applying it a second time reverses its effect, similar to the $CNOT$ gate.
+
+@[exercise]({
+    "id": "multi_qubit_gates__relative_phase_minusone",
+    "title": "Relative Phase -1",
+    "path": "./relative_phase_minusone/",
+    "qsDependencies": [
+        "../KatasLibrary.qs"
+    ]
+})
+
 @[section]({
     "id": "multi_qubit_gates__ket_bra_representation",
     "title": "Ket-Bra Representation"
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs
new file mode 100644
index 00000000..c5b83b31
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl {
+        // Implement your solution here...
+
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs
new file mode 100644
index 00000000..119e4afa
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs
@@ -0,0 +1,5 @@
+namespace Kata {
+    operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl {
+        CZ(qs[0], qs[1]);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs
new file mode 100644
index 00000000..57fcf1a7
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs
@@ -0,0 +1,5 @@
+namespace Kata {
+    operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl {
+        Controlled Z([qs[0]], qs[1]);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs
new file mode 100644
index 00000000..e0c35e86
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs
@@ -0,0 +1,47 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Diagnostics;
+
+    operation PrepareState(qs : Qubit[]) : Unit is Adj + Ctl {
+        ApplyToEachCA(H, qs);
+    }
+    operation  RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl {
+        CZ(qs[0], qs[1]);
+    }
+
+    operation CheckOperationsEquivalenceOnInitialStateStrict(
+        initialState : Qubit[] => Unit is Adj,
+        op : (Qubit[] => Unit is Adj + Ctl),
+        reference : (Qubit[] => Unit is Adj + Ctl),
+        inputSize : Int
+    ) : Bool {
+        use (control, target) = (Qubit(), Qubit[inputSize]);
+        within {
+            H(control);
+            initialState(target);
+        }
+        apply {
+            Controlled op([control], target);
+            Adjoint Controlled reference([control], target);
+        }
+        let isCorrect = CheckAllZero([control] + target);
+        ResetAll([control] + target);
+        isCorrect
+    }
+
+    operation CheckSolution() : Bool {
+        let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict(
+            PrepareState,
+            Kata.RelativePhaseMinusOne, 
+            RelativePhaseMinusOne, 
+            2);
+        if isCorrect {
+            Message("Correct!");
+        } else {
+            Message("Incorrect");
+            ShowQuantumStateComparison(2, PrepareState, Kata.RelativePhaseMinusOne, RelativePhaseMinusOne);
+        }
+
+        return isCorrect;
+    }
+}
\ No newline at end of file
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/index.md b/katas/content/multi_qubit_gates/relative_phase_minusone/index.md
new file mode 100644
index 00000000..33bcf050
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/index.md
@@ -0,0 +1,3 @@
+**Input:** Two unentangled qubits (stored in an array of length 2) in state $|+\rangle \otimes |+\rangle = \frac{1}{2} \big( |00\rangle + |01\rangle + |10\rangle {\color{blue}+} |11\rangle \big)$.
+
+**Goal:** Change the two-qubit state to $\frac{1}{2} \big( |00\rangle + |01\rangle + |10\rangle {\color{red}-} |11\rangle \big)$.
diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md b/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md
new file mode 100644
index 00000000..b94bb4f4
--- /dev/null
+++ b/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md
@@ -0,0 +1,22 @@
+Firstly we notice that we are dealing with an unentangled pair of qubits.
+In vector form the transformation we need is 
+$$
+\frac{1}{2}\begin{bmatrix}1\\\ 1\\\ 1\\\ 1\\\ \end{bmatrix} 
+\rightarrow 
+\frac{1}{2}\begin{bmatrix}1\\\ 1\\\ 1\\\ -1\\\ \end{bmatrix}
+$$
+
+All that needs to happen to change the input into the goal is that the $|11\rangle$ basis state needs to have its sign flipped.
+
+We remember that the Pauli $Z$ gate flips signs in the single qubit case, and that $CZ$ is the 2-qubit version of this gate. And indeed, the effect of the $CZ$ gate is exactly the transformation we're looking for here.
+
+@[solution]({
+"id": "multi_qubit_gates__two_qubit_gate_2_solution_a",
+"codePath": "./SolutionA.qs"
+})
+Alternatively, we can express this gate using the intrinsic gate Z and its controlled variant using the Controlled functor:
+
+@[solution]({
+"id": "multi_qubit_gates__two_qubit_gate_2_solution_b",
+"codePath": "./SolutionB.qs"
+})
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/four_bitstrings/Placeholder.qs b/katas/content/superposition/four_bitstrings/Placeholder.qs
new file mode 100644
index 00000000..97e8bf32
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit {
+        // Hint: remember that you can allocate extra qubits.
+
+        // Implement your solution here...
+    }
+}
diff --git a/katas/content/superposition/four_bitstrings/SolutionA.qs b/katas/content/superposition/four_bitstrings/SolutionA.qs
new file mode 100644
index 00000000..fcaf4bf1
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/SolutionA.qs
@@ -0,0 +1,26 @@
+namespace Kata {
+    operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit {
+        use anc = Qubit[2];
+        // Put two ancillas into equal superposition of 2-qubit basis states
+        ApplyToEachA(H, anc);
+
+        // Set up the right pattern on the main qubits with control on ancillas
+        for i in 0 .. 3 {
+            for j in 0 .. Length(qs) - 1 {
+                if bits[i][j] {
+                    ApplyControlledOnInt(i, X, anc, qs[j]);
+                }
+            }
+        }
+
+        // Uncompute the ancillas, using patterns on main qubits as control
+        for i in 0 .. 3 {
+            if i % 2 == 1 {
+                ApplyControlledOnBitString(bits[i], X, qs, anc[0]);
+            }
+            if i / 2 == 1 {
+                ApplyControlledOnBitString(bits[i], X, qs, anc[1]);
+            }
+        }
+    }
+}
diff --git a/katas/content/superposition/four_bitstrings/SolutionB.qs b/katas/content/superposition/four_bitstrings/SolutionB.qs
new file mode 100644
index 00000000..10c62a3a
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/SolutionB.qs
@@ -0,0 +1,40 @@
+namespace Kata {
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Math;
+
+    operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit {
+        FourBitstringSuperposition_Recursive([], qs, bits);
+    }
+
+    operation FourBitstringSuperposition_Recursive (currentBitString : Bool[], qs : Qubit[], bits : Bool[][]) : Unit {
+        // an array of bit strings whose columns we are considering begin with |0⟩
+        mutable zeroLeads = [];
+        // an array of bit strings whose columns we are considering begin with |1⟩
+        mutable oneLeads = [];
+        // the number of bit strings we're considering
+        let rows = Length(bits);
+        // the current position we're considering
+        let currentIndex = Length(currentBitString);
+
+        if rows >= 1 and currentIndex < Length(qs) {
+            // figure out what percentage of the bits should be |0⟩
+            for row in 0 .. rows - 1 {
+                if bits[row][currentIndex] {
+                    set oneLeads = oneLeads + [bits[row]];
+                } else {
+                    set zeroLeads = zeroLeads + [bits[row]];
+                }
+            }
+            // rotate the qubit to adjust coefficients based on the previous bit string
+            // for the first path through, when the bit string has zero length,
+            // the Controlled version of the rotation will perform a regular rotation
+            let theta = ArcCos(Sqrt(IntAsDouble(Length(zeroLeads)) / IntAsDouble(rows)));
+            ApplyControlledOnBitString(currentBitString, Ry, qs[0 .. currentIndex - 1],
+                                                        (2.0 * theta, qs[currentIndex]));
+
+            // call state preparation recursively based on the bit strings so far
+            FourBitstringSuperposition_Recursive(currentBitString + [false], qs, zeroLeads);
+            FourBitstringSuperposition_Recursive(currentBitString + [true], qs, oneLeads);
+        }
+    }
+}
diff --git a/katas/content/superposition/four_bitstrings/Verification.qs b/katas/content/superposition/four_bitstrings/Verification.qs
new file mode 100644
index 00000000..3adcf610
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/Verification.qs
@@ -0,0 +1,61 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Katas;
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Random;
+
+    operation FourBitstringSuperposition_Reference (qs : Qubit[], bits : Bool[][]) : Unit is Adj {
+        use anc = Qubit[2];
+        ApplyToEachA(H, anc);
+
+        for i in 0 .. 3 {
+            for j in 0 .. Length(qs) - 1 {
+                if bits[i][j] {
+                    ApplyControlledOnInt(i, X, anc, qs[j]);
+                }
+            }
+        }
+
+        for i in 0 .. 3 {
+            if i % 2 == 1 {
+                ApplyControlledOnBitString(bits[i], X, qs, anc[0]);
+            }
+            if i / 2 == 1 {
+                ApplyControlledOnBitString(bits[i], X, qs, anc[1]);
+            }
+        }
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+
+        let bits = [[false, false], [false, true], [true, false], [true, true]];
+        Message($"Testing for bits = {bits}...");
+        if not CheckOperationsEquivalenceOnZeroStateWithFeedback(
+            Kata.FourBitstringSuperposition(_, bits),
+            ApplyToEachA(H, _),
+            2
+        ) {
+            return false;
+        }
+
+        let bitstrings = [
+            [[false, true, false], [true, false, false], [false, false, true], [true, true, false]],
+            [[true, false, false], [false, false, true], [false, true, false], [true, true, true]],
+            [[false, false, false], [false, true, false], [true, true, false], [true, false, true]]
+        ];
+
+        for bitstring in bitstrings {
+            Message($"Testing for bits = {bitstring}...");
+            if not CheckOperationsEquivalenceOnZeroStateWithFeedback(
+                Kata.FourBitstringSuperposition(_, bitstring),
+                FourBitstringSuperposition_Reference(_, bitstring),
+                3
+            ) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/katas/content/superposition/four_bitstrings/index.md b/katas/content/superposition/four_bitstrings/index.md
new file mode 100644
index 00000000..25b98c4c
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/index.md
@@ -0,0 +1,14 @@
+**Inputs:**
+
+1. $N$ qubits in the $|0 \dots 0\rangle$ state.
+2. Four bit strings represented as `Bool[][]` `bits`.
+
+    `bits` is an array of size $4 \times N$ which describes the bit strings as follows:
+    - `bits[i]` describes the *i*th bit string and has $N$ elements.
+    - All four bit strings will be distinct.
+
+**Goal:** Create an equal superposition of the four basis states given by the bit strings.
+
+**Example:**
+
+For $N = 3$ and `bits =  [[false, true, false], [true, false, false], [false, false, true], [true, true, false]]`, the state you need to prepare is $\frac{1}{2} \big(|010\rangle + |100\rangle + |001\rangle + |110\rangle\big)$.
diff --git a/katas/content/superposition/four_bitstrings/solution.md b/katas/content/superposition/four_bitstrings/solution.md
new file mode 100644
index 00000000..4009f1f4
--- /dev/null
+++ b/katas/content/superposition/four_bitstrings/solution.md
@@ -0,0 +1,60 @@
+We are going to use the same trick of auxiliary qubits that we used in the previous task.
+
+Since the desired superposition has 4 basis states with equal amplitudes, we are going to need two qubits to define a unique basis to control preparation of each of the basis states in the superposition.
+
+We start by allocating two extra qubits and preparing an equal superposition of all 2-qubit states on them by applying an H gate to each of them:
+
+$$\frac12 (|00\rangle + |01\rangle + |10\rangle + |11\rangle)_a \otimes |0 \dots 0\rangle_r$$
+
+Then, for each of the four given bit strings, we walk through it and prepare the matching basis state on the main register of qubits, using controlled X gates with the corresponding basis state of the auxiliary qubits as control.
+
+For example, when preparing the bit string `bits[0]`, we apply X gates controlled on the basis state $|00\rangle$; when preparing the bit string `bits[1]`, we apply X gates controlled on $|10\rangle$, and so on.
+
+> We can choose an arbitrary matching of the 2-qubit basis states used as controls and the bit strings prepared on the main register.
+> Since all amplitudes are the same, the result does not depend on which state controlled which bit string preparation.
+> It can be convenient to use indices of the bit strings, converted to little-endian, to control preparation of the bit strings.
+> Q# library function [`ApplyControlledOnInt`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonint) does exactly that.
+
+After this the system will be in the state
+
+$$\frac12 (|00\rangle_a |bits_0\rangle_r + |10\rangle_a |bits_1\rangle_r + |01\rangle_a |bits_2\rangle_r + |11\rangle_a |bits_3\rangle_r)$$
+
+As the last step, we must uncompute the auxiliary qubits, i.e., return them to the $|00\rangle$ state to unentangle them from the main register.
+
+Same as we did in the previous task, we will use [`ApplyControlledOnBitString`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonbitstring) with the corresponding bit string and the X operation as arguments, the quantum register as the control, and the auxiliary qubits as the target.
+
+We will uncompute each of them separately, so one of the auxiliary qubits will be uncomputed with the `bits[1]` and `bits[3]` bit strings as controls, and the other - with the `bits[2]` and `bits[3]`.
+
+@[solution]({
+    "id": "superposition__four_bitstrings_solution_a",
+    "codePath": "./SolutionA.qs"
+})
+
+Alternatively, we can leverage the recursion abilities of Q# to create a superposition of the four bit strings.  This solution also extends to an arbitrary number of bit strings with no code changes.
+
+For this process we will look at the first bits of each string and adjust the probability of measuring a $|0\rangle$ or $|1\rangle$ accordingly on the first qubit of our answer.  We will then recursively call (as needed) the process again to adjust the probabilities of measurement on the second bit depending on the first bit.  This process recurses until no more input bits are provided.
+
+Consider, for example, the following four bit strings on which to create a superposition: $|001\rangle, |101\rangle, |111\rangle, |110\rangle$.
+
+We can rewrite the superposition state we need to prepare as
+
+$$\frac12 \big(|001\rangle + |101\rangle + |111\rangle + |110\rangle \big) = \frac12 |0\rangle \otimes |01\rangle + \frac{\sqrt3}{2} |1\rangle \otimes \frac{1}{\sqrt3} \big(|10\rangle + |11\rangle + |10\rangle \big)$$
+
+As the first step of the solution, we need to prepare a state $\frac12 |0\rangle + \frac{\sqrt3}{2} |1\rangle$ on the first qubit (to measure $|0\rangle$ with $\frac14$ probability and to measure $|1\rangle$ with $\frac34$ probability).  To do this, we will apply an [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation to the first qubit.
+
+After this, we'll need to prepare the rest of the qubits in appropriate states depending on the state of the first qubit - state $|01\rangle$ if the first qubit is in state $|0\rangle$ and state $\frac{1}{\sqrt3} \big(|10\rangle + |11\rangle + |10\rangle \big)$ if the first qubit is in state $|1\rangle$. We can do this recursively using the same logic. Let's finish walking through this example in detail.
+
+The second qubit of the recursion follows similarly but depends on the first qubit.  If the first qubit measures $|0\rangle$, then we want the second qubit to measure $|0\rangle$ with 100% probability, but if it measures $|1\rangle$, we want it to measure $|0\rangle$ with $\frac13$ probability and $|1\rangle$ with $\frac23$ probability.  For this, we can do a controlled [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation on the second qubit with the first qubit as control.
+
+The third qubit in this example will have three cases because it depends on the first two qubits; this follows naturally from the recursion.
+
+1. If the first two qubits measure $|00\rangle$, then we need the third qubit to measure $|0\rangle$ with 100% probability.
+2. If the first two qubits measure $|10\rangle$, then we need the third qubit to measure $|1\rangle$ with 100% probability.
+3. If the first two qubits measure $|11\rangle$, then we need the third qubit to measure $|0\rangle$ with $\frac12$ probability and $|1\rangle$ with $\frac12$ probability.  Just as with the second qubit, a controlled [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation on the third qubit will accomplish this goal.
+
+> We will use [`ApplyControlledOnBitString`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonbitstring) operation to perform rotations depending on the state of several previous qubits.
+
+@[solution]({
+    "id": "superposition__four_bitstrings_solution_b",
+    "codePath": "./SolutionB.qs"
+})
diff --git a/katas/content/superposition/index.md b/katas/content/superposition/index.md
index f5378072..9104292a 100644
--- a/katas/content/superposition/index.md
+++ b/katas/content/superposition/index.md
@@ -143,6 +143,16 @@ This kata is designed to get you familiar with the concept of superposition and
     ]
 })
 
+@[exercise]({
+    "id": "superposition__four_bitstrings",
+    "title": "Four Bit Strings",
+    "path": "./four_bitstrings/",
+    "qsDependencies": [
+        "../KatasLibrary.qs",
+        "./Common.qs"
+    ]
+})
+
 @[section]({
     "id": "superposition__arbitrary_rotations",
     "title": "Arbitrary Rotations"
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/library/README.md b/library/README.md
index de906317..23dce7d6 100644
--- a/library/README.md
+++ b/library/README.md
@@ -19,3 +19,30 @@ One notable exception to this is the `Microsoft.Quantum.Core.Length` function, w
 The library includes a set of one- and two-qubit gates represented as `__quantum__qis__*__body` or `__quantum__qis__*__adj` QIR declarations. The expectation is that these gates form a common QIR API surface that Q# is compiled into. Any target would either need to support these quantum gate instructions natively or provide a QIR definition of that gate instruction in terms of supported native gates. These definitions can then be linked with the QIR program via LLVM tools and resolve to the target specific gate set. This approach provides broader compatibility at the QIR level and avoids the need for front end language targetting; as a result, this Q# library design is not expected to require any target packages or operation substitution.
 
 To avoid using any opaque array types in the quantum gate instruction set, the library utilizes decomposition strategies to express the Q# controlled specialization (which allows an arbitrary number of controls) in terms of singly- and doubly-controlled gates. While this makes simulation somewhat more expensive in terms of allocating extra controls and performing more operations, it more closely matches the patterns used by hardware and provides better API surface for QIR programs to have hardware compatibility.
+
+### Doc comments
+
+Use the following guidance when providing doc comments for functions and operations in the standard library. These doc comments are used to generate standard library documentation automatically.
+
+| Section        | LaTeX allowed? | Mandatory? | What is it for
+|---------------:|:--------------:|:----------:|----------------
+| Summary        | No             | Yes        | Short summary of the functionality provided.
+| Description    | Yes            | No         | Detailed explanation of the functionality provided.
+| Remarks        | Yes            | No         | General knowledge that may be useful for understanding of the functionality provided.
+| Type Parameters| No             | No         | Description of type parameters (for each type parameter).
+| Input          | No             | No         | Description of input parameters (for each input parameter)
+| Output         | No             | No         | Description of the output.
+| Example        | No             | No         | Example(s) of use. Should be a code fragment that compiles.
+| References     | No             | No         | References to papers and other information published on the web or elsewhere.
+| See Also       | No             | No         | References to other functions of the standard library that are similar or relevant.
+
+* Only `Summary` section is mandatory. There's no need to include other
+  sections just to repeat the information that is already stated in the summary.
+* For sections where LaTeX is not allowed, Unicode characters may be used
+  to represent mathematical formulas even though it is not recommended in
+  general. These sections are used in places where LaTeX rendering is
+  not supported.
+* For sections where LaTeX is supported, unicode characters may be used to
+  represent mathematical formulas only if the rendering fidelity is
+  not compromised.
+* Use playground to check documentation rendering.
diff --git a/library/core/core.qs b/library/core/core.qs
index b8abde55..c3dec923 100644
--- a/library/core/core.qs
+++ b/library/core/core.qs
@@ -3,20 +3,32 @@
 
 namespace Microsoft.Quantum.Core {
     /// # Summary
-    /// Returns the number of elements in an array.
+    /// Returns the number of elements in the input array `a`.
     ///
     /// # Input
     /// ## a
     /// Input array.
     ///
     /// # Output
-    /// The total count of elements in an array.
+    /// The total number of elements in the input array `a`.
+    ///
+    /// # Example
+    /// ```qsharp
+    /// Message($"{ Length([0, 0, 0]) }"); // Prints 3
+    /// ```
     function Length<'T>(a : 'T[]) : Int {
         body intrinsic;
     }
 
     /// # Summary
-    /// Creates an array of given length with all elements equal to given value.
+    /// Creates an array of given `length` with all elements equal to given
+    /// `value`. `length` must be a non-negative integer.
+    ///
+    /// # Description
+    /// Use this function to create an array of length `length` where each
+    /// element is equal to `value`. This way of creating an array is preferred
+    /// over other methods if all elements of the array must be the same and
+    /// the length is known upfront.
     ///
     /// # Input
     /// ## value
@@ -28,8 +40,8 @@ namespace Microsoft.Quantum.Core {
     /// A new array of length `length`, such that every element is `value`.
     ///
     /// # Example
-    /// The following code creates an array of 3 Boolean values, each equal to `true`:
     /// ```qsharp
+    /// // Create an array of 3 Boolean values, each equal to `true`
     /// let array = Repeated(true, 3);
     /// ```
     function Repeated<'T>(value : 'T, length : Int) : 'T[] {
diff --git a/library/std/arrays.qs b/library/std/arrays.qs
index beffbc38..f5fe2449 100644
--- a/library/std/arrays.qs
+++ b/library/std/arrays.qs
@@ -203,7 +203,7 @@ namespace Microsoft.Quantum.Arrays {
     ///
     /// # Example
     /// ```qsharp
-    ///  let evensCount = Count(x -> x % 2 == 0, [1, 3, 6, 7, 9]);
+    /// let evensCount = Count(x -> x % 2 == 0, [1, 3, 6, 7, 9]);
     /// // evensCount is 1.
     /// ```
     function Count<'T>(predicate : ('T -> Bool), array : 'T[]) : Int {
diff --git a/library/std/canon.qs b/library/std/canon.qs
index 8b1d62a3..7116b8c1 100644
--- a/library/std/canon.qs
+++ b/library/std/canon.qs
@@ -173,10 +173,12 @@ namespace Microsoft.Quantum.Canon {
     /// This operation can be simulated by the unitary matrix
     /// $$
     /// \begin{align}
-    ///     1 & 0 & 0 & 0 \\\\
-    ///     0 & 1 & 0 & 0 \\\\
-    ///     0 & 0 & 0 & -i \\\\
-    ///     0 & 0 & i & 0
+    ///     \left(\begin{matrix}
+    ///         1 & 0 & 0 & 0 \\\\
+    ///         0 & 1 & 0 & 0 \\\\
+    ///         0 & 0 & 0 & -i \\\\
+    ///         0 & 0 & i & 0
+    ///      \end{matrix}\right)
     /// \end{align},
     /// $$
     /// where rows and columns are organized as in the quantum concepts guide.
@@ -208,10 +210,12 @@ namespace Microsoft.Quantum.Canon {
     /// This operation can be simulated by the unitary matrix
     /// $$
     /// \begin{align}
-    ///     1 & 0 & 0 & 0 \\\\
-    ///     0 & 1 & 0 & 0 \\\\
-    ///     0 & 0 & 1 & 0 \\\\
-    ///     0 & 0 & 0 & -1
+    ///     \left(\begin{matrix}
+    ///         1 & 0 & 0 & 0 \\\\
+    ///         0 & 1 & 0 & 0 \\\\
+    ///         0 & 0 & 1 & 0 \\\\
+    ///         0 & 0 & 0 & -1
+    ///     \end{matrix}\right)
     /// \end{align},
     /// $$
     /// where rows and columns are organized as in the quantum concepts guide.
@@ -432,8 +436,17 @@ namespace Microsoft.Quantum.Canon {
     }
 
     /// # Summary
-    /// Applies a unitary operation on the target,
-    /// controlled on a state specified by a given bit mask.
+    /// Applies `oracle` on `target` when `controlRegister`
+    /// is in the state specified by `bits`.
+    ///
+    /// # Description
+    /// Applies a unitary operation `oracle` on the `target`, controlled
+    /// on a state specified by a given bit mask `bits`.
+    /// The bit at `bits[i]` corresponds to qubit at `controlRegister[i]`.
+    /// The pattern given by `bits` may be shorter than `controlRegister`,
+    /// in which case additional control qubits are ignored (that is, neither
+    /// controlled on |0⟩ nor |1⟩).
+    /// If `bits` is longer than `controlRegister`, an error is raised.
     ///
     /// # Input
     /// ## bits
@@ -445,14 +458,16 @@ namespace Microsoft.Quantum.Canon {
     /// ## controlRegister
     /// A quantum register that controls application of `oracle`.
     ///
-    /// # Remarks
-    /// The pattern given by `bits` may be shorter than `controlRegister`,
-    /// in which case additional control qubits are ignored (that is, neither
-    /// controlled on $\ket{0}$ nor $\ket{1}$).
-    /// If `bits` is longer than `controlRegister`, an error is raised.
-    ///
-    /// For example, `bits = [0,1,0,0,1]` means that `oracle` is applied if and only if `controlRegister`
-    /// is in the state $\ket{0}\ket{1}\ket{0}\ket{0}\ket{1}$.
+    /// # Example
+    /// ```qsharp
+    /// // When bits = [1,0,0] oracle is applied if and only if controlRegister
+    /// // is in the state |100⟩.
+    /// use t = Qubit();
+    /// use c = Qubit[3];
+    /// X(c[0]);
+    /// ApplyControlledOnBitString([true, false, false], X, c, t);
+    /// Message($"{M(t)}"); // Prints `One` since oracle `X` was applied.
+    /// ```
     operation ApplyControlledOnBitString<'T>(
         bits : Bool[],
         oracle : ('T => Unit is Adj + Ctl),
diff --git a/library/std/convert.qs b/library/std/convert.qs
index a2b3659b..db5c8627 100644
--- a/library/std/convert.qs
+++ b/library/std/convert.qs
@@ -6,13 +6,27 @@ namespace Microsoft.Quantum.Convert {
     open Microsoft.Quantum.Math;
 
     /// # Summary
-    /// Converts a given integer to an equivalent double-precision floating-point number.
+    /// Converts a given integer `number` to an equivalent
+    /// double-precision floating-point number.
+    ///
+    /// # Description
+    /// Converts a given integer to a double-precision floating point number.
+    /// Please note that the double-precision representation may have fewer
+    /// bits allocated to represent [significant digits](https://en.wikipedia.org/wiki/Significand)
+    /// so the conversion may be approximate for large numbers. For example,
+    /// the current simulator converts 4,611,686,018,427,387,919 = 2^64+15
+    /// to 4,611,686,018,427,387,904.0 = 2^64.
+    ///
+    /// # Example
+    /// ```qsharp
+    /// Message($"{IntAsDouble(1)}"); // Prints 1.0 rather than 1
+    /// ```
     function IntAsDouble(number : Int) : Double {
         body intrinsic;
     }
 
     /// # Summary
-    /// Converts a given integer to an equivalent big integer.
+    /// Converts a given integer `number` to an equivalent big integer.
     function IntAsBigInt(number : Int) : BigInt {
         body intrinsic;
     }
@@ -48,7 +62,8 @@ namespace Microsoft.Quantum.Convert {
     }
 
     /// # Summary
-    /// Produces a non-negative integer from a string of bits in little endian format.
+    /// Produces a non-negative integer from a string of bits in little-endian format.
+    /// `bits[0]` represents the least significant bit.
     ///
     /// # Input
     /// ## bits
diff --git a/library/std/core.qs b/library/std/core.qs
index 8442c912..93c477db 100644
--- a/library/std/core.qs
+++ b/library/std/core.qs
@@ -19,11 +19,15 @@ namespace Microsoft.Quantum.Core {
     ///
     /// Note that the defined start value of a range is the same as the first element of the sequence,
     /// unless the range specifies an empty sequence (for example, 2 .. 1).
+    ///
+    /// # Example
+    /// ```qsharp
+    /// Message($"{ RangeStart(7..-1..3) }"); // Prints 7
+    /// ```
     function RangeStart(r : Range) : Int {
         r::Start
     }
 
-
     /// # Summary
     /// Returns the defined end value of the given range,
     /// which is not necessarily the last element in the sequence.
diff --git a/library/std/logical.qs b/library/std/logical.qs
index 5008a37a..8ff39f93 100644
--- a/library/std/logical.qs
+++ b/library/std/logical.qs
@@ -4,7 +4,8 @@
 namespace Microsoft.Quantum.Logical {
 
     /// # Summary
-    /// Returns the boolean exclusive disjunction (XOR) of two input boolean values.
+    /// Returns the boolean exclusive disjunction (eXclusive OR, XOR)
+    /// of two input boolean values.
     ///
     /// # Input
     /// ## first
@@ -16,9 +17,11 @@ namespace Microsoft.Quantum.Logical {
     /// # Output
     /// A `Bool` which is `true` if and only if exactly one of `first` and `second` is `true`.
     ///
+    /// # Remarks
+    /// In Q#, `Xor(a, b)` is equivalent to `a != b`.
+    ///
     /// # Example
     /// ```qsharp
-    ///
     /// let result = Xor(true, false);
     /// // result is true
     /// ```
diff --git a/library/std/math.qs b/library/std/math.qs
index fd593701..0dc774d4 100644
--- a/library/std/math.qs
+++ b/library/std/math.qs
@@ -10,11 +10,16 @@ namespace Microsoft.Quantum.Math {
     //
 
     /// # Summary
-    /// Represents the ratio of the circumference of a circle to its diameter.
+    /// Returns a double-precision approximation of the
+    /// matematical constant 𝝅 ≈ 3.14159265358979323846
     ///
-    /// # Output
-    /// A double-precision approximation of the circumference of a circle
-    /// to its diameter, π ≈ 3.14159265358979323846.
+    /// # Remarks
+    /// Mathematical constant 𝝅 represents the ratio of the circumference
+    /// of a circle to its diameter. It is useful in many applications
+    /// such as rotations and complex arithmetic.
+    ///
+    /// # References
+    /// [Wikipedia article - Pi](https://en.wikipedia.org/wiki/Pi)
     ///
     /// # See Also
     /// - Microsoft.Quantum.Math.E
@@ -23,11 +28,15 @@ namespace Microsoft.Quantum.Math {
     }
 
     /// # Summary
-    /// Returns the natural logarithmic base to double-precision.
+    /// Returns a double-precision approximation of the
+    /// mathematical constant 𝒆 ≈ 2.7182818284590452354
     ///
-    /// # Output
-    /// A double-precision approximation of the natural logarithmic base,
-    /// e ≈ 2.7182818284590452354.
+    /// # Remarks
+    /// Mathematical constant 𝒆 is the base of the natural logarithm
+    /// also known as the Euler's number
+    ///
+    /// # References
+    /// [Wikipedia article - e](https://en.wikipedia.org/wiki/E_(mathematical_constant))
     ///
     /// # See Also
     /// - Microsoft.Quantum.Math.PI
@@ -36,10 +45,14 @@ namespace Microsoft.Quantum.Math {
     }
 
     /// # Summary
-    /// Returns the natural logarithm of 2.
+    /// Returns a double-precision approximation of the constant
+    /// ㏑2 ≈ 0.6931471805599453
     ///
-    /// # Output
-    /// Returns a `Double` equal to 0.6931471805599453.
+    /// # Remarks
+    /// ㏑2 is the natural logarithm of 2, or the logarithm of 2 base 𝒆.
+    ///
+    /// # References
+    /// [Wikipedia article - Natural logarithm](https://en.wikipedia.org/wiki/Natural_logarithm)
     function LogOf2() : Double {
         0.6931471805599453
     }
@@ -383,7 +396,7 @@ namespace Microsoft.Quantum.Math {
 
     /// # Summary
     /// Returns the nearest integer to the specified number.
-    /// For example: Floor(3.7) = 4; Floor(-3.7) = -4
+    /// For example: Round(3.7) = 4; Round(-3.7) = -4
     function Round(value : Double) : Int {
         let (truncated, remainder, isPositive) = ExtendedTruncation(value);
         if AbsD(remainder) <= 1e-15 {
diff --git a/library/std/measurement.qs b/library/std/measurement.qs
index 70c7facc..8b9f6ec1 100644
--- a/library/std/measurement.qs
+++ b/library/std/measurement.qs
@@ -13,23 +13,31 @@ namespace Microsoft.Quantum.Measurement {
     /// # Description
     /// Measures a register of qubits in the `Z ⊗ Z ⊗ ••• ⊗ Z`
     /// basis, representing the parity of the entire register.
+    /// This operation does not reset the measured qubits to the |0⟩ state,
+    /// leaving them in the state that corresponds to the measurement result.
     ///
     /// # Input
     /// ## register
-    /// The register to be measured.
+    /// The register to be jointly measured.
     ///
     /// # Output
-    /// The result of measuring `Z ⊗ Z ⊗ ••• ⊗ Z`.
+    /// The result of measuring in the `Z ⊗ Z ⊗ ••• ⊗ Z` basis.
     ///
-    /// # Remarks
-    /// This operation does not reset the measured qubits to the |0⟩ state,
-    /// leaving them in the state that corresponds to the measurement result.
+    /// # See also
+    /// - Microsoft.Quantum.Measurement.MeasureEachZ
     operation MeasureAllZ(register : Qubit[]) : Result {
         Measure(Repeated(PauliZ, Length(register)), register)
     }
 
     /// # Summary
     /// Measures each qubit in a given array in the standard basis.
+    ///
+    /// # Description
+    /// Measures each qubit in a register in the `Z` basis
+    /// and retuns the result of each measurement.
+    /// This operation does not reset the measured qubits to the |0⟩ state,
+    /// leaving them in the state that corresponds to the measurement results.
+    ///
     /// # Input
     /// ## targets
     /// An array of qubits to be measured.
@@ -37,8 +45,17 @@ namespace Microsoft.Quantum.Measurement {
     /// An array of measurement results.
     ///
     /// # Remarks
-    /// This operation does not reset the measured qubits to the |0⟩ state,
-    /// leaving them in the state that corresponds to the measurement results.
+    /// Please note the following differences:
+    /// - Operation `MeasureEachZ` performs one measurement for each qubit and retuns
+    ///   an array of results. The operation does not reset the qubits.
+    /// - Operation `MResetEachZ` performs one measurement for each qubit and retuns
+    ///   an array of results. The operation resets all qubits to |0⟩ state.
+    /// - Operation `MeasureAllZ` performs a joint measurement on all qubits
+    ///   and returns one result. The operation does not reset the qubits.
+    ///
+    /// # See also
+    /// - Microsoft.Quantum.Measurement.MeasureAllZ
+    /// - Microsoft.Quantum.Measurement.MResetEachZ
     operation MeasureEachZ(register : Qubit[]) : Result[] {
         mutable results = [];
         for qubit in register {
@@ -50,11 +67,16 @@ namespace Microsoft.Quantum.Measurement {
     /// # Summary
     /// Measures each qubit in a given array in the Z basis
     /// and resets them to a fixed initial state.
+    ///
     /// # Input
     /// ## targets
     /// An array of qubits to be measured.
+    ///
     /// # Output
     /// An array of measurement results.
+    ///
+    /// # See also
+    /// - Microsoft.Quantum.Measurement.MeasureEachZ
     operation MResetEachZ(register : Qubit[]) : Result[] {
         mutable results = [];
         for qubit in register {
diff --git a/library/std/random.qs b/library/std/random.qs
index f28f9e13..c568ccbc 100644
--- a/library/std/random.qs
+++ b/library/std/random.qs
@@ -4,7 +4,8 @@
 namespace Microsoft.Quantum.Random {
 
     /// # Summary
-    /// Draws a random integer in a given inclusive range.
+    /// Draws a random integer from a uniform distribution
+    /// in a given inclusive range. Fails if `max < min`.
     ///
     /// # Input
     /// ## min
@@ -16,9 +17,6 @@ namespace Microsoft.Quantum.Random {
     /// An integer in the inclusive range from `min` to `max` with uniform
     /// probability.
     ///
-    /// # Remarks
-    /// Fails if `max < min`.
-    ///
     /// # Example
     /// The following Q# snippet randomly rolls a six-sided die:
     /// ```qsharp
@@ -30,7 +28,8 @@ namespace Microsoft.Quantum.Random {
     }
 
     /// # Summary
-    /// Draws a random real number in a given inclusive interval.
+    /// Draws a random real number from a uniform distribution
+    /// in a given inclusive interval. Fails if `max < min`.
     ///
     /// # Input
     /// ## min
@@ -42,9 +41,6 @@ namespace Microsoft.Quantum.Random {
     /// A random real number in the inclusive interval from `min` to `max` with
     /// uniform probability.
     ///
-    /// # Remarks
-    /// Fails if `max < min`.
-    ///
     /// # Example
     /// The following Q# snippet randomly draws an angle between 0 and 2π:
     /// ```qsharp
diff --git a/library/std/re.qs b/library/std/re.qs
index af29c0c0..433b5aea 100644
--- a/library/std/re.qs
+++ b/library/std/re.qs
@@ -127,7 +127,6 @@ namespace Microsoft.Quantum.ResourceEstimation {
     }
 
     /// # Summary
-    ///
     /// Instructs the resource estimator to assume that the resources from the
     /// call of this operation until a call to `EndRepeatEstimates` are
     /// accounted for `count` times, without the need to execute the code that many
@@ -150,7 +149,6 @@ namespace Microsoft.Quantum.ResourceEstimation {
     }
 
     /// # Summary
-    ///
     /// Companion operation to `BeginRepeatEstimates`.
     operation EndRepeatEstimates() : Unit {
         body ... {
@@ -164,7 +162,6 @@ namespace Microsoft.Quantum.ResourceEstimation {
     }
 
     /// # Summary
-    ///
     /// Instructs the resource estimator to assume that the resources from the
     /// call of this operation until a call to `Adjoint RepeatEstimates` are
     /// accounted for `count` times, without the need to execute the code that many
diff --git a/library/std/unstable_arithmetic.qs b/library/std/unstable_arithmetic.qs
index 48418013..4269e934 100644
--- a/library/std/unstable_arithmetic.qs
+++ b/library/std/unstable_arithmetic.qs
@@ -36,16 +36,14 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// where each |i⟩ is a basis state representing an integer i,
     /// reflects the state of the register about the basis state |j⟩
     /// for a given integer j: ∑ᵢ(-1)^(δᵢⱼ)(αᵢ|i⟩)
+    /// This operation is implemented in-place, without explicit allocation of
+    /// additional auxiliary qubits.
     ///
     /// # Input
     /// ## index
     /// The classical integer j indexing the basis state about which to reflect.
     /// ## reg
     /// Little-endian quantum register to reflect.
-    ///
-    /// # Remarks
-    /// This operation is implemented in-place, without explicit allocation of
-    /// additional auxiliary qubits.
     operation ReflectAboutInteger(index : Int, reg : Qubit[]) : Unit is Adj + Ctl {
         within {
             // Evaluation optimization for case index == 0
@@ -147,10 +145,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// qubits otherwise.
     ///
     /// # References
-    ///     - [arXiv:0910.2530](https://arxiv.org/abs/0910.2530)
-    ///       "Quantum Addition Circuits and Unbounded Fan-Out"
-    ///       by Yasuhiro Takahashi, Seiichiro Tani, Noboru Kunihiro
-    ///
+    /// - [arXiv:0910.2530](https://arxiv.org/abs/0910.2530)
+    ///   "Quantum Addition Circuits and Unbounded Fan-Out",
+    ///   Yasuhiro Takahashi, Seiichiro Tani, Noboru Kunihiro
     operation RippleCarryTTKIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl {
         let xsLen = Length(xs);
         let ysLen = Length(ys);
@@ -198,8 +195,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// This operation uses the ripple-carry algorithm.
     ///
     /// # Reference
-    ///     - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf)
-    ///       "Halving the cost of quantum addition" by Craig Gidney.
+    /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf)
+    ///   "Halving the cost of quantum addition", Craig Gidney.
     operation RippleCarryCGIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl {
         let xsLen = Length(xs);
         let ysLen = Length(ys);
@@ -255,8 +252,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// NOTE: `zs[Length(xs)]` can be used as carry-out, if `zs` is longer than `xs`.
     ///
     /// # Reference
-    ///     - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf)
-    ///       "Halving the cost of quantum addition" by Craig Gidney.
+    /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf)
+    ///   "Halving the cost of quantum addition", Craig Gidney.
     operation RippleCarryCGAddLE(xs : Qubit[], ys : Qubit[], zs : Qubit[]) : Unit is Adj {
         let xsLen = Length(xs);
         let zsLen = Length(zs);
@@ -289,9 +286,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// This operation uses the carry-lookahead algorithm.
     ///
     /// # Reference
-    ///     - [arXiv:quant-ph/0406142](https://arxiv.org/abs/quant-ph/0406142)
-    ///      "A logarithmic-depth quantum carry-lookahead adder" by
-    ///      Thomas G. Draper, Samuel A. Kutin, Eric M. Rains, Krysta M. Svore
+    /// - [arXiv:quant-ph/0406142](https://arxiv.org/abs/quant-ph/0406142)
+    ///   "A logarithmic-depth quantum carry-lookahead adder",
+    ///   Thomas G. Draper, Samuel A. Kutin, Eric M. Rains, Krysta M. Svore
     operation LookAheadDKRSAddLE(xs : Qubit[], ys : Qubit[], zs : Qubit[]) : Unit is Adj {
         let xsLen = Length(xs);
         let zsLen = Length(zs);
@@ -338,8 +335,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// This operation uses Quantum Fourier Transform.
     ///
     /// # Reference
-    ///     - [arXiv:quant-ph/0008033](https://arxiv.org/abs/quant-ph/0008033)
-    ///      "Addition on a Quantum Computer" by Thomas G. Draper
+    /// - [arXiv:quant-ph/0008033](https://arxiv.org/abs/quant-ph/0008033)
+    ///   "Addition on a Quantum Computer", Thomas G. Draper
     operation FourierTDIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl {
         within {
             ApplyQFT(ys);
@@ -430,9 +427,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic {
     /// the adders is controlled,
     ///
     /// # Reference
-    ///     - [arXiv:2012.01624](https://arxiv.org/abs/2012.01624)
-    ///       "Quantum block lookahead adders and the wait for magic states"
-    ///       by Craig Gidney.
+    /// - [arXiv:2012.01624](https://arxiv.org/abs/2012.01624)
+    ///   "Quantum block lookahead adders and the wait for magic states",
+    ///   Craig Gidney.
     operation IncByLEUsingAddLE(
         forwardAdder : (Qubit[], Qubit[], Qubit[]) => Unit is Adj,
         backwardAdder : (Qubit[], Qubit[], Qubit[]) => Unit is Adj,
diff --git a/library/std/unstable_state_preparation.qs b/library/std/unstable_state_preparation.qs
index cefe72bd..57376878 100644
--- a/library/std/unstable_state_preparation.qs
+++ b/library/std/unstable_state_preparation.qs
@@ -46,9 +46,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation {
     /// ```
     ///
     /// # References
-    /// - Synthesis of Quantum Logic Circuits
+    /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176)
+    ///   "Synthesis of Quantum Logic Circuits",
     ///   Vivek V. Shende, Stephen S. Bullock, Igor L. Markov
-    ///   https://arxiv.org/abs/quant-ph/0406176
     ///
     /// # See Also
     /// - Microsoft.Quantum.Unstable.StatePreparation.ApproximatelyPreparePureStateCP
@@ -101,9 +101,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation {
     /// specified.
     ///
     /// # References
-    /// - Synthesis of Quantum Logic Circuits
+    /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176)
+    ///   "Synthesis of Quantum Logic Circuits",
     ///   Vivek V. Shende, Stephen S. Bullock, Igor L. Markov
-    ///   https://arxiv.org/abs/quant-ph/0406176
     operation ApproximatelyPreparePureStateCP(
         tolerance : Double,
         coefficients : ComplexPolar[],
@@ -314,9 +314,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation {
     /// fewer than $2^n$ are specified.
     ///
     /// # References
-    /// - Synthesis of Quantum Logic Circuits
+    /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176)
+    ///   "Synthesis of Quantum Logic Circuits",
     ///   Vivek V. Shende, Stephen S. Bullock, Igor L. Markov
-    ///   https://arxiv.org/abs/quant-ph/0406176
     internal operation ApproximatelyMultiplexZ(
         tolerance : Double,
         coefficients : Double[],
@@ -385,4 +385,3 @@ namespace Microsoft.Quantum.Unstable.StatePreparation {
     }
 
 }
-
diff --git a/library/std/unstable_table_lookup.qs b/library/std/unstable_table_lookup.qs
index 7c3b9dd9..b748aa07 100644
--- a/library/std/unstable_table_lookup.qs
+++ b/library/std/unstable_table_lookup.qs
@@ -36,13 +36,13 @@ namespace Microsoft.Quantum.Unstable.TableLookup {
     /// is not optimized using this technique.
     ///
     /// # References
-    /// [1] [arXiv:1805.03662](https://arxiv.org/abs/1805.03662)
-    ///     "Encoding Electronic Spectra in Quantum Circuits with Linear T
-    ///      Complexity"
-    /// [2] [arXiv:1905.07682](https://arxiv.org/abs/1905.07682)
-    ///     "Windowed arithmetic"
-    /// [3] [arXiv:2211.01133](https://arxiv.org/abs/2211.01133)
-    ///     "Space-time optimized table lookup"
+    /// 1. [arXiv:1805.03662](https://arxiv.org/abs/1805.03662)
+    ///    "Encoding Electronic Spectra in Quantum Circuits with Linear T
+    ///    Complexity"
+    /// 2. [arXiv:1905.07682](https://arxiv.org/abs/1905.07682)
+    ///    "Windowed arithmetic"
+    /// 3. [arXiv:2211.01133](https://arxiv.org/abs/2211.01133)
+    ///    "Space-time optimized table lookup"
     @Config(Adaptive)
     operation Select(
         data : Bool[][],
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 75d148bb..f0218288 100644
--- a/pip/qsharp/_qsharp.py
+++ b/pip/qsharp/_qsharp.py
@@ -10,7 +10,7 @@ from ._native import (
     Circuit,
 )
 from warnings import warn
-from typing import Any, Callable, Dict, Optional, TypedDict, Union, List
+from typing import Any, Callable, Dict, Optional, Tuple, TypedDict, Union, List
 from .estimator._estimator import EstimatorResult, EstimatorParams
 import json
 
@@ -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:
@@ -349,6 +343,39 @@ class StateDump:
     def _repr_html_(self) -> str:
         return self.__data._repr_html_()
 
+    def check_eq(
+        self, state: Union[Dict[int, complex], List[complex]], tolerance: float = 1e-10
+    ) -> bool:
+        """
+        Checks if the state dump is equal to the given state. This is not mathematical equality,
+        as the check ignores global phase.
+
+        :param state: The state to check against, provided either as a dictionary of state indices to complex amplitudes,
+            or as a list of real amplitudes.
+        :param tolerance: The tolerance for the check. Defaults to 1e-10.
+        """
+        phase = None
+        # Convert a dense list of real amplitudes to a dictionary of state indices to complex amplitudes
+        if isinstance(state, list):
+            state = {i: state[i] for i in range(len(state))}
+        # Filter out zero states from the state dump and the given state based on tolerance
+        state = {k: v for k, v in state.items() if abs(v) > tolerance}
+        inner_state = {k: v for k, v in self.__inner.items() if abs(v) > tolerance}
+        if len(state) != len(inner_state):
+            return False
+        for key in state:
+            if key not in inner_state:
+                return False
+            if phase is None:
+                # Calculate the phase based on the first state pair encountered.
+                # Every pair of states after this must have the same phase for the states to be equivalent.
+                phase = inner_state[key] / state[key]
+            elif abs(phase - inner_state[key] / state[key]) > tolerance:
+                # This pair of states does not have the same phase,
+                # within tolerance, so the equivalence check fails.
+                return False
+        return True
+
 
 def dump_machine() -> StateDump:
     """
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..6a413357 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,12 @@ import {
   monacoPositionToLsPosition,
 } from "./utils.js";
 
-export type ActiveTab = "results-tab" | "hir-tab" | "ast-tab" | "logs-tab";
+export type ActiveTab =
+  | "results-tab"
+  | "hir-tab"
+  | "ast-tab"
+  | "qir-tab"
+  | "logs-tab";
 
 const basePath = (window as any).qscBasePath || "";
 const monacoPath = basePath + "libs/monaco/vs";
@@ -70,6 +75,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 +98,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 +159,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 +167,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 +180,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 +190,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..81fdcae8 100644
--- a/playground/src/tabs.tsx
+++ b/playground/src/tabs.tsx
@@ -9,6 +9,7 @@ const tabArray: Array<[ActiveTab, string]> = [
   ["results-tab", "RESULTS"],
   ["hir-tab", "HIR"],
   ["ast-tab", "AST"],
+  ["qir-tab", "QIR"],
   ["logs-tab", "LOGS"],
 ];
 
@@ -28,6 +29,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 +44,7 @@ export function OutputTabs(props: {
   kataMode?: boolean;
   ast: string;
   hir: string;
+  qir: string;
   activeTab: ActiveTab;
   setActiveTab: (tab: ActiveTab) => void;
 }) {
@@ -55,6 +65,7 @@ export function OutputTabs(props: {
       <ResultsTab {...props} />
       <HirTab {...props} />
       <AstTab {...props} />
+      <QirTab {...props} />
     </div>
   );
 }
diff --git a/version.py b/version.py
index c545a5a1..93b1ba16 100755
--- a/version.py
+++ b/version.py
@@ -7,7 +7,7 @@ import os
 from datetime import datetime, timezone
 
 # To be updated every time we start a new major.minor version.
-major_minor = "1.3"
+major_minor = "1.4"
 
 # Default to 'dev' builds
 BUILD_TYPE = os.environ.get("BUILD_TYPE") or "dev"
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 aca47931..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};
 
@@ -39,10 +39,20 @@ mod given_interpreter {
         (interpreter.eval_entry(&mut receiver), receiver.dump())
     }
 
+    fn fragment(
+        interpreter: &mut Interpreter,
+        fragments: &str,
+        package: crate::ast::Package,
+    ) -> (Result<Value, Vec<crate::interpret::Error>>, String) {
+        let mut cursor = Cursor::new(Vec::<u8>::new());
+        let mut receiver = CursorReceiver::new(&mut cursor);
+        let result = interpreter.eval_ast_fragments(&mut receiver, fragments, package);
+        (result, receiver.dump())
+    }
+
     mod without_sources {
         use expect_test::expect;
         use indoc::indoc;
-        use qsc_frontend::compile::TargetCapabilityFlags;
 
         use super::*;
 
@@ -716,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");
@@ -767,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);
         }
@@ -783,7 +802,7 @@ mod given_interpreter {
                 true,
                 SourceMap::default(),
                 PackageType::Lib,
-                TargetCapabilityFlags::Adaptive,
+                TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::QubitReset,
                 LanguageFeatures::default(),
             )
             .expect("interpreter should be created");
@@ -837,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);
         }
@@ -1424,14 +1450,17 @@ mod given_interpreter {
 
     #[cfg(test)]
     mod with_sources {
-        use std::sync::Arc;
+        use std::{sync::Arc, vec};
 
         use super::*;
         use crate::interpret::Debugger;
         use crate::line_column::Encoding;
         use expect_test::expect;
         use indoc::indoc;
-        use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags};
+
+        use qsc_ast::ast::{Expr, ExprKind, NodeId, Package, Path, Stmt, StmtKind, TopLevelNode};
+        use qsc_data_structures::span::Span;
+        use qsc_frontend::compile::SourceMap;
         use qsc_passes::PackageType;
 
         #[test]
@@ -1617,5 +1646,177 @@ mod given_interpreter {
                 "#]],
             );
         }
+
+        #[test]
+        fn interpreter_can_be_created_from_ast() {
+            let sources = SourceMap::new(
+                [(
+                    "test".into(),
+                    "namespace A {
+                        operation B(): Result {
+                            use qs = Qubit[2];
+                            X(qs[0]);
+                            CNOT(qs[0], qs[1]);
+                            let res = Measure([PauliZ, PauliZ], qs[...1]);
+                            ResetAll(qs);
+                            res
+                        }
+                    }
+                    "
+                    .into(),
+                )],
+                Some("A.B()".into()),
+            );
+
+            let (package_type, capabilities, language_features) = (
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+            );
+
+            let mut store = crate::PackageStore::new(crate::compile::core());
+            let dependencies = vec![store.insert(crate::compile::std(&store, capabilities))];
+
+            let (unit, errors) = crate::compile::compile(
+                &store,
+                &dependencies,
+                sources,
+                package_type,
+                capabilities,
+                language_features,
+            );
+            for e in &errors {
+                eprintln!("{e:?}");
+            }
+            assert!(errors.is_empty(), "compilation failed: {}", errors[0]);
+            let package_id = store.insert(unit);
+
+            let mut interpreter =
+                Interpreter::from(store, package_id, capabilities, language_features)
+                    .expect("interpreter should be created");
+            let (result, output) = entry(&mut interpreter);
+            is_only_value(
+                &result,
+                &output,
+                &Value::Result(qsc_eval::val::Result::Val(false)),
+            );
+        }
+
+        #[test]
+        fn ast_fragments_can_be_evaluated() {
+            let sources = SourceMap::new(
+                [(
+                    "test".into(),
+                    "namespace A {
+                        operation B(): Result {
+                            use qs = Qubit[2];
+                            X(qs[0]);
+                            CNOT(qs[0], qs[1]);
+                            let res = Measure([PauliZ, PauliZ], qs[...1]);
+                            ResetAll(qs);
+                            res
+                        }
+                    }
+                    "
+                    .into(),
+                )],
+                None,
+            );
+
+            let mut interpreter = Interpreter::new(
+                true,
+                sources,
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+            )
+            .expect("interpreter should be created");
+            let package = get_package_for_call("A", "B");
+            let (result, output) = fragment(&mut interpreter, "A.B()", package);
+            is_only_value(
+                &result,
+                &output,
+                &Value::Result(qsc_eval::val::Result::Val(false)),
+            );
+        }
+
+        #[test]
+        fn ast_fragments_evaluation_returns_runtime_errors() {
+            let sources = SourceMap::new(
+                [(
+                    "test".into(),
+                    "namespace A {
+                        operation B(): Int {
+                            42 / 0
+                        }
+                    }
+                    "
+                    .into(),
+                )],
+                None,
+            );
+
+            let mut interpreter = Interpreter::new(
+                true,
+                sources,
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+            )
+            .expect("interpreter should be created");
+            let package = get_package_for_call("A", "B");
+            let (result, output) = fragment(&mut interpreter, "A.B()", package);
+            is_only_error(
+                &result,
+                &output,
+                &expect![[r#"
+                    runtime error: division by zero
+                      cannot divide by zero [test] [0]
+                "#]],
+            );
+        }
+
+        fn get_package_for_call(ns: &str, name: &str) -> crate::ast::Package {
+            let args = Expr {
+                id: NodeId::default(),
+                span: Span::default(),
+                kind: Box::new(ExprKind::Tuple(Box::new([]))),
+            };
+            let path = Path {
+                id: NodeId::default(),
+                span: Span::default(),
+                namespace: Some(Box::new(qsc_ast::ast::Ident {
+                    id: NodeId::default(),
+                    span: Span::default(),
+                    name: ns.into(),
+                })),
+                name: Box::new(qsc_ast::ast::Ident {
+                    id: NodeId::default(),
+                    span: Span::default(),
+                    name: name.into(),
+                }),
+            };
+            let path_expr = Expr {
+                id: NodeId::default(),
+                span: Span::default(),
+                kind: Box::new(ExprKind::Path(Box::new(path))),
+            };
+            let expr = Expr {
+                id: NodeId::default(),
+                span: Span::default(),
+                kind: Box::new(ExprKind::Call(Box::new(path_expr), Box::new(args))),
+            };
+            let stmt = Stmt {
+                id: NodeId::default(),
+                span: Span::default(),
+                kind: Box::new(StmtKind::Expr(Box::new(expr))),
+            };
+            let top_level = TopLevelNode::Stmt(Box::new(stmt));
+            Package {
+                id: NodeId::default(),
+                nodes: vec![top_level].into_boxed_slice(),
+                entry: None,
+            }
+        }
     }
 }
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_doc_gen/src/generate_docs/tests.rs b/compiler/qsc_doc_gen/src/generate_docs/tests.rs
index 9308e0a6..b24f920e 100644
--- a/compiler/qsc_doc_gen/src/generate_docs/tests.rs
+++ b/compiler/qsc_doc_gen/src/generate_docs/tests.rs
@@ -24,7 +24,7 @@ fn docs_generation() {
         qsharp.kind: function
         qsharp.namespace: Microsoft.Quantum.Core
         qsharp.name: Length
-        qsharp.summary: "Returns the number of elements in an array."
+        qsharp.summary: "Returns the number of elements in the input array `a`."
         ---
 
         # Length function
@@ -36,14 +36,19 @@ fn docs_generation() {
         ```
 
         ## Summary
-        Returns the number of elements in an array.
+        Returns the number of elements in the input array `a`.
 
         ## Input
         ### a
         Input array.
 
         ## Output
-        The total count of elements in an array.
+        The total number of elements in the input array `a`.
+
+        ## Example
+        ```qsharp
+        Message($"{ Length([0, 0, 0]) }"); // Prints 3
+        ```
     "#]]
     .assert_eq(full_contents.as_str());
 }
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/tests.rs b/compiler/qsc_eval/src/tests.rs
index 479ccbdd..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};
 
@@ -439,16 +439,16 @@ fn block_qubit_use_array_invalid_count_expr() {
                             0,
                         ),
                         span: Span {
-                            lo: 1566,
-                            hi: 1623,
+                            lo: 2034,
+                            hi: 2091,
                         },
                     },
                 ),
                 [
                     Frame {
                         span: Span {
-                            lo: 1566,
-                            hi: 1623,
+                            lo: 2034,
+                            hi: 2091,
                         },
                         id: StoreItemId {
                             package: PackageId(
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:
diff --git a/pip/tests/test_qsharp.py b/pip/tests/test_qsharp.py
index 4cd2316d..719ac290 100644
--- a/pip/tests/test_qsharp.py
+++ b/pip/tests/test_qsharp.py
@@ -98,6 +98,34 @@ def test_dump_machine() -> None:
     # Check that the state dump correctly supports iteration and membership checks
     for idx in state_dump:
         assert idx in state_dump
+    # Check that the state dump is correct and equivalence check ignores global phase, allowing passing
+    # in of different, potentially unnormalized states. The state should be
+    # |01⟩: 0.7071+0.0000𝑖, |11⟩: −0.7071+0.0000𝑖
+    assert state_dump.check_eq({1: complex(0.7071, 0.0), 3: complex(-0.7071, 0.0)})
+    assert state_dump.check_eq({1: complex(0.0, 0.7071), 3: complex(0.0, -0.7071)})
+    assert state_dump.check_eq({1: complex(0.5, 0.0), 3: complex(-0.5, 0.0)})
+    assert state_dump.check_eq(
+        {1: complex(0.7071, 0.0), 3: complex(-0.7071, 0.0), 0: complex(0.0, 0.0)}
+    )
+    assert state_dump.check_eq([0.0, 0.5, 0.0, -0.5])
+    assert state_dump.check_eq([0.0, 0.5001, 0.00001, -0.5], tolerance=1e-3)
+    assert state_dump.check_eq(
+        [complex(0.0, 0.0), complex(0.0, -0.5), complex(0.0, 0.0), complex(0.0, 0.5)]
+    )
+    assert not state_dump.check_eq({1: complex(0.7071, 0.0), 3: complex(0.7071, 0.0)})
+    assert not state_dump.check_eq({1: complex(0.5, 0.0), 3: complex(0.0, 0.5)})
+    assert not state_dump.check_eq({2: complex(0.5, 0.0), 3: complex(-0.5, 0.0)})
+    assert not state_dump.check_eq([0.0, 0.5001, 0.0, -0.5], tolerance=1e-6)
+    # Reset the qubits and apply a small rotation to q1, to confirm that tolerance applies to the dump
+    # itself and not just the state.
+    qsharp.eval("ResetAll([q1, q2]);")
+    qsharp.eval("Ry(0.0001, q1);")
+    state_dump = qsharp.dump_machine()
+    assert state_dump.qubit_count == 2
+    assert len(state_dump) == 2
+    assert not state_dump.check_eq([1.0])
+    assert state_dump.check_eq([0.99999999875, 0.0, 4.999999997916667e-05])
+    assert state_dump.check_eq([1.0], tolerance=1e-4)
 
 
 def test_dump_operation() -> None: