Repository: microsoft/qsharp
Pull request #1514 (2024-05-13 19:52:10)

Fix RIR transform bugs
This fixes multiple bugs in RIR transformations:

 - Updates variable remapping logic during SSA pass to check whether the proposed replacement itself has a pending remap, fixing scenario where mutable variables are mapped to other mutable variables.
 - Includes variables created by `Store` instructions in the count for identifying highest variable id to use for inserted phi-nodes, fixing issues where a phi variable may overlap with an existing remapped mutable variable.
 - Avoids wrapping subtraction in qubit remapping mass when there are no qubits in the program.
 - Fixes type check pass to validate that comparison instruction produces a Boolean value rather than the same value as arguments.

This change adds some new test cases and expands test coverage significatly by running all programs produced in partial eval tests through RIR transforms in the test utility.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e1b78089..1e192269 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1195,6 +1195,7 @@ dependencies = [
  "bitflags 2.4.2",
  "expect-test",
  "indenter",
+ "miette",
  "qsc",
  "qsc_data_structures",
  "qsc_fir",
@@ -1202,6 +1203,7 @@ dependencies = [
  "qsc_lowerer",
  "qsc_passes",
  "rustc-hash",
+ "thiserror",
 ]
 
 [[package]]
diff --git a/cgmanifest.json b/cgmanifest.json
new file mode 100644
index 00000000..b5503b6b
--- /dev/null
+++ b/cgmanifest.json
@@ -0,0 +1,17 @@
+{
+  "$schema": "https://json.schemastore.org/component-detection-manifest.json",
+  "version": 1,
+  "registrations": [
+    {
+      "component": {
+        "type": "git",
+        "git": {
+          "repositoryUrl": "https://github.com/microsoft/mimalloc",
+          "commitHash": "43ce4bd7fd34bcc730c1c7471c99995597415488",
+          "tag": "v2.1.2"
+        }
+      },
+      "developmentDependency": false
+    }
+  ]
+}
diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs
index 311c2c70..48a22ef3 100644
--- a/compiler/qsc_doc_gen/src/generate_docs.rs
+++ b/compiler/qsc_doc_gen/src/generate_docs.rs
@@ -7,8 +7,9 @@ mod tests;
 use crate::display::{increase_header_level, parse_doc_for_summary};
 use crate::display::{CodeDisplay, Lookup};
 use qsc_ast::ast;
+use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_data_structures::target::TargetCapabilityFlags;
-use qsc_frontend::compile::{self, PackageStore};
+use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
 use qsc_frontend::resolve;
 use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Visibility};
 use qsc_hir::{hir, ty};
@@ -27,10 +28,34 @@ struct Compilation {
 }
 
 impl Compilation {
-    /// Creates a new `Compilation` by compiling sources.
-    pub(crate) fn new() -> Self {
+    /// Creates a new `Compilation` by compiling standard library
+    /// and additional sources.
+    pub(crate) fn new(
+        additional_sources: Option<SourceMap>,
+        capabilities: Option<TargetCapabilityFlags>,
+        language_features: Option<LanguageFeatures>,
+    ) -> Self {
         let mut package_store = PackageStore::new(compile::core());
-        package_store.insert(compile::std(&package_store, TargetCapabilityFlags::all()));
+        let actual_capabilities = capabilities.unwrap_or_default();
+        let std_unit = compile::std(&package_store, actual_capabilities);
+        let std_package_id = package_store.insert(std_unit);
+
+        if let Some(sources) = additional_sources {
+            let actual_language_features = language_features.unwrap_or_default();
+
+            let unit = compile(
+                &package_store,
+                &[std_package_id],
+                sources,
+                actual_capabilities,
+                actual_language_features,
+            );
+            // We ignore errors here (unit.errors vector) and use whatever
+            // documentation we can produce. In future we may consider
+            // displaying the fact of error presence on documentation page.
+
+            package_store.insert(unit);
+        }
 
         Self { package_store }
     }
@@ -103,9 +128,15 @@ impl Lookup for Compilation {
     }
 }
 
+/// Generates and returns documentation files for the standard library
+/// and additional sources (if specified.)
 #[must_use]
-pub fn generate_docs() -> Files {
-    let compilation = Compilation::new();
+pub fn generate_docs(
+    additional_sources: Option<SourceMap>,
+    capabilities: Option<TargetCapabilityFlags>,
+    language_features: Option<LanguageFeatures>,
+) -> Files {
+    let compilation = Compilation::new(additional_sources, capabilities, language_features);
     let mut files: Files = vec![];
 
     let display = &CodeDisplay {
diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs
index 3abf992a..b9e39cf2 100644
--- a/compiler/qsc_partial_eval/src/lib.rs
+++ b/compiler/qsc_partial_eval/src/lib.rs
@@ -8,6 +8,7 @@
 mod evaluation_context;
 mod management;
 
+use core::panic;
 use evaluation_context::{
     Arg, BlockNode, BranchControlFlow, EvalControlFlow, EvaluationContext, MutableKind, MutableVar,
     Scope,
@@ -32,7 +33,12 @@ use qsc_fir::{
     },
     ty::{Prim, Ty},
 };
-use qsc_rca::{ComputeKind, ComputePropertiesLookup, PackageStoreComputeProperties};
+use qsc_rca::errors::{generate_errors_from_runtime_features, get_missing_runtime_features};
+use qsc_rca::{
+    errors::Error as CapabilityError, ComputeKind, ComputePropertiesLookup,
+    PackageStoreComputeProperties,
+};
+use qsc_rca::{ItemComputeProperties, QuantumProperties, RuntimeFeatureFlags};
 use qsc_rir::{
     builder,
     rir::{
@@ -59,6 +65,17 @@ pub fn partially_evaluate(
 /// A partial evaluation error.
 #[derive(Clone, Debug, Diagnostic, Error)]
 pub enum Error {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    CapabilityError(CapabilityError),
+
+    #[error("use of unanalyzed dynamic value")]
+    #[diagnostic(code("Qsc.PartialEval.UnexpectedDynamicValue"))]
+    #[diagnostic(help(
+        "analysis is limited for callables that cannot be uniquely resolved at compile time, try invoking the desired callable directly"
+    ))]
+    UnexpectedDynamicValue(#[label] Span),
+
     #[error("partial evaluation failed with error {0}")]
     #[diagnostic(code("Qsc.PartialEval.EvaluationFailed"))]
     EvaluationFailed(String, #[label] Span),
@@ -678,7 +695,7 @@ impl<'a> PartialEvaluator<'a> {
             }
             ExprKind::Block(block_id) => self.try_eval_block(*block_id),
             ExprKind::Call(callee_expr_id, args_expr_id) => {
-                self.eval_expr_call(*callee_expr_id, *args_expr_id)
+                self.eval_expr_call(expr_id, *callee_expr_id, *args_expr_id)
             }
             ExprKind::Closure(args, callable) => {
                 let closure = resolve_closure(
@@ -900,27 +917,12 @@ impl<'a> PartialEvaluator<'a> {
 
     fn eval_expr_call(
         &mut self,
+        call_expr_id: ExprId,
         callee_expr_id: ExprId,
         args_expr_id: ExprId,
     ) -> Result<EvalControlFlow, Error> {
-        // Visit the both the callee and arguments expressions to get their values.
-        let callee_control_flow = self.try_eval_expr(callee_expr_id)?;
-        if callee_control_flow.is_return() {
-            let callee_expr = self.get_expr(callee_expr_id);
-            return Err(Error::Unexpected(
-                "embedded return in callee".to_string(),
-                callee_expr.span,
-            ));
-        }
-
-        let args_control_flow = self.try_eval_expr(args_expr_id)?;
-        if args_control_flow.is_return() {
-            let args_expr = self.get_expr(args_expr_id);
-            return Err(Error::Unexpected(
-                "embedded return in call arguments".to_string(),
-                args_expr.span,
-            ));
-        }
+        let (callee_control_flow, args_control_flow) =
+            self.try_eval_callee_and_args(callee_expr_id, args_expr_id)?;
 
         // Get the callable.
         let (store_item_id, functor_app, fixed_args) = match callee_control_flow.into_value() {
@@ -937,30 +939,132 @@ impl<'a> PartialEvaluator<'a> {
             panic!("global is not a callable");
         };
 
+        // Set up the scope for the call, which allows additional error checking if the callable was
+        // previously unresolved.
+        let spec_decl = if let CallableImpl::Spec(spec_impl) = &callable_decl.implementation {
+            Some(get_spec_decl(spec_impl, functor_app))
+        } else {
+            None
+        };
+
+        let args_value = args_control_flow.into_value();
+        let ctls = if let Some(Some(ctls_pat_id)) = spec_decl.map(|spec_decl| spec_decl.input) {
+            assert!(
+                functor_app.controlled > 0,
+                "control qubits count was expected to be greater than zero"
+            );
+            Some((
+                StorePatId::from((store_item_id.package, ctls_pat_id)),
+                functor_app.controlled,
+            ))
+        } else {
+            assert!(
+                functor_app.controlled == 0,
+                "control qubits count was expected to be zero"
+            );
+            None
+        };
+        let (args, ctls_arg) = self.resolve_args(
+            (store_item_id.package, callable_decl.input).into(),
+            args_value.clone(),
+            ctls,
+            fixed_args,
+        );
+        let call_scope = Scope::new(
+            store_item_id.package,
+            Some((store_item_id.item, functor_app)),
+            args,
+            ctls_arg,
+        );
+
+        // If the call has the unresolved flag, it tells us that RCA could not perform static analysis on this call site.
+        // Now that we are in evaluation, we have a distinct callable resolved and can perform runtime capability check
+        // ahead of performing the actual call and return the appropriate capabilities error if this call is not supported
+        // by the target.
+        let call_expr_compute_kind = self.get_expr_compute_kind(call_expr_id);
+        let call_was_unresolved = match call_expr_compute_kind {
+            ComputeKind::Quantum(props) => props
+                .runtime_features
+                .contains(RuntimeFeatureFlags::CallToUnresolvedCallee),
+            ComputeKind::Classical => false,
+        };
+        if call_was_unresolved {
+            let call_compute_kind = self.get_call_compute_kind(&call_scope);
+            if let ComputeKind::Quantum(QuantumProperties {
+                runtime_features,
+                value_kind,
+            }) = call_compute_kind
+            {
+                let missing_features = get_missing_runtime_features(
+                    runtime_features,
+                    self.program.config.capabilities,
+                ) & !RuntimeFeatureFlags::CallToUnresolvedCallee;
+                if !missing_features.is_empty() {
+                    if let Some(error) = generate_errors_from_runtime_features(
+                        missing_features,
+                        self.get_expr(call_expr_id).span,
+                    )
+                    .drain(..)
+                    .next()
+                    {
+                        return Err(Error::CapabilityError(error));
+                    }
+                }
+
+                // If the call produces a dynamic value, we treat it as an error because we know that later
+                // analysis has not taken that dynamism into account and further partial evaluation may fail
+                // when it encounters that value.
+                if value_kind.is_dynamic() {
+                    return Err(Error::UnexpectedDynamicValue(
+                        self.get_expr(call_expr_id).span,
+                    ));
+                }
+            }
+        }
+
         // We generate instructions differently depending on whether we are calling an intrinsic or a specialization
         // with an implementation.
-        let value = match &callable_decl.implementation {
-            CallableImpl::Intrinsic => {
+        let value = match spec_decl {
+            None => {
                 let callee_expr = self.get_expr(callee_expr_id);
                 self.eval_expr_call_to_intrinsic(
                     store_item_id,
                     callable_decl,
-                    args_control_flow.into_value(),
+                    args_value,
                     callee_expr.span,
                 )?
             }
-            CallableImpl::Spec(spec_impl) => self.eval_expr_call_to_spec(
-                store_item_id,
-                functor_app,
-                spec_impl,
-                callable_decl.input,
-                args_control_flow.into_value(),
-                fixed_args,
-            )?,
+            Some(spec_decl) => {
+                self.eval_expr_call_to_spec(call_scope, store_item_id, functor_app, spec_decl)?
+            }
         };
         Ok(EvalControlFlow::Continue(value))
     }
 
+    fn try_eval_callee_and_args(
+        &mut self,
+        callee_expr_id: ExprId,
+        args_expr_id: ExprId,
+    ) -> Result<(EvalControlFlow, EvalControlFlow), Error> {
+        let callee_control_flow = self.try_eval_expr(callee_expr_id)?;
+        if callee_control_flow.is_return() {
+            let callee_expr = self.get_expr(callee_expr_id);
+            return Err(Error::Unexpected(
+                "embedded return in callee".to_string(),
+                callee_expr.span,
+            ));
+        }
+        let args_control_flow = self.try_eval_expr(args_expr_id)?;
+        if args_control_flow.is_return() {
+            let args_expr = self.get_expr(args_expr_id);
+            return Err(Error::Unexpected(
+                "embedded return in call arguments".to_string(),
+                args_expr.span,
+            ));
+        }
+        Ok((callee_control_flow, args_control_flow))
+    }
+
     fn eval_expr_call_to_intrinsic(
         &mut self,
         store_item_id: StoreItemId,
@@ -1036,44 +1140,11 @@ impl<'a> PartialEvaluator<'a> {
 
     fn eval_expr_call_to_spec(
         &mut self,
+        call_scope: Scope,
         global_callable_id: StoreItemId,
         functor_app: FunctorApp,
-        spec_impl: &SpecImpl,
-        args_pat: PatId,
-        args_value: Value,
-        fixed_args: Option<Rc<[Value]>>,
+        spec_decl: &SpecDecl,
     ) -> Result<Value, Error> {
-        let spec_decl = get_spec_decl(spec_impl, functor_app);
-
-        // Create new call scope.
-        let ctls = if let Some(ctls_pat_id) = spec_decl.input {
-            assert!(
-                functor_app.controlled > 0,
-                "control qubits count was expected to be greater than zero"
-            );
-            Some((
-                StorePatId::from((global_callable_id.package, ctls_pat_id)),
-                functor_app.controlled,
-            ))
-        } else {
-            assert!(
-                functor_app.controlled == 0,
-                "control qubits count was expected to be zero"
-            );
-            None
-        };
-        let (args, ctls_arg) = self.resolve_args(
-            (global_callable_id.package, args_pat).into(),
-            args_value,
-            ctls,
-            fixed_args,
-        );
-        let call_scope = Scope::new(
-            global_callable_id.package,
-            Some((global_callable_id.item, functor_app)),
-            args,
-            ctls_arg,
-        );
         self.eval_context.push_scope(call_scope);
         let block_value = self.try_eval_block(spec_decl.block)?.into_value();
         let popped_scope = self.eval_context.pop_scope();
@@ -1563,6 +1634,40 @@ impl<'a> PartialEvaluator<'a> {
         expr_generator_set.generate_application_compute_kind(&callable_scope.args_value_kind)
     }
 
+    fn get_call_compute_kind(&self, callable_scope: &Scope) -> ComputeKind {
+        let store_item_id = StoreItemId::from((
+            callable_scope.package_id,
+            callable_scope
+                .callable
+                .expect("callable should be present")
+                .0,
+        ));
+        let ItemComputeProperties::Callable(callable_compute_properties) =
+            self.compute_properties.get_item(store_item_id)
+        else {
+            panic!("item compute properties not found");
+        };
+        let callable_generator_set = match &callable_scope.callable {
+            Some((_, functor_app)) => match (functor_app.adjoint, functor_app.controlled) {
+                (false, 0) => &callable_compute_properties.body,
+                (false, _) => callable_compute_properties
+                    .ctl
+                    .as_ref()
+                    .expect("controlled should be supported"),
+                (true, 0) => callable_compute_properties
+                    .adj
+                    .as_ref()
+                    .expect("adjoint should be supported"),
+                (true, _) => callable_compute_properties
+                    .ctl_adj
+                    .as_ref()
+                    .expect("controlled adjoint should be supported"),
+            },
+            None => panic!("call compute kind should have callable"),
+        };
+        callable_generator_set.generate_application_compute_kind(&callable_scope.args_value_kind)
+    }
+
     fn try_create_mutable_variable(
         &mut self,
         local_var_id: LocalVarId,
diff --git a/compiler/qsc_passes/src/capabilitiesck.rs b/compiler/qsc_passes/src/capabilitiesck.rs
index 6fe0c9d6..38d6b6c1 100644
--- a/compiler/qsc_passes/src/capabilitiesck.rs
+++ b/compiler/qsc_passes/src/capabilitiesck.rs
@@ -13,7 +13,6 @@ mod tests_adaptive_plus_integers;
 #[cfg(test)]
 pub mod tests_common;
 
-use miette::Diagnostic;
 use qsc_data_structures::{span::Span, target::TargetCapabilityFlags};
 
 use qsc_fir::{
@@ -28,172 +27,11 @@ use qsc_fir::{
 
 use qsc_lowerer::map_hir_package_to_fir;
 use qsc_rca::{
+    errors::{generate_errors_from_runtime_features, get_missing_runtime_features, Error},
     Analyzer, ComputeKind, ItemComputeProperties, PackageComputeProperties,
     PackageStoreComputeProperties, RuntimeFeatureFlags,
 };
 use rustc_hash::FxHashMap;
-use thiserror::Error;
-
-#[derive(Clone, Debug, Diagnostic, Error)]
-pub enum Error {
-    #[error("cannot use a dynamic bool value")]
-    #[diagnostic(help(
-        "using a bool value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicBool"))]
-    UseOfDynamicBool(#[label] Span),
-
-    #[error("cannot use a dynamic integer value")]
-    #[diagnostic(help(
-        "using an integer value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicInt"))]
-    UseOfDynamicInt(#[label] Span),
-
-    #[error("cannot use a dynamic Pauli value")]
-    #[diagnostic(help(
-        "using a Pauli value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicPauli"))]
-    UseOfDynamicPauli(#[label] Span),
-
-    #[error("cannot use a dynamic Range value")]
-    #[diagnostic(help(
-        "using a Range value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicRange"))]
-    UseOfDynamicRange(#[label] Span),
-
-    #[error("cannot use a dynamic double value")]
-    #[diagnostic(help(
-        "using a double value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicDouble"))]
-    UseOfDynamicDouble(#[label] Span),
-
-    #[error("cannot use a dynamic qubit")]
-    #[diagnostic(help(
-        "using a qubit whose allocation depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicQubit"))]
-    UseOfDynamicQubit(#[label] Span),
-
-    #[error("cannot use a dynamic big integer value")]
-    #[diagnostic(help(
-        "using a big integer value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicBigInt"))]
-    UseOfDynamicBigInt(#[label] Span),
-
-    #[error("cannot use a dynamic string value")]
-    #[diagnostic(help(
-        "using a string value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicString"))]
-    UseOfDynamicString(#[label] Span),
-
-    #[error("cannot use a dynamically-sized array")]
-    #[diagnostic(help(
-        "using an array whose size depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicallySizedArray"))]
-    UseOfDynamicallySizedArray(#[label] Span),
-
-    #[error("cannot use a dynamic user-defined type")]
-    #[diagnostic(help(
-        "using a user-defined type in which one or more of its members depend on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicUdt"))]
-    UseOfDynamicUdt(#[label] Span),
-
-    #[error("cannot use a dynamic function")]
-    #[diagnostic(help(
-        "using a function whose resolution depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicArrowFunction"))]
-    UseOfDynamicArrowFunction(#[label] Span),
-
-    #[error("cannot use a dynamic operation")]
-    #[diagnostic(help(
-        "using an operation whose resolution depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicArrowOperation"))]
-    UseOfDynamicArrowOperation(#[label] Span),
-
-    #[error("cannot call a cyclic function with a dynamic value as argument")]
-    #[diagnostic(help(
-        "calling a cyclic function with an argument value that depends on a measurement result is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicFunctionWithDynamicArg"))]
-    CallToCyclicFunctionWithDynamicArg(#[label] Span),
-
-    #[error("cannot define a cyclic operation specialization")]
-    #[diagnostic(help("operation specializations that contain call cycles are not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.CyclicOperationSpec"))]
-    CyclicOperationSpec(#[label] Span),
-
-    #[error("cannot call a cyclic operation")]
-    #[diagnostic(help("calling an operation specialization that contains call cycles is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicOperation"))]
-    CallToCyclicOperation(#[label] Span),
-
-    #[error("cannot call a function or operation whose resolution is dynamic")]
-    #[diagnostic(help("calling a function or operation whose resolution depends on a measurement result is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.CallToDynamicCallee"))]
-    CallToDynamicCallee(#[label] Span),
-
-    #[error("cannot call a function or operation that can only be resolved at runtime")]
-    #[diagnostic(help("calling a function or operation that can only be resolved at runtime is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.CallToUnresolvedCallee"))]
-    CallToUnresolvedCallee(#[label] Span),
-
-    #[error("cannot perform a measurement within a dynamic scope")]
-    #[diagnostic(help("performing a measurement within a scope that depends on a measurement result is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.MeasurementWithinDynamicScope"))]
-    MeasurementWithinDynamicScope(#[label] Span),
-
-    #[error("cannot access an array using a dynamic index")]
-    #[diagnostic(help("accessing an array using an index that depends on a measurement result is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicIndex"))]
-    UseOfDynamicIndex(#[label] Span),
-
-    #[error("cannot use a return within a dynamic scope")]
-    #[diagnostic(help("using a return within a scope that depends on a measurement result is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.ReturnWithinDynamicScope"))]
-    ReturnWithinDynamicScope(#[label] Span),
-
-    #[error("cannot have a loop with a dynamic condition")]
-    #[diagnostic(help("using a loop with a condition that depends on a measurement result is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.LoopWithDynamicCondition"))]
-    LoopWithDynamicCondition(#[label] Span),
-
-    #[error("cannot use a closure")]
-    #[diagnostic(help("closures are not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfClosure"))]
-    UseOfClosure(#[label] Span),
-
-    #[error("cannot use a bool value as an output")]
-    #[diagnostic(help("using a bool value as an output is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfBoolOutput"))]
-    UseOfBoolOutput(#[label] Span),
-
-    #[error("cannot use a double value as an output")]
-    #[diagnostic(help("using a Double as an output is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDoubleOutput"))]
-    UseOfDoubleOutput(#[label] Span),
-
-    #[error("cannot use an integer value as an output")]
-    #[diagnostic(help("using an integer as an output is not supported by the current target"))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfIntOutput"))]
-    UseOfIntOutput(#[label] Span),
-
-    #[error("cannot use value with advanced type as an output")]
-    #[diagnostic(help(
-        "using a value of type callable, range, big integer, Pauli, Qubit or string as an output is not supported by the current target"
-    ))]
-    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfAdvancedOutput"))]
-    UseOfAdvancedOutput(#[label] Span),
-}
 
 /// Lower a package store from `qsc_frontend` HIR store to a `qsc_fir` FIR store.
 pub fn lower_store(
@@ -529,100 +367,6 @@ impl<'a> Checker<'a> {
     }
 }
 
-fn generate_errors_from_runtime_features(
-    runtime_features: RuntimeFeatureFlags,
-    span: Span,
-) -> Vec<Error> {
-    let mut errors = Vec::<Error>::new();
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicBool) {
-        errors.push(Error::UseOfDynamicBool(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicInt) {
-        errors.push(Error::UseOfDynamicInt(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicPauli) {
-        errors.push(Error::UseOfDynamicPauli(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicRange) {
-        errors.push(Error::UseOfDynamicRange(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicDouble) {
-        errors.push(Error::UseOfDynamicDouble(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicQubit) {
-        errors.push(Error::UseOfDynamicQubit(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicBigInt) {
-        errors.push(Error::UseOfDynamicBigInt(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicString) {
-        errors.push(Error::UseOfDynamicString(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicallySizedArray) {
-        errors.push(Error::UseOfDynamicallySizedArray(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicUdt) {
-        errors.push(Error::UseOfDynamicUdt(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicArrowFunction) {
-        errors.push(Error::UseOfDynamicArrowFunction(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicArrowOperation) {
-        errors.push(Error::UseOfDynamicArrowOperation(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg) {
-        errors.push(Error::CallToCyclicFunctionWithDynamicArg(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::CyclicOperationSpec) {
-        errors.push(Error::CyclicOperationSpec(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicOperation) {
-        errors.push(Error::CallToCyclicOperation(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::CallToDynamicCallee) {
-        errors.push(Error::CallToDynamicCallee(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::CallToUnresolvedCallee) {
-        errors.push(Error::CallToUnresolvedCallee(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::MeasurementWithinDynamicScope) {
-        errors.push(Error::MeasurementWithinDynamicScope(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicIndex) {
-        errors.push(Error::UseOfDynamicIndex(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::ReturnWithinDynamicScope) {
-        errors.push(Error::ReturnWithinDynamicScope(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::LoopWithDynamicCondition) {
-        errors.push(Error::LoopWithDynamicCondition(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfClosure) {
-        errors.push(Error::UseOfClosure(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfBoolOutput) {
-        errors.push(Error::UseOfBoolOutput(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfDoubleOutput) {
-        errors.push(Error::UseOfDoubleOutput(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfIntOutput) {
-        errors.push(Error::UseOfIntOutput(span));
-    }
-    if runtime_features.contains(RuntimeFeatureFlags::UseOfAdvancedOutput) {
-        errors.push(Error::UseOfAdvancedOutput(span));
-    }
-    errors
-}
-
-fn get_missing_runtime_features(
-    runtime_features: RuntimeFeatureFlags,
-    target_capabilities: TargetCapabilityFlags,
-) -> RuntimeFeatureFlags {
-    let missing_capabilities = !target_capabilities & runtime_features.target_capabilities();
-    runtime_features.contributing_features(missing_capabilities)
-}
-
 fn get_spec_level_runtime_features(runtime_features: RuntimeFeatureFlags) -> RuntimeFeatureFlags {
     const SPEC_LEVEL_RUNTIME_FEATURES: RuntimeFeatureFlags =
         RuntimeFeatureFlags::CyclicOperationSpec;
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index 45ff8988..aabadc75 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -43,7 +43,7 @@ pub enum Error {
     BaseProfCk(baseprofck::Error),
     BorrowCk(borrowck::Error),
     CallableLimits(callable_limits::Error),
-    CapabilitiesCk(capabilitiesck::Error),
+    CapabilitiesCk(qsc_rca::errors::Error),
     ConjInvert(conjugate_invert::Error),
     EntryPoint(entry_point::Error),
     SpecGen(spec_gen::Error),
diff --git a/compiler/qsc_rca/Cargo.toml b/compiler/qsc_rca/Cargo.toml
index 0ff5ffcb..f254bf46 100644
--- a/compiler/qsc_rca/Cargo.toml
+++ b/compiler/qsc_rca/Cargo.toml
@@ -17,6 +17,8 @@ qsc_fir = { path = "../qsc_fir" }
 qsc_frontend = { path = "../qsc_frontend" }
 qsc_lowerer = { path = "../qsc_lowerer" }
 rustc-hash = { workspace = true }
+miette = { workspace = true }
+thiserror = { workspace = true }
 
 [dev-dependencies]
 expect-test = { workspace = true }
diff --git a/compiler/qsc_rca/src/core.rs b/compiler/qsc_rca/src/core.rs
index 5da592bf..0b6c43f2 100644
--- a/compiler/qsc_rca/src/core.rs
+++ b/compiler/qsc_rca/src/core.rs
@@ -483,9 +483,10 @@ impl<'a> Analyzer<'a> {
 
         // If the callee could not be resolved, return a compute kind with certain runtime features.
         let Some(callee) = maybe_callee else {
-            // The value kind of a call expression with an unresolved callee is dynamic but its specific variant depends
-            // on the expression's type.
-            let value_kind = ValueKind::new_dynamic_from_type(expr_type);
+            // The value kind of a call expression with an unresolved callee is not known, so to avoid
+            // spurious errors in later analysis where the value is used we assume static.
+            // During partial-evaluation, the callable is known the actual return kind will be checked.
+            let value_kind = ValueKind::new_static_from_type(expr_type);
             let compute_kind = ComputeKind::Quantum(QuantumProperties {
                 runtime_features: RuntimeFeatureFlags::CallToUnresolvedCallee,
                 value_kind,
diff --git a/compiler/qsc_rca/src/errors.rs b/compiler/qsc_rca/src/errors.rs
new file mode 100644
index 00000000..45408cde
--- /dev/null
+++ b/compiler/qsc_rca/src/errors.rs
@@ -0,0 +1,260 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use miette::Diagnostic;
+use qsc_data_structures::{span::Span, target::TargetCapabilityFlags};
+use thiserror::Error;
+
+use crate::RuntimeFeatureFlags;
+
+#[derive(Clone, Debug, Diagnostic, Error)]
+pub enum Error {
+    #[error("cannot use a dynamic bool value")]
+    #[diagnostic(help(
+        "using a bool value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicBool"))]
+    UseOfDynamicBool(#[label] Span),
+
+    #[error("cannot use a dynamic integer value")]
+    #[diagnostic(help(
+        "using an integer value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicInt"))]
+    UseOfDynamicInt(#[label] Span),
+
+    #[error("cannot use a dynamic Pauli value")]
+    #[diagnostic(help(
+        "using a Pauli value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicPauli"))]
+    UseOfDynamicPauli(#[label] Span),
+
+    #[error("cannot use a dynamic Range value")]
+    #[diagnostic(help(
+        "using a Range value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicRange"))]
+    UseOfDynamicRange(#[label] Span),
+
+    #[error("cannot use a dynamic double value")]
+    #[diagnostic(help(
+        "using a double value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicDouble"))]
+    UseOfDynamicDouble(#[label] Span),
+
+    #[error("cannot use a dynamic qubit")]
+    #[diagnostic(help(
+        "using a qubit whose allocation depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicQubit"))]
+    UseOfDynamicQubit(#[label] Span),
+
+    #[error("cannot use a dynamic big integer value")]
+    #[diagnostic(help(
+        "using a big integer value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicBigInt"))]
+    UseOfDynamicBigInt(#[label] Span),
+
+    #[error("cannot use a dynamic string value")]
+    #[diagnostic(help(
+        "using a string value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicString"))]
+    UseOfDynamicString(#[label] Span),
+
+    #[error("cannot use a dynamically-sized array")]
+    #[diagnostic(help(
+        "using an array whose size depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicallySizedArray"))]
+    UseOfDynamicallySizedArray(#[label] Span),
+
+    #[error("cannot use a dynamic user-defined type")]
+    #[diagnostic(help(
+        "using a user-defined type in which one or more of its members depend on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicUdt"))]
+    UseOfDynamicUdt(#[label] Span),
+
+    #[error("cannot use a dynamic function")]
+    #[diagnostic(help(
+        "using a function whose resolution depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicArrowFunction"))]
+    UseOfDynamicArrowFunction(#[label] Span),
+
+    #[error("cannot use a dynamic operation")]
+    #[diagnostic(help(
+        "using an operation whose resolution depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicArrowOperation"))]
+    UseOfDynamicArrowOperation(#[label] Span),
+
+    #[error("cannot call a cyclic function with a dynamic value as argument")]
+    #[diagnostic(help(
+        "calling a cyclic function with an argument value that depends on a measurement result is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicFunctionWithDynamicArg"))]
+    CallToCyclicFunctionWithDynamicArg(#[label] Span),
+
+    #[error("cannot define a cyclic operation specialization")]
+    #[diagnostic(help("operation specializations that contain call cycles are not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.CyclicOperationSpec"))]
+    CyclicOperationSpec(#[label] Span),
+
+    #[error("cannot call a cyclic operation")]
+    #[diagnostic(help("calling an operation specialization that contains call cycles is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicOperation"))]
+    CallToCyclicOperation(#[label] Span),
+
+    #[error("cannot call a function or operation whose resolution is dynamic")]
+    #[diagnostic(help("calling a function or operation whose resolution depends on a measurement result is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.CallToDynamicCallee"))]
+    CallToDynamicCallee(#[label] Span),
+
+    #[error("cannot perform a measurement within a dynamic scope")]
+    #[diagnostic(help("performing a measurement within a scope that depends on a measurement result is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.MeasurementWithinDynamicScope"))]
+    MeasurementWithinDynamicScope(#[label] Span),
+
+    #[error("cannot access an array using a dynamic index")]
+    #[diagnostic(help("accessing an array using an index that depends on a measurement result is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicIndex"))]
+    UseOfDynamicIndex(#[label] Span),
+
+    #[error("cannot use a return within a dynamic scope")]
+    #[diagnostic(help("using a return within a scope that depends on a measurement result is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.ReturnWithinDynamicScope"))]
+    ReturnWithinDynamicScope(#[label] Span),
+
+    #[error("cannot have a loop with a dynamic condition")]
+    #[diagnostic(help("using a loop with a condition that depends on a measurement result is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.LoopWithDynamicCondition"))]
+    LoopWithDynamicCondition(#[label] Span),
+
+    #[error("cannot use a closure")]
+    #[diagnostic(help("closures are not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfClosure"))]
+    UseOfClosure(#[label] Span),
+
+    #[error("cannot use a bool value as an output")]
+    #[diagnostic(help("using a bool value as an output is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfBoolOutput"))]
+    UseOfBoolOutput(#[label] Span),
+
+    #[error("cannot use a double value as an output")]
+    #[diagnostic(help("using a Double as an output is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDoubleOutput"))]
+    UseOfDoubleOutput(#[label] Span),
+
+    #[error("cannot use an integer value as an output")]
+    #[diagnostic(help("using an integer as an output is not supported by the current target"))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfIntOutput"))]
+    UseOfIntOutput(#[label] Span),
+
+    #[error("cannot use value with advanced type as an output")]
+    #[diagnostic(help(
+        "using a value of type callable, range, big integer, Pauli, Qubit or string as an output is not supported by the current target"
+    ))]
+    #[diagnostic(code("Qsc.CapabilitiesCk.UseOfAdvancedOutput"))]
+    UseOfAdvancedOutput(#[label] Span),
+}
+
+#[must_use]
+pub fn generate_errors_from_runtime_features(
+    runtime_features: RuntimeFeatureFlags,
+    span: Span,
+) -> Vec<Error> {
+    let mut errors = Vec::<Error>::new();
+
+    // Errors are reported in order of relative importance, which makes it easier to read them
+    // and is helpful during partial evaluation when only the first error is reported to the user.
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicBool) {
+        errors.push(Error::UseOfDynamicBool(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicInt) {
+        errors.push(Error::UseOfDynamicInt(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicPauli) {
+        errors.push(Error::UseOfDynamicPauli(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicRange) {
+        errors.push(Error::UseOfDynamicRange(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicDouble) {
+        errors.push(Error::UseOfDynamicDouble(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicQubit) {
+        errors.push(Error::UseOfDynamicQubit(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicBigInt) {
+        errors.push(Error::UseOfDynamicBigInt(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicString) {
+        errors.push(Error::UseOfDynamicString(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicallySizedArray) {
+        errors.push(Error::UseOfDynamicallySizedArray(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicUdt) {
+        errors.push(Error::UseOfDynamicUdt(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicArrowFunction) {
+        errors.push(Error::UseOfDynamicArrowFunction(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicArrowOperation) {
+        errors.push(Error::UseOfDynamicArrowOperation(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg) {
+        errors.push(Error::CallToCyclicFunctionWithDynamicArg(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::CyclicOperationSpec) {
+        errors.push(Error::CyclicOperationSpec(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicOperation) {
+        errors.push(Error::CallToCyclicOperation(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::CallToDynamicCallee) {
+        errors.push(Error::CallToDynamicCallee(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::MeasurementWithinDynamicScope) {
+        errors.push(Error::MeasurementWithinDynamicScope(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicIndex) {
+        errors.push(Error::UseOfDynamicIndex(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::ReturnWithinDynamicScope) {
+        errors.push(Error::ReturnWithinDynamicScope(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::LoopWithDynamicCondition) {
+        errors.push(Error::LoopWithDynamicCondition(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfClosure) {
+        errors.push(Error::UseOfClosure(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfBoolOutput) {
+        errors.push(Error::UseOfBoolOutput(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfDoubleOutput) {
+        errors.push(Error::UseOfDoubleOutput(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfIntOutput) {
+        errors.push(Error::UseOfIntOutput(span));
+    }
+    if runtime_features.contains(RuntimeFeatureFlags::UseOfAdvancedOutput) {
+        errors.push(Error::UseOfAdvancedOutput(span));
+    }
+    errors
+}
+
+#[must_use]
+pub fn get_missing_runtime_features(
+    runtime_features: RuntimeFeatureFlags,
+    target_capabilities: TargetCapabilityFlags,
+) -> RuntimeFeatureFlags {
+    let missing_capabilities = !target_capabilities & runtime_features.target_capabilities();
+    runtime_features.contributing_features(missing_capabilities)
+}
diff --git a/compiler/qsc_rca/src/lib.rs b/compiler/qsc_rca/src/lib.rs
index 367c4369..559ddb82 100644
--- a/compiler/qsc_rca/src/lib.rs
+++ b/compiler/qsc_rca/src/lib.rs
@@ -12,6 +12,7 @@ mod common;
 mod core;
 mod cycle_detection;
 mod cyclic_callables;
+pub mod errors;
 mod overrider;
 mod scaffolding;
 
@@ -557,7 +558,7 @@ pub struct QuantumProperties {
     /// The runtime features used by the program element.
     pub runtime_features: RuntimeFeatureFlags,
     /// The kind of value of the program element.
-    pub(crate) value_kind: ValueKind,
+    pub value_kind: ValueKind,
 }
 
 impl Display for QuantumProperties {
diff --git a/compiler/qsc_rir/src/passes/reindex_qubits.rs b/compiler/qsc_rir/src/passes/reindex_qubits.rs
index 1f44f1b1..649048e1 100644
--- a/compiler/qsc_rir/src/passes/reindex_qubits.rs
+++ b/compiler/qsc_rir/src/passes/reindex_qubits.rs
@@ -4,7 +4,7 @@
 #[cfg(test)]
 mod tests;
 
-use std::collections::hash_map::Entry;
+use std::{collections::hash_map::Entry, ops::Sub};
 
 use qsc_data_structures::index_map::IndexMap;
 use rustc_hash::FxHashMap;
@@ -47,7 +47,11 @@ pub fn reindex_qubits(program: &mut Program) {
         used_cx,
         cx_id,
         mresetz_id,
-        highest_used_id: program.num_qubits - 1,
+        // For this calculation, qubit IDs can never be lower than zero but a program may not use
+        // any qubits. Since `highest_used_id` is only needed for remapping pass and a program without any
+        // qubits won't do any remapping, it's safe to treat this as 1 and let `highest_used_id` default
+        // to zero.
+        highest_used_id: program.num_qubits.max(1).sub(1),
     };
 
     let pred_map = build_predecessors_map(program);
diff --git a/compiler/qsc_rir/src/passes/ssa_transform.rs b/compiler/qsc_rir/src/passes/ssa_transform.rs
index 0239459b..d11a0cbe 100644
--- a/compiler/qsc_rir/src/passes/ssa_transform.rs
+++ b/compiler/qsc_rir/src/passes/ssa_transform.rs
@@ -17,12 +17,6 @@ pub fn transform_to_ssa(program: &mut Program, preds: &IndexMap<BlockId, Vec<Blo
     // Ensure that the graph is acyclic before proceeding. Current approach does not support cycles.
     ensure_acyclic(preds);
 
-    // First, remove store instructions and propagate variables through individual blocks.
-    // This produces a per-block map of dynamic variables to their values.
-    // Orphan variables may be left behind where a variable is defined in one block and used in another, which
-    // will be resolved by inserting phi nodes.
-    let mut block_var_map = map_store_to_dominated_ssa(program, preds);
-
     // Get the next available variable ID for use in newly generated phi nodes.
     let mut next_var_id = get_variable_assignments(program)
         .iter()
@@ -30,6 +24,12 @@ pub fn transform_to_ssa(program: &mut Program, preds: &IndexMap<BlockId, Vec<Blo
         .map(|(var_id, _)| var_id.successor())
         .unwrap_or_default();
 
+    // First, remove store instructions and propagate variables through individual blocks.
+    // This produces a per-block map of dynamic variables to their values.
+    // Orphan variables may be left behind where a variable is defined in one block and used in another, which
+    // will be resolved by inserting phi nodes.
+    let mut block_var_map = map_store_to_dominated_ssa(program, preds);
+
     // Insert phi nodes where necessary, mapping any remaining orphaned uses to the new variable
     // created by the phi node.
     // This can be done in one pass because the graph is assumed to be acyclic.
@@ -160,14 +160,14 @@ fn map_store_to_dominated_ssa(
 
 // Propagates stored variables through a block, tracking the latest stored value and replacing
 // usage of the variable with the stored value.
-fn map_variable_use_in_block(block: &mut Block, var_map: &mut impl VariableMapper) {
+fn map_variable_use_in_block(block: &mut Block, var_map: &mut FxHashMap<VariableId, Operand>) {
     let instrs = block.0.drain(..).collect::<Vec<_>>();
 
     for mut instr in instrs {
         match &mut instr {
             // Track the new value of the variable and omit the store instruction.
             Instruction::Store(operand, var) => {
-                var_map.insert(*var, *operand);
+                var_map.insert(var.variable_id, *operand);
                 continue;
             }
 
@@ -176,7 +176,7 @@ fn map_variable_use_in_block(block: &mut Block, var_map: &mut impl VariableMappe
                 *args = args
                     .iter()
                     .map(|arg| match arg {
-                        Operand::Variable(var) => var_map.to_operand(*var),
+                        Operand::Variable(var) => var.map_to_operand(var_map),
                         Operand::Literal(_) => *arg,
                     })
                     .collect();
@@ -184,7 +184,7 @@ fn map_variable_use_in_block(block: &mut Block, var_map: &mut impl VariableMappe
 
             // Replace the branch condition with the new value of the variable.
             Instruction::Branch(var, _, _) => {
-                *var = var_map.to_variable(*var);
+                *var = var.map_to_variable(var_map);
             }
 
             // Two variable instructions, replace left and right operands with new values.
@@ -219,37 +219,35 @@ fn map_variable_use_in_block(block: &mut Block, var_map: &mut impl VariableMappe
 }
 
 impl Operand {
-    fn mapped(&self, var_map: &impl VariableMapper) -> Operand {
+    fn mapped(&self, var_map: &FxHashMap<VariableId, Operand>) -> Operand {
         match self {
             Operand::Literal(_) => *self,
-            Operand::Variable(var) => var_map.to_operand(*var),
+            Operand::Variable(var) => var.map_to_operand(var_map),
         }
     }
 }
 
-trait VariableMapper {
-    fn insert(&mut self, var: Variable, operand: Operand);
-    fn to_operand(&self, var: Variable) -> Operand;
-    fn to_variable(&self, var: Variable) -> Variable;
-}
-
-impl VariableMapper for FxHashMap<VariableId, Operand> {
-    fn insert(&mut self, var: Variable, operand: Operand) {
-        self.insert(var.variable_id, operand);
-    }
-
-    fn to_operand(&self, var: Variable) -> Operand {
-        self.get(&var.variable_id)
-            .copied()
-            .unwrap_or(Operand::Variable(var))
+impl Variable {
+    fn map_to_operand(self, var_map: &FxHashMap<VariableId, Operand>) -> Operand {
+        let mut var = self;
+        while let Some(operand) = var_map.get(&var.variable_id) {
+            if let Operand::Variable(new_var) = operand {
+                var = *new_var;
+            } else {
+                return *operand;
+            }
+        }
+        Operand::Variable(var)
     }
 
-    fn to_variable(&self, var: Variable) -> Variable {
-        self.get(&var.variable_id)
-            .copied()
-            .map_or(var, |operand| match operand {
-                Operand::Literal(_) => panic!("literal not supported in this context"),
-                Operand::Variable(var) => var,
-            })
+    fn map_to_variable(self, var_map: &FxHashMap<VariableId, Operand>) -> Variable {
+        let mut var = self;
+        while let Some(operand) = var_map.get(&var.variable_id) {
+            let Operand::Variable(new_var) = operand else {
+                panic!("literal not supported in this context");
+            };
+            var = *new_var;
+        }
+        var
     }
 }
diff --git a/compiler/qsc_rir/src/passes/type_check.rs b/compiler/qsc_rir/src/passes/type_check.rs
index 757657ff..35a9c2af 100644
--- a/compiler/qsc_rir/src/passes/type_check.rs
+++ b/compiler/qsc_rir/src/passes/type_check.rs
@@ -31,12 +31,16 @@ fn check_instr_types(program: &Program, instr: &Instruction) {
         | Instruction::LogicalOr(opr1, opr2, var)
         | Instruction::BitwiseAnd(opr1, opr2, var)
         | Instruction::BitwiseOr(opr1, opr2, var)
-        | Instruction::BitwiseXor(opr1, opr2, var)
-        | Instruction::Icmp(_, opr1, opr2, var) => {
+        | Instruction::BitwiseXor(opr1, opr2, var) => {
             assert_eq!(opr1.get_type(), opr2.get_type());
             assert_eq!(opr1.get_type(), var.ty);
         }
 
+        Instruction::Icmp(_, opr1, opr2, var) => {
+            assert_eq!(opr1.get_type(), opr2.get_type());
+            assert_eq!(Ty::Boolean, var.ty);
+        }
+
         Instruction::Store(opr, var)
         | Instruction::LogicalNot(opr, var)
         | Instruction::BitwiseNot(opr, var) => {
diff --git a/compiler/qsc_rir/src/rir.rs b/compiler/qsc_rir/src/rir.rs
index e283e73d..7e0dbb63 100644
--- a/compiler/qsc_rir/src/rir.rs
+++ b/compiler/qsc_rir/src/rir.rs
@@ -6,7 +6,7 @@ use qsc_data_structures::{index_map::IndexMap, target::TargetCapabilityFlags};
 use std::fmt::{self, Display, Formatter, Write};
 
 /// The root of the RIR.
-#[derive(Default)]
+#[derive(Default, Clone)]
 pub struct Program {
     pub entry: CallableId,
     pub callables: IndexMap<CallableId, Callable>,
@@ -63,7 +63,7 @@ impl Program {
     }
 }
 
-#[derive(Default)]
+#[derive(Default, Clone, Copy)]
 pub struct Config {
     pub capabilities: TargetCapabilityFlags,
 }
@@ -129,7 +129,7 @@ impl BlockId {
 }
 
 /// A block is a collection of instructions.
-#[derive(Default)]
+#[derive(Default, Clone)]
 pub struct Block(pub Vec<Instruction>);
 
 /// A unique identifier for a callable in a RIR program.
@@ -156,7 +156,7 @@ impl CallableId {
 }
 
 /// A callable.
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Callable {
     /// The name of the callable.
     pub name: String,
diff --git a/compiler/qsc_rir/src/utils.rs b/compiler/qsc_rir/src/utils.rs
index a6f3c948..f38f2d43 100644
--- a/compiler/qsc_rir/src/utils.rs
+++ b/compiler/qsc_rir/src/utils.rs
@@ -72,6 +72,8 @@ pub fn build_predecessors_map(program: &Program) -> IndexMap<BlockId, Vec<BlockI
 #[must_use]
 pub fn get_variable_assignments(program: &Program) -> IndexMap<VariableId, (BlockId, usize)> {
     let mut assignments = IndexMap::default();
+    let mut has_store = false;
+    let mut has_phi = false;
     for (block_id, block) in program.blocks.iter() {
         for (idx, instr) in block.0.iter().enumerate() {
             match instr {
@@ -97,18 +99,24 @@ pub fn get_variable_assignments(program: &Program) -> IndexMap<VariableId, (Bloc
                         "Duplicate assignment to {:?} in {block_id:?}, instruction {idx}",
                         var.variable_id
                     );
+                    has_phi |= matches!(instr, Instruction::Phi(_, _));
                     assignments.insert(var.variable_id, (block_id, idx));
                 }
+                Instruction::Store(_, var) => {
+                    has_store = true;
+                    assignments.insert(var.variable_id, (block_id, idx));
+                }
+
                 Instruction::Call(_, _, None)
                 | Instruction::Jump(..)
                 | Instruction::Branch(..)
                 | Instruction::Return => {}
-
-                Instruction::Store(..) => {
-                    panic!("Unexpected Store at {block_id:?}, instruction {idx}")
-                }
             }
         }
     }
+    assert!(
+        !(has_store && has_phi),
+        "Program has both store and phi instructions."
+    );
     assignments
 }
diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts
index 8e06c3ec..0775d0bb 100644
--- a/npm/qsharp/src/compiler/compiler.ts
+++ b/npm/qsharp/src/compiler/compiler.ts
@@ -93,7 +93,11 @@ export interface ICompiler {
     operation?: IOperationInfo,
   ): Promise<CircuitData>;
 
-  getDocumentation(): Promise<IDocFile[]>;
+  getDocumentation(
+    additionalSources?: [string, string][],
+    targetProfile?: string,
+    languageFeatures?: string[],
+  ): Promise<IDocFile[]>;
 
   checkExerciseSolution(
     userCode: string,
@@ -273,8 +277,20 @@ export class Compiler implements ICompiler {
     );
   }
 
-  async getDocumentation(): Promise<IDocFile[]> {
-    return this.wasm.generate_docs();
+  // Returns all autogenerated documentation files for the standard library
+  // and loaded project (if requested). This include file names and metadata,
+  // including specially formatted table of content file.
+  async getDocumentation(
+    additionalSources?: [string, string][],
+    targetProfile?: string,
+    languageFeatures?: string[],
+  ): Promise<IDocFile[]> {
+    // Get documentation from wasm layer
+    return this.wasm.generate_docs(
+      additionalSources,
+      targetProfile,
+      languageFeatures,
+    );
   }
 
   async checkExerciseSolution(
diff --git a/playground/build.js b/playground/build.js
index 086939ba..5b5f7e34 100644
--- a/playground/build.js
+++ b/playground/build.js
@@ -58,7 +58,7 @@ function copyLibs() {
   mkdirSync(monacoDest, { recursive: true });
   cpSync(monacoBase, monacoDest, { recursive: true });
 
-  copyKatex(join(thisDir, "public/libs/katex"), true);
+  copyKatex(join(thisDir, "public/libs/katex"));
 
   copyWasmToPlayground();
 }
diff --git a/playground/public/index.html b/playground/public/index.html
index 62ffcd01..165b4cb3 100644
--- a/playground/public/index.html
+++ b/playground/public/index.html
@@ -16,7 +16,7 @@
       rel="icon"
       href="data:image/gif;base64,R0lGODlhEAAQAAAAACwAAAAAEAAQAAACDvAxdbn9YZSTVntx1qcAADs="
     />
-    <link rel="stylesheet" href="libs/katex/github-markdown.css" />
+    <link rel="stylesheet" href="libs/katex/github-markdown-light.css" />
     <link rel="stylesheet" href="libs/katex/katex.min.css" />
     <link rel="stylesheet" href="libs/main.css" />
     <title>Q# playground</title>
diff --git a/samples/algorithms/BellState.qs b/samples/algorithms/BellState.qs
index c6ae0479..73661c54 100644
--- a/samples/algorithms/BellState.qs
+++ b/samples/algorithms/BellState.qs
@@ -35,23 +35,31 @@ namespace Sample {
         return measurements;
     }
 
+    /// # Summary
+    /// Prepares |Φ+⟩ = (|00⟩+|11⟩)/√2 state assuming `register` is in |00⟩ state.
     operation PreparePhiPlus(register : Qubit[]) : Unit {
         H(register[0]);                 // |+0〉
         CNOT(register[0], register[1]); // 1/sqrt(2)(|00〉 + |11〉)
     }
 
+    /// # Summary
+    /// Prepares |Φ−⟩ = (|00⟩-|11⟩)/√2 state assuming `register` is in |00⟩ state.
     operation PreparePhiMinus(register : Qubit[]) : Unit {
         H(register[0]);                 // |+0〉
         Z(register[0]);                 // |-0〉
         CNOT(register[0], register[1]); // 1/sqrt(2)(|00〉 - |11〉)
     }
 
+    /// # Summary
+    /// Prepares |Ψ+⟩ = (|01⟩+|10⟩)/√2 state assuming `register` is in |00⟩ state.
     operation PreparePsiPlus(register : Qubit[]) : Unit {
         H(register[0]);                 // |+0〉
         X(register[1]);                 // |+1〉
         CNOT(register[0], register[1]); // 1/sqrt(2)(|01〉 + |10〉)
     }
 
+    /// # Summary
+    /// Prepares |Ψ−⟩ = (|01⟩-|10⟩)/√2 state assuming `register` is in |00⟩ state.
     operation PreparePsiMinus(register : Qubit[]) : Unit {
         H(register[0]);                 // |+0〉
         Z(register[0]);                 // |-0〉
diff --git a/vscode/build.mjs b/vscode/build.mjs
index 7ce44728..8357ed96 100644
--- a/vscode/build.mjs
+++ b/vscode/build.mjs
@@ -64,9 +64,8 @@ export function copyWasmToVsCode() {
 /**
  *
  * @param {string} [destDir]
- * @param {boolean} [useLightTheme]
  */
-export function copyKatex(destDir, useLightTheme) {
+export function copyKatex(destDir) {
   let katexBase = join(libsDir, `katex/dist`);
   let katexDest = destDir ?? join(thisDir, `out/katex`);
 
@@ -78,12 +77,13 @@ export function copyKatex(destDir, useLightTheme) {
   );
 
   // Also copy the GitHub markdown CSS
-  const cssFileName = useLightTheme
-    ? "github-markdown-light.css"
-    : "github-markdown.css";
   copyFileSync(
-    join(libsDir, `github-markdown-css/${cssFileName}`),
-    join(katexDest, "github-markdown.css"),
+    join(libsDir, `github-markdown-css/github-markdown-light.css`),
+    join(katexDest, "github-markdown-light.css"),
+  );
+  copyFileSync(
+    join(libsDir, `github-markdown-css/github-markdown-dark.css`),
+    join(katexDest, "github-markdown-dark.css"),
   );
 
   const fontsDir = join(katexBase, "fonts");
diff --git a/vscode/package.json b/vscode/package.json
index 7b25a2eb..78ebc764 100644
--- a/vscode/package.json
+++ b/vscode/package.json
@@ -199,6 +199,10 @@
           "command": "qsharp-vscode.showCircuit",
           "when": "resourceLangId == qsharp"
         },
+        {
+          "command": "qsharp-vscode.showDocumentation",
+          "when": "resourceLangId == qsharp"
+        },
         {
           "command": "qsharp-vscode.setTargetProfile",
           "when": "resourceLangId == qsharp"
@@ -316,6 +320,11 @@
         "title": "Show circuit",
         "category": "Q#"
       },
+      {
+        "command": "qsharp-vscode.showDocumentation",
+        "title": "Show API documentation",
+        "category": "Q#"
+      },
       {
         "command": "qsharp-vscode.workspacesRefresh",
         "category": "Q#",
diff --git a/vscode/src/documentation.ts b/vscode/src/documentation.ts
new file mode 100644
index 00000000..d496f86c
--- /dev/null
+++ b/vscode/src/documentation.ts
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { getCompilerWorker } from "qsharp-lang";
+import { isQsharpDocument } from "./common";
+import { getTarget } from "./config";
+import { Uri, window } from "vscode";
+import { loadProject } from "./projectSystem";
+import { sendMessageToPanel } from "./webviewPanel";
+
+export async function showDocumentationCommand(extensionUri: Uri) {
+  const editor = window.activeTextEditor;
+  if (!editor || !isQsharpDocument(editor.document)) {
+    throw new Error("The currently active window is not a Q# file");
+  }
+
+  // Reveal panel and show 'Loading...' for immediate feedback.
+  sendMessageToPanel(
+    "documentation", // This is needed to route the message to the proper panel
+    true, // Reveal panel
+    null, // With no message
+  );
+
+  const docUri = editor.document.uri;
+  const program = await loadProject(docUri);
+  const targetProfile = getTarget();
+
+  // Get API documentation from compiler.
+  const compilerWorkerScriptPath = Uri.joinPath(
+    extensionUri,
+    "./out/compilerWorker.js",
+  ).toString();
+  const worker = getCompilerWorker(compilerWorkerScriptPath);
+  const docFiles = await worker.getDocumentation(
+    program.sources,
+    targetProfile,
+    program.languageFeatures,
+  );
+
+  const documentation: string[] = [];
+  for (const file of docFiles) {
+    // Some files may contain information other than documentation
+    // For example, table of content is a separate file in a special format
+    // We check presence of qsharp.name in metadata to make sure we take
+    // only files that contain documentation from some qsharp object.
+    if (file.metadata.indexOf("qsharp.name:") >= 0) {
+      documentation.push(file.contents);
+    }
+  }
+
+  const message = {
+    command: "showDocumentationCommand", // This is handled in webview.tsx onMessage
+    fragmentsToRender: documentation,
+  };
+
+  sendMessageToPanel(
+    "documentation", // This is needed to route the message to the proper panel
+    true, // Reveal panel
+    message, // And ask it to display documentation
+  );
+}
diff --git a/vscode/src/webview/docview.tsx b/vscode/src/webview/docview.tsx
new file mode 100644
index 00000000..94627a16
--- /dev/null
+++ b/vscode/src/webview/docview.tsx
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { Markdown } from "qsharp-lang/ux";
+
+export function DocumentationView(props: { fragmentsToRender: string[] }) {
+  // Concatenate all documentation.
+  // The following adds an empty line and a horizontal line
+  // between documentation for different functions.
+  // We may consider filtering of fragments later.
+  const contentToRender = props.fragmentsToRender.join("<br/>\n\n---\n\n");
+
+  return <Markdown markdown={contentToRender} />;
+}
diff --git a/vscode/src/webview/webview.tsx b/vscode/src/webview/webview.tsx
index 9e2d78e8..786c4bf6 100644
--- a/vscode/src/webview/webview.tsx
+++ b/vscode/src/webview/webview.tsx
@@ -15,12 +15,13 @@ import {
   type ReData,
 } from "qsharp-lang/ux";
 import { HelpPage } from "./help";
+import { DocumentationView } from "./docview";
 
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 // @ts-ignore - there are no types for this
 import mk from "@vscode/markdown-it-katex";
 import markdownIt from "markdown-it";
-const md = markdownIt("commonmark", { html: true, breaks: true });
+const md = markdownIt("commonmark");
 md.use(mk, {
   enableMathBlockInHtml: true,
   enableMathInlineInHtml: true,
@@ -49,19 +50,78 @@ type CircuitState = {
   props: CircuitProps;
 };
 
+type DocumentationState = {
+  viewType: "documentation";
+  fragmentsToRender: string[];
+};
+
 type State =
   | { viewType: "loading" }
   | { viewType: "help" }
   | HistogramState
   | EstimatesState
-  | CircuitState;
+  | CircuitState
+  | DocumentationState;
 const loadingState: State = { viewType: "loading" };
 const helpState: State = { viewType: "help" };
 let state: State = loadingState;
 
+const themeAttribute = "data-vscode-theme-kind";
+
+function updateGitHubTheme() {
+  let isDark = true;
+
+  const themeType = document.body.getAttribute(themeAttribute);
+
+  switch (themeType) {
+    case "vscode-light":
+    case "vscode-high-contrast-light":
+      isDark = false;
+      break;
+    default:
+      isDark = true;
+  }
+
+  // Update the stylesheet href
+  document.head.querySelectorAll("link").forEach((el) => {
+    const ref = el.getAttribute("href");
+    if (ref && ref.includes("github-markdown")) {
+      const newVal = ref.replace(
+        /(dark\.css)|(light\.css)/,
+        isDark ? "dark.css" : "light.css",
+      );
+      el.setAttribute("href", newVal);
+    }
+  });
+}
+
+function setThemeStylesheet() {
+  // We need to add the right Markdown style-sheet for the theme.
+
+  // For VS Code, there will be an attribute on the body called
+  // "data-vscode-theme-kind" that is "vscode-light" or "vscode-high-contrast-light"
+  // for light themes, else assume dark (will be "vscode-dark" or "vscode-high-contrast").
+
+  // Use a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
+  // to detect changes to the theme attribute.
+  const callback = (mutations: MutationRecord[]) => {
+    for (const mutation of mutations) {
+      if (mutation.attributeName === themeAttribute) {
+        updateGitHubTheme();
+      }
+    }
+  };
+  const observer = new MutationObserver(callback);
+  observer.observe(document.body, { attributeFilter: [themeAttribute] });
+
+  // Run it once for initial value
+  updateGitHubTheme();
+}
+
 function main() {
   state = (vscodeApi.getState() as any) || loadingState;
   render(<App state={state} />, document.body);
+  setThemeStylesheet();
   vscodeApi.postMessage({ command: "ready" });
 }
 
@@ -121,6 +181,14 @@ function onMessage(event: any) {
         };
       }
       break;
+    case "showDocumentationCommand":
+      {
+        state = {
+          viewType: "documentation",
+          fragmentsToRender: message.fragmentsToRender,
+        };
+      }
+      break;
     default:
       console.error("Unknown command: ", message.command);
       return;
@@ -178,6 +246,12 @@ function App({ state }: { state: State }) {
       return <CircuitPanel {...state.props}></CircuitPanel>;
     case "help":
       return <HelpPage />;
+    case "documentation":
+      // Ideally we'd have this on all web views, but it makes the font a little
+      // too large in the others right now. Something to unify later.
+      document.body.classList.add("markdown-body");
+      document.body.style.fontSize = "0.8em";
+      return <DocumentationView fragmentsToRender={state.fragmentsToRender} />;
     default:
       console.error("Unknown view type in state", state);
       return <div>Loading error</div>;
diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts
index dca94609..551ffccf 100644
--- a/vscode/src/webviewPanel.ts
+++ b/vscode/src/webviewPanel.ts
@@ -23,6 +23,7 @@ import { loadProject } from "./projectSystem";
 import { EventType, sendTelemetryEvent } from "./telemetry";
 import { getRandomGuid } from "./utils";
 import { showCircuitCommand } from "./circuit";
+import { showDocumentationCommand } from "./documentation";
 
 const QSharpWebViewType = "qsharp-webview";
 const compilerRunTimeoutMs = 1000 * 60 * 5; // 5 minutes
@@ -379,9 +380,20 @@ export function registerWebViewCommands(context: ExtensionContext) {
       },
     ),
   );
+
+  context.subscriptions.push(
+    commands.registerCommand("qsharp-vscode.showDocumentation", async () => {
+      await showDocumentationCommand(context.extensionUri);
+    }),
+  );
 }
 
-type PanelType = "histogram" | "estimates" | "help" | "circuit";
+type PanelType =
+  | "histogram"
+  | "estimates"
+  | "help"
+  | "circuit"
+  | "documentation";
 
 const panelTypeToPanel: Record<
   PanelType,
@@ -391,6 +403,11 @@ const panelTypeToPanel: Record<
   estimates: { title: "Q# Estimates", panel: undefined, state: {} },
   circuit: { title: "Q# Circuit", panel: undefined, state: {} },
   help: { title: "Q# Help", panel: undefined, state: {} },
+  documentation: {
+    title: "Q# Documentation",
+    panel: undefined,
+    state: {},
+  },
 };
 
 export function sendMessageToPanel(
@@ -410,6 +427,7 @@ export function sendMessageToPanel(
       {
         enableCommandUris: true,
         enableScripts: true,
+        enableFindWidget: true,
         retainContextWhenHidden: true,
         // Note: If retainContextWhenHidden is false, the webview gets reloaded
         // every time you hide it by switching to another tab and then switch
@@ -458,7 +476,7 @@ export class QSharpWebViewPanel {
     }
 
     const katexCss = getUri(["out", "katex", "katex.min.css"]);
-    const githubCss = getUri(["out", "katex", "github-markdown.css"]);
+    const githubCss = getUri(["out", "katex", "github-markdown-dark.css"]);
     const webviewCss = getUri(["out", "webview", "webview.css"]);
     const webviewJs = getUri(["out", "webview", "webview.js"]);
     const resourcesUri = getUri(["resources"]);
@@ -528,7 +546,8 @@ export class QSharpViewViewPanelSerializer implements WebviewPanelSerializer {
       panelType !== "estimates" &&
       panelType !== "histogram" &&
       panelType !== "circuit" &&
-      panelType !== "help"
+      panelType !== "help" &&
+      panelType != "documentation"
     ) {
       // If it was loading when closed, that's fine
       if (panelType === "loading") {
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index 764d6ef1..ac1fe003 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -20,6 +20,7 @@ use qsc::{
     },
     target::Profile,
     LanguageFeatures, PackageStore, PackageType, SourceContents, SourceMap, SourceName, SparseSim,
+    TargetCapabilityFlags,
 };
 use qsc_codegen::qir_base::generate_qir;
 use resource_estimator::{self as re, estimate_entry};
@@ -459,8 +460,22 @@ serializable_type! {
 
 #[wasm_bindgen]
 #[must_use]
-pub fn generate_docs() -> Vec<IDocFile> {
-    let docs = qsc_doc_gen::generate_docs::generate_docs();
+pub fn generate_docs(
+    additionalSources: Option<Vec<js_sys::Array>>,
+    targetProfile: Option<String>,
+    languageFeatures: Option<Vec<String>>,
+) -> Vec<IDocFile> {
+    let source_map: Option<SourceMap> = additionalSources.map(|s| get_source_map(s, &None));
+
+    let target_profile: Option<TargetCapabilityFlags> = targetProfile.map(|p| {
+        Profile::from_str(&p)
+            .expect("invalid target profile")
+            .into()
+    });
+
+    let features: Option<LanguageFeatures> = languageFeatures.map(LanguageFeatures::from_iter);
+
+    let docs = qsc_doc_gen::generate_docs::generate_docs(source_map, target_profile, features);
     let mut result: Vec<IDocFile> = vec![];
 
     for (name, metadata, contents) in docs {


Test patch:
diff --git a/compiler/qsc/src/codegen/tests.rs b/compiler/qsc/src/codegen/tests.rs
index df3598f7..f1d021a9 100644
--- a/compiler/qsc/src/codegen/tests.rs
+++ b/compiler/qsc/src/codegen/tests.rs
@@ -625,8 +625,8 @@ mod adaptive_ri_profile {
             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)
+              %var_3 = phi i64 [0, %block_1], [1, %block_2]
+              call void @__quantum__rt__integer_record_output(i64 %var_3, i8* null)
               ret void
             }
 
diff --git a/compiler/qsc_doc_gen/src/generate_docs/tests.rs b/compiler/qsc_doc_gen/src/generate_docs/tests.rs
index b24f920e..928e00cb 100644
--- a/compiler/qsc_doc_gen/src/generate_docs/tests.rs
+++ b/compiler/qsc_doc_gen/src/generate_docs/tests.rs
@@ -8,7 +8,7 @@ use expect_test::expect;
 
 #[test]
 fn docs_generation() {
-    let files = generate_docs();
+    let files = generate_docs(None, None, None);
     let (_, metadata, contents) = files
         .iter()
         .find(|(file_name, _, _)| &**file_name == "Microsoft.Quantum.Core/Length.md")
diff --git a/compiler/qsc_partial_eval/tests/calls.rs b/compiler/qsc_partial_eval/tests/calls.rs
index 1872be61..24e49479 100644
--- a/compiler/qsc_partial_eval/tests/calls.rs
+++ b/compiler/qsc_partial_eval/tests/calls.rs
@@ -11,8 +11,13 @@ pub mod test_utils;
 
 use expect_test::expect;
 use indoc::indoc;
+use qsc::TargetCapabilityFlags;
 use qsc_rir::rir::{BlockId, CallableId};
-use test_utils::{assert_block_instructions, assert_blocks, assert_callable, get_rir_program};
+use test_utils::{
+    assert_block_instructions, assert_blocks, assert_callable, assert_error,
+    get_partial_evaluation_error_with_capabilities, get_rir_program,
+    get_rir_program_with_capabilities,
+};
 
 #[test]
 fn call_to_single_qubit_unitary_with_two_calls_to_the_same_intrinsic() {
@@ -1200,3 +1205,86 @@ fn call_to_closue_with_one_bound_local_two_unbound() {
                 Return"#]],
     );
 }
+
+#[test]
+fn call_to_unresolved_callee_with_classical_arg_allowed() {
+    let program = get_rir_program_with_capabilities(
+        indoc! {"
+        namespace Test {
+            open Microsoft.Quantum.Convert;
+            operation Op(i : Int, q : Qubit) : Unit {
+                Rx(IntAsDouble(i), q);
+            }
+            @EntryPoint()
+            operation Main() : Unit {
+                use q = Qubit();
+                let f = [Op][0];
+                f(1, q);
+            }
+        }"},
+        TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations,
+    );
+
+    assert_block_instructions(
+        &program,
+        BlockId(0),
+        &expect![[r#"
+        Block:
+            Call id(1), args( Double(1), Qubit(0), )
+            Call id(2), args( Integer(0), Pointer, )
+            Return"#]],
+    );
+}
+
+#[test]
+fn call_to_unresolved_callee_with_dynamic_arg_fails() {
+    let error = get_partial_evaluation_error_with_capabilities(
+        indoc! {"
+        namespace Test {
+            open Microsoft.Quantum.Convert;
+            operation Op(i : Int, q : Qubit) : Unit {
+                Rx(IntAsDouble(i), q);
+            }
+            @EntryPoint()
+            operation Main() : Unit {
+                use q = Qubit();
+                let i = if MResetZ(q) == One { 1 } else { 0 };
+                let f = [Op][0];
+                f(i, q);
+            }
+        }"},
+        TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations,
+    );
+
+    assert_error(
+        &error,
+        &expect!["CapabilityError(UseOfDynamicDouble(Span { lo: 298, hi: 305 }))"],
+    );
+}
+
+#[test]
+fn call_to_unresolved_callee_producing_dynamic_value_fails() {
+    let error = get_partial_evaluation_error_with_capabilities(
+        indoc! {"
+        namespace Test {
+            open Microsoft.Quantum.Convert;
+            operation Op(i : Int, q : Qubit) : Int {
+                X(q);
+                i
+            }
+            @EntryPoint()
+            operation Main() : Unit {
+                use q = Qubit();
+                let i = if MResetZ(q) == One { 1 } else { 0 };
+                let f = [Op][0];
+                let _ = f(i, q);
+            }
+        }"},
+        TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations,
+    );
+
+    assert_error(
+        &error,
+        &expect!["UnexpectedDynamicValue(Span { lo: 298, hi: 305 })"],
+    );
+}
diff --git a/compiler/qsc_partial_eval/tests/output_recording.rs b/compiler/qsc_partial_eval/tests/output_recording.rs
index 4b5fd288..a83fcc41 100644
--- a/compiler/qsc_partial_eval/tests/output_recording.rs
+++ b/compiler/qsc_partial_eval/tests/output_recording.rs
@@ -83,7 +83,7 @@ fn output_recording_for_tuple_of_different_types() {
                     Call id(5), args( Variable(1, Boolean), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -169,7 +169,7 @@ fn output_recording_for_nested_tuples() {
                     Call id(5), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -263,7 +263,7 @@ fn output_recording_for_tuple_of_arrays() {
                     Call id(6), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -357,7 +357,7 @@ fn output_recording_for_array_of_tuples() {
                     Call id(6), args( Variable(3, Boolean), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 1
             num_results: 1"#]]
     .assert_eq(&program.to_string());
@@ -399,7 +399,7 @@ fn output_recording_for_literal_bool() {
                     Call id(1), args( Bool(true), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -441,7 +441,7 @@ fn output_recording_for_literal_int() {
                     Call id(1), args( Integer(42), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             num_qubits: 0
             num_results: 0"#]]
     .assert_eq(&program.to_string());
@@ -512,7 +512,7 @@ fn output_recording_for_mix_of_literal_and_variable() {
                     Call id(4), args( Bool(true), Pointer, )
                     Return
             config: Config:
-                capabilities: Base
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
             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 a5a358b8..de75525e 100644
--- a/compiler/qsc_partial_eval/tests/test_utils.rs
+++ b/compiler/qsc_partial_eval/tests/test_utils.rs
@@ -9,7 +9,10 @@ use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap};
 use qsc_lowerer::{map_hir_package_to_fir, Lowerer};
 use qsc_partial_eval::{partially_evaluate, Error, ProgramEntry};
 use qsc_rca::{Analyzer, PackageStoreComputeProperties};
-use qsc_rir::rir::{BlockId, CallableId, Program};
+use qsc_rir::{
+    passes::check_and_transform,
+    rir::{BlockId, CallableId, Program},
+};
 
 pub fn assert_block_instructions(program: &Program, block_id: BlockId, expected_insts: &Expect) {
     let block = program.get_block(block_id);
@@ -37,7 +40,19 @@ pub fn assert_error(error: &Error, expected_error: &Expect) {
 
 #[must_use]
 pub fn get_partial_evaluation_error(source: &str) -> Error {
-    let maybe_program = compile_and_partially_evaluate(source);
+    let maybe_program = compile_and_partially_evaluate(source, TargetCapabilityFlags::all());
+    match maybe_program {
+        Ok(_) => panic!("partial evaluation succeeded"),
+        Err(error) => error,
+    }
+}
+
+#[must_use]
+pub fn get_partial_evaluation_error_with_capabilities(
+    source: &str,
+    capabilities: TargetCapabilityFlags,
+) -> Error {
+    let maybe_program = compile_and_partially_evaluate(source, capabilities);
     match maybe_program {
         Ok(_) => panic!("partial evaluation succeeded"),
         Err(error) => error,
@@ -46,20 +61,39 @@ pub fn get_partial_evaluation_error(source: &str) -> Error {
 
 #[must_use]
 pub fn get_rir_program(source: &str) -> Program {
-    let maybe_program = compile_and_partially_evaluate(source);
+    let maybe_program = compile_and_partially_evaluate(source, TargetCapabilityFlags::all());
+    match maybe_program {
+        Ok(program) => {
+            // Verify the program can go through transformations.
+            check_and_transform(&mut program.clone());
+            program
+        }
+        Err(error) => panic!("partial evaluation failed: {error:?}"),
+    }
+}
+
+#[must_use]
+pub fn get_rir_program_with_capabilities(
+    source: &str,
+    capabilities: TargetCapabilityFlags,
+) -> Program {
+    let maybe_program = compile_and_partially_evaluate(source, capabilities);
     match maybe_program {
         Ok(program) => program,
         Err(error) => panic!("partial evaluation failed: {error:?}"),
     }
 }
 
-fn compile_and_partially_evaluate(source: &str) -> Result<Program, Error> {
-    let compilation_context = CompilationContext::new(source);
+fn compile_and_partially_evaluate(
+    source: &str,
+    capabilities: TargetCapabilityFlags,
+) -> Result<Program, Error> {
+    let compilation_context = CompilationContext::new(source, capabilities);
     partially_evaluate(
         &compilation_context.fir_store,
         &compilation_context.compute_properties,
         &compilation_context.entry,
-        TargetCapabilityFlags::empty(),
+        capabilities,
     )
 }
 
@@ -70,13 +104,13 @@ struct CompilationContext {
 }
 
 impl CompilationContext {
-    fn new(source: &str) -> Self {
+    fn new(source: &str, capabilities: TargetCapabilityFlags) -> Self {
         let source_map = SourceMap::new([("test".into(), source.into())], Some("".into()));
         let compiler = Compiler::new(
             true,
             source_map,
             PackageType::Exe,
-            TargetCapabilityFlags::all(),
+            capabilities,
             LanguageFeatures::default(),
         )
         .expect("should be able to create a new compiler");
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
index 531b2d4d..f4f6c97f 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs
@@ -428,24 +428,11 @@ fn call_to_dynamic_operation_yields_errors() {
 }
 
 #[test]
-fn call_to_unresolved_yields_errors() {
+fn call_to_unresolved_allowed() {
     check_profile(
         CALL_UNRESOLVED_FUNCTION,
         &expect![[r#"
-            [
-                UseOfDynamicDouble(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-                CallToUnresolvedCallee(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-            ]
+            []
         "#]],
     );
 }
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 ede61329..b36c52d0 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs
@@ -368,24 +368,11 @@ fn call_to_dynamic_operation_yields_errors() {
 }
 
 #[test]
-fn call_to_unresolved_yields_errors() {
+fn call_to_unresolved_allowed() {
     check_profile(
         CALL_UNRESOLVED_FUNCTION,
         &expect![[r#"
-            [
-                UseOfDynamicDouble(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-                CallToUnresolvedCallee(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-            ]
+            []
         "#]],
     );
 }
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
index d6e30d66..c45700d5 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs
@@ -516,24 +516,11 @@ fn call_to_dynamic_operation_yields_errors() {
 }
 
 #[test]
-fn call_to_unresolved_yields_errors() {
+fn call_to_unresolved_allowed() {
     check_profile(
         CALL_UNRESOLVED_FUNCTION,
         &expect![[r#"
-            [
-                UseOfDynamicDouble(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-                CallToUnresolvedCallee(
-                    Span {
-                        lo: 172,
-                        hi: 180,
-                    },
-                ),
-            ]
+            []
         "#]],
     );
 }
diff --git a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
index 8c5c8ae8..9bb6ad8d 100644
--- a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
+++ b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs
@@ -2221,3 +2221,121 @@ fn ssa_transform_propagates_updates_from_multiple_predecessors_to_later_single_s
             num_qubits: 0
             num_results: 0"#]].assert_eq(&program.to_string());
 }
+
+#[test]
+fn ssa_transform_maps_store_instrs_that_use_values_from_other_store_instrs() {
+    let mut program = new_program();
+    program.callables.insert(
+        CallableId(1),
+        Callable {
+            name: "dynamic_bool".to_string(),
+            input_type: Vec::new(),
+            output_type: Some(Ty::Boolean),
+            body: None,
+            call_type: CallableType::Regular,
+        },
+    );
+
+    program.blocks.insert(
+        BlockId(0),
+        Block(vec![
+            Instruction::Call(
+                CallableId(1),
+                Vec::new(),
+                Some(Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                }),
+            ),
+            Instruction::Store(
+                Operand::Variable(Variable {
+                    variable_id: VariableId(0),
+                    ty: Ty::Boolean,
+                }),
+                Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Store(
+                Operand::Variable(Variable {
+                    variable_id: VariableId(1),
+                    ty: Ty::Boolean,
+                }),
+                Variable {
+                    variable_id: VariableId(2),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::LogicalNot(
+                Operand::Variable(Variable {
+                    variable_id: VariableId(2),
+                    ty: Ty::Boolean,
+                }),
+                Variable {
+                    variable_id: VariableId(3),
+                    ty: Ty::Boolean,
+                },
+            ),
+            Instruction::Return,
+        ]),
+    );
+
+    // Before
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+                Callable 0: Callable:
+                    name: main
+                    call_type: Regular
+                    input_type: <VOID>
+                    output_type: <VOID>
+                    body: 0
+                Callable 1: Callable:
+                    name: dynamic_bool
+                    call_type: Regular
+                    input_type: <VOID>
+                    output_type: Boolean
+                    body: <NONE>
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Call id(1), args( )
+                    Variable(1, Boolean) = Store Variable(0, Boolean)
+                    Variable(2, Boolean) = Store Variable(1, Boolean)
+                    Variable(3, Boolean) = LogicalNot Variable(2, Boolean)
+                    Return
+            config: Config:
+                capabilities: Base
+            num_qubits: 0
+            num_results: 0"#]]
+    .assert_eq(&program.to_string());
+
+    // After
+    transform_program(&mut program);
+    expect![[r#"
+        Program:
+            entry: 0
+            callables:
+                Callable 0: Callable:
+                    name: main
+                    call_type: Regular
+                    input_type: <VOID>
+                    output_type: <VOID>
+                    body: 0
+                Callable 1: Callable:
+                    name: dynamic_bool
+                    call_type: Regular
+                    input_type: <VOID>
+                    output_type: Boolean
+                    body: <NONE>
+            blocks:
+                Block 0: Block:
+                    Variable(0, Boolean) = Call id(1), args( )
+                    Variable(3, Boolean) = LogicalNot Variable(0, Boolean)
+                    Return
+            config: Config:
+                capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
+            num_qubits: 0
+            num_results: 0"#]].assert_eq(&program.to_string());
+}
diff --git a/wasm/src/tests.rs b/wasm/src/tests.rs
index 0b30b0c7..c5335ab7 100644
--- a/wasm/src/tests.rs
+++ b/wasm/src/tests.rs
@@ -446,7 +446,7 @@ fn test_runtime_error_default_span() {
 
 #[test]
 fn test_doc_gen() {
-    let docs = qsc_doc_gen::generate_docs::generate_docs();
+    let docs = qsc_doc_gen::generate_docs::generate_docs(None, None, None);
     assert!(docs.len() > 100);
     for (name, metadata, contents) in docs {
         // filename will be something like "Microsoft.Quantum.Canon/ApplyToEachC.md"