diff --git a/Simulation.sln b/Simulation.sln index 9a65f34a9db..ac2435996de 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -113,6 +113,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StandaloneInputReference", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "qir-standalone-input-reference", "src\Qir\Samples\StandaloneInputReference\qsharp\qir-standalone-input-reference.csproj", "{D7D34736-A719-4B45-A33F-2723F59EC29D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Simulation", "Simulation", "{3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoSubstitution", "src\Simulation\AutoSubstitution\AutoSubstitution.csproj", "{33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.AutoSubstitution", "src\Simulation\AutoSubstitution.Tests\Tests.AutoSubstitution.csproj", "{4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.AutoSubstitution.Integration", "src\Simulation\AutoSubstitution.Integration.Tests\Tests.AutoSubstitution.Integration.csproj", "{D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -749,6 +757,54 @@ Global {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU {D7D34736-A719-4B45-A33F-2723F59EC29D}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Debug|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|Any CPU.Build.0 = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|x64.ActiveCfg = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.Release|x64.Build.0 = Release|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Debug|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|Any CPU.Build.0 = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|x64.ActiveCfg = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.Release|x64.Build.0 = Release|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Debug|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|Any CPU.Build.0 = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|x64.ActiveCfg = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.Release|x64.Build.0 = Release|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -802,6 +858,10 @@ Global {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} {A7DB7367-9FD6-4164-8263-A05077BE54AB} = {AAFB81D3-BC87-404D-BA64-AF40B2D2E45A} {D7D34736-A719-4B45-A33F-2723F59EC29D} = {A7DB7367-9FD6-4164-8263-A05077BE54AB} + {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} = {020356B7-C3FC-4100-AE37-97E5D8288D1D} + {33D66E90-049F-4A0B-A2B1-79E7E7E0ED0F} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} + {4EBC65DF-3B5E-419B-8E26-3EEF0B5CD300} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} + {D23480EE-88FC-4DF2-86BD-1C5BDD6CD98C} = {3CD26906-C7F3-47B8-AF43-FF6BCF1CB3EF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {929C0464-86D8-4F70-8835-0A5EAF930821} diff --git a/bootstrap.ps1 b/bootstrap.ps1 index 7ad031ef42d..d8b102b7937 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -16,6 +16,14 @@ if (-not (Test-Path Env:AGENT_OS)) { Pop-Location $Env:BUILD_CONFIGURATION = $null } + if ($Env:ENABLE_QIRRUNTIME -ne "false") { + Write-Host "Build release flavor of the QIR Runtime" + $Env:BUILD_CONFIGURATION = "Release" + Push-Location (Join-Path $PSScriptRoot "src/Qir/Runtime") + .\build-qir-runtime.ps1 + Pop-Location + $Env:BUILD_CONFIGURATION = $null + } Write-Host "Build simulation solution" dotnet build Simulation.sln diff --git a/build/manifest.ps1 b/build/manifest.ps1 index 4ce93ed25b1..a66e133c80c 100644 --- a/build/manifest.ps1 +++ b/build/manifest.ps1 @@ -20,6 +20,7 @@ param( $artifacts = @{ Packages = @( "Microsoft.Azure.Quantum.Client", + "Microsoft.Quantum.AutoSubstitution", "Microsoft.Quantum.CSharpGeneration", "Microsoft.Quantum.Development.Kit", "Microsoft.Quantum.EntryPointDriver", @@ -35,6 +36,7 @@ $artifacts = @{ Assemblies = @( ".\src\Azure\Azure.Quantum.Client\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Azure.Quantum.Client.dll", + ".\src\Simulation\AutoSubstitution\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.AutoSubstitution.dll", ".\src\Simulation\CSharpGeneration\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.CSharpGeneration.dll", ".\src\Simulation\CSharpGeneration.App\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.CSharpGeneration.App.dll", ".\src\Simulation\RoslynWrapper\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.RoslynWrapper.dll", diff --git a/build/pack.ps1 b/build/pack.ps1 index 7f6dbe702a4..c786bf8bff9 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -90,6 +90,7 @@ function Pack-Dotnet() { Write-Host "##[info]Using nuget to create packages" Pack-Dotnet '../src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj' Pack-One '../src/Simulation/CSharpGeneration/Microsoft.Quantum.CSharpGeneration.fsproj' '-IncludeReferencedProjects' +Pack-One '../src/Simulation/AutoSubstitution/AutoSubstitution.csproj' '-IncludeReferencedProjects' Pack-Dotnet '../src/Simulation/EntryPointDriver/Microsoft.Quantum.EntryPointDriver.csproj' Pack-Dotnet '../src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj' Pack-Dotnet '../src/Simulation/TargetDefinitions/Interfaces/Microsoft.Quantum.Targets.Interfaces.csproj' diff --git a/src/Qir/Common/Include/QirUtils.hpp b/src/Qir/Common/Include/QirUtils.hpp new file mode 100644 index 00000000000..e5e351d6844 --- /dev/null +++ b/src/Qir/Common/Include/QirUtils.hpp @@ -0,0 +1,7 @@ +#ifndef QIRUTILS_HPP +#define QIRUTILS_HPP + +// Calm down the "Unused Entity" compiler warning: +#define UNUSED(expr) ((void) (expr)) + +#endif // #ifndef QIRUTILS_HPP \ No newline at end of file diff --git a/src/Qir/Common/Include/SimulatorStub.hpp b/src/Qir/Common/Include/SimulatorStub.hpp index 5a2a2459e9f..c67ac469f0c 100644 --- a/src/Qir/Common/Include/SimulatorStub.hpp +++ b/src/Qir/Common/Include/SimulatorStub.hpp @@ -15,113 +15,113 @@ namespace Quantum { throw std::logic_error("not_implemented: AllocateQubit"); } - void ReleaseQubit(Qubit qubit) override + void ReleaseQubit(Qubit /* qubit */) override { throw std::logic_error("not_implemented: ReleaseQubit"); } - virtual std::string QubitToString(Qubit qubit) override + virtual std::string QubitToString(Qubit /* qubit */) override { throw std::logic_error("not_implemented: QubitToString"); } - void X(Qubit target) override + void X(Qubit /*target*/) override { throw std::logic_error("not_implemented: X"); } - void Y(Qubit target) override + void Y(Qubit /*target*/) override { throw std::logic_error("not_implemented: Y"); } - void Z(Qubit target) override + void Z(Qubit /* target */) override { throw std::logic_error("not_implemented: Z"); } - void H(Qubit target) override + void H(Qubit /*target*/) override { throw std::logic_error("not_implemented: H"); } - void S(Qubit target) override + void S(Qubit /*target*/) override { throw std::logic_error("not_implemented: S"); } - void T(Qubit target) override + void T(Qubit /*target*/) override { throw std::logic_error("not_implemented: T"); } - void R(PauliId axis, Qubit target, double theta) override + void R(PauliId /* axis */, Qubit /* target */, double /* theta */) override { throw std::logic_error("not_implemented: R"); } - void Exp(long numTargets, PauliId paulis[], Qubit targets[], double theta) override + void Exp(long /* numTargets */, PauliId* /* paulis */, Qubit* /* targets */, double /* theta */) override { throw std::logic_error("not_implemented: Exp"); } - void ControlledX(long numControls, Qubit controls[], Qubit target) override + void ControlledX(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledX"); } - void ControlledY(long numControls, Qubit controls[], Qubit target) override + void ControlledY(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledY"); } - void ControlledZ(long numControls, Qubit controls[], Qubit target) override + void ControlledZ(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledZ"); } - void ControlledH(long numControls, Qubit controls[], Qubit target) override + void ControlledH(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledH"); } - void ControlledS(long numControls, Qubit controls[], Qubit target) override + void ControlledS(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledS"); } - void ControlledT(long numControls, Qubit controls[], Qubit target) override + void ControlledT(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledT"); } - void ControlledR(long numControls, Qubit controls[], PauliId axis, Qubit target, double theta) override + void ControlledR(long /*numControls*/, Qubit* /*controls*/, PauliId /*axis*/, Qubit /*target*/, double /*theta*/) override { throw std::logic_error("not_implemented: ControlledR"); } void ControlledExp( - long numControls, - Qubit controls[], - long numTargets, - PauliId paulis[], - Qubit targets[], - double theta) override + long /*numControls*/, + Qubit* /*controls*/, + long /*numTargets*/, + PauliId* /*paulis*/, + Qubit* /*targets*/, + double /*theta*/) override { throw std::logic_error("not_implemented: ControlledExp"); } - void AdjointS(Qubit target) override + void AdjointS(Qubit /*target*/) override { throw std::logic_error("not_implemented: AdjointS"); } - void AdjointT(Qubit target) override + void AdjointT(Qubit /*target*/) override { throw std::logic_error("not_implemented: AdjointT"); } - void ControlledAdjointS(long numControls, Qubit controls[], Qubit target) override + void ControlledAdjointS(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledAdjointS"); } - void ControlledAdjointT(long numControls, Qubit controls[], Qubit target) override + void ControlledAdjointT(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("not_implemented: ControlledAdjointT"); } - Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override + Result Measure(long /*numBases*/, PauliId* /*bases*/, long /*numTargets*/, Qubit* /*targets*/) override { throw std::logic_error("not_implemented: Measure"); } - void ReleaseResult(Result result) override + void ReleaseResult(Result /*result*/) override { throw std::logic_error("not_implemented: ReleaseResult"); } - bool AreEqualResults(Result r1, Result r2) override + bool AreEqualResults(Result /*r1*/, Result /*r2*/) override { throw std::logic_error("not_implemented: AreEqualResults"); } - ResultValue GetResultValue(Result result) override + ResultValue GetResultValue(Result /*result*/) override { throw std::logic_error("not_implemented: GetResultValue"); } diff --git a/src/Qir/QIR-RT-API-Design-Guidelines.md b/src/Qir/QIR-RT-API-Design-Guidelines.md new file mode 100644 index 00000000000..ccdbdbd7a5f --- /dev/null +++ b/src/Qir/QIR-RT-API-Design-Guidelines.md @@ -0,0 +1,601 @@ +# QIR Runtime API Design Guidelines + +## Contents +* [Summary of This Document](#summary-of-this-document) +* [Guidelines](#guidelines) + * [Terms](#terms) + * [Know the Issues When Crossing the ABI Boundary](#know-the-issues-when-crossing-the-abi-boundary) + * [Be Aware of Different Compilers for Different Binaries](#be-aware-of-different-compilers-for-different-binaries) + * [Expose as Little as Necessary](#expose-as-little-as-necessary) + * [Expose C Only](#expose-c-only) + * [Allocate and Deallocate In The Same Binary](#allocate-and-deallocate-in-the-same-binary) + * [Avoid Exposing Entities With Multiple Layouts](#avoid-exposing-entities-with-multiple-layouts) + * [Be Careful When Exposing Certain Data Types](#be-careful-when-exposing-certain-data-types) + * [Integer Types](#integer-types) + * [`char`](#char) + * [`enum`](#enum) + * [`bool`](#bool) + * [Floating-Point Types](#floating-point-types) + * [Don't Let the C++ Exceptions Cross the ABI Boundary](#dont-let-the-c-exceptions-cross-the-abi-boundary) +* [Considerations Taken Into Account](#considerations-taken-into-account) + * [The `bool` Type](#the-bool-type) + * [The `char` Type](#the-char-type) + * [The `enum` Type](#the-enum-type) + * [The Floating-Point Types](#the-floating-point-types) + * [The Integer Types](#the-integer-types) +* [Information Sources](#information-sources) + +## Summary of This Document + +At the moment of writing the [QIR Runtime](https://github.com/microsoft/qsharp-runtime/tree/main/src/Qir/Runtime) +was a set of dynamic libraries exposing the API in the form of both C and C++ entities +(plus IR wrappers around certain C functions). +The C++ part and some other features, according to +[this post](https://stackoverflow.com/questions/22797418/how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dll/22797419#22797419), +were to cause problems for the users. + +Since the QIR Runtime was going public, we needed to mitigate those problems. These guidelines were among the first steps in that effort, +intended to provide the instructions about what to do and what to avoid when designing the QIR Runtime API or making changes to it. + +Those who need just the summary of recommendations (the _result_ of the decision-making process), +can run throuhg the [Guidelines](#guidelines) section. +Those who would like to see the grounds/reasoning behind the decisions made, can see the +[Considerations Taken Into Account](#considerations-taken-into-account) section. + + +## Guidelines + +### Terms + +**ABI** - [Application Binary Interface](https://en.wikipedia.org/wiki/Application_binary_interface) +**binary** - A dynamic library or an executable file. +The interacting binaries can be written in different languages and built with different compilers. +**cross-ABI interface** - The interface between the binaries. +This document mostly deals with the interface between the QIR Runtime dynamic library on the one hand, +and the executable and other dynamic libraries (like simulator) on the other hand. +**IR** - +[intermediate representation](https://blog.gopheracademy.com/advent-2018/llvm-ir-and-go/#:~:text=LLVM%20IR%20is%20a%20low,number%20of%20function%20local%20registers.&text=Front%2Dend%3A%20compiles%20source%20language,compiles%20IR%20to%20machine%20code.) +of [LLVM](https://llvm.org/). +**QIR** - quantum intermediate representation - a subset of the IR. Is generated by the Q# compiler from the Q# code. +**RT** - Runtime. + +### Know the Issues When Crossing the ABI Boundary +See +[the post](https://stackoverflow.com/questions/22797418/how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dll/22797419#22797419) +that resulted in these guidelines. + +### Be Aware of Different Compilers for Different Binaries +Take into account that different binaries can be written in different languages, by different people, +built with different compilers or different versions of the compiler, +with different command line arguments, by different scripts, on different machines. + +Bear in mind that QIR RT API needs to be callable from other languages and environments. +At the moment of writing the particular plan was to call the QIR RT API from the +[IQ#](https://ms-quantum.visualstudio.com/Quantum%20Program/_wiki/wikis/Quantum-OKRs.wiki/470/IQSharp). +Next, there was a chance of calling from Python, and possibly Rust. + +Avoid using the features that preclude calling the QIR RT API from other languages and environments. + + +### Expose as Little as Necessary +Every piece of code has a long-term cost associated with it. +The more code we write, the more bugs we can introduce, the more care is needed during migration to the newer compiler +and while porting to a different platform. The higher is the risk of identifier conflict with the other people's code +and new features of the language. The more identifiers we use, the less freedom remains for a different use of those identifiers. + +### Expose C Only +Do not expose the C++ identifiers and features (not present in C) - +the C++ data types (e.g. `class`), templates, function overloads, namespaces, etc. +To facilitate that for yourself and for the others, consider strictly separating +the C and C++ code into .h/.c files for C, and .hpp/.cpp. files for C++. Expose C headers only. + +If the exposed headers need to include the other headers, then include the C headers only, not the C++ headers. +In particular, the standard C headers, +e.g. [``](https://en.cppreference.com/w/c/io), have the corresponding wrapping headers in C++, +e.g. [``](https://en.cppreference.com/w/cpp/io/c). Such wrapping headers add the namespace prefix +`std::` to certain C declarations, but not all. The C++'s wrapping headers +may enclose the corresponding C header in the `extern "C"` block (and probably do something else). +This is the right thing for the C++ code internal to a binary, but not what we want in the API exposed by the QIR RT. +So, in the C headers that QIR RT exposes, include C's header `` (if needed), but not C++'s header ``. + +Everything that your C header exposes, needs to be within the +[`extern "C"`](https://en.cppreference.com/w/cpp/language/language_linkage) block +(visible to C++ compiler but not to C compiler, because `extern "C"` is a C++ feature). Example: +```c++ +#ifdef __cplusplus +extern "C" +{ +#endif + +.. // `#include`s +.. // Exposed API. + +#ifdef __cplusplus +} // extern "C" +#endif +``` +Such that the exposed API will be treated as C code (code having C language linkage), +regardless of whether this header is included by the C or C++ source file. + +### Allocate and Deallocate In The Same Binary +Since different binaries can be built with differnt compilers, +these bianaries can be linked with different C/C++ runtimes, i.e. they can be using different heaps. +The allocation made in a heap must be deallocated in the same heap. +I.e. the allocation and corresponding deallocation must be done by the same binary. +If the binary exposes a function that returns a pointer to the heap-allocated block, +then that binary must also expose a function that deallocates such a pointer. +Otherwise the heap used for allocation can leak the data, +and the heap used for deallocation can be corrupted. + +### Avoid Exposing Entities With Multiple Layouts +Avoid exposing the entities that may have different layouts in different binaries. +E.g. the alignment padding between the `struct` fields +can be different in the QIR RT dynamic library and the executable that uses the QIR RT. Thus + +_Avoid exposing_ `struct`. + +If you need to expose a mechanism that manipulates a `struct` instance, then you can expose +* a function that allocates the `struct` and returns the `void*` (`void` pointer) to it, +* a function that deallocates the `struct`, taking the `void*` parameter, +* functions that manipulate the `struct`, taking the `void*` to the `struct` as the first argument. + +E.g. for the +[`QirArray`](https://github.com/microsoft/qsharp-runtime/blob/9eb00a0f3f4beb1eee97dccb23e16ad995f5398e/src/Qir/Runtime/public/QirTypes.hpp#L15) + +| Instead Of | Possible Option | +|--------------------------------------------------------------------|---------------------| +| `QirArray::QirArray(int64_t cQubits)` (constructor) | `void* QirArray_NewQubits(int64_t qubitCount)` | +| `QirArray::QirArray(const QirArray* other)` (copy constructor) | `void* QirArray_NewCopy(void* other)` | +| `QirArray::~QirArray()` (destructor) | `void QirArray_Delete(void *)` | +| `char* QirArray::GetItemPointer(int64_t index)` (memeber function) | `void* QirArray_GetItemPointer(uint64_t index)` | + +Note: In IR the `void*` is not available as a type. You can expose `i8*` instead, and in the calling C/C++ code use `int8_t*`. + +If it is still better to expose `struct` then strictly document the `struct` layout and size your code expects. +You can enforce the layout checks with the +[compile-time C assertions](https://en.cppreference.com/w/c/error/static_assert): +```c +#include // offsetof() +#include // static_assert() + +typedef struct +{ + char c; // 1-byte field. + // 3 bytes of alignment gap. + uint32_t u32; // Is expected at offset 4. + // Total size is 8. +} +MyStruct; + +static_assert(offsetof(MyStruct, u32) == sizeof(uint32_t), "Unexpected layout of `MyStruct`"); +static_assert(sizeof(MyStruct) == (2 * sizeof(uint32_t)), "Unexpected size of `MyStruct`"); +``` + +Note: The +[`#pragma pack`](https://stackoverflow.com/questions/3318410/pragma-pack-effect/3318475#3318475) +is not guaranteed to help your exposed API. The pragma can be silently ignored by the user's compiler. + +Note: For providing a direct access to the `struct` fileds +you may want to expose functions that return the address of a particular field inside of the `struct`: +```c +uint32_t* MyStruct_GetU32Ptr(void* pStruct); // Returns the address of `u32` field of a struct + // pointed to by the parameter. +``` + +### Be Careful When Exposing Certain Data Types +The size (in bytes) of such data types as `bool`, C's `enum`, `signed` and `unsigned` {`short`, `int`, `long`, `long long`} +is implementation-specific, thus can be different on different sides of the ABI boundary. +In general prefer the fixed-size alternatives. See the type-specific details below. + +#### Integer Types +In the exposed API, for integers, prefer the fixed-size types defined in +[``](https://www.cplusplus.com/reference/cstdint/?kw=stdint.h), e.g. `uint8_t`, `int32_t`, etc. + +Note: Avoid using in the exposed API the _non-fixed-size_ types from that header, +such as `uint_least8_t`, `uint_fast16_t` (but feel free to use them internally in your binary). + +Note: If you use the [printf-family functions](https://man7.org/linux/man-pages/man3/printf.3.html) +for formatted output of the types defined in ``, +or [scanf-family functions](https://man7.org/linux/man-pages/man3/scanf.3.html) for formatted input, +then use the macros defined in [``](https://www.cplusplus.com/reference/cinttypes/). +Example: +```c +#include // uint32_t +#include // SCNu32, PRIu32 +#include // scanf(), printf() + +uint32_t u32; // Fixed-size variable. +scanf(SCNu32, &u32); // Formatted input. +printf("Value is " PRIu32 "\n", u32); // Formatted output. +``` + +#### `char` +The size of types `char`, `unsigned char`, `signed char` is fixed and is equal to 1 byte. + +In the exposed API it is safe to use `char` for characters and C strings (null-terminated sequences of `char`s). + +For integers of size 1 prefer `uint8_t` or `int8_t`. If you use `char` for integers, +then specify explicitly either `unsigned char` or `signed char`, +because the plain `char` is interpreted differently in different implementations. +In some implementations `char` defaults to `unsigned char`, in the others - to `signed char`. + +#### `enum` +The [C's `enum`](https://en.cppreference.com/w/c/language/enum) type is _non-fixed-size_ +(as opposed to [C++'s `enum`](https://en.cppreference.com/w/cpp/language/enum) that can have an _underlying type_). + +The code will be safe if you do not use `enum` type in the function signatures (function parameter types or return type). +Exposing `enum` as a stand-alone type (and exposing variables of that type) requires care, +control over the compatible fixed-size types that are safe to cast the `enum` to. +So, think twice before exposing `enum`. Document well the ranges of values, safe types to cast to, etc. +Use mechanisms that prevent the erroneous use. +See details in [The `enum` Type](#the-`enum`-type) section. + +If you still plan to expose `enum`: + +Note: In C language if you define the `enum` type like this: +```c +enum MyEnumType +{ + .. +}; +``` +then _in C_ you will have to write `enum MyEnumType`, i.e. 2 words, every time you use the `enum`. +If you want to use just 1 word - `MyEnumType` - you can define the `enum` type like this: +```c +typedef enum +{ + .. +} +MyEnumType; +``` + +Note: You can expose the enumerators only (rather that the whole `enum` type). +But remember, the enumerators in C have the type `int` - _non-fixed-size type_. +```c +enum +{ + ENUMERATOR_A, // Has type `int`. + ENUMERATOR_B // Has type `int`. +}; +``` +Trick: Chris Granade has shown how Rust's cbindgen exposes enums to C as having a fixed size: +```c +enum Pauli +{ + I = 0, + X = 1, + Y = 3, + Z = 2, +}; +typedef uint8_t Pauli; +``` +As long as in the exposed API you type `Pauli` (rather than `enum Pauli`), you use the fixed-size type `uint8_t`. +And the type `enum Pauli` defines the range of values +and the constants that can be used with `Pauli` type (internally in the binaries). + +#### `bool` +The `bool` type is non-fixed-size (both in [C](https://en.cppreference.com/w/c/types/boolean) and +[C++](https://en.cppreference.com/w/cpp/language/types#Boolean_type)). +Be careful if using `bool` in the exposed API, prefer a fixed-size integer type instead (e.g. `uint8_t`). + +If using fixed-size integer instead of `bool`, strictly document the range of values, +in particular what value corresponds to the concept of `true` +(value of `1`, or any non-zero value, or other). + +For your information: [In Rust](https://doc.rust-lang.org/reference/types/boolean.html) +> **An object with the boolean type has a size** and alignment **of 1** each. +The value false has the bit pattern `0x00` and the value true has the bit pattern `0x01`. + +Note: One may want for `bool` to follow the Chris Granade's trick for `enum` mentioned above. +Example: +```c +// enum Bool : uint8_t { False, True }; +typedef uint8_t Bool; +``` +As long as in the exposed API you write `Bool` (rather than `bool`), you use the fixed-size type `uint8_t`. +And the comment explains the type `Bool`. + +When choosing the range of values, +and for the safe internal use of Booleans and their substitutes, see also +* [Avoid Comparing Booleans to `true`](https://github.com/kuzminrobin/code_review_notes/blob/master/cpp_design_bookmarks.md#avoid-comparing-booleans-to-true) +* [Know the Limitations of `memset()` When Initializing](https://github.com/kuzminrobin/code_review_notes/blob/master/cpp_design_bookmarks.md#know-the-limitations-of-memset-when-initializing) + + +#### Floating-Point Types + +Strictly speaking, the floating-point types (`float`, `double`, `long double`) are non-fixed-size. +However these guidelines suggest to expect that the following floating-point types conform to the same standards +on both sides of the ABI boundary: +* `float` matches IEEE-754 _32 bit_ floating point type (IEC 60559 single format), +* `double` matches IEEE-754 _64 bit_ floating point type (IEC 60559 double format). + +To detect the size discrepancy, you can use the following +[compile-time C assertions](https://en.cppreference.com/w/c/error/static_assert) +in the exposed C headers (if your exposed API uses a floating-point type): +```c +static_assert(sizeof(float ) == sizeof(uint32_t), "Unexpected size of `float`" ); +static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected size of `double`"); +``` +As for [`long double` type](https://en.cppreference.com/w/c/language/arithmetic_types#Real_floating_types), +the decision has not been made at the time of writing. If you need to expose the mechanisms that use this type, +then you can follow the recomendations in the +[Avoid Exposing Entities With Multiple Layouts](#avoid-exposing-entities-with-multiple-layouts) section. + +The examples of where the floating-point types were used at the moment of writing are +* [`%String* @__quantum__rt__double_to_string(double %val)`](https://github.com/microsoft/qsharp-runtime/blob/a15856ea9aac6244718865481dbbd89b70056166/src/Qir/Runtime/lib/QIR/bridge-rt.ll#L457), +* [`QIR_SHARED_API QirString* quantum__rt__double_to_string(double)`](https://github.com/microsoft/qsharp-runtime/blob/4ee33c0a1fb89cc859254175fea74b841a4fd01c/src/Qir/Runtime/public/QirRuntime.hpp#L203), + +a number of math functions, e.g. +* [`dllexport double @__quantum__qis__sin__body(double %theta)`](https://github.com/microsoft/qsharp-runtime/blob/4ee33c0a1fb89cc859254175fea74b841a4fd01c/src/Qir/Runtime/lib/QSharpFoundation/qsharp-foundation-qis.ll#L121). + + +### Don't Let the C++ Exceptions Cross the ABI Boundary +Our cross-ABI interface is a subset of C interface. The C interface does not include the C++ exceptions. The C++ exceptions +crossing the ABI Boundary are likely to stay uncaught and result in a crash. +If you are still thinking about designing an interface that tunnels the C++ exceptions through our subset of C +(an interface where the C++ exceptions are thrown on one side and caught on the other side of the ABI boundary), +then you should know the following. + +In some implementations the C++ exception instances are allocated on the heap during `throw`, and then deallocated during `catch`. +If `throw` and `catch` are on different sides of the ABI boundary then they are likely to use different heaps. +The consequence is described in the +[Allocate and Deallocate In The Same Binary](#allocate-and-deallocate-in-the-same-binary) section above. + +At the moment of writing the +[Fail-Statement](https://docs.microsoft.com/en-us/azure/quantum/user-guide/language/statements/returnsandtermination#fail-statement) +implementation +([`quantum__rt__fail_cstr()`](https://github.com/microsoft/qsharp-runtime/blob/b10447eff672b7ba4f2e8564302888a8eb6bce8a/src/Qir/Runtime/lib/QIR/utils.cpp#L60)) +was throwing an exception not caught anywhere. +This was causing a reasonably peaceful termination as long as the same compiler was used on both sides of the ABI boundary. +The plan was to replace the exception throw with the [`exit()`](https://www.cplusplus.com/reference/cstdlib/exit/) +or [`terminate()`](https://www.cplusplus.com/reference/exception/terminate) +or [`abort()`](https://www.cplusplus.com/reference/cstdlib/abort) or other alternatives. + +On the other hand, exceptions are a natural part of the C++ standard. It is virtually impossible to use +the standard library (STL, `std` namespace) and not to use exceptions. All _exceptional_ situations are +reported by using exceptions (such as [`out_of_range`](https://en.cppreference.com/w/cpp/error/out_of_range) or [`invalid_argument`](https://en.cppreference.com/w/cpp/error/invalid_argument) to name a few). So it is safe to assume +that the C++ code we are developing will throw exceptions even if they aren't thrown right in the code we write. + +Recommendation is to use `std` namespace and exceptions in C++ code **only where appropriate**. Many recommendations +exist on how to use exceptions properly and minimize performance penalties. +* As part of [C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). +* MSVC Documentation: [Modern C++ best practices for exceptions and error handling](https://docs.microsoft.com/en-us/cpp/cpp/errors-and-exception-handling-modern-cpp?view=msvc-160) and [How to: Design for exception safety](https://docs.microsoft.com/en-us/cpp/cpp/how-to-design-for-exception-safety?view=msvc-160). +* [Exceptions and Error Handling](https://isocpp.org/wiki/faq/exceptions) As part of Modern C++ FAQ. +* Google guidelines recommend against exceptions, but [allow them in Windows code, especially while using STL](https://google.github.io/styleguide/cppguide.html#Windows_Code). +* [Vishal Chovatiya Guidelines](http://www.vishalchovatiya.com/7-best-practices-for-exception-handling-in-cpp-with-example/), which may be easier to read. +This section will describe only exceptions with regards to the runtime API. + +* Do not handle hardware exceptions and out-of-memory exception + +It is not recommended to catch asynchronous harware exceptions. (MSVC compiler calls this +[Structured Exception Handling](https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160).) +Examples of hardware exceptions are the exceptions thrown when accessing a forbidden memory location as a result of a null pointer access, stack overflow, corrupt pointer dereferencing, unaligned memory access, etc.. If such exceptional situation arises, application should fail fast and quit. Typically hardware exceptions are handled in a system level code, and our runtime is an application with +regards to the host OS. NOTE: The [MSVC's default exception handling behavior](https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-160#default-exception-handling-behavior) does not conform to the C++ standard. Use the `/EHsc` flag to [conform to the standard](https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-160#standard-c-exception-handling). + +It is also not recommended to handle out-of-memory exception as there's a very little chance of handling it right. +For such handling to work correctly there shouldn't be any memory allocation from the point of exception, in the stack +unwinding process, till the handling of exception and in the handling of exception until the out-of-memory situation +is resolved. Just let the application fail. For the same reason, using non-throwing version of operator new is not recommended. + +* Mark interface functions with `noexcept` + +If you implement an interface function mark it with the [noexcept specifier](https://en.cppreference.com/w/cpp/language/noexcept_spec): + +Inside of your binary if you implement a function that should not throw exceptions, then mark it with the [noexcept specifier](https://en.cppreference.com/w/cpp/language/noexcept_spec). + +> void interface_func() **noexcept** + +The `extern "C"` functions aren't considered `noexcept` by default. Consider marking them `noexcept`. +Keep in mind that `noexcept` specifier is a C++ feature. If you need to add `noexcept` specifier +to a public header or any other header that should be compatible with a C compiler +make sure to use `__cplusplus` guards. For example: + +```c++ +#ifdef __cplusplus +#define NOEXCEPT noexcept // `noexcept` in C++ only. +extern "C" +{ +#else +#define NOEXCEPT // In C this macro is empty. +#endif + +.. // `#include`s +void f() NOEXCEPT; // Exposed function. It is `noexcept` if the header is included to the C++ source file. + +#ifdef __cplusplus +} // extern "C" +#endif +``` + +If exception is thrown (and not caught) within the `noexcept` function, the application will terminate. Termination will +ensure that exceptions will never cross the API boundaries. An overhead is incurred if a noexcept function +calls functions that can throw exceptions, so consider marking all functions that shouldn't throw exceptions as `noexcept`. +If you are calling functions that can throw exceptions, make sure to handle them properly. + +* If using the [`dynamic_cast<>()`](https://en.cppreference.com/w/cpp/language/dynamic_cast), +consider casting the _pointers_ rather than _references_, e.g. +```c++ +MyClass myInstance = .. // Create an instance of a class that may or may not implement the `IMyInterface` interface. +IMyInterface & myItfRef = dynamic_cast( myInstance); // Casts the _reference_, in case of failure THROWS! Avoid using this. +IMyInterface * myItfPtr = dynamic_cast(&myInstance); // Casts the _pointer_, in case of failure returns the nullptr. Consider this first. +``` +This way you can handle the erroneous situation without raising exceptions. + +## Considerations Taken Into Account + +### The `bool` Type + +In C language `bool` is a macro that expands to the keyword `_Bool` ([details](https://en.cppreference.com/w/c/types/boolean)). + +[[C17_N2176](https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf)], +6.2.5 Types, paragraph 2 +> An object declared as type `_Bool` **is large enough to store the values 0 and 1**. + +I.e. the size of `bool` is _at least 1 bit_, but there is no upper limit. Type `bool` is _non-fixed-size_. + +Be careful if using `bool` in the exposed API, prefer a fixed-size integer type instead (e.g. `uint8_t`). +Illustration: +* Your dynamic library exposes the `bool` global variable - `extern bool MyVar;`. +* In your dynamic library the type `bool` is _1 byte_. +* The user's executable includes your header. +* In the user's executable the type `bool` is _4 bytes_. +* When the user's code updates your variable - `MyVar = true`, the code updates _4 bytes_ (rather than _1_) - +the byte of your variable and _3 more bytes outside of it_ (thus corrupting the data after `MyVar`). + +### The `char` Type + +**`sizeof(char)`, `sizeof(unsigned char)`, `sizeof(signed char)`, are 1** +[[C17_N2176](https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf)]: +6.5.3.4 The `sizeof` and `_Alignof` operators, paragraph 4: +> When `sizeof` is applied to an operand that has type `char`, `unsigned char`, or `signed char`, +(or a qualified version thereof) the result is 1. + +**`char` can default to `signed char` or `unsigned char`** +6.2.5 Types, paragraph 15: +> The three types `char`, `signed char`, and `unsigned char` are collectively called the _character types_. +The **implementation shall define** `char` to have the same range, +representation, and behavior as **either `signed char` or `unsigned char`**.45) + +> 45\) `CHAR_MIN`, defined in ``, will have one of the values 0 or `SCHAR_MIN`, +and this can be used to distinguish the two options. Irrespective of the choice made, +**`char` is a separate type from the other two and is not compatible with either**. + +So, don't use `char` for integers, use `unsigned char` or `signed char` instead. + +6.3.1.1 Boolean, characters, and integers. Paragraph 3 +> ... whether a "plain" char can hold negative values is **implementation-defined**. + + +### The `enum` Type +[[C17_N2176](https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf)] +6.7.2.2 Enumeration specifiers, Paragraph 4 +> Each enumerated type shall be compatible with `char`, a signed integer type, or an unsigned integer type. +**The choice of type is implementation-defined**... + +I.e. the [C's `enum`](https://en.cppreference.com/w/c/language/enum) type is _non-fixed-size_ +(as opposed to [C++'s `enum`](https://en.cppreference.com/w/cpp/language/enum) that can have an _underlying type_). + + +**The `enum` in function signatures** +The code will be safe if you _do not_ use `enum` type in the function signatures (function parameter types or return type). + +Illustration: +* Let's say your dynamic library exposes a function having a parameter `e` of the `enum` type, +and some parameters before and after it: +```c +typedef enum { .. } MyEnumType; +void func(uint16 uia, MyEnumType e, uint16_t uib); +``` +* Let's imagine that (according to the [calling convention](https://en.wikipedia.org/wiki/Calling_convention)) +the parameter `e` needs to be passed through the _stack_ (rather than a CPU register). +* Let's say in your dynamic library the `enum` type is 2 bytes in size. So, during the call, +the 2 bytes need to be allocated on the stack for the parameter `e`. +* Immediately after it, the 2 bytes need to be allocated on the stack for the parameter `uib` +(or the parameter `uia`, the order of the parameters in the stack depends on the +[language linkage](https://en.cppreference.com/w/cpp/language/language_linkage) and/or +calling convention). +* The user's executable includes your header and calls your function `func()`. +* But in the user's executable the `MyEnumType` is _4 bytes_ in size. +So the 4 bytes are allocated on the stack (instead of 2). +* And the value for parameter `uib` is pushed to the stack _after those 4 bytes_, +to a place where the function `func()` does not expect it. + +To summarize, the caller passes `uib` through that part of the stack where the callee `func()` does not expect it, +and where the callee expects it, the 0 is passed. + +**The stand-alone `enum`** +Exposing `enum` as a stand-alone type (and exposing variables of that type) requires care, +control over the compatible fixed-size types that are safe to cast the `enum` to. + +Illustration: +* You need your dynamic library to expose a function having a parameter of the `enum` type. +* You expose the `enum` type separately. +```c +typedef enum +{ + ENUMERATOR_A, + ENUMERATOR_B +} +MyEnumType; +``` +* You expose the function, but, to be safe, you expose the parameter as a fixed-size type `uint8_t`. +```c +void func(uint8_t param); +``` + +The user's executable uses your dynamic library. The executable's source file (.c) includes your header +(that exposes the `enum` type and the function). The user's code creates a local varaible of the `enum` type, +initializes the variable with one of the enumerators (of that `enum` type), +and passes the variable as an argument to your function. +The argument (of `enum` type) is implicitly cast to the type of the parameter - `uint8_t`. + +_Is it safe_ to implicitly cast such an `enum` to `unit8_t`? Or, maybe the `enum` type has enumerators with value above `0xFF`, +and that's why the `enum` type is at least 2 bytes in size +(and upon cast to `uint8_t` only the least significant byte will end up in the parameter)? + +Do you rely on the _C_ compiler to warn? +Even if it _does_ warn, the programmers sometimes just carelessly _explicitly_ cast `enum` to `unit8_t` +(without the anlysis of the value range). + +So, think twice before exposing `enum`. Document well the ranges of values, safe types to cast to, etc. +Use mechanisms that prevent the erroneous use, e.g. +[compile-time C assertions](https://en.cppreference.com/w/c/error/static_assert): +```c +static_assert(ENUMERATOR_B <= UINT8_MAX, + "`MyEnumType` is incompatible with the parameter of `func()`. " + "Increase the parameter type"); +``` + + +### The Floating-Point Types + +The explicit specification of the *size* for the floating-point types was not found in one of the late drafts +of the C17 standard (see [Information Sources](#information-sources)). However the section "F.2 Types" says: +> — The `float` type matches the IEC 60559 single format. +— The `double` type matches the IEC 60559 double format. + +Unfortunately in the ISO/IEC 60559, Edition 2.0 2020-05 (and IEEE Std 754™-2019, IEEE Std 754™-2008), the size specification +of "single format" and "double format" was not found. + +However the C's [Real floating types](https://en.cppreference.com/w/c/language/arithmetic_types#Real_floating_types) +section of cppreference states: +> +* `float` - single precision floating point type. Matches IEEE-754 32 bit floating point type if supported. +* `double` - double precision floating point type. Matches IEEE-754 64 bit floating point type if supported +* `long double` - extended precision floating point type. Matches IEEE-754 extended floating-point type if supported, +otherwise matches some non-standard extended floating-point type as long as its precision is better than `double` +and range is at least as good as `double`, otherwise matches the type `double`. +Some x86 and x86_64 implementations use the 80-bit x87 floating point type. + +Also the following was taken into account +* [Single-precision floating-point format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) +(Wikipedia) +* [Double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) +(Wikipedia) +* [Fixed-size floating point types](https://stackoverflow.com/questions/2524737/fixed-size-floating-point-types) +(Stackoverflow) +* [What is the size of float and double in C and C++? [duplicate]](https://stackoverflow.com/questions/25524355/what-is-the-size-of-float-and-double-in-c-and-c) +(Stackoverflow) + + +### The Integer Types + +(Not strictly related to this document, but is still provided for those who are interested. +Also answers a number of other questions about the integer types, and supports certain statements in the other sections). + +It seemed commonly known that +`sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)` +But looks like C does not guarantee that, as opposed to C++, and the topic is rather arguable. +See details [here](https://stackoverflow.com/a/67678644/6362941). + +## Information Sources + +* [C/C++ Standards and Late Drafts](https://stackoverflow.com/a/83763/6362941) +* [[C11_N1570]](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) - One of the late drafts of C11. +* [[C11_N1570_HTML]](https://port70.net/~nsz/c/c11/n1570.html) - HTML version of the late draft. +* [[C17_N2176]](https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf) - +One of the late drafts of C17. +* IEEE Std 754™-2008. +* IEEE Std 754™-2019. +* [How do I safely pass objects, especially STL objects, to and from a DLL?](https://stackoverflow.com/questions/22797418/how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dll/22797419#22797419) (Stackoverflow answer). +* ISO/IEC 60559, Edition 2.0 2020-05. diff --git a/src/Qir/Runtime/CMakeLists.txt b/src/Qir/Runtime/CMakeLists.txt index 5aea1a8065f..378834a938b 100644 --- a/src/Qir/Runtime/CMakeLists.txt +++ b/src/Qir/Runtime/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) message(INFO "*** build config: ${CMAKE_BUILD_TYPE}") diff --git a/src/Qir/Runtime/lib/QIR/CMakeLists.txt b/src/Qir/Runtime/lib/QIR/CMakeLists.txt index 9dbd2b9cd74..08a30df8cfd 100644 --- a/src/Qir/Runtime/lib/QIR/CMakeLists.txt +++ b/src/Qir/Runtime/lib/QIR/CMakeLists.txt @@ -19,12 +19,16 @@ set(rt_sup_source_files delegated.cpp strings.cpp utils.cpp + QubitManager.cpp ) # Produce object lib we'll use to create a shared lib (so/dll) later on add_library(qir-rt-support-obj OBJECT ${rt_sup_source_files}) target_source_from_qir(qir-rt-support-obj bridge-rt.ll) -target_include_directories(qir-rt-support-obj PUBLIC ${public_includes}) +target_include_directories(qir-rt-support-obj PUBLIC + ${public_includes} + ${common_includes} +) set_property(TARGET qir-rt-support-obj PROPERTY POSITION_INDEPENDENT_CODE ON) target_compile_definitions(qir-rt-support-obj PRIVATE EXPORT_QIR_API) diff --git a/src/Qir/Runtime/lib/QIR/QubitManager.cpp b/src/Qir/Runtime/lib/QIR/QubitManager.cpp new file mode 100644 index 00000000000..2a254ca349f --- /dev/null +++ b/src/Qir/Runtime/lib/QIR/QubitManager.cpp @@ -0,0 +1,522 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "QubitManager.hpp" +#include "QirRuntime.hpp" // For quantum__rt__fail_cstr +#include // For memcpy + +namespace Microsoft +{ +namespace Quantum +{ + +// +// Failing in case of errors +// + +[[noreturn]] static void FailNow(const char* message) +{ + quantum__rt__fail_cstr(message); +} + +static void FailIf(bool condition, const char* message) +{ + if (condition) + { + quantum__rt__fail_cstr(message); + } +} + +// +// QubitListInSharedArray +// + +CQubitManager::QubitListInSharedArray::QubitListInSharedArray( + QubitIdType startId, + QubitIdType endId, + QubitIdType* sharedQubitStatusArray): + firstElement(startId), + lastElement(endId) +{ + FailIf(startId > endId || startId < 0 || endId == MaximumQubitCapacity, + "Incorrect boundaries in the linked list initialization."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + for (QubitIdType i = startId; i < endId; i++) { + sharedQubitStatusArray[i] = i + 1; // Current element points to the next element. + } + sharedQubitStatusArray[endId] = NoneMarker; // Last element ends the chain. +} + +bool CQubitManager::QubitListInSharedArray::IsEmpty() const +{ + return firstElement == NoneMarker; +} + +void CQubitManager::QubitListInSharedArray::AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray) +{ + FailIf(id == NoneMarker, "Incorrect qubit id, cannot add it to the list."); + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // If the list is empty, we initialize it with the new element. + if (IsEmpty()) + { + firstElement = id; + lastElement = id; + sharedQubitStatusArray[id] = NoneMarker; // List with a single elemenet in the chain. + return; + } + + if (addToFront) + { + sharedQubitStatusArray[id] = firstElement; // The new element will point to the former first element. + firstElement = id; // The new element is now the first in the chain. + } else + { + sharedQubitStatusArray[lastElement] = id; // The last element will point to the new element. + sharedQubitStatusArray[id] = NoneMarker; // The new element will end the chain. + lastElement = id; // The new element will be the last element in the chain. + } +} + +CQubitManager::QubitIdType CQubitManager::QubitListInSharedArray::TakeQubitFromFront(QubitIdType* sharedQubitStatusArray) +{ + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // First element will be returned. It is 'NoneMarker' if the list is empty. + QubitIdType result = firstElement; + + // Advance list start to the next element if list is not empty. + if (!IsEmpty()) + { + firstElement = sharedQubitStatusArray[firstElement]; // The second element will be the first. + } + + // Drop pointer to the last element if list becomes empty. + if (IsEmpty()) + { + lastElement = NoneMarker; + } + + if (result != NoneMarker) + { + sharedQubitStatusArray[result] = AllocatedMarker; + } + + return result; +} + +void CQubitManager::QubitListInSharedArray::MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray) +{ + FailIf(sharedQubitStatusArray == nullptr, "Shared status array is not provided."); + + // No need to do anthing if source is empty. + if (source.IsEmpty()) + { + return; + } + + if (this->IsEmpty()) + { + // If this list is empty, we'll just set it to the source list. + lastElement = source.lastElement; + } else + { + // Attach source at the beginning of the list if both lists aren't empty. + sharedQubitStatusArray[source.lastElement] = firstElement; // The last element of the source chain will point to the first element of this chain. + } + firstElement = source.firstElement; // The first element from the source chain will be the first element of this chain. + + // Remove all elements from source. + source.firstElement = NoneMarker; + source.lastElement = NoneMarker; +} + + +// +// RestrictedReuseArea +// + +CQubitManager::RestrictedReuseArea::RestrictedReuseArea( + QubitListInSharedArray freeQubits): + FreeQubitsReuseProhibited(), // Default costructor + FreeQubitsReuseAllowed(freeQubits) // Default shallow copy. +{ +} + + +// +// CRestrictedReuseAreaStack +// + +void CQubitManager::CRestrictedReuseAreaStack::PushToBack(RestrictedReuseArea area) +{ + FailIf(Count() >= std::numeric_limits::max(), "Too many nested restricted reuse areas."); + this->insert(this->end(), area); +} + +CQubitManager::RestrictedReuseArea CQubitManager::CRestrictedReuseAreaStack::PopFromBack() +{ + FailIf(this->empty(), "Cannot remove restricted reuse area from an empty set."); + RestrictedReuseArea result = this->back(); + this->pop_back(); + return result; +} + +CQubitManager::RestrictedReuseArea& CQubitManager::CRestrictedReuseAreaStack::PeekBack() +{ + return this->back(); +} + +int32_t CQubitManager::CRestrictedReuseAreaStack::Count() const +{ + // The size should never exceed int32_t. + return static_cast(this->size()); +} + +// +// CQubitManager +// + +CQubitManager::CQubitManager( + QubitIdType initialQubitCapacity, + bool mayExtendCapacity, + bool encourageReuse): + mayExtendCapacity(mayExtendCapacity), + encourageReuse(encourageReuse), + qubitCapacity(initialQubitCapacity) +{ + FailIf(qubitCapacity <= 0, "Qubit capacity must be positive."); + sharedQubitStatusArray = new QubitIdType[qubitCapacity]; + + // These objects are passed by value (copies are created) + QubitListInSharedArray FreeQubitsFresh(0, qubitCapacity - 1, sharedQubitStatusArray); + RestrictedReuseArea outermostArea(FreeQubitsFresh); + freeQubitsInAreas.PushToBack(outermostArea); + + freeQubitCount = qubitCapacity; +} + +CQubitManager::~CQubitManager() +{ + if (sharedQubitStatusArray != nullptr) + { + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = nullptr; + } + // freeQubitsInAreas - direct member of the class, no need to delete. +} + +// Although it is not necessary to pass area IDs to these functions, such support may be added for extra checks. +void CQubitManager::StartRestrictedReuseArea() +{ + RestrictedReuseArea newArea; + freeQubitsInAreas.PushToBack(newArea); +} + +void CQubitManager::NextRestrictedReuseSegment() +{ + FailIf(freeQubitsInAreas.Count() <= 0, "Internal error! No reuse areas."); + FailIf(freeQubitsInAreas.Count() == 1, "NextRestrictedReuseSegment() without an active area."); + RestrictedReuseArea& currentArea = freeQubitsInAreas.PeekBack(); + // When new segment starts, reuse of all free qubits in the current area becomes prohibited. + currentArea.FreeQubitsReuseProhibited.MoveAllQubitsFrom(currentArea.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +void CQubitManager::EndRestrictedReuseArea() +{ + FailIf(freeQubitsInAreas.Count() < 2, "EndRestrictedReuseArea() without an active area."); + RestrictedReuseArea areaAboutToEnd = freeQubitsInAreas.PopFromBack(); + RestrictedReuseArea& containingArea = freeQubitsInAreas.PeekBack(); + // When area ends, reuse of all free qubits from this area becomes allowed. + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseProhibited, sharedQubitStatusArray); + containingArea.FreeQubitsReuseAllowed.MoveAllQubitsFrom(areaAboutToEnd.FreeQubitsReuseAllowed, sharedQubitStatusArray); +} + +Qubit CQubitManager::Allocate() +{ + QubitIdType newQubitId = AllocateQubitId(); + FailIf(newQubitId == NoneMarker, "Not enough qubits."); + return CreateQubitObject(newQubitId); +} + +void CQubitManager::Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate) +{ + if (qubitCountToAllocate == 0) + { + return; + } + FailIf(qubitCountToAllocate < 0, "Cannot allocate negative number of qubits."); + FailIf(qubitsToAllocate == nullptr, "No array provided for qubits to be allocated."); + + // Consider optimization for initial allocation of a large array at once + for (int32_t i = 0; i < qubitCountToAllocate; i++) + { + QubitIdType newQubitId = AllocateQubitId(); + if (newQubitId == NoneMarker) + { + for (int32_t k = 0; k < i; k++) + { + Release(qubitsToAllocate[k]); + } + FailNow("Not enough qubits."); + } + qubitsToAllocate[i] = CreateQubitObject(newQubitId); + } +} + +void CQubitManager::Release(Qubit qubit) +{ + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); + ReleaseQubitId(QubitToId(qubit)); + DeleteQubitObject(qubit); +} + +void CQubitManager::Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease) { + if (qubitCountToRelease == 0) + { + return; + } + FailIf(qubitCountToRelease < 0, "Cannot release negative number of qubits."); + FailIf(qubitsToRelease == nullptr, "No array provided with qubits to be released."); + + for (int32_t i = 0; i < qubitCountToRelease; i++) + { + Release(qubitsToRelease[i]); + qubitsToRelease[i] = nullptr; + } +} + +Qubit CQubitManager::Borrow() +{ + // We don't support true borrowing/returning at the moment. + return Allocate(); +} + +void CQubitManager::Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow) +{ + // We don't support true borrowing/returning at the moment. + return Allocate(qubitsToBorrow, qubitCountToBorrow); +} + +void CQubitManager::Return(Qubit qubit) +{ + // We don't support true borrowing/returning at the moment. + Release(qubit); +} + +void CQubitManager::Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn) +{ + // We don't support true borrowing/returning at the moment. + Release(qubitsToReturn, qubitCountToReturn); +} + +void CQubitManager::Disable(Qubit qubit) +{ + FailIf(!IsValidQubit(qubit), "Qubit is not valid."); + QubitIdType id = QubitToId(qubit); + + // We can only disable explicitly allocated qubits that were not borrowed. + FailIf(!IsExplicitlyAllocatedId(id), "Cannot disable qubit that is not explicitly allocated."); + sharedQubitStatusArray[id] = DisabledMarker; + + disabledQubitCount++; + FailIf(disabledQubitCount <= 0, "Incorrect disabled qubit count."); + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); +} + +void CQubitManager::Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable) +{ + if (qubitCountToDisable == 0) + { + return; + } + FailIf(qubitCountToDisable < 0, "Cannot disable negative number of qubits."); + FailIf(qubitsToDisable == nullptr, "No array provided with qubits to be disabled."); + + for (int32_t i = 0; i < qubitCountToDisable; i++) + { + Disable(qubitsToDisable[i]); + } +} + +bool CQubitManager::IsValidId(QubitIdType id) const +{ + return (id >= 0) && (id < qubitCapacity); +} + +bool CQubitManager::IsDisabledId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == DisabledMarker; +} + +bool CQubitManager::IsExplicitlyAllocatedId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] == AllocatedMarker; +} + +bool CQubitManager::IsFreeId(QubitIdType id) const +{ + return sharedQubitStatusArray[id] >= 0; +} + + +bool CQubitManager::IsValidQubit(Qubit qubit) const +{ + return IsValidId(QubitToId(qubit)); +} + +bool CQubitManager::IsDisabledQubit(Qubit qubit) const +{ + return IsValidQubit(qubit) && IsDisabledId(QubitToId(qubit)); +} + +bool CQubitManager::IsExplicitlyAllocatedQubit(Qubit qubit) const +{ + return IsValidQubit(qubit) && IsExplicitlyAllocatedId(QubitToId(qubit)); +} + +bool CQubitManager::IsFreeQubitId(QubitIdType id) const +{ + return IsValidId(id) && IsFreeId(id); +} + +CQubitManager::QubitIdType CQubitManager::GetQubitId(Qubit qubit) const +{ + FailIf(!IsValidQubit(qubit), "Not a valid qubit."); + return QubitToId(qubit); +} + + +Qubit CQubitManager::CreateQubitObject(QubitIdType id) +{ + // Make sure the static_cast won't overflow: + FailIf(id < 0 || id >= MaximumQubitCapacity, "Qubit id is out of range."); + intptr_t pointerSizedId = static_cast(id); + return reinterpret_cast(pointerSizedId); +} + +void CQubitManager::DeleteQubitObject(Qubit /*qubit*/) +{ + // Do nothing. By default we store qubit Id in place of a pointer to a qubit. +} + +CQubitManager::QubitIdType CQubitManager::QubitToId(Qubit qubit) const +{ + intptr_t pointerSizedId = reinterpret_cast(qubit); + // Make sure the static_cast won't overflow: + FailIf(pointerSizedId < 0 || pointerSizedId > std::numeric_limits::max(), "Qubit id is out of range."); + return static_cast(pointerSizedId); +} + +void CQubitManager::EnsureCapacity(QubitIdType requestedCapacity) +{ + FailIf(requestedCapacity <= 0, "Requested qubit capacity must be positive."); + if (requestedCapacity <= qubitCapacity) + { + return; + } + // We need to reallocate shared status array, but there's no need to adjust + // existing values (NonMarker or indexes in the array). + + // Prepare new shared status array + QubitIdType* newStatusArray = new QubitIdType[requestedCapacity]; + memcpy(newStatusArray, sharedQubitStatusArray, qubitCapacity * sizeof(newStatusArray[0])); + QubitListInSharedArray newFreeQubits(qubitCapacity, requestedCapacity - 1, newStatusArray); + + // Set new data. All fresh new qubits are added to the free qubits in the outermost area. + freeQubitCount += requestedCapacity - qubitCapacity; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); + delete[] sharedQubitStatusArray; + sharedQubitStatusArray = newStatusArray; + qubitCapacity = requestedCapacity; + freeQubitsInAreas[0].FreeQubitsReuseAllowed.MoveAllQubitsFrom(newFreeQubits, sharedQubitStatusArray); +} + +CQubitManager::QubitIdType CQubitManager::TakeFreeQubitId() +{ + // Possible future optimization: we may store and maintain links to the next + // area with non-empty free list. Need to check amortized complexity... + + QubitIdType id = NoneMarker; + if (encourageReuse) + { + // When reuse is encouraged, we start with the innermost area + for (CRestrictedReuseAreaStack::reverse_iterator rit = freeQubitsInAreas.rbegin(); rit != freeQubitsInAreas.rend(); ++rit) + { + id = rit->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) + { + break; + } + } + } else + { + // When reuse is discouraged, we start with the outermost area + for (CRestrictedReuseAreaStack::iterator it = freeQubitsInAreas.begin(); it != freeQubitsInAreas.end(); ++it) + { + id = it->FreeQubitsReuseAllowed.TakeQubitFromFront(sharedQubitStatusArray); + if (id != NoneMarker) + { + break; + } + } + } + if (id != NoneMarker) { + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Allocated invalid qubit."); + allocatedQubitCount++; + FailIf(allocatedQubitCount <= 0, "Incorrect allocated qubit count."); + freeQubitCount--; + FailIf(freeQubitCount < 0, "Incorrect free qubit count."); + } + return id; +} + +CQubitManager::QubitIdType CQubitManager::AllocateQubitId() +{ + QubitIdType newQubitId = TakeFreeQubitId(); + if (newQubitId == NoneMarker && mayExtendCapacity) + { + QubitIdType newQubitCapacity = qubitCapacity * 2; + FailIf(newQubitCapacity <= qubitCapacity, "Cannot extend capacity."); + EnsureCapacity(newQubitCapacity); + newQubitId = TakeFreeQubitId(); + } + return newQubitId; +} + +void CQubitManager::ReleaseQubitId(QubitIdType id) +{ + FailIf(id < 0 || id >= qubitCapacity, "Internal Error: Cannot release an invalid qubit."); + if (IsDisabledId(id)) + { + // Nothing to do. Qubit will stay disabled. + return; + } + + FailIf(!IsExplicitlyAllocatedId(id), "Attempt to free qubit that has not been allocated."); + + if (mayExtendCapacity && !encourageReuse) + { + // We can extend capcity and don't want reuse => Qubits will never be reused => Discard qubit. + // We put it in its own "free" list, this list will never be found again and qubit will not be reused. + sharedQubitStatusArray[id] = NoneMarker; + } else + { + // Released qubits are added to reuse area/segment in which they were released + // (rather than area/segment where they are allocated). + // Although counterintuitive, this makes code simple. + // This is reasonable because qubits should be allocated and released in the same segment. (This is not enforced) + freeQubitsInAreas.PeekBack().FreeQubitsReuseAllowed.AddQubit(id, encourageReuse, sharedQubitStatusArray); + } + + freeQubitCount++; + FailIf(freeQubitCount <= 0, "Incorrect free qubit count."); + allocatedQubitCount--; + FailIf(allocatedQubitCount < 0, "Incorrect allocated qubit count."); +} + + +} +} diff --git a/src/Qir/Runtime/lib/QIR/arrays.cpp b/src/Qir/Runtime/lib/QIR/arrays.cpp index 4c58691a3f3..fa8ff504b36 100644 --- a/src/Qir/Runtime/lib/QIR/arrays.cpp +++ b/src/Qir/Runtime/lib/QIR/arrays.cpp @@ -198,7 +198,7 @@ static int64_t GetLinearIndex(const std::vector& dimensionSizes, const // It's equal to the product of the dimension sizes in higher dimensions. static int64_t RunCount(const std::vector& dimensionSizes, int dimension) { - assert(dimension < dimensionSizes.size()); + assert((0 <= dimension) && ((size_t)dimension < dimensionSizes.size())); return std::accumulate(dimensionSizes.begin() + dimension + 1, dimensionSizes.end(), 1, std::multiplies()); } diff --git a/src/Qir/Runtime/lib/QIR/bridge-rt.ll b/src/Qir/Runtime/lib/QIR/bridge-rt.ll index c328a845edf..6bf2efe9cbd 100644 --- a/src/Qir/Runtime/lib/QIR/bridge-rt.ll +++ b/src/Qir/Runtime/lib/QIR/bridge-rt.ll @@ -37,8 +37,6 @@ ;------------------------------------------------------------------------------ ; classical ; -declare i8* @quantum__rt__heap_alloc(i64) -declare void @quantum__rt__heap_free(i8*) declare i8* @quantum__rt__memory_allocate(i64) declare void @quantum__rt__fail(%"struct.QirString"*) @@ -121,15 +119,6 @@ declare void @quantum__rt__message(%"struct.QirString"* %str) ;------------------------------------------------------------------------------ ; classical bridge ; -define dllexport i8* @__quantum__rt__heap_alloc(i64 %size) { - %mem = call i8* @quantum__rt__heap_alloc(i64 %size) - ret i8* %mem -} - -define dllexport void @__quantum__rt__heap_free(i8* %mem) { - call void @quantum__rt__heap_free(i8* %mem) - ret void -} ; Returns a pointer to the malloc-allocated block. define dllexport i8* @__quantum__rt__memory_allocate(i64 %size) { diff --git a/src/Qir/Runtime/lib/QIR/callables.cpp b/src/Qir/Runtime/lib/QIR/callables.cpp index c7db55ea269..78d475e5f88 100644 --- a/src/Qir/Runtime/lib/QIR/callables.cpp +++ b/src/Qir/Runtime/lib/QIR/callables.cpp @@ -8,6 +8,7 @@ #include #include +#include "QirUtils.hpp" #include "QirContext.hpp" #include "QirTypes.hpp" #include "QirRuntime.hpp" @@ -370,7 +371,8 @@ QirTupleHeader* FlattenControlArrays(QirTupleHeader* tuple, int depth) QirArray* controls = current->controls; const size_t blockSize = qubitSize * controls->count; - assert(dst + blockSize <= dstEnd); + assert(dst + blockSize <= dstEnd); + UNUSED(dstEnd); memcpy(dst, controls->buffer, blockSize); dst += blockSize; // in the last iteration the innerTuple isn't valid, but we are not going to use it diff --git a/src/Qir/Runtime/lib/QIR/utils.cpp b/src/Qir/Runtime/lib/QIR/utils.cpp index 0110fd77fd0..9fd473c2d3a 100644 --- a/src/Qir/Runtime/lib/QIR/utils.cpp +++ b/src/Qir/Runtime/lib/QIR/utils.cpp @@ -13,39 +13,9 @@ #include "QirRuntime.hpp" #include "OutputStream.hpp" -static std::unordered_set& UseMemoryTracker() -{ - static std::unordered_set memoryTracker; - return memoryTracker; -} extern "C" { - // Allocate a block of memory on the heap. - char* quantum__rt__heap_alloc(uint64_t size) // NOLINT - { - char* buffer = new (std::nothrow) char[size]; - if(buffer == nullptr) - { - quantum__rt__fail(quantum__rt__string_create("Allocation Failed")); - } - #ifndef NDEBUG - UseMemoryTracker().insert(buffer); - #endif - return buffer; - } - - // Release a block of allocated heap memory. - void quantum__rt__heap_free(char* buffer) // NOLINT - { - #ifndef NDEBUG - auto iter = UseMemoryTracker().find(buffer); - assert(iter != UseMemoryTracker().end()); - UseMemoryTracker().erase(iter); - #endif - delete[] buffer; - } - char* quantum__rt__memory_allocate(uint64_t size) { return (char *)malloc((size_t)size); diff --git a/src/Qir/Runtime/lib/QSharpFoundation/AssertMeasurement.cpp b/src/Qir/Runtime/lib/QSharpFoundation/AssertMeasurement.cpp index 8f05e89ce2a..a18f85b17f9 100644 --- a/src/Qir/Runtime/lib/QSharpFoundation/AssertMeasurement.cpp +++ b/src/Qir/Runtime/lib/QSharpFoundation/AssertMeasurement.cpp @@ -36,7 +36,7 @@ extern "C" // Convert paulis from sequence of bytes to sequence of PauliId: std::vector paulis(bases->count); - for(size_t i = 0; i < bases->count; ++i) + for(int64_t i = 0; i < bases->count; ++i) { paulis[i] = (PauliId)(bases->buffer[i]); } diff --git a/src/Qir/Runtime/lib/Simulators/CMakeLists.txt b/src/Qir/Runtime/lib/Simulators/CMakeLists.txt index 589b53283e2..4277958afbb 100644 --- a/src/Qir/Runtime/lib/Simulators/CMakeLists.txt +++ b/src/Qir/Runtime/lib/Simulators/CMakeLists.txt @@ -13,6 +13,9 @@ set(includes # Produce object lib we'll use to create a shared lib (so/dll) later on add_library(simulators-obj OBJECT ${source_files}) -target_include_directories(simulators-obj PUBLIC ${includes}) +target_include_directories(simulators-obj PUBLIC + ${includes} + ${common_includes} +) set_property(TARGET simulators-obj PROPERTY POSITION_INDEPENDENT_CODE ON) target_compile_definitions(simulators-obj PRIVATE EXPORT_QIR_API) diff --git a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp index 6d9103d6779..e242cfec42d 100644 --- a/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/FullstateSimulator.cpp @@ -17,6 +17,7 @@ #include "QSharpSimApi_I.hpp" #include "SimFactory.hpp" #include "OutputStream.hpp" +#include "QubitManager.hpp" #ifdef _WIN32 #include @@ -58,14 +59,6 @@ QUANTUM_SIMULATOR LoadQuantumSimulator() return handle; } -bool UnloadQuantumSimulator(QUANTUM_SIMULATOR handle) -{ -#ifdef _WIN32 - return ::FreeLibrary(handle); -#else // not _WIN32 - return ::dlclose(handle); -#endif -} void* LoadProc(QUANTUM_SIMULATOR handle, const char* procName) { @@ -107,11 +100,13 @@ namespace Quantum const QUANTUM_SIMULATOR handle = 0; unsigned simulatorId = -1; - unsigned nextQubitId = 0; // the QuantumSimulator expects contiguous ids, starting from 0 + // the QuantumSimulator expects contiguous ids, starting from 0 + std::unique_ptr qubitManager; unsigned GetQubitId(Qubit qubit) const { - return static_cast(reinterpret_cast(qubit)); + // Qubit manager uses unsigned range of int32_t for qubit ids. + return static_cast(qubitManager->GetQubitId(qubit)); } std::vector GetQubitIds(long num, Qubit* qubits) const @@ -120,7 +115,7 @@ namespace Quantum ids.reserve(num); for (long i = 0; i < num; i++) { - ids.push_back(static_cast(reinterpret_cast(qubits[i]))); + ids.push_back(GetQubitId(qubits[i])); } return ids; } @@ -156,11 +151,12 @@ namespace Quantum typedef unsigned (*TInit)(); static TInit initSimulatorInstance = reinterpret_cast(this->GetProc("init")); + qubitManager = std::make_unique(); this->simulatorId = initSimulatorInstance(); } ~CFullstateSimulator() { - if (this->simulatorId != -1) + if (this->simulatorId != (unsigned)-1) { typedef unsigned (*TDestroy)(unsigned); static TDestroy destroySimulatorInstance = @@ -195,10 +191,10 @@ namespace Quantum typedef void (*TAllocateQubit)(unsigned, unsigned); static TAllocateQubit allocateQubit = reinterpret_cast(this->GetProc("allocateQubit")); - const unsigned id = this->nextQubitId; - allocateQubit(this->simulatorId, id); - this->nextQubitId++; - return reinterpret_cast(id); + Qubit q = qubitManager->Allocate(); // Allocate qubit in qubit manager. + unsigned id = GetQubitId(q); // Get its id. + allocateQubit(this->simulatorId, id); // Allocate it in the simulator. + return q; } void ReleaseQubit(Qubit q) override @@ -206,7 +202,8 @@ namespace Quantum typedef void (*TReleaseQubit)(unsigned, unsigned); static TReleaseQubit releaseQubit = reinterpret_cast(this->GetProc("release")); - releaseQubit(this->simulatorId, GetQubitId(q)); + releaseQubit(this->simulatorId, GetQubitId(q)); // Release qubit in the simulator. + qubitManager->Release(q); // Release it in the qubit manager. } Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override @@ -219,7 +216,7 @@ namespace Quantum m(this->simulatorId, numBases, reinterpret_cast(bases), ids.data())); } - void ReleaseResult(Result r) override {} + void ReleaseResult(Result /*r*/) override {} ResultValue GetResultValue(Result r) override { @@ -403,7 +400,7 @@ namespace Quantum Qubit targets[], double probabilityOfZero, double precision, - const char* failureMessage) override + const char* /*failureMessage*/) override { typedef double (*TOp)(unsigned id, unsigned n, int* b, unsigned* q); static TOp jointEnsembleProbability = reinterpret_cast(this->GetProc("JointEnsembleProbability")); diff --git a/src/Qir/Runtime/lib/Simulators/ToffoliSimulator.cpp b/src/Qir/Runtime/lib/Simulators/ToffoliSimulator.cpp index f02742fcfd1..b1556e1ebf8 100644 --- a/src/Qir/Runtime/lib/Simulators/ToffoliSimulator.cpp +++ b/src/Qir/Runtime/lib/Simulators/ToffoliSimulator.cpp @@ -5,6 +5,7 @@ #include #include +#include "QirUtils.hpp" #include "QirRuntimeApi_I.hpp" #include "QSharpSimApi_I.hpp" #include "SimFactory.hpp" @@ -42,7 +43,7 @@ namespace Quantum /// /// Implementation of IRuntimeDriver /// - void ReleaseResult(Result result) override {} + void ReleaseResult(Result /* result */) override {} bool AreEqualResults(Result r1, Result r2) override { @@ -75,6 +76,7 @@ namespace Quantum const long id = GetQubitId(qubit); assert(id <= this->lastUsedId); assert(!this->states.at(id)); + UNUSED(id); this->lastUsedId--; this->states.pop_back(); } @@ -88,7 +90,7 @@ namespace Quantum /// /// Implementation of IDiagnostics /// - bool Assert(long numTargets, PauliId* bases, Qubit* targets, Result result, const char* failureMessage) override + bool Assert(long numTargets, PauliId* bases, Qubit* targets, Result result, const char* /* failureMessage */) override { // Measurements in Toffoli simulator don't change the state. // TODO: log failureMessage? @@ -101,7 +103,7 @@ namespace Quantum Qubit targets[], double probabilityOfZero, double precision, - const char* failureMessage) override + const char* /* failureMessage */) override { assert(precision >= 0); @@ -111,17 +113,17 @@ namespace Quantum } // Deprecated, use `DumpMachine()` and `DumpRegister()` instead. - void GetState(TGetStateCallback callback) override + void GetState(TGetStateCallback /* callback */) override { throw std::logic_error("operation_not_supported"); } - void DumpMachine(const void* location) override + void DumpMachine(const void* /* location */) override { std::cerr << __func__ << " is not yet implemented" << std::endl; // #645 } - void DumpRegister(const void* location, const QirArray* qubits) override + void DumpRegister(const void* /* location */, const QirArray* /* qubits */) override { std::cerr << __func__ << " is not yet implemented" << std::endl; // #645 } @@ -154,7 +156,7 @@ namespace Quantum } - Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override + Result Measure(long numBases, PauliId bases[], long /* numTargets */, Qubit targets[]) override { bool odd = false; for (long i = 0; i < numBases; i++) @@ -175,81 +177,81 @@ namespace Quantum // // The rest of the gate set Toffoli simulator doesn't support // - void Y(Qubit target) override + void Y(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void Z(Qubit target) override + void Z(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void H(Qubit target) override + void H(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void S(Qubit target) override + void S(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void T(Qubit target) override + void T(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void R(PauliId axis, Qubit target, double theta) override + void R(PauliId /* axis */, Qubit /*target*/, double /* theta */) override { throw std::logic_error("operation_not_supported"); } - void Exp(long numTargets, PauliId paulis[], Qubit targets[], double theta) override + void Exp(long /* numTargets */, PauliId* /* paulis */, Qubit* /*targets*/, double /* theta */) override { throw std::logic_error("operation_not_supported"); } - void ControlledY(long numControls, Qubit controls[], Qubit target) override + void ControlledY(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledZ(long numControls, Qubit controls[], Qubit target) override + void ControlledZ(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledH(long numControls, Qubit controls[], Qubit target) override + void ControlledH(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledS(long numControls, Qubit controls[], Qubit target) override + void ControlledS(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledT(long numControls, Qubit controls[], Qubit target) override + void ControlledT(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledR(long numControls, Qubit controls[], PauliId axis, Qubit target, double theta) override + void ControlledR(long /*numControls*/, Qubit* /*controls*/, PauliId /*axis*/, Qubit /*target*/, double /*theta*/) override { throw std::logic_error("operation_not_supported"); } void ControlledExp( - long numControls, - Qubit controls[], - long numTargets, - PauliId paulis[], - Qubit targets[], - double theta) override + long /*numControls*/, + Qubit* /*controls*/, + long /*numTargets*/, + PauliId* /*paulis*/, + Qubit* /*targets*/, + double /* theta */) override { throw std::logic_error("operation_not_supported"); } - void AdjointS(Qubit target) override + void AdjointS(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void AdjointT(Qubit target) override + void AdjointT(Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledAdjointS(long numControls, Qubit controls[], Qubit target) override + void ControlledAdjointS(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } - void ControlledAdjointT(long numControls, Qubit controls[], Qubit target) override + void ControlledAdjointT(long /*numControls*/, Qubit* /*controls*/, Qubit /*target*/) override { throw std::logic_error("operation_not_supported"); } diff --git a/src/Qir/Runtime/lib/Tracer/tracer-qis.cpp b/src/Qir/Runtime/lib/Tracer/tracer-qis.cpp index dd608602215..37f6a671190 100644 --- a/src/Qir/Runtime/lib/Tracer/tracer-qis.cpp +++ b/src/Qir/Runtime/lib/Tracer/tracer-qis.cpp @@ -18,14 +18,14 @@ namespace Quantum using namespace Microsoft::Quantum; extern "C" { - void quantum__qis__on_operation_start(int64_t id) // NOLINT + void quantum__qis__on_operation_start(int64_t /* id */) // NOLINT { } - void quantum__qis__on_operation_end(int64_t id) // NOLINT + void quantum__qis__on_operation_end(int64_t /* id */) // NOLINT { } - void quantum__qis__swap(Qubit q1, Qubit q2) // NOLINT + void quantum__qis__swap(Qubit /*q1*/, Qubit /*q2*/) // NOLINT { } diff --git a/src/Qir/Runtime/lib/Tracer/tracer.cpp b/src/Qir/Runtime/lib/Tracer/tracer.cpp index c1cbe70f4bd..a4ccfbb0eae 100644 --- a/src/Qir/Runtime/lib/Tracer/tracer.cpp +++ b/src/Qir/Runtime/lib/Tracer/tracer.cpp @@ -116,7 +116,7 @@ namespace Quantum const LayerId barrier = this->GetEffectiveFence(); const LayerId firstLayerAfterBarrier = (barrier == INVALID ? (this->metricsByLayer.empty() ? REQUESTNEW : 0) - : ((barrier + 1 == this->metricsByLayer.size()) ? REQUESTNEW : barrier + 1)); + : (((size_t)(barrier + 1) == this->metricsByLayer.size()) ? REQUESTNEW : barrier + 1)); LayerId candidate = CTracer::LaterLayerOf(qstate.layer, firstLayerAfterBarrier); assert(candidate != INVALID); @@ -132,7 +132,7 @@ namespace Quantum } else { - for (candidate += 1; candidate < this->metricsByLayer.size(); ++candidate) + for (candidate += 1; (size_t)candidate < this->metricsByLayer.size(); ++candidate) { if (opDuration <= this->metricsByLayer[candidate].duration) { @@ -151,7 +151,7 @@ namespace Quantum //------------------------------------------------------------------------------------------------------------------ void CTracer::AddOperationToLayer(OpId id, LayerId layer) { - assert(layer < this->metricsByLayer.size()); + assert((size_t)layer < this->metricsByLayer.size()); assert(this->metricsByLayer[layer].barrierId == -1 && "Should not add operations to barriers"); this->metricsByLayer[layer].operations[id] += 1; @@ -319,7 +319,7 @@ namespace Quantum { return; } - assert(this->fence < this->tracer->metricsByLayer.size()); + assert((size_t)(this->fence) < this->tracer->metricsByLayer.size()); this->tracer->conditionalFences.push_back(this->fence); this->tracer->latestConditionalFence = CTracer::LaterLayerOf(this->tracer->latestConditionalFence, this->fence); diff --git a/src/Qir/Runtime/lib/Tracer/tracer.hpp b/src/Qir/Runtime/lib/Tracer/tracer.hpp index 35f242f1ecc..a49a9b9166e 100644 --- a/src/Qir/Runtime/lib/Tracer/tracer.hpp +++ b/src/Qir/Runtime/lib/Tracer/tracer.hpp @@ -148,11 +148,11 @@ namespace Quantum std::string QubitToString(Qubit qubit) override; void ReleaseResult(Result result) override; - bool AreEqualResults(Result r1, Result r2) override + bool AreEqualResults(Result /*r1*/, Result /*r2*/) override { throw std::logic_error("Cannot compare results while tracing!"); } - ResultValue GetResultValue(Result result) override + ResultValue GetResultValue(Result /*result*/) override { throw std::logic_error("Result values aren't available while tracing!"); } diff --git a/src/Qir/Runtime/public/QubitManager.hpp b/src/Qir/Runtime/public/QubitManager.hpp new file mode 100644 index 00000000000..c45a74ce292 --- /dev/null +++ b/src/Qir/Runtime/public/QubitManager.hpp @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +#include "CoreTypes.hpp" + +namespace Microsoft +{ +namespace Quantum +{ + // CQubitManager maintains mapping between user qubit objects and + // underlying qubit identifiers (Ids). When user program allocates + // a qubit, Qubit Manager decides whether to allocate a fresh id or + // reuse existing id that was previously freed. When user program + // releases a qubit, Qubit Manager tracks it as a free qubit id. + // Decision to reuse a qubit id is influenced by restricted reuse + // areas. When a qubit id is freed in one section of a restricted + // reuse area, it cannot be reused in other sections of the same area. + // True borrowing of qubits is not supported and is currently + // implemented as a plain allocation. + class QIR_SHARED_API CQubitManager + { + public: + using QubitIdType = ::int32_t; + + // We want status array to be reasonably large. + constexpr static QubitIdType DefaultQubitCapacity = 8; + + // Indexes in the status array can potentially be in range 0 .. QubitIdType.MaxValue-1. + // This gives maximum capacity as QubitIdType.MaxValue. Actual configured capacity may be less than this. + // Index equal to QubitIdType.MaxValue doesn't exist and is reserved for 'NoneMarker' - list terminator. + constexpr static QubitIdType MaximumQubitCapacity = std::numeric_limits::max(); + + public: + CQubitManager( + QubitIdType initialQubitCapacity = DefaultQubitCapacity, + bool mayExtendCapacity = true, + bool encourageReuse = true); + + // No complex scenarios for now. Don't need to support copying/moving. + CQubitManager(const CQubitManager&) = delete; + CQubitManager& operator = (const CQubitManager&) = delete; + virtual ~CQubitManager(); + + // Restricted reuse area control + void StartRestrictedReuseArea(); + void NextRestrictedReuseSegment(); + void EndRestrictedReuseArea(); + + // Allocate a qubit. Extend capacity if necessary and possible. + // Fail if the qubit cannot be allocated. + // Computation complexity is O(number of nested restricted reuse areas). + Qubit Allocate(); + // Allocate qubitCountToAllocate qubits and store them in the provided array. Extend manager capacity if necessary and possible. + // Fail without allocating any qubits if the qubits cannot be allocated. + // Caller is responsible for providing array of sufficient size to hold qubitCountToAllocate. + void Allocate(Qubit* qubitsToAllocate, int32_t qubitCountToAllocate); + + // Releases a given qubit. + void Release(Qubit qubit); + // Releases qubitCountToRelease qubits in the provided array. + // Caller is responsible for managing memory used by the array itself (i.e. delete[] array if it was dynamically allocated). + void Release(Qubit* qubitsToRelease, int32_t qubitCountToRelease); + + // Borrow (We treat borrowing as allocation currently) + Qubit Borrow(); + void Borrow(Qubit* qubitsToBorrow, int32_t qubitCountToBorrow); + // Return (We treat returning as release currently) + void Return(Qubit qubit); + void Return(Qubit* qubitsToReturn, int32_t qubitCountToReturn); + + // Disables a given qubit. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit qubit); + // Disables a set of given qubits. + // Once a qubit is disabled it can never be "enabled" or reallocated. + void Disable(Qubit* qubitsToDisable, int32_t qubitCountToDisable); + + bool IsValidQubit(Qubit qubit) const; + bool IsDisabledQubit(Qubit qubit) const; + bool IsExplicitlyAllocatedQubit(Qubit qubit) const; + bool IsFreeQubitId(QubitIdType id) const; + + QubitIdType GetQubitId(Qubit qubit) const; + + // Qubit counts: + + // Number of qubits that are disabled. When an explicitly allocated qubit + // gets disabled, it is removed from allocated count and is added to + // disabled count immediately. Subsequent Release doesn't affect counts. + int32_t GetDisabledQubitCount() const { return disabledQubitCount; } + + // Number of qubits that are explicitly allocated. This counter gets + // increased on allocation of a qubit and decreased on release of a qubit. + // Note that we treat borrowing as allocation now. + int32_t GetAllocatedQubitCount() const { return allocatedQubitCount; } + + // Number of free qubits that are currently tracked by this qubit manager. + // Note that when qubit manager may extend capacity, this doesn't account + // for qubits that may be potentially added in future via capacity extension. + // If qubit manager may extend capacity and reuse is discouraged, released + // qubits still increase this number even though they cannot be reused. + int32_t GetFreeQubitCount() const { return freeQubitCount; } + + // Total number of qubits that are currently tracked by this qubit manager. + int32_t GetQubitCapacity() const { return qubitCapacity; } + bool GetMayExtendCapacity() const { return mayExtendCapacity; } + bool GetEncourageReuse() const { return encourageReuse; } + + protected: + // May be overriden to create a custom Qubit object. + // When not overriden, it just stores qubit Id in place of a pointer to a qubit. + // id: unique qubit id + // Returns a newly instantiated qubit. + virtual Qubit CreateQubitObject(QubitIdType id); + + // May be overriden to delete a custom Qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it does nothing. + // qubit: pointer to QUBIT + virtual void DeleteQubitObject(Qubit qubit); + + // May be overriden to get a qubit id from a custom qubit object. + // Must be overriden if CreateQubitObject is overriden. + // When not overriden, it just reinterprets pointer to qubit as a qubit id. + // qubit: pointer to QUBIT + // Returns id of a qubit pointed to by qubit. + virtual QubitIdType QubitToId(Qubit qubit) const; + + private: + // The end of free lists are marked with NoneMarker value. It is used like null for pointers. + // This value is non-negative just like other values in the free lists. See sharedQubitStatusArray. + constexpr static QubitIdType NoneMarker = std::numeric_limits::max(); + + // Explicitly allocated qubits are marked with AllocatedMarker value. + // If borrowing is implemented, negative values may be used for refcounting. + // See sharedQubitStatusArray. + constexpr static QubitIdType AllocatedMarker = std::numeric_limits::min(); + + // Disabled qubits are marked with this value. See sharedQubitStatusArray. + constexpr static QubitIdType DisabledMarker = -1; + + // QubitListInSharedArray implements a singly-linked list with "pointers" + // to the first and the last element stored. Pointers are the indexes + // in a single shared array. Shared array isn't sotored in this class + // because it can be reallocated. This class maintains status of elements + // in the list by virtue of linking them as part of this list. This class + // sets Allocated status of elementes taken from the list (via TakeQubitFromFront). + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct QubitListInSharedArray final + { + private: + QubitIdType firstElement = NoneMarker; + QubitIdType lastElement = NoneMarker; + // We are not storing pointer to shared array because it can be reallocated. + // Indexes and special values remain the same on such reallocations. + + public: + // Initialize empty list + QubitListInSharedArray() = default; + + // Initialize as a list with sequential elements from startId to endId inclusve. + QubitListInSharedArray(QubitIdType startId, QubitIdType endId, QubitIdType* sharedQubitStatusArray); + + bool IsEmpty() const; + void AddQubit(QubitIdType id, bool addToFront, QubitIdType* sharedQubitStatusArray); + QubitIdType TakeQubitFromFront(QubitIdType* sharedQubitStatusArray); + void MoveAllQubitsFrom(QubitListInSharedArray& source, QubitIdType* sharedQubitStatusArray); + }; + + // Restricted reuse area consists of multiple segments. Qubits released + // in one segment cannot be reused in another. One restricted reuse area + // can be nested in a segment of another restricted reuse area. This class + // tracks current segment of an area by maintaining a list of free qubits + // in a shared status array FreeQubitsReuseAllowed. Previous segments are + // tracked collectively (not individually) by maintaining FreeQubitsReuseProhibited. + // This class is small, contains no C++ pointers and relies on default shallow copying/destruction. + struct RestrictedReuseArea final + { + public: + QubitListInSharedArray FreeQubitsReuseProhibited; + QubitListInSharedArray FreeQubitsReuseAllowed; + + RestrictedReuseArea() = default; + RestrictedReuseArea(QubitListInSharedArray freeQubits); + }; + + // This is NOT a pure stack! We modify it only by push/pop, but we also iterate over elements. + class CRestrictedReuseAreaStack final : public std::vector + { + public: + // No complex scenarios for now. Don't need to support copying/moving. + CRestrictedReuseAreaStack() = default; + CRestrictedReuseAreaStack(const CRestrictedReuseAreaStack&) = delete; + CRestrictedReuseAreaStack& operator = (const CRestrictedReuseAreaStack&) = delete; + ~CRestrictedReuseAreaStack() = default; + + void PushToBack(RestrictedReuseArea area); + RestrictedReuseArea PopFromBack(); + RestrictedReuseArea& PeekBack(); + int32_t Count() const; + }; + + private: + void EnsureCapacity(QubitIdType requestedCapacity); + + // Take free qubit id from a free list without extending capacity. + // First non-empty free list among nested restricted reuse areas are considered. + QubitIdType TakeFreeQubitId(); + // Allocate free qubit id extending capacity if necessary and possible. + QubitIdType AllocateQubitId(); + // Put qubit id back into a free list for the current restricted reuse area. + void ReleaseQubitId(QubitIdType id); + + bool IsValidId(QubitIdType id) const; + bool IsDisabledId(QubitIdType id) const; + bool IsFreeId(QubitIdType id) const; + bool IsExplicitlyAllocatedId(QubitIdType id) const; + + // Configuration Properties: + bool mayExtendCapacity = true; + bool encourageReuse = true; + + // State: + // sharedQubitStatusArray is used to store statuses of all known qubits. + // Integer value at the index of the qubit id represents the status of that qubit. + // (Ex: sharedQubitStatusArray[4] is the status of qubit with id = 4). + // Therefore qubit ids are in the range of [0..qubitCapacity). + // Capacity may be extended if MayExtendCapacity = true. + // If qubit X is allocated, sharedQubitStatusArray[X] = AllocatedMarker (negative number) + // If qubit X is disabled, sharedQubitStatusArray[X] = DisabledMarker (negative number) + // If qubit X is free, sharedQubitStatusArray[X] is a non-negative number, denote it Next(X). + // Next(X) is either the index of the next element in the list or the list terminator - NoneMarker. + // All free qubits form disjoint singly linked lists bound to to respective resricted reuse areas. + // Each area has two lists of free qubits - see RestrictedReuseArea. + QubitIdType* sharedQubitStatusArray = nullptr; + // qubitCapacity is always equal to the array size. + QubitIdType qubitCapacity = 0; + // All nested restricted reuse areas at the current moment. + // Fresh Free Qubits are added to the outermost area: freeQubitsInAreas[0].FreeQubitsReuseAllowed + CRestrictedReuseAreaStack freeQubitsInAreas; + + // Counts: + int32_t disabledQubitCount = 0; + int32_t allocatedQubitCount = 0; + int32_t freeQubitCount = 0; + }; + +} +} diff --git a/src/Qir/Runtime/unittests/CMakeLists.txt b/src/Qir/Runtime/unittests/CMakeLists.txt index 40c41776d2a..664fc359a8f 100644 --- a/src/Qir/Runtime/unittests/CMakeLists.txt +++ b/src/Qir/Runtime/unittests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(qir-runtime-unittests QirRuntimeTests.cpp ToffoliTests.cpp TracerTests.cpp + QubitManagerTests.cpp $ $ $ diff --git a/src/Qir/Runtime/unittests/QirRuntimeTests.cpp b/src/Qir/Runtime/unittests/QirRuntimeTests.cpp index 66127fb69ae..8d668a7fd9f 100644 --- a/src/Qir/Runtime/unittests/QirRuntimeTests.cpp +++ b/src/Qir/Runtime/unittests/QirRuntimeTests.cpp @@ -65,7 +65,7 @@ struct ResultsReferenceCountingTestQAPI : public SimulatorStub bool HaveResultsInFlight() const { - for (const auto& b : this->allocated) + for (const auto b : this->allocated) { if (b) { @@ -716,7 +716,7 @@ struct QubitTestQAPI : public SimulatorStub bool HaveQubitsInFlight() const { - for (const auto& b : this->allocated) + for (const auto b : this->allocated) { if (b) { @@ -768,7 +768,7 @@ struct ControlledCallablesTestSimulator : public SimulatorStub { return reinterpret_cast(++this->lastId); } - void ReleaseQubit(Qubit qubit) override {} + void ReleaseQubit(Qubit /*qubit*/) override {} Result UseZero() override { return reinterpret_cast(0); @@ -984,7 +984,7 @@ struct AdjointsTestSimulator : public SimulatorStub { return reinterpret_cast(++this->lastId); } - void ReleaseQubit(Qubit qubit) override {} + void ReleaseQubit(Qubit /*qubit*/) override {} Result UseZero() override { return reinterpret_cast(0); diff --git a/src/Qir/Runtime/unittests/QubitManagerTests.cpp b/src/Qir/Runtime/unittests/QubitManagerTests.cpp new file mode 100644 index 00000000000..966b36f9645 --- /dev/null +++ b/src/Qir/Runtime/unittests/QubitManagerTests.cpp @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include "catch.hpp" + +#include "QubitManager.hpp" + +using namespace Microsoft::Quantum; + +TEST_CASE("Simple allocation and release of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(); + Qubit q = qm->Allocate(); + qm->Release(q); +} + +TEST_CASE("Allocation and reallocation of one qubit", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(1, false, true); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + Qubit q = qm->Allocate(); + REQUIRE(qm->GetQubitId(q) == 0); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + qm->Release(q); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + qm->Release(q0); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 0); +} + +TEST_CASE("Qubit Status", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2, false, true); + + Qubit q0 = qm->Allocate(); + CQubitManager::QubitIdType q0id = qm->GetQubitId(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(!qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetAllocatedQubitCount() == 1); + + qm->Disable(q0); + REQUIRE(qm->IsValidQubit(q0)); + REQUIRE(!qm->IsExplicitlyAllocatedQubit(q0)); + REQUIRE(qm->IsDisabledQubit(q0)); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetDisabledQubitCount() == 1); + + qm->Release(q0); + REQUIRE(!qm->IsFreeQubitId(q0id)); + REQUIRE(qm->GetFreeQubitCount() == 1); + + Qubit q1 = qm->Allocate(); + CQubitManager::QubitIdType q1id = qm->GetQubitId(q1); + REQUIRE(q0id != q1id); + + REQUIRE(qm->IsValidQubit(q1)); + REQUIRE(qm->IsExplicitlyAllocatedQubit(q1)); + REQUIRE(!qm->IsDisabledQubit(q1)); + REQUIRE(!qm->IsFreeQubitId(q1id)); + + REQUIRE(qm->GetAllocatedQubitCount() == 1); + REQUIRE(qm->GetDisabledQubitCount() == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + + qm->Release(q1); + REQUIRE(qm->IsFreeQubitId(q1id)); +} + +TEST_CASE("Qubit Counts", "[QubitManagerBasic]") +{ + constexpr int totalQubitCount = 100; + constexpr int disabledQubitCount = 29; + constexpr int extraQubitCount = 43; + static_assert(extraQubitCount <= totalQubitCount); + static_assert(disabledQubitCount <= totalQubitCount); + // We don't want capacity to be extended at first... + static_assert(extraQubitCount + disabledQubitCount <= totalQubitCount); + + std::unique_ptr qm = std::make_unique(totalQubitCount, true, true); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + Qubit* qubits = new Qubit[disabledQubitCount]; + qm->Allocate(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == disabledQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == 0); + + qm->Disable(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, disabledQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[extraQubitCount]; + qm->Allocate(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount-extraQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == extraQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, extraQubitCount); + REQUIRE(qm->GetQubitCapacity() == totalQubitCount); + REQUIRE(qm->GetFreeQubitCount() == totalQubitCount-disabledQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; + + qubits = new Qubit[totalQubitCount]; + qm->Allocate(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == totalQubitCount); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + + qm->Release(qubits, totalQubitCount); + REQUIRE(qm->GetQubitCapacity() > totalQubitCount); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + REQUIRE(qm->GetDisabledQubitCount() == disabledQubitCount); + delete[] qubits; +} + +TEST_CASE("Allocation of released qubits when reuse is encouraged", "[QubitManagerBasic]") +{ + std::unique_ptr qm = std::make_unique(2, false, true); + REQUIRE(qm->GetFreeQubitCount() == 2); + Qubit q0 = qm->Allocate(); + Qubit q1 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); // Qubit ids should be in order + REQUIRE(qm->GetQubitId(q1) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0a) == 0); // It was the only one available + REQUIRE_THROWS(qm->Allocate()); + + qm->Release(q1); + qm->Release(q0a); + REQUIRE(qm->GetFreeQubitCount() == 2); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + + Qubit q0b = qm->Allocate(); + Qubit q1a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0b) == 0); // By default reuse is encouraged, LIFO is used + REQUIRE(qm->GetQubitId(q1a) == 1); + REQUIRE_THROWS(qm->Allocate()); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0b); + qm->Release(q1a); +} + +TEST_CASE("Extending capacity", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(1, true, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + Qubit q1 = qm->Allocate(); // This should double capacity + REQUIRE(qm->GetQubitId(q1) == 1); + REQUIRE(qm->GetFreeQubitCount() == 0); + REQUIRE(qm->GetAllocatedQubitCount() == 2); + + qm->Release(q0); + Qubit q0a = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0a) == 0); + Qubit q2 = qm->Allocate(); // This should double capacity again + REQUIRE(qm->GetQubitId(q2) == 2); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); + + qm->Release(q1); + qm->Release(q0a); + qm->Release(q2); + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); + + Qubit* qqq = new Qubit[3]; + qm->Allocate(qqq, 3); + REQUIRE(qm->GetFreeQubitCount() == 1); + REQUIRE(qm->GetAllocatedQubitCount() == 3); + qm->Release(qqq, 3); + delete[] qqq; + REQUIRE(qm->GetFreeQubitCount() == 4); + REQUIRE(qm->GetAllocatedQubitCount() == 0); +} + +TEST_CASE("Restricted Area", "[QubitManager]") +{ + std::unique_ptr qm = std::make_unique(3, false, true); + + Qubit q0 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q0) == 0); + + qm->StartRestrictedReuseArea(); + + // Allocates fresh qubit + Qubit q1 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q1) == 1); + qm->Release(q1); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // Allocates fresh qubit, q1 cannot be reused - it belongs to a differen segment. + Qubit q2 = qm->Allocate(); + REQUIRE(qm->GetQubitId(q2) == 2); + qm->Release(q2); + + Qubit q2a = qm->Allocate(); // Getting the same one as the one that's just released. + REQUIRE(qm->GetQubitId(q2a) == 2); + qm->Release(q2a); // Released, but cannot be used in the next segment. + + qm->NextRestrictedReuseSegment(); + + // There's no qubits left here. q0 is allocated, q1 and q2 are from different segments. + REQUIRE_THROWS(qm->Allocate()); + + qm->EndRestrictedReuseArea(); + + // Qubits 1 and 2 are available here again. + Qubit* qqq = new Qubit[2]; + qm->Allocate(qqq, 2); + // OK to destruct qubit manager while qubits are still allocated. + REQUIRE_THROWS(qm->Allocate()); +} + diff --git a/src/Qir/Samples/CMakeLists.txt b/src/Qir/Samples/CMakeLists.txt index c6ebe6ecb69..f1172ecfd24 100644 --- a/src/Qir/Samples/CMakeLists.txt +++ b/src/Qir/Samples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) message(INFO "*** build config: ${CMAKE_BUILD_TYPE}") diff --git a/src/Qir/Tests/CMakeLists.txt b/src/Qir/Tests/CMakeLists.txt index 86083256e05..07b859bfaa9 100644 --- a/src/Qir/Tests/CMakeLists.txt +++ b/src/Qir/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) message(INFO "*** build config: ${CMAKE_BUILD_TYPE}") diff --git a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp index f9369c87fa6..7fe5e789d80 100644 --- a/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp +++ b/src/Qir/Tests/FullstateSimulator/FullstateSimulatorTests.cpp @@ -77,6 +77,36 @@ TEST_CASE("Fullstate simulator: X and measure", "[fullstate_simulator]") sim->ReleaseResult(r2); } +TEST_CASE("Fullstate simulator: X, M, reuse, M", "[fullstate_simulator]") +{ + std::unique_ptr sim = CreateFullstateSimulator(); + IQuantumGateSet* iqa = dynamic_cast(sim.get()); + + Qubit q = sim->AllocateQubit(); + Result r1 = MZ(iqa, q); + REQUIRE(Result_Zero == sim->GetResultValue(r1)); + REQUIRE(sim->AreEqualResults(r1, sim->UseZero())); + + iqa->X(q); + Result r2 = MZ(iqa, q); + REQUIRE(Result_One == sim->GetResultValue(r2)); + REQUIRE(sim->AreEqualResults(r2, sim->UseOne())); + + sim->ReleaseQubit(q); + sim->ReleaseResult(r1); + sim->ReleaseResult(r2); + + Qubit qq = sim->AllocateQubit(); + Result r3 = MZ(iqa, qq); + // Allocated qubit should always be in |0> state even though we released + // q in |1> state, and qq is likely reusing the same underlying qubit as q. + REQUIRE(Result_Zero == sim->GetResultValue(r3)); + REQUIRE(sim->AreEqualResults(r3, sim->UseZero())); + + sim->ReleaseQubit(qq); + sim->ReleaseResult(r3); +} + TEST_CASE("Fullstate simulator: measure Bell state", "[fullstate_simulator]") { std::unique_ptr sim = CreateFullstateSimulator(); diff --git a/src/Qir/Tests/QIR-static/qir-driver.cpp b/src/Qir/Tests/QIR-static/qir-driver.cpp index 143b3b40c9b..b18f5dc264d 100644 --- a/src/Qir/Tests/QIR-static/qir-driver.cpp +++ b/src/Qir/Tests/QIR-static/qir-driver.cpp @@ -8,6 +8,7 @@ #include #include +#include "QirUtils.hpp" #include "CoreTypes.hpp" #include "QirContext.hpp" #include "QirTypes.hpp" @@ -73,7 +74,7 @@ struct QubitsResultsTestSimulator : public Microsoft::Quantum::SimulatorStub { const int id = static_cast(reinterpret_cast(qubit)); REQUIRE(id >= 0); - REQUIRE(id < this->qubits.size()); + REQUIRE((size_t)id < this->qubits.size()); return id; } @@ -82,7 +83,7 @@ struct QubitsResultsTestSimulator : public Microsoft::Quantum::SimulatorStub { const int id = static_cast(reinterpret_cast(result)); REQUIRE(id >= 0); - REQUIRE(id < this->results.size()); + REQUIRE((size_t)id < this->results.size()); return id; } @@ -107,9 +108,10 @@ struct QubitsResultsTestSimulator : public Microsoft::Quantum::SimulatorStub this->qubits[id] = 1 - this->qubits[id]; } - Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override + Result Measure(long numBases, PauliId* /* bases */, long /* numTargets */, Qubit targets[]) override { assert(numBases == 1 && "QubitsResultsTestSimulator doesn't support joint measurements"); + UNUSED(numBases); const int id = GetQubitId(targets[0]); REQUIRE(this->qubits[id] != RELEASED); // the qubit must be alive @@ -223,7 +225,7 @@ struct FunctorsTestSimulator : public Microsoft::Quantum::SimulatorStub { const int id = static_cast(reinterpret_cast(qubit)); REQUIRE(id >= 0); - REQUIRE(id < this->qubits.size()); + REQUIRE((size_t)id < this->qubits.size()); return id; } @@ -261,9 +263,10 @@ struct FunctorsTestSimulator : public Microsoft::Quantum::SimulatorStub X(qubit); } - Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override + Result Measure(long numBases, PauliId* /* bases */, long /* numTargets */, Qubit targets[]) override { assert(numBases == 1 && "FunctorsTestSimulator doesn't support joint measurements"); + UNUSED(numBases); const int id = GetQubitId(targets[0]); REQUIRE(this->qubits[id] != RELEASED); @@ -276,7 +279,7 @@ struct FunctorsTestSimulator : public Microsoft::Quantum::SimulatorStub return (r1 == r2); } - void ReleaseResult(Result result) override {} // the results aren't allocated by this test simulator + void ReleaseResult(Result /*result*/) override {} // the results aren't allocated by this test simulator Result UseZero() override { diff --git a/src/Qir/Tests/QIR-static/qir-test-conditionals.cpp b/src/Qir/Tests/QIR-static/qir-test-conditionals.cpp index ab16565f211..4551d301505 100644 --- a/src/Qir/Tests/QIR-static/qir-test-conditionals.cpp +++ b/src/Qir/Tests/QIR-static/qir-test-conditionals.cpp @@ -61,14 +61,14 @@ struct ConditionalsTestSimulator : public Microsoft::Quantum::SimulatorStub { return nullptr; } - void ReleaseQubit(Qubit qubit) override {} + void ReleaseQubit(Qubit /*qubit*/) override {} void X(Qubit) override { this->xCallbacks.push_back(this->nGateCallback); this->nGateCallback++; } - void ControlledX(long numControls, Qubit controls[], Qubit qubit) override + void ControlledX(long /* numControls */, Qubit* /* controls */, Qubit /* qubit */) override { this->cxCallbacks.push_back(this->nGateCallback); this->nGateCallback++; @@ -78,16 +78,16 @@ struct ConditionalsTestSimulator : public Microsoft::Quantum::SimulatorStub this->otherCallbacks.push_back(this->nGateCallback); this->nGateCallback++; } - void ControlledY(long numControls, Qubit controls[], Qubit qubit) override + void ControlledY(long /* numControls */, Qubit* /* controls */, Qubit /* qubit */) override { this->otherCallbacks.push_back(this->nGateCallback); this->nGateCallback++; } - Result Measure(long numBases, PauliId bases[], long numTargets, Qubit targets[]) override + Result Measure(long /* numBases */, PauliId* /* bases */, long /* numTargets */, Qubit* /* targets */) override { assert( - this->nextMeasureResult < this->mockMeasurements.size() && + (size_t)(this->nextMeasureResult) < this->mockMeasurements.size() && "ConditionalsTestSimulator isn't set up correctly"); Result r = (this->mockMeasurements[this->nextMeasureResult] == Result_Zero) ? UseZero() : UseOne(); @@ -101,7 +101,7 @@ struct ConditionalsTestSimulator : public Microsoft::Quantum::SimulatorStub return (r1 == r2); } - void ReleaseResult(Result result) override {} // the results aren't allocated by this test simulator + void ReleaseResult(Result /*result*/) override {} // the results aren't allocated by this test simulator Result UseZero() override { diff --git a/src/Qir/qir-utils.ps1 b/src/Qir/qir-utils.ps1 index cfd0c31cc2f..544959d33a4 100644 --- a/src/Qir/qir-utils.ps1 +++ b/src/Qir/qir-utils.ps1 @@ -35,9 +35,40 @@ function Build-CMakeProject { $oldCC = $env:CC $oldCXX = $env:CXX $oldRC = $env:RC + $oldCCFLAGS = $env:CCFLAGS + $oldCXXFLAGS = $env:CXXFLAGS $clangTidy = "" + $warningFlags = "-Werror" + + # -Wall + # -Wmisleading-indentation, + # -Wmost, + # -Wcast-of-sel-type, -Winfinite-recursion, -Woverloaded-virtual, -Wstring-plus-int, + # -Wchar-subscripts, -Wint-in-bool-context, -Wprivate-extern, -Wtautological-compare, + # -Wcomment, -Wmismatched-tags, -Wrange-loop-construct, -Wtrigraphs, + # -Wdelete-non-virtual-dtor, -Wmissing-braces, -Wreorder, -Wuninitialized, + # -Wextern-c-compat, -Wmove, -Wreturn-type, -Wunknown-pragmas, + # -Wfor-loop-analysis, -Wmultichar, -Wself-assign, -Wunused, + # -Wformat, -Wobjc-designated-initializers, -Wself-move, -Wuser-defined-warnings, + # -Wframe-address, -Wobjc-flexible-array, -Wsizeof-array-argument, -Wvolatile-register-var. + # -Wimplicit, -Wobjc-missing-super-calls, -Wsizeof-array-decay, + # -Wparentheses, + # -Wswitch, + # -Wswitch-bool. + $warningFlags += " -Wall" # https://clang.llvm.org/docs/DiagnosticsReference.html#wall + + # -Wextra + # -Wdeprecated-copy, -Wempty-init-stmt, -Wfuse-ld-path, -Wignored-qualifiers, -Winitializer-overrides, + # -Wmissing-field-initializers, -Wmissing-method-return-type, -Wnull-pointer-arithmetic, + # -Wsemicolon-before-method-body, -Wsign-compare, -Wstring-concatenation, -Wunused-but-set-parameter, + # -Wunused-parameter. + $warningFlags += " -Wextra" # https://clang.llvm.org/docs/DiagnosticsReference.html#wextra + + $env:CFLAGS += $warningFlags + $env:CXXFLAGS += $warningFlags + if (($IsMacOS) -or ((Test-Path Env:AGENT_OS) -and ($Env:AGENT_OS.StartsWith("Darwin")))) { Write-Host "On MacOS build $Name using the default C/C++ compiler (should be AppleClang)" @@ -100,6 +131,9 @@ function Build-CMakeProject { Pop-Location + $env:CXXFLAGS = $oldCXXFLAGS + $env:CCFLAGS = $oldCCFLAGS + $env:CC = $oldCC $env:CXX = $oldCXX $env:RC = $oldRC diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs new file mode 100644 index 00000000000..c8e9979c4a4 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Quantum.Simulation.Simulators; +using Xunit; + +namespace Microsoft.Quantum.AutoSubstitution.Testing +{ + public class CodeGenerationTests + { + [Fact] + public void CanSimulateWithAlternativeSimulator() + { + var sim = new ToffoliSimulator(); + TestQuantumSwap.Run(sim).Wait(); + } + } +} diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs new file mode 100644 index 00000000000..eb2adb4a726 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Integration.qs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.AutoSubstitution.Testing { + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Measurement; + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("Microsoft.Quantum.Intrinsic.SWAP", "ToffoliSimulator") + operation QuantumSwap(a : Qubit, b : Qubit) : Unit { + within { + CNOT(a, b); + H(a); + H(b); + } apply { + CNOT(a, b); + } + } + + operation TestQuantumSwap() : Unit { + use a = Qubit(); + use b = Qubit(); + + X(a); + + QuantumSwap(a, b); + + EqualityFactR(MResetZ(a), Zero, "unexpected value for a after swap"); + EqualityFactR(MResetZ(b), One, "unexpected value for b after swap"); + } +} diff --git a/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj new file mode 100644 index 00000000000..7260a0b8e2c --- /dev/null +++ b/src/Simulation/AutoSubstitution.Integration.Tests/Tests.AutoSubstitution.Integration.csproj @@ -0,0 +1,22 @@ + + + + Library + netcoreapp3.1 + x64 + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs b/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs new file mode 100644 index 00000000000..218482a0069 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/CodeGenerationTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.Quantum.QsCompiler.AutoSubstitution; +using Microsoft.Quantum.QsCompiler.CompilationBuilder; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Xunit; + +namespace Microsoft.Quantum.AutoSubstitution.Testing +{ + public class CodeGenerationTests + { + [Fact] + public void CanGenerateAutoSubstitutionCode() + { + TestOneSuccessfulFile("Success"); + TestOneSuccessfulFile("SuccessA"); + TestOneSuccessfulFile("SuccessC"); + TestOneSuccessfulFile("SuccessCA"); + } + + [Fact] + public void CanFailForVariousReasons() + { + TestOneFailingFile("FailAlternativeDoesNotExist"); + TestOneFailingFile("FailDifferentSignatures"); + TestOneFailingFile("FailDifferentSpecializationKinds"); + TestOneFailingFile("FailNoNamespace"); + } + + private void TestOneSuccessfulFile(string fileName) + { + var step = new RewriteStep(); + var path = CreateNewTemporaryPath(); + step.AssemblyConstants[AssemblyConstants.OutputPath] = path; + + var compilation = CreateCompilation(Path.Combine("TestFiles", "Core.qs"), "Substitution.qs", Path.Combine("TestFiles", $"{fileName}.qs")); + + Assert.True(step.Transformation(compilation, out var transformed)); + var generatedFileName = Path.Combine(path, "__AutoSubstitution__.g.cs"); + Assert.True(File.Exists(generatedFileName)); + + // uncomment this line, when creating new unit tests to + // create files with expected content + //File.Copy(generatedFileName, $"{fileName}.cs_", true); + + Assert.Equal(File.ReadAllText(Path.Combine("TestFiles", $"{fileName}.cs_")).Replace("\r\n", "\n"), File.ReadAllText(generatedFileName).Replace("\r\n", "\n")); + + Directory.Delete(path, true); + } + + private void TestOneFailingFile(string fileName) + { + var step = new RewriteStep(); + var path = CreateNewTemporaryPath(); + step.AssemblyConstants[AssemblyConstants.OutputPath] = path; + + var compilation = CreateCompilation(Path.Combine("TestFiles", "Core.qs"), "Substitution.qs", Path.Combine("TestFiles", $"{fileName}.qs")); + + Assert.False(step.Transformation(compilation, out var transformed)); + Assert.Equal(2, step.GeneratedDiagnostics.Count()); + Assert.Equal(CodeAnalysis.DiagnosticSeverity.Error, step.GeneratedDiagnostics.Last().Severity); + } + + private QsCompilation CreateCompilation(params string[] fileNames) + { + var mgr = new CompilationUnitManager(); + var files = CreateFileManager(fileNames); + mgr.AddOrUpdateSourceFilesAsync(files).Wait(); + return mgr.Build().BuiltCompilation; + } + + private ImmutableHashSet CreateFileManager(params string[] fileNames) => + CompilationUnitManager.InitializeFileManagers( + fileNames.Select(fileName => { + var fileId = new Uri(Path.GetFullPath(fileName)); + return (id: fileId, content: File.ReadAllText(fileName)); + }).ToDictionary(t => t.id, t => t.content) + ); + + private readonly System.Random random = new System.Random(); + private string CreateNewTemporaryPath() => + Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"substitution-test-{random.Next(Int32.MaxValue)}")).FullName; + } +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs new file mode 100644 index 00000000000..2fdd2185eb8 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Core.qs @@ -0,0 +1,5 @@ +// This file is needed to ensure that the Microsoft.Quantum.Core namespace +// exists for the tests. + +namespace Microsoft.Quantum.Core { +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs new file mode 100644 index 00000000000..7896578878e --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailAlternativeDoesNotExist.qs @@ -0,0 +1,6 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("Namespace.NotExisting", "ToffoliSimulator") + operation Fail() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs new file mode 100644 index 00000000000..548496c4a26 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSignatures.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.FailClassical", "ToffoliSimulator") + operation Fail(a : Int) : Unit {} + + operation FailClassical(a : Double) : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs new file mode 100644 index 00000000000..fcfba7c3866 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailDifferentSpecializationKinds.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("FailClassical", "ToffoliSimulator") + operation Fail() : Unit is Adj {} + + operation FailClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs new file mode 100644 index 00000000000..81ed06e2fa4 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/FailNoNamespace.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("FailClassical", "ToffoliSimulator") + operation Fail() : Unit {} + + operation FailClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ new file mode 100644 index 00000000000..1e3cdbfda1a --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.cs_ @@ -0,0 +1,49 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs new file mode 100644 index 00000000000..4e5c8390aec --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/Success.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit {} + + operation SuccessClassical() : Unit {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ new file mode 100644 index 00000000000..c8cd88d9fa6 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.cs_ @@ -0,0 +1,69 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func __AdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__AdjointBody__(args); + } + else + { + return base.__AdjointBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs new file mode 100644 index 00000000000..99a4407bb37 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessA.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Adj {} + + operation SuccessClassical() : Unit is Adj {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ new file mode 100644 index 00000000000..73b7cfe76d9 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.cs_ @@ -0,0 +1,69 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledBody__(args); + } + else + { + return base.__ControlledBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs new file mode 100644 index 00000000000..612e1671ac7 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessC.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Ctl {} + + operation SuccessClassical() : Unit is Ctl {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ new file mode 100644 index 00000000000..eaeac06869c --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.cs_ @@ -0,0 +1,109 @@ +using System; +using Microsoft.Quantum.Simulation.Core; +using Microsoft.Quantum.Simulation.Simulators; + +namespace AutoSubstitutionTests +{ + public partial class Success + { + public class Native : Success + { + public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m): base(m) + { + sim0 = ((m) as ToffoliSimulator); + } + + public override void __Init__() + { + base.__Init__(); + if ((sim0) != (null)) + { + alternative0 = (__Factory__.Get(typeof(AutoSubstitutionTests.SuccessClassical))); + } + } + + public override Func __Body__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__Body__(args); + } + else + { + return base.__Body__(args); + } + } + + ; + } + } + + public override Func __AdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__AdjointBody__(args); + } + else + { + return base.__AdjointBody__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledBody__(args); + } + else + { + return base.__ControlledBody__(args); + } + } + + ; + } + } + + public override Func<(IQArray, QVoid), QVoid> __ControlledAdjointBody__ + { + get + { + return args => + { + if ((sim0) != (null)) + { + return alternative0.__ControlledAdjointBody__(args); + } + else + { + return base.__ControlledAdjointBody__(args); + } + } + + ; + } + } + + private AutoSubstitutionTests.SuccessClassical alternative0 = null; + private ToffoliSimulator sim0 = null; + } + } +} \ No newline at end of file diff --git a/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs new file mode 100644 index 00000000000..58b4652ef94 --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/TestFiles/SuccessCA.qs @@ -0,0 +1,8 @@ +namespace AutoSubstitutionTests { + open Microsoft.Quantum.Targeting; + + @SubstitutableOnTarget("AutoSubstitutionTests.SuccessClassical", "ToffoliSimulator") + operation Success() : Unit is Adj+Ctl {} + + operation SuccessClassical() : Unit is Adj+Ctl {} +} diff --git a/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj b/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj new file mode 100644 index 00000000000..d2ce8ad57ba --- /dev/null +++ b/src/Simulation/AutoSubstitution.Tests/Tests.AutoSubstitution.csproj @@ -0,0 +1,69 @@ + + + + netcoreapp3.1 + Tests.Microsoft.Quantum.AutoSubstitution + false + x64 + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + diff --git a/src/Simulation/AutoSubstitution/AutoSubstitution.csproj b/src/Simulation/AutoSubstitution/AutoSubstitution.csproj new file mode 100644 index 00000000000..636efa33244 --- /dev/null +++ b/src/Simulation/AutoSubstitution/AutoSubstitution.csproj @@ -0,0 +1,23 @@ + + + + + + Library + netstandard2.1 + Enable + x64 + true + Microsoft.Quantum.AutoSubstitution + + + + + + + + + + + + diff --git a/src/Simulation/AutoSubstitution/CodeGenerator.cs b/src/Simulation/AutoSubstitution/CodeGenerator.cs new file mode 100644 index 00000000000..50e1ec8e0b4 --- /dev/null +++ b/src/Simulation/AutoSubstitution/CodeGenerator.cs @@ -0,0 +1,297 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.Quantum.QsCompiler.CsharpGeneration; +using Microsoft.Quantum.QsCompiler.SyntaxTree; + +namespace Microsoft.Quantum.QsCompiler.AutoSubstitution +{ + public class CodeGenerator + { + public CodeGenerator(CodegenContext context) + { + ctx = context; + gen = Microsoft.CodeAnalysis.Editing.SyntaxGenerator.GetGenerator(new AdhocWorkspace(), LanguageNames.CSharp); + } + + /// + /// Generates an substitution class for a given callable with a given name + /// + /// + /// + /// In the following we illustrate the syntax that is generated using + /// as an example the `Microsoft.Quantum.Canon.ApplyAnd` operation with + /// `Microsoft.Quantum.Intrinsic.CCNOT` as an alternative when using + /// `ToffoliSimulator`. + /// + /// The generated code looks as follows: + /// + /// + /// namespace Microsoft.Quantum.Canon { + /// public partial class ApplyAnd { + /// public class Native : ApplyAnd { + /// public Native(Microsoft.Quantum.Simulation.Core.IOperationFactory m) : base(m) { + /// sim0 = m as ToffoliSimulator; + /// } + /// + /// public override void __Init__() { + /// base.Init(); + /// if (sim0 != null) alternative0 = __Factory__.Get<Microsoft.Quantum.Intrinsic.CCNOT>(typeof(Microsoft.Quantum.Intrinsic.CCNOT)); + /// } + /// + /// public override Func<(Qubit, Qubit, Qubit), QVoid> __Body__ => args => { + /// if (sim0 != null) return alternative0.__Body__(args); + /// else return base.__Body__(args); + /// } + /// + /// // methods for other specializations ... + /// + /// private ToffoliSimulator sim0 = null; + /// private Microsoft.Quantum.Intrinsic.CCNOT alternative0 = null; + /// } + /// } + /// } + /// + /// + /// + /// Namespace and name of the callable + /// Q# Callable + /// All attribute values from @HasSubstitutionAttribute attributes of that callable + public void AddCallable(QsQualifiedName name, QsCallable callable, IEnumerable<(string AlternativeOperation, string InSimulator)> substitutionAttributes) + { + var attributes = substitutionAttributes.Select((attr, idx) => new SubstitutionAttribute( + SyntaxFactory.ParseTypeName(attr.AlternativeOperation), + gen.IdentifierName($"alternative{idx}"), + SyntaxFactory.ParseTypeName(attr.InSimulator), + gen.IdentifierName($"sim{idx}") + )); + + var operationFields = attributes.Select(CreateOperationField); + var simulatorFields = attributes.Select(CreateSimulatorField); + + var specializationProperties = callable.Specializations.Select(specialization => + CreateSpecializationProperty( + specializationName: specialization.Kind switch + { + var kind when kind.IsQsBody => "__Body__", + var kind when kind.IsQsAdjoint => "__AdjointBody__", + var kind when kind.IsQsControlled => "__ControlledBody__", + var kind when kind.IsQsControlledAdjoint => "__ControlledAdjointBody__", + _ => throw new Exception("unexpected specialization kind") + }, + attributes: attributes, + argumentType: SimulationCode.roslynTypeName(ctx, specialization.Signature.ArgumentType), + returnType: SimulationCode.roslynTypeName(ctx, specialization.Signature.ReturnType)) + ); + + var innerClass = gen.ClassDeclaration( + "Native", + accessibility: Accessibility.Public, + baseType: gen.IdentifierName(name.Name), + members: new[] { + CreateConstructor(attributes.Select(CreateSimulatorCast)), + CreateInitMethod(attributes.Select(CreateOperationAssignment)) + }.Concat(specializationProperties).Concat(operationFields).Concat(simulatorFields)); + + var cls = gen.ClassDeclaration( + name.Name, + accessibility: callable.Access.IsInternal ? Accessibility.Internal : Accessibility.Public, + modifiers: DeclarationModifiers.Partial, + members: new[] { innerClass }); + + InsertClassNode(name.Namespace, cls); + } + + /// + /// Creates a syntax node for the constructor of the inner Native class + /// + private SyntaxNode CreateConstructor(IEnumerable bodyStatements) => + gen.ConstructorDeclaration( + "Native", + parameters: new[] { gen.ParameterDeclaration("m", SyntaxFactory.ParseTypeName("Microsoft.Quantum.Simulation.Core.IOperationFactory")) }, + accessibility: Accessibility.Public, + baseConstructorArguments: new[] { gen.IdentifierName("m") }, + statements: bodyStatements); + + /// + /// Creates a syntax node for the Init() method of the inner Native class + /// + private SyntaxNode CreateInitMethod(IEnumerable bodyStatements) => + gen.MethodDeclaration( + "__Init__", + accessibility: Accessibility.Public, + modifiers: DeclarationModifiers.Override, + statements: new[] { + gen.ExpressionStatement( + gen.InvocationExpression(gen.MemberAccessExpression(gen.BaseExpression(), "__Init__")) + ) + }.Concat(bodyStatements)); + + /// + /// Creates a syntax node for field declarations for the alternative operations + /// + private SyntaxNode CreateOperationField(SubstitutionAttribute attr) => + gen.FieldDeclaration( + attr.OperationName.ToFullString(), + type: attr.OperationType, + accessibility: Accessibility.Private, + initializer: gen.NullLiteralExpression()); + + /// + /// Creates a syntax node for operation field assignments in the Init() method + /// + private SyntaxNode CreateOperationAssignment(SubstitutionAttribute attr) => + gen.IfStatement( + condition: gen.ValueNotEqualsExpression(attr.SimulatorName, gen.NullLiteralExpression()), + trueStatements: new[] + { + gen.AssignmentStatement( + left: attr.OperationName, + right: CreateFactoryGetStatement(attr.OperationType)) + } + ); + + /// + /// Creates a syntax node for the __Factory__.Get statement in operation field assignments + /// + private SyntaxNode CreateFactoryGetStatement(SyntaxNode type) => + gen.InvocationExpression( + gen.MemberAccessExpression( + gen.IdentifierName("__Factory__"), + gen.GenericName("Get", type)), + gen.TypeOfExpression(type)); + + /// + /// Creates a syntax node for field declarations for the simulators + /// + /// + /// + private SyntaxNode CreateSimulatorField(SubstitutionAttribute attr) => + gen.FieldDeclaration( + attr.SimulatorName.ToFullString(), + type: attr.SimulatorType, + accessibility: Accessibility.Private, + initializer: gen.NullLiteralExpression()); + + /// + /// Creates a syntax node for the simulator field cast assignments in the constructor + /// + /// + /// + private SyntaxNode CreateSimulatorCast(SubstitutionAttribute attr) => + gen.AssignmentStatement( + left: attr.SimulatorName, + right: gen.TryCastExpression(gen.IdentifierName("m"), attr.SimulatorType) + ); + + /// + /// Creates a syntax node for one specialization property + /// + private SyntaxNode CreateSpecializationProperty(string specializationName, IEnumerable attributes, string argumentType, string returnType) => + gen.PropertyDeclaration( + specializationName, + type: SyntaxFactory.ParseTypeName($"Func<{argumentType}, {returnType}>"), + accessibility: Accessibility.Public, + modifiers: DeclarationModifiers.Override | DeclarationModifiers.ReadOnly, + getAccessorStatements: new[] { gen.ReturnStatement(CreateSpecializationLambda(specializationName, attributes)) }); + + /// + /// Creates a syntax node for the lambda expression, returned in the + /// `get` accessor of a specialization property + /// + private SyntaxNode CreateSpecializationLambda(string specializationName, IEnumerable attrs) => + gen.ValueReturningLambdaExpression( + "args", + new[] + { + CreateSpecializationBody(specializationName, attrs) + }); + + /// + /// Creates a syntax node for a nested if-statement for all possible + /// operation alternatives + /// + private SyntaxNode CreateSpecializationBody(string specializationName, IEnumerable attrs) => + attrs.Count() switch + { + 0 => gen.ReturnStatement( + gen.InvocationExpression( + gen.MemberAccessExpression(gen.BaseExpression(), specializationName), + gen.IdentifierName("args"))), + _ => gen.IfStatement( + condition: gen.ValueNotEqualsExpression(attrs.First().SimulatorName, gen.NullLiteralExpression()), + trueStatements: new[] { + gen.ReturnStatement( + gen.InvocationExpression( + gen.MemberAccessExpression(attrs.First().OperationName, specializationName), + gen.IdentifierName("args"))) }, + falseStatement: CreateSpecializationBody(specializationName, attrs.Skip(1))) + }; + + /// + /// Inserts a syntax node for a class declaration into a list for its + /// corresponding namespace + /// + /// Namespace name + /// Syntax node for class declaration + private void InsertClassNode(string @namespace, SyntaxNode classNode) + { + if (!classNodes.ContainsKey(@namespace)) + { + classNodes.Add(@namespace, new List()); + } + + classNodes[@namespace].Add(classNode); + } + + /// + /// Group class declarations of the same namespace into a namespace node + /// + private IEnumerable GetNamespaceNodes() => + classNodes.Select(pair => gen.NamespaceDeclaration(pair.Key, pair.Value)); + + /// + /// Write namespaces into a text writer + /// + /// Text writer, e.g., `Console.Out` + public void WriteTo(TextWriter writer) => + gen.CompilationUnit( + new[] { "System", "Microsoft.Quantum.Simulation.Core", "Microsoft.Quantum.Simulation.Simulators" }.Select(gen.NamespaceImportDeclaration) + .Concat(GetNamespaceNodes()) + ).NormalizeWhitespace().WriteTo(writer); + + /// + /// Helper struct to prepare syntax nodes for @SubstitutableOnTarget attribute values + /// + private class SubstitutionAttribute + { + public SubstitutionAttribute(SyntaxNode operationType, SyntaxNode operationName, SyntaxNode simulatorType, SyntaxNode simulatorName) + { + OperationType = operationType; + OperationName = operationName; + SimulatorType = simulatorType; + SimulatorName = simulatorName; + } + + public SyntaxNode OperationType { get; private set; } + public SyntaxNode OperationName { get; private set; } + public SyntaxNode SimulatorType { get; private set; } + public SyntaxNode SimulatorName { get; private set; } + } + + private readonly CodegenContext ctx; + private readonly Microsoft.CodeAnalysis.Editing.SyntaxGenerator gen; + + /// + /// Contains class nodes by namespace names + /// + private readonly Dictionary> classNodes = new Dictionary>(); + } +} diff --git a/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template new file mode 100644 index 00000000000..f9d83f01d9a --- /dev/null +++ b/src/Simulation/AutoSubstitution/Microsoft.Quantum.AutoSubstitution.nuspec.template @@ -0,0 +1,29 @@ + + + + Microsoft.Quantum.AutoSubstitution + $version$ + $title$ + Microsoft + QuantumEngineering, Microsoft + + MIT + https://docs.microsoft.com/azure/quantum + images\qdk-nuget-icon.png + + false + C# code generation for automatic target substitution. + + See: https://docs.microsoft.com/azure/quantum/qdk-relnotes/ + + $copyright$ + Quantum Q# QSharp + + + + + + + + + diff --git a/src/Simulation/AutoSubstitution/RewriteStep.cs b/src/Simulation/AutoSubstitution/RewriteStep.cs new file mode 100644 index 00000000000..2108bd8c93c --- /dev/null +++ b/src/Simulation/AutoSubstitution/RewriteStep.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.Quantum.QsCompiler.CsharpGeneration; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; + +namespace Microsoft.Quantum.QsCompiler.AutoSubstitution +{ + /// + /// A Q# rewrite step for auto substitution + /// + /// + /// + /// This rewrite step creates custom emulators for operations that have the + /// SubstitutableOnTarget attribute. This attribute holds an alternative operation, + /// with the same signature, as its first argument, and a simulator, for which + /// the alternative operation should be used, as its second argument. + /// + public class RewriteStep : IRewriteStep + { + public string Name => "AutoSubstitution"; + + // This rewrite step needs to be run before C# code generation + public int Priority => -2; + + public IDictionary AssemblyConstants { get; } = new Dictionary(); + + public IEnumerable GeneratedDiagnostics => diagnostics; + + public bool ImplementsPreconditionVerification => false; + + public bool ImplementsTransformation => true; + + public bool ImplementsPostconditionVerification => false; + + public bool PostconditionVerification(QsCompilation compilation) => throw new System.NotImplementedException(); + + public bool PreconditionVerification(QsCompilation compilation) => throw new System.NotImplementedException(); + + public bool Transformation(QsCompilation compilation, [NotNullWhen(true)] out QsCompilation? transformed) + { + // we do not change the Q# syntax tree + transformed = compilation; + + // global callables + var globalCallables = compilation.Namespaces.GlobalCallableResolutions(); + + // collect all callables that have an substitution attribute + var globals = globalCallables.Where(p => p.Value.Source.CodeFile.EndsWith(".qs")) + .Where(p => p.Value.Attributes.Any(HasSubstitutionAttribute)); + + if (!globals.Any()) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Info, + Message = "AutoSubstitution: no operations have @SubstitutableOnTarget attribute", + Stage = IRewriteStep.Stage.Transformation + }); + return true; + } + + // no need to generate any C# file, if there is no substitution attribute, or if we cannot retrieve the output path + if (!AssemblyConstants.TryGetValue(Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants.OutputPath, out var outputPath)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = "AutoSubstitution: cannot determine output path for generated C# code", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Info, + Message = $"AutoSubstitution: Generating file __AutoSubstitution__.g.cs in {outputPath}", + Stage = IRewriteStep.Stage.Transformation + }); + + using var writer = new StreamWriter(Path.Combine(outputPath, "__AutoSubstitution__.g.cs")); + var context = CodegenContext.Create(compilation, AssemblyConstants); + + var generator = new CodeGenerator(context); + foreach (var (key, callable) in globals) + { + var attributeArguments = callable.Attributes.Where(HasSubstitutionAttribute).Select(GetSubstitutionAttributeArguments); + foreach (var (alternativeOperation, _) in attributeArguments) + { + var period = alternativeOperation.LastIndexOf('.'); + if (period == -1) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: name of alternative operation in {key.Namespace}.{key.Name} must be completely specified (including namespace)", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + var qualifiedName = new QsQualifiedName(alternativeOperation.Substring(0, period), alternativeOperation.Substring(period + 1)); + if (!globalCallables.TryGetValue(qualifiedName, out var alternativeCallable)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: cannot find alternative operation `{alternativeOperation}`", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + var callableSignature = callable.Signature; + var alternativeSignature = alternativeCallable.Signature; + + if (!callableSignature.ArgumentType.Equals(alternativeSignature.ArgumentType) || !callableSignature.ReturnType.Equals(alternativeSignature.ReturnType)) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: signature of `{alternativeOperation}` does not match the one of {key.Namespace}.{key.Name}", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + + if (!GetSpecializationKinds(callable).IsSubsetOf(GetSpecializationKinds(alternativeCallable))) + { + diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Error, + Message = $"AutoSubstitution: specializations of `{alternativeOperation}` must be a superset of specializations of {key.Namespace}.{key.Name}", + Stage = IRewriteStep.Stage.Transformation + }); + return false; + } + } + generator.AddCallable(key, callable, attributeArguments); + } + generator.WriteTo(writer); + + + return true; + } + + private static bool HasSubstitutionAttribute(QsDeclarationAttribute attribute) => + attribute.TypeId.IsValue && attribute.TypeId.Item.Namespace == "Microsoft.Quantum.Targeting" && attribute.TypeId.Item.Name == "SubstitutableOnTarget"; + + private static (string AlternativeOperation, string InSimulator) GetSubstitutionAttributeArguments(QsDeclarationAttribute attribute) => + attribute.Argument.Expression switch + { + QsExpressionKind.ValueTuple tuple => + tuple.Item switch + { + var arr when arr.Count() == 2 => + (arr.ElementAt(0).Expression, arr.ElementAt(1).Expression) switch + { + (QsExpressionKind.StringLiteral alternativeOperation, QsExpressionKind.StringLiteral inSimulator) => (alternativeOperation.Item1, inSimulator.Item1), + _ => throw new Exception("Unexpected argument") + }, + _ => throw new Exception("Unexpected argument") + }, + _ => throw new Exception("Unexpected argument") + }; + + private static ISet GetSpecializationKinds(QsCallable callable) => + callable.Specializations.Select(spec => spec.Kind).OrderBy(kind => kind).ToHashSet(); + + private List diagnostics = new List(); + } +} diff --git a/src/Simulation/AutoSubstitution/RewriteStep.props b/src/Simulation/AutoSubstitution/RewriteStep.props new file mode 100644 index 00000000000..ad16b42a1b3 --- /dev/null +++ b/src/Simulation/AutoSubstitution/RewriteStep.props @@ -0,0 +1,8 @@ + + + + + $(MSBuildThisFileDirectory)/../lib/netstandard2.1/Microsoft.Quantum.AutoSubstitution.dll + + + diff --git a/src/Simulation/AutoSubstitution/Substitution.qs b/src/Simulation/AutoSubstitution/Substitution.qs new file mode 100644 index 00000000000..ccaf539b6a0 --- /dev/null +++ b/src/Simulation/AutoSubstitution/Substitution.qs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Targeting { + /// # Summary + /// Enables to substitute an operation with an alternative operation for a given target + /// + /// # Named Items + /// ## AlternativeOperation + /// Fully qualified name of alternative operation to substitute operation with. + /// + /// ## TargetName + /// One of `QuantumSimulator`, `ToffoliSimulator`, or `ResourcesEstimator`, or a fully qualified name + /// of a custom target. + @Attribute() + newtype SubstitutableOnTarget = (AlternativeOperation : String, TargetName : String); +} diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln b/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln deleted file mode 100644 index 00e2b89cd81..00000000000 Binary files a/src/Simulation/EntryPointDriver.Tests/Tests.Microsoft.Quantum.EntryPointDriver.sln and /dev/null differ diff --git a/src/Simulation/QSharpFoundation/Canon/NoOp.qs b/src/Simulation/QSharpFoundation/Canon/NoOp.qs index b9a778f6e26..9b83852176d 100644 --- a/src/Simulation/QSharpFoundation/Canon/NoOp.qs +++ b/src/Simulation/QSharpFoundation/Canon/NoOp.qs @@ -21,11 +21,6 @@ namespace Microsoft.Quantum.Canon { /// ## input /// A value to be ignored. /// - /// # Remarks - /// In almost all cases, the type parameter for `NoOp` needs to be specified - /// explicitly. For instance, `NoOp` is identical to - /// . - /// /// # See Also /// - Microsoft.Quantum.Intrinsic.I operation NoOp<'T>(input : 'T) : Unit is Adj + Ctl { diff --git a/src/Simulation/QSharpFoundation/Diagnostics/Assert.qs b/src/Simulation/QSharpFoundation/Diagnostics/Assert.qs index 994dd2170f2..f79281280ed 100644 --- a/src/Simulation/QSharpFoundation/Diagnostics/Assert.qs +++ b/src/Simulation/QSharpFoundation/Diagnostics/Assert.qs @@ -24,6 +24,18 @@ namespace Microsoft.Quantum.Diagnostics { /// # See Also /// - Microsoft.Quantum.Diagnostics.AssertMeasurementProbability /// - Microsoft.Quantum.Intrinsic.Measure + /// + /// # Example + /// The following snippet will execute without errors on the full-state + /// simulator: + /// ```qsharp + /// use q = Qubit(); + /// within { + /// H(q); + /// } apply { + /// AssertMeasurement([PauliX], [q], Zero, "Expected |+⟩ state."); + /// } + /// ``` operation AssertMeasurement(bases : Pauli[], qubits : Qubit[], result : Result, msg : String) : Unit is Adj + Ctl { body (...) { diff --git a/src/Simulation/QSharpFoundation/Diagnostics/Dump.qs b/src/Simulation/QSharpFoundation/Diagnostics/Dump.qs index 316b3305290..47109c3b503 100644 --- a/src/Simulation/QSharpFoundation/Diagnostics/Dump.qs +++ b/src/Simulation/QSharpFoundation/Diagnostics/Dump.qs @@ -10,9 +10,6 @@ namespace Microsoft.Quantum.Diagnostics { /// ## location /// Provides information on where to generate the machine's dump. /// - /// # Output - /// None. - /// /// # Remarks /// This method allows you to dump information about the current status of the /// target machine into a file or some other location. @@ -25,6 +22,20 @@ namespace Microsoft.Quantum.Diagnostics { /// the path to a file in which it will write the wave function as a /// one-dimensional array of complex numbers, in which each element represents /// the amplitudes of the probability of measuring the corresponding state. + /// + /// # Example + /// When run on the full-state simulator, the following snippet dumps + /// the Bell state $(\ket{00} + \ket{11}) / \sqrt{2}$ to the console: + /// ```qsharp + /// use left = Qubit(); + /// use right = Qubit(); + /// within { + /// H(left); + /// CNOT(left, right); + /// } apply { + /// DumpMachine(); + /// } + /// ``` function DumpMachine<'T> (location : 'T) : Unit { body intrinsic; } @@ -38,9 +49,6 @@ namespace Microsoft.Quantum.Diagnostics { /// ## qubits /// The list of qubits to report. /// - /// # Output - /// None. - /// /// # Remarks /// This method allows you to dump the information associated with the state of the /// given qubits into a file or some other location. @@ -56,6 +64,22 @@ namespace Microsoft.Quantum.Diagnostics { /// the amplitudes of the probability of measuring the corresponding state. /// If the given qubits are entangled with some other qubit and their /// state can't be separated, it just reports that the qubits are entangled. + /// + /// # Example + /// When run on the full-state simulator, the following snippet dumps + /// the Bell state $(\ket{00} + \ket{11}) / \sqrt{2}$ to the console: + /// ```qsharp + /// use left = Qubit(); + /// use right = Qubit(); + /// within { + /// H(left); + /// CNOT(left, right); + /// } apply { + /// // The () input here denotes that the state dumped by the + /// // full-state simulator should be reported to the console. + /// DumpRegister((), [left, right]); + /// } + /// ``` function DumpRegister<'T> (location : 'T, qubits : Qubit[]) : Unit { body intrinsic; } diff --git a/src/Simulation/QSharpFoundation/Diagnostics/UnitTests.qs b/src/Simulation/QSharpFoundation/Diagnostics/UnitTests.qs index 9b94aa84d9c..176278a06c2 100644 --- a/src/Simulation/QSharpFoundation/Diagnostics/UnitTests.qs +++ b/src/Simulation/QSharpFoundation/Diagnostics/UnitTests.qs @@ -12,6 +12,14 @@ namespace Microsoft.Quantum.Diagnostics { /// The name has to be either one of the known targets, or a fully qualified name. /// Known targets are: QuantumSimulator, ToffoliSimulator, ResourcesEstimator. /// + /// # Example + /// The following is a unit test that checks if `2 + 3` is `5`: + /// ```qsharp + /// @Test("QuantumSimulator") + /// function AdditionIsCorrect() : Unit { + /// EqualityFactI(2 + 3, 5, "Addition did not work correctly."); + /// } + /// ``` @Attribute() newtype Test = (ExecutionTarget : String); diff --git a/src/Simulation/QSharpFoundation/Math/Types.qs b/src/Simulation/QSharpFoundation/Math/Types.qs index 722aba91eb8..5383426fc87 100644 --- a/src/Simulation/QSharpFoundation/Math/Types.qs +++ b/src/Simulation/QSharpFoundation/Math/Types.qs @@ -11,6 +11,12 @@ namespace Microsoft.Quantum.Math { /// # Summary /// Represents a complex number by its real and imaginary components. /// The first element of the tuple is the real component, the second one - the imaginary component. + /// + /// # Example + /// The following snippet defines the imaginary unit $0 + 1i$: + /// ```qsharp + /// let imagUnit = Complex(0.0, 1.0); + /// ``` newtype Complex = (Real: Double, Imag: Double); } diff --git a/src/Simulation/QSharpFoundation/Message.qs b/src/Simulation/QSharpFoundation/Message.qs index 2a5a920491b..13cc71484cf 100644 --- a/src/Simulation/QSharpFoundation/Message.qs +++ b/src/Simulation/QSharpFoundation/Message.qs @@ -12,6 +12,14 @@ namespace Microsoft.Quantum.Intrinsic { /// # Remarks /// The specific behavior of this function is simulator-dependent, /// but in most cases the given message will be written to the console. + /// + /// # Example + /// The following causes `"Hello, world!"` to be reported (typically to + /// the console): + /// ```qsharp + /// let name = "world"; + /// Message($"Hello, {name}!"); + /// ``` function Message (msg : String) : Unit { body intrinsic; }