diff --git a/Simulation.sln b/Simulation.sln index 8bf6718fb86..1a4a4abbd59 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -117,12 +117,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Controller", "Controller", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QirController", "src\Qir\Controller\QirController.csproj", "{A77E6661-D143-4E3E-BCD1-8E321A966829}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.QirController", "src\Qir\Controller\Tests.QirController\Tests.QirController.csproj", "{2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Execution", "Execution", "{442E66C8-F69F-44E9-9CD9-1F52C37EA41B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Qir.Tools", "src\Qir\Execution\Tools\Microsoft.Quantum.Qir.Tools.csproj", "{C60226E3-98DE-4E92-AED4-B4A93D4CA063}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Microsoft.Quantum.Qir.Tools", "src\Qir\Execution\Tests.Microsoft.Quantum.Qir.Tools\Tests.Microsoft.Quantum.Qir.Tools.csproj", "{4794FC80-4594-403F-AFEC-4889EFE87EA0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -775,22 +775,6 @@ Global {A77E6661-D143-4E3E-BCD1-8E321A966829}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {A77E6661-D143-4E3E-BCD1-8E321A966829}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {A77E6661-D143-4E3E-BCD1-8E321A966829}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Debug|x64.ActiveCfg = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Debug|x64.Build.0 = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Release|Any CPU.Build.0 = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Release|x64.ActiveCfg = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.Release|x64.Build.0 = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|Any CPU.Build.0 = Debug|Any CPU {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -807,6 +791,22 @@ Global {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {C60226E3-98DE-4E92-AED4-B4A93D4CA063}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Debug|x64.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|Any CPU.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|x64.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.Release|x64.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {4794FC80-4594-403F-AFEC-4889EFE87EA0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -862,9 +862,9 @@ Global {D7D34736-A719-4B45-A33F-2723F59EC29D} = {A7DB7367-9FD6-4164-8263-A05077BE54AB} {4E07F247-ED93-4497-8B58-022314308E67} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} {A77E6661-D143-4E3E-BCD1-8E321A966829} = {4E07F247-ED93-4497-8B58-022314308E67} - {2E4B9604-A5CD-4B49-B1D4-A7AC8ABAEF68} = {4E07F247-ED93-4497-8B58-022314308E67} {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} = {F6C2D4C0-12DC-40E3-9C86-FA5308D9B567} {C60226E3-98DE-4E92-AED4-B4A93D4CA063} = {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} + {4794FC80-4594-403F-AFEC-4889EFE87EA0} = {442E66C8-F69F-44E9-9CD9-1F52C37EA41B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {929C0464-86D8-4F70-8835-0A5EAF930821} diff --git a/src/Qir/Controller/Controller.cs b/src/Qir/Controller/Controller.cs index 70378632128..09a8b1ef8c8 100644 --- a/src/Qir/Controller/Controller.cs +++ b/src/Qir/Controller/Controller.cs @@ -3,11 +3,11 @@ using System; using System.IO; +using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.Quantum.Qir.Driver; -using Microsoft.Quantum.Qir.Executable; using Microsoft.Quantum.Qir.Model; +using Microsoft.Quantum.Qir.Tools.Executable; using Microsoft.Quantum.Qir.Utility; using QirExecutionWrapperSerialization = Microsoft.Quantum.QsCompiler.BondSchemas.QirExecutionWrapper.Protocols; @@ -15,7 +15,6 @@ namespace Microsoft.Quantum.Qir { public static class Controller { - private const string SourceDirectoryPath = "src"; private const string BinaryDirectoryPath = "bin"; private const string ExecutableName = "simulation.exe"; @@ -25,9 +24,6 @@ public static async Task ExecuteAsync( DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory, FileInfo errorFile, - IQirSourceFileGenerator driverGenerator, - IQirExecutableGenerator executableGenerator, - IQuantumExecutableRunner executableRunner, ILogger logger) { try @@ -37,20 +33,20 @@ public static async Task ExecuteAsync( using var inputFileStream = inputFile.OpenRead(); var input = QirExecutionWrapperSerialization.DeserializeFromFastBinary(inputFileStream); - // Step 2: Create driver. - logger.LogInfo("Creating driver file."); - var sourceDirectory = new DirectoryInfo(SourceDirectoryPath); - await driverGenerator.GenerateQirSourceFilesAsync(sourceDirectory, input.EntryPoint, input.QirBytecode); - - // Step 3: Create executable. - logger.LogInfo("Compiling and linking executable."); - var binaryDirectory = new DirectoryInfo(BinaryDirectoryPath); + // Step 2: Create executable. + logger.LogInfo("Creating executable."); + var bytecodeArray = input.QirBytecode.Array.Skip(input.QirBytecode.Offset).Take(input.QirBytecode.Count).ToList().ToArray(); var executableFile = new FileInfo(Path.Combine(BinaryDirectoryPath, ExecutableName)); - await executableGenerator.GenerateExecutableAsync(executableFile, sourceDirectory, libraryDirectory, includeDirectory); + var executable = new QirFullStateExecutable(executableFile, bytecodeArray, logger); + await executable.BuildAsync(input.EntryPoint, libraryDirectory, includeDirectory); - // Step 4: Run executable. - logger.LogInfo("Running executable."); - await executableRunner.RunExecutableAsync(executableFile, input.EntryPoint, outputFile); + // Step 3: Run executable. + if (outputFile.Exists) + { + outputFile.Delete(); + } + using var outputStream = outputFile.OpenWrite(); + await executable.RunAsync(input.EntryPoint, outputStream); } catch (Exception e) { diff --git a/src/Qir/Controller/Driver/IQirSourceFileGenerator.cs b/src/Qir/Controller/Driver/IQirSourceFileGenerator.cs deleted file mode 100644 index 92e0449c77a..00000000000 --- a/src/Qir/Controller/Driver/IQirSourceFileGenerator.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; - -namespace Microsoft.Quantum.Qir.Driver -{ - public interface IQirSourceFileGenerator - { - /// - /// Generates the C++ driver source file and writes the bytecode to a file. - /// - /// Directory to which driver and bytecode will be written. - /// Entry point information. - /// The QIR bytecode. - /// - Task GenerateQirSourceFilesAsync(DirectoryInfo sourceDirectory, EntryPointOperation entryPointOperation, ArraySegment bytecode); - } -} diff --git a/src/Qir/Controller/Driver/QirSourceFileGenerator.cs b/src/Qir/Controller/Driver/QirSourceFileGenerator.cs deleted file mode 100644 index 7a7cfe891a2..00000000000 --- a/src/Qir/Controller/Driver/QirSourceFileGenerator.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Quantum.Qir.Utility; -using Microsoft.Quantum.QsCompiler; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; - -namespace Microsoft.Quantum.Qir.Driver -{ - public class QirSourceFileGenerator : IQirSourceFileGenerator - { - private const string BytecodeFileName = "qir.bc"; - private const string DriverFileName = "driver.cpp"; - private readonly ILogger logger; - - public QirSourceFileGenerator(ILogger logger) - { - this.logger = logger; - } - - public async Task GenerateQirSourceFilesAsync(DirectoryInfo sourceDirectory, EntryPointOperation entryPointOperation, ArraySegment bytecode) - { - if (sourceDirectory.Exists) - { - sourceDirectory.Delete(true); - } - - sourceDirectory.Create(); - logger.LogInfo($"Created source directory at {sourceDirectory.FullName}."); - - // Create driver. - var driverFile = new FileInfo(Path.Combine(sourceDirectory.FullName, DriverFileName)); - using var driverFileStream = driverFile.OpenWrite(); - GenerateQirDriverCppHelper(entryPointOperation, driverFileStream); - logger.LogInfo($"Created driver file at {driverFile.FullName}."); - - // Create bytecode file. - var bytecodeFile = new FileInfo(Path.Combine(sourceDirectory.FullName, BytecodeFileName)); - using var bytecodeFileStream = bytecodeFile.OpenWrite(); - await bytecodeFileStream.WriteAsync(bytecode.Array, bytecode.Offset, bytecode.Count); - logger.LogInfo($"Created bytecode file at {bytecodeFile.FullName}."); - } - - // Virtual method wrapper is to enable mocking in unit tests. - public virtual void GenerateQirDriverCppHelper(EntryPointOperation entryPointOperation, Stream stream) - { - QirDriverGeneration.GenerateQirDriverCpp(entryPointOperation, stream); - } - } -} diff --git a/src/Qir/Controller/Executable/IQuantumExecutableRunner.cs b/src/Qir/Controller/Executable/IQuantumExecutableRunner.cs deleted file mode 100644 index 135fbbbfb4f..00000000000 --- a/src/Qir/Controller/Executable/IQuantumExecutableRunner.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.IO; -using System.Threading.Tasks; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; - -namespace Microsoft.Quantum.Qir.Executable -{ - public interface IQuantumExecutableRunner - { - /// - /// Runs a quantum program executable with the given arguments. - /// - /// Location of the executable to run. - /// Entry point and arguments to pass. - /// Location to write program output. - /// - Task RunExecutableAsync(FileInfo executableFile, EntryPointOperation entryPointOperation, FileInfo outputFile); - } -} diff --git a/src/Qir/Controller/Executable/QuantumExecutableRunner.cs b/src/Qir/Controller/Executable/QuantumExecutableRunner.cs deleted file mode 100644 index 8ce2bcd41d7..00000000000 --- a/src/Qir/Controller/Executable/QuantumExecutableRunner.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine.Invocation; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Quantum.Qir.Utility; -using Microsoft.Quantum.QsCompiler; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; - -namespace Microsoft.Quantum.Qir.Executable -{ - public class QuantumExecutableRunner : IQuantumExecutableRunner - { - private readonly ILogger logger; - - public QuantumExecutableRunner(ILogger logger) - { - this.logger = logger; - } - - public async Task RunExecutableAsync(FileInfo executableFile, EntryPointOperation entryPointOperation, FileInfo outputFile) - { - var arguments = QirDriverGeneration.GenerateCommandLineArguments(entryPointOperation.Arguments); - logger.LogInfo($"Invoking executable {executableFile.FullName} with the following arguments: {arguments}"); - arguments = $"--simulation-output {outputFile.FullName} {arguments}"; - if (outputFile.Exists) - { - outputFile.Delete(); - } - - outputFile.Create().Dispose(); - var result = await Process.ExecuteAsync( - executableFile.FullName, - arguments, - stdOut: s => { logger.LogInfo("executable: " + s); }, - stdErr: s => { logger.LogError("executable: " + s); }); - logger.LogInfo($"Executable has finished running. Result code: {result}"); - } - } -} diff --git a/src/Qir/Controller/Program.cs b/src/Qir/Controller/Program.cs index 6def5f9d223..df24e0632f9 100644 --- a/src/Qir/Controller/Program.cs +++ b/src/Qir/Controller/Program.cs @@ -4,8 +4,6 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.IO; -using Microsoft.Quantum.Qir.Driver; -using Microsoft.Quantum.Qir.Executable; using Microsoft.Quantum.Qir.Utility; namespace Microsoft.Quantum.Qir @@ -15,9 +13,6 @@ class Program static void Main(string[] args) { var logger = new Logger(new Clock()); - var execGenerator = new QirExecutableGenerator(new ClangClient(logger), logger); - var driverGenerator = new QirSourceFileGenerator(logger); - var execRunner = new QuantumExecutableRunner(logger); logger.LogInfo("QIR controller beginning."); var rootCommand = new RootCommand( @@ -69,7 +64,7 @@ static void Main(string[] args) // Bind to a handler and invoke. rootCommand.Handler = CommandHandler.Create( async (input, output, libraryDirectory, includeDirectory, error) => - await Controller.ExecuteAsync(input, output, libraryDirectory, includeDirectory, error, driverGenerator, execGenerator, execRunner, logger)); + await Controller.ExecuteAsync(input, output, libraryDirectory, includeDirectory, error, logger)); rootCommand.Invoke(args); } } diff --git a/src/Qir/Controller/QirController.csproj b/src/Qir/Controller/QirController.csproj index e9574233c00..c4398261366 100644 --- a/src/Qir/Controller/QirController.csproj +++ b/src/Qir/Controller/QirController.csproj @@ -26,6 +26,10 @@ + + + + True diff --git a/src/Qir/Controller/Tests.QirController/ControllerTests.cs b/src/Qir/Controller/Tests.QirController/ControllerTests.cs deleted file mode 100644 index 6e85b07976b..00000000000 --- a/src/Qir/Controller/Tests.QirController/ControllerTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Quantum.Qir; -using Microsoft.Quantum.Qir.Driver; -using Microsoft.Quantum.Qir.Executable; -using Microsoft.Quantum.Qir.Model; -using Microsoft.Quantum.Qir.Utility; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; -using Microsoft.Quantum.QsCompiler.BondSchemas.QirExecutionWrapper; -using Moq; -using Xunit; -using QirExecutionWrapperSerialization = Microsoft.Quantum.QsCompiler.BondSchemas.QirExecutionWrapper.Protocols; - -namespace Tests.QirController -{ - public class ControllerTests : IDisposable - { - private Mock driverGeneratorMock; - private Mock executableGeneratorMock; - private Mock executableRunnerMock; - private Mock loggerMock; - private FileInfo inputFile; - private FileInfo bytecodeFile; - private FileInfo errorFile; - private FileInfo outputFile; - private QirExecutionWrapper input; - - public ControllerTests() - { - driverGeneratorMock = new Mock(); - executableGeneratorMock = new Mock(); - executableRunnerMock = new Mock(); - inputFile = new FileInfo($"{Guid.NewGuid()}-input"); - bytecodeFile = new FileInfo($"{Guid.NewGuid()}-bytecode"); - errorFile = new FileInfo($"{Guid.NewGuid()}-error"); - outputFile = new FileInfo($"{Guid.NewGuid()}-output"); - loggerMock = new Mock(); - - // Create a QirExecutableWrapper to be used by the tests. - byte[] bytecode = { 1, 2, 3, 4, 5 }; - input = new QirExecutionWrapper() - { - EntryPoint = new EntryPointOperation() - { - Arguments = new List - { - new Argument() - { - Position = 0, - Name = "argname", - Values = new List { new ArgumentValue { String = "argvalue" } }, - } - } - }, - QirBytecode = new ArraySegment(bytecode, 1, 3), - }; - using var fileStream = inputFile.OpenWrite(); - QirExecutionWrapperSerialization.SerializeToFastBinary(input, fileStream); - } - - public void Dispose() - { - inputFile.Delete(); - bytecodeFile.Delete(); - errorFile.Delete(); - outputFile.Delete(); - } - - [Fact] - public async Task TestExecute() - { - var libraryDirectory = new DirectoryInfo("libraries"); - var includeDirectory = new DirectoryInfo("includes"); - FileInfo actualExecutableFile = null; - Action generateExecutableCallback = async (executableFile, srcDir, libDir, inclDir) => - { - actualExecutableFile = executableFile; - await Task.CompletedTask; - }; - executableGeneratorMock.Setup(obj => obj.GenerateExecutableAsync( - It.IsAny(), - It.IsAny(), - It.Is(actualLibraryDirectory => actualLibraryDirectory.FullName == libraryDirectory.FullName), - It.Is(actualIncludeDirectory => actualIncludeDirectory.FullName == includeDirectory.FullName))).Callback(generateExecutableCallback); - - await Controller.ExecuteAsync( - inputFile, - outputFile, - libraryDirectory, - includeDirectory, - errorFile, - driverGeneratorMock.Object, - executableGeneratorMock.Object, - executableRunnerMock.Object, - loggerMock.Object); - - // Verify driver was created. - driverGeneratorMock.Verify(obj => obj.GenerateQirSourceFilesAsync( - It.IsAny(), - It.Is(entryPoint => Util.EntryPointsAreEqual(entryPoint, input.EntryPoint)), - It.Is>(bytecode => BytecodesAreEqual(bytecode, input.QirBytecode)))); - - // Verify executable was generated. - executableGeneratorMock.Verify(obj => obj.GenerateExecutableAsync( - It.IsAny(), - It.IsAny(), - It.Is(actualLibraryDirectory => actualLibraryDirectory.FullName == libraryDirectory.FullName), - It.Is(actualIncludeDirectory => actualIncludeDirectory.FullName == includeDirectory.FullName))); - Assert.NotNull(actualExecutableFile); - - // Verify executable was run. - executableRunnerMock.Verify(obj => obj.RunExecutableAsync( - It.Is(executableFile => actualExecutableFile.FullName == executableFile.FullName), - It.Is(entryPoint => Util.EntryPointsAreEqual(entryPoint, input.EntryPoint)), - It.Is(actualOutputFile => actualOutputFile.FullName == outputFile.FullName))); - } - - [Fact] - public async Task TestExecuteEncountersGenericException() - { - var libraryDirectory = new DirectoryInfo("libraries"); - var includeDirectory = new DirectoryInfo("includes"); - executableGeneratorMock.Setup(obj => obj.GenerateExecutableAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ThrowsAsync(new Exception("exception message")); - - // Execute controller. - await Controller.ExecuteAsync( - inputFile, - outputFile, - libraryDirectory, - includeDirectory, - errorFile, - driverGeneratorMock.Object, - executableGeneratorMock.Object, - executableRunnerMock.Object, - loggerMock.Object); - - // Verify error file was created and contains the error. - Assert.True(errorFile.Exists); - using var errorFileStream = errorFile.OpenRead(); - using var streamReader = new StreamReader(errorFileStream); - var errorFileContents = await streamReader.ReadToEndAsync(); - var error = JsonSerializer.Deserialize(errorFileContents); - Assert.Equal(ErrorMessages.InternalError, error.Message); - Assert.Equal(Constant.ErrorCode.InternalError, error.Code); - } - - [Fact] - public async Task TestExecuteEncountersControllerException() - { - var exceptionMessage = "exception message"; - var errorCode = "error code"; - var libraryDirectory = new DirectoryInfo("libraries"); - var includeDirectory = new DirectoryInfo("includes"); - executableGeneratorMock.Setup(obj => obj.GenerateExecutableAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .ThrowsAsync(new ControllerException(exceptionMessage, errorCode)); - - // Execute controller. - await Controller.ExecuteAsync( - inputFile, - outputFile, - libraryDirectory, - includeDirectory, - errorFile, - driverGeneratorMock.Object, - executableGeneratorMock.Object, - executableRunnerMock.Object, - loggerMock.Object); - - // Verify error file was created and contains the error. - Assert.True(errorFile.Exists); - using var errorFileStream = errorFile.OpenRead(); - using var streamReader = new StreamReader(errorFileStream); - var errorFileContents = await streamReader.ReadToEndAsync(); - var error = JsonSerializer.Deserialize(errorFileContents); - Assert.Equal(exceptionMessage, error.Message); - Assert.Equal(errorCode, error.Code); - } - - private bool BytecodesAreEqual(ArraySegment bytecodeA, ArraySegment bytecodeB) - { - if (bytecodeA.Count != bytecodeB.Count) - { - return false; - } - - for (var i = 0; i < bytecodeA.Count; ++i) - { - if (bytecodeA[i] != bytecodeB[i]) - { - return false; - } - } - - return true; - } - } -} diff --git a/src/Qir/Controller/Tests.QirController/QirDriverGeneratorTests.cs b/src/Qir/Controller/Tests.QirController/QirDriverGeneratorTests.cs deleted file mode 100644 index e93837e62ea..00000000000 --- a/src/Qir/Controller/Tests.QirController/QirDriverGeneratorTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Quantum.Qir.Driver; -using Microsoft.Quantum.Qir.Utility; -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; -using Moq; -using Xunit; - -namespace Tests.QirController -{ - public class QirDriverGeneratorTests : IDisposable - { - private readonly Mock driverGeneratorMock; - private readonly DirectoryInfo sourceDirectory; - - public QirDriverGeneratorTests() - { - driverGeneratorMock = new Mock(Mock.Of()) { CallBase = true }; - driverGeneratorMock.Setup(obj => obj.GenerateQirDriverCppHelper(It.IsAny(), It.IsAny())); - sourceDirectory = new DirectoryInfo($"{Guid.NewGuid()}-source"); - } - - public void Dispose() - { - Util.DeleteDirectory(sourceDirectory); - } - - [Fact] - public async Task TestGenerateDriver() - { - var entryPoint = new EntryPointOperation() - { - Arguments = new List - { - new Argument() - { - Position = 0, - Name = "argname", - Values = new List { new ArgumentValue { String = "argvalue" } }, - } - } - }; - byte[] bytes = { 1, 2, 3, 4, 5 }; - var bytecode = new ArraySegment(bytes, 1, 3); - await driverGeneratorMock.Object.GenerateQirSourceFilesAsync(sourceDirectory, entryPoint, bytecode); - driverGeneratorMock.Verify(obj => obj.GenerateQirDriverCppHelper(It.Is(actualEntryPoint => Util.EntryPointsAreEqual(entryPoint, actualEntryPoint)), It.IsAny())); - - // Verify that the "bytecode" file was created correctly. - var bytecodeFilePath = new FileInfo(Path.Combine(sourceDirectory.FullName, "qir.bc")); - using var bytecodeFileStream = bytecodeFilePath.OpenRead(); - Assert.Equal(bytecodeFileStream.Length, bytecode.Count); - for (var i = 0; i < bytecodeFileStream.Length; ++i) - { - Assert.Equal(bytecode[i], bytecodeFileStream.ReadByte()); - } - } - } -} diff --git a/src/Qir/Controller/test-cases/standalone-input-test.in b/src/Qir/Controller/test-cases/standalone-input-test.in index 2251eb15f36..82a74f144f9 100644 Binary files a/src/Qir/Controller/test-cases/standalone-input-test.in and b/src/Qir/Controller/test-cases/standalone-input-test.in differ diff --git a/src/Qir/Controller/Tests.QirController/LoggerTests.cs b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/LoggerTests.cs similarity index 98% rename from src/Qir/Controller/Tests.QirController/LoggerTests.cs rename to src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/LoggerTests.cs index ee7adfee70a..1df4bf96552 100644 --- a/src/Qir/Controller/Tests.QirController/LoggerTests.cs +++ b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/LoggerTests.cs @@ -7,7 +7,7 @@ using Moq; using Xunit; -namespace Tests.QirController +namespace Tests.Microsoft.Quantum.Qir.Tools { public class LoggerTests { diff --git a/src/Qir/Controller/Tests.QirController/QirExecutableGeneratorTests.cs b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableGeneratorTests.cs similarity index 92% rename from src/Qir/Controller/Tests.QirController/QirExecutableGeneratorTests.cs rename to src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableGeneratorTests.cs index 3d9da400687..93a6f98a786 100644 --- a/src/Qir/Controller/Tests.QirController/QirExecutableGeneratorTests.cs +++ b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableGeneratorTests.cs @@ -6,12 +6,12 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.Quantum.Qir.Executable; +using Microsoft.Quantum.Qir.Tools.Executable; using Microsoft.Quantum.Qir.Utility; using Moq; using Xunit; -namespace Tests.QirController +namespace Tests.Microsoft.Quantum.Qir.Tools { public class QirExecutableGeneratorTests : IDisposable { @@ -23,6 +23,7 @@ public class QirExecutableGeneratorTests : IDisposable private readonly DirectoryInfo binDirectory; private readonly IList libraryFiles; private readonly IList sourceFiles; + private IList linkLibraries; public QirExecutableGeneratorTests() { @@ -49,6 +50,7 @@ public QirExecutableGeneratorTests() CreateFile("src1.cpp", sourceDirectory, "src1 contents"), CreateFile("src2.bc", sourceDirectory, "src2 contents"), }; + linkLibraries = new List { "lib1", "lib2" }; } public void Dispose() @@ -62,19 +64,13 @@ public void Dispose() [Fact] public async Task TestGenerateExecutable() { - string[] expectedLibraries = { - "Microsoft.Quantum.Qir.Runtime", - "Microsoft.Quantum.Qir.QSharp.Foundation", - "Microsoft.Quantum.Qir.QSharp.Core" - }; - var executableFile = new FileInfo(Path.Combine(binDirectory.FullName, "executableFile")); - await executableGenerator.GenerateExecutableAsync(executableFile, sourceDirectory, libraryDirectory, includeDirectory); + await executableGenerator.GenerateExecutableAsync(executableFile, sourceDirectory, libraryDirectory, includeDirectory, linkLibraries); // Verify invocation of clang. clangClientMock.Verify(obj => obj.CreateExecutableAsync( It.Is(s => s.SequenceEqual(sourceFiles.Select(fileInfo => fileInfo.FullName))), - It.Is(s => s.SequenceEqual(expectedLibraries)), + It.Is(s => s.SequenceEqual(linkLibraries)), libraryDirectory.FullName, includeDirectory.FullName, executableFile.FullName)); diff --git a/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableTests.cs b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableTests.cs new file mode 100644 index 00000000000..217a12ca09c --- /dev/null +++ b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/QirExecutableTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Tools.Driver; +using Microsoft.Quantum.Qir.Tools.Executable; +using Microsoft.Quantum.Qir.Utility; +using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; +using Moq; +using Xunit; + +namespace Tests.Microsoft.Quantum.Qir.Tools +{ + public class QirExecutableTests : IDisposable + { + private readonly DirectoryInfo sourceDirectory; + private readonly DirectoryInfo includeDirectory; + private readonly DirectoryInfo libraryDirectory; + private readonly DirectoryInfo binDirectory; + private readonly Mock driverGeneratorMock; + private readonly Mock executableGeneratorMock; + private readonly Mock runnerMock; + private readonly Mock qirExecutable; + private readonly FileInfo executableFile; + private readonly byte[] qirBytecode = { 1, 2, 3, 4, 5 }; + private readonly IList linkLibraries; + + public QirExecutableTests() + { + // Set up files. + var prefix = Guid.NewGuid().ToString(); + binDirectory = new DirectoryInfo($"{prefix}-bin"); + binDirectory.Create(); + libraryDirectory = new DirectoryInfo($"{prefix}-library"); + libraryDirectory.Create(); + includeDirectory = new DirectoryInfo($"{prefix}-include"); + includeDirectory.Create(); + sourceDirectory = new DirectoryInfo($"{prefix}-source"); + sourceDirectory.Create(); + executableFile = new FileInfo(Path.Combine(binDirectory.FullName, "executable")); + driverGeneratorMock = new Mock(); + executableGeneratorMock = new Mock(); + runnerMock = new Mock(); + qirExecutable = new Mock(executableFile, qirBytecode, Mock.Of(), driverGeneratorMock.Object, executableGeneratorMock.Object, runnerMock.Object) { CallBase = true }; + linkLibraries = new List { "lib1", "lib2" }; + qirExecutable.SetupGet(obj => obj.LinkLibraries).Returns(linkLibraries); + qirExecutable.SetupGet(obj => obj.SourceDirectoryPath).Returns(sourceDirectory.FullName); + qirExecutable.SetupGet(obj => obj.DriverFileExtension).Returns(".cpp"); + } + + public void Dispose() + { + Util.DeleteDirectory(sourceDirectory); + Util.DeleteDirectory(includeDirectory); + Util.DeleteDirectory(libraryDirectory); + Util.DeleteDirectory(binDirectory); + } + + [Fact] + public async Task TestBuild() + { + // Set up. + var entryPoint = new EntryPointOperation(); + var driverFileContents = "driver file contents"; + driverGeneratorMock.Setup(obj => obj.GenerateAsync(entryPoint, It.IsAny())).Callback((entryPoint, stream) => + { + using var streamWriter = new StreamWriter(stream); + streamWriter.Write(driverFileContents); + }); + + // Build the executable. + await qirExecutable.Object.BuildAsync(entryPoint, libraryDirectory, includeDirectory); + + // Verify that the "bytecode" file was created correctly. + var bytecodeFilePath = new FileInfo(Path.Combine(sourceDirectory.FullName, "qir.bc")); + using var bytecodeFileStream = bytecodeFilePath.OpenRead(); + Assert.Equal(bytecodeFileStream.Length, qirBytecode.Length); + for (var i = 0; i < bytecodeFileStream.Length; ++i) + { + Assert.Equal(qirBytecode[i], bytecodeFileStream.ReadByte()); + } + + // Verify that the driver was written to the correct file. + var driver = new FileInfo(Path.Combine(sourceDirectory.FullName, "driver.cpp")); + using var driverStreamReader = driver.OpenText(); + var actualDriverContents = driverStreamReader.ReadToEnd(); + Assert.Equal(driverFileContents, actualDriverContents); + + // Verify that the executable was generated. + executableGeneratorMock.Verify(obj => obj.GenerateExecutableAsync(executableFile, It.Is(arg => arg.FullName == sourceDirectory.FullName), libraryDirectory, includeDirectory, linkLibraries)); + } + + [Fact] + public async Task TestRun() + { + // Set up. + using var outputStream = new MemoryStream(); + var entryPoint = new EntryPointOperation(); + var arguments = "arguments"; + driverGeneratorMock.Setup(obj => obj.GetCommandLineArguments(entryPoint)).Returns(arguments); + + // Run executable. + await qirExecutable.Object.RunAsync(entryPoint, outputStream); + + // Verify runner was invoked properly. + runnerMock.Verify(obj => obj.RunExecutableAsync(executableFile, outputStream, arguments)); + } + } +} diff --git a/src/Qir/Controller/Tests.QirController/Tests.QirController.csproj b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Tests.Microsoft.Quantum.Qir.Tools.csproj similarity index 87% rename from src/Qir/Controller/Tests.QirController/Tests.QirController.csproj rename to src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Tests.Microsoft.Quantum.Qir.Tools.csproj index ba5183ca742..55ffaa29bd4 100644 --- a/src/Qir/Controller/Tests.QirController/Tests.QirController.csproj +++ b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Tests.Microsoft.Quantum.Qir.Tools.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Qir/Controller/Tests.QirController/Util.cs b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Util.cs similarity index 95% rename from src/Qir/Controller/Tests.QirController/Util.cs rename to src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Util.cs index 476848705ea..85c1d3a90e6 100644 --- a/src/Qir/Controller/Tests.QirController/Util.cs +++ b/src/Qir/Execution/Tests.Microsoft.Quantum.Qir.Tools/Util.cs @@ -5,7 +5,7 @@ using System.Reflection; using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; -namespace Tests.QirController +namespace Tests.Microsoft.Quantum.Qir.Tools { public static class Util { diff --git a/src/Qir/Execution/Tools/AssemblyInfo.cs b/src/Qir/Execution/Tools/AssemblyInfo.cs new file mode 100644 index 00000000000..1a412a83263 --- /dev/null +++ b/src/Qir/Execution/Tools/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// In SDK-style projects such as this one, several assembly attributes that were historically +// defined in this file are now automatically added during build and populated with +// values defined in project properties. For details of which attributes are included +// and how to customise this process see: https://aka.ms/assembly-info-properties + + +// Setting ComVisible to false makes the types in this assembly not visible to COM +// components. If you need to access a type in this assembly from COM, set the ComVisible +// attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. + +[assembly: Guid("1594aae8-0e24-442b-9201-430ce9ee4d2e")] + +[assembly: InternalsVisibleTo("Tests.Microsoft.Quantum.Qir.Tools")] + +// This is required to mock internals in tests. +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs b/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs new file mode 100644 index 00000000000..5c995bd7d0a --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/IQirDriverGenerator.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public interface IQirDriverGenerator + { + Task GenerateAsync(EntryPointOperation entryPoint, Stream stream); + + string GetCommandLineArguments(EntryPointOperation entryPoint); + } +} diff --git a/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs b/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs new file mode 100644 index 00000000000..e09ac1196c1 --- /dev/null +++ b/src/Qir/Execution/Tools/Driver/QirFullStateDriverGenerator.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.QsCompiler; +using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; + +namespace Microsoft.Quantum.Qir.Tools.Driver +{ + public class QirFullStateDriverGenerator: IQirDriverGenerator + { + public async Task GenerateAsync(EntryPointOperation entryPoint, Stream stream) + { + await Task.Run(() => QirDriverGeneration.GenerateQirDriverCpp(entryPoint, stream)); + } + + public string GetCommandLineArguments(EntryPointOperation entryPoint) + { + return QirDriverGeneration.GenerateCommandLineArguments(entryPoint.Arguments); + } + } +} diff --git a/src/Qir/Controller/Executable/ClangClient.cs b/src/Qir/Execution/Tools/Executable/ClangClient.cs similarity index 54% rename from src/Qir/Controller/Executable/ClangClient.cs rename to src/Qir/Execution/Tools/Executable/ClangClient.cs index 81bd435aff4..d4a204642a2 100644 --- a/src/Qir/Controller/Executable/ClangClient.cs +++ b/src/Qir/Execution/Tools/Executable/ClangClient.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.CommandLine.Invocation; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Quantum.Qir.Utility; -namespace Microsoft.Quantum.Qir.Executable +namespace Microsoft.Quantum.Qir.Tools.Executable { - public class ClangClient : IClangClient + internal class ClangClient : IClangClient { private const string LinkFlag = " -l "; private readonly ILogger logger; @@ -23,13 +23,19 @@ public async Task CreateExecutableAsync(string[] inputFiles, string[] libraries, // string.Join does not automatically prepend the delimiter, so it is included again in the string here. var librariesArg = $"{LinkFlag} {string.Join(LinkFlag, libraries)}"; - var arguments = $"-v {inputsArg} -I {includePath} -L {libraryPath} {librariesArg} -o {outputPath}"; + var arguments = $"{inputsArg} -I {includePath} -L {libraryPath} {librariesArg} -o {outputPath}"; logger.LogInfo($"Invoking clang with the following arguments: {arguments}"); - var result = await Process.ExecuteAsync( - "clang", - arguments, - stdOut: s => { logger.LogInfo("clang: " + s); }, - stdErr: s => { logger.LogError("clang: " + s); }); + var taskCompletionSource = new TaskCompletionSource(); + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = "clang", + Arguments = arguments, + }; + process.EnableRaisingEvents = true; + process.Exited += (sender, args) => { taskCompletionSource.SetResult(true); }; + process.Start(); + await taskCompletionSource.Task; } } } diff --git a/src/Qir/Controller/Executable/IClangClient.cs b/src/Qir/Execution/Tools/Executable/IClangClient.cs similarity index 80% rename from src/Qir/Controller/Executable/IClangClient.cs rename to src/Qir/Execution/Tools/Executable/IClangClient.cs index 7e7a8e1c95d..f5a1bb38135 100644 --- a/src/Qir/Controller/Executable/IClangClient.cs +++ b/src/Qir/Execution/Tools/Executable/IClangClient.cs @@ -3,12 +3,12 @@ using System.Threading.Tasks; -namespace Microsoft.Quantum.Qir.Executable +namespace Microsoft.Quantum.Qir.Tools.Executable { /// /// Wraps the 'clang' tool used for compilation. /// - public interface IClangClient + internal interface IClangClient { Task CreateExecutableAsync(string[] inputFiles, string[] libraries, string libraryPath, string includePath, string outputPath); } diff --git a/src/Qir/Execution/Tools/Executable/IQirExecutable.cs b/src/Qir/Execution/Tools/Executable/IQirExecutable.cs new file mode 100644 index 00000000000..6dc5914b5ea --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IQirExecutable.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + public interface IQirExecutable + { + /// + /// Builds the executable. + /// + /// Entry point operation. + /// Directory containing libraries to link. + /// Directory containing files to include. + /// + Task BuildAsync(EntryPointOperation entryPoint, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory); + + /// + /// Runs the executable. + /// + /// Entry point operation. + /// Stream to which output will be written. + /// + Task RunAsync(EntryPointOperation entryPoint, Stream output); + } +} diff --git a/src/Qir/Controller/Executable/IQirExecutableGenerator.cs b/src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs similarity index 75% rename from src/Qir/Controller/Executable/IQirExecutableGenerator.cs rename to src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs index e8ce44f7b7a..3ddb310f98e 100644 --- a/src/Qir/Controller/Executable/IQirExecutableGenerator.cs +++ b/src/Qir/Execution/Tools/Executable/IQirExecutableGenerator.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -namespace Microsoft.Quantum.Qir.Executable +namespace Microsoft.Quantum.Qir.Tools.Executable { - public interface IQirExecutableGenerator + internal interface IQirExecutableGenerator { /// /// Generates a quantum simulation program executable. @@ -15,7 +16,8 @@ public interface IQirExecutableGenerator /// Location of the source files. /// Location of the libraries that must be linked. /// Location of the headers that must be included. + /// Libraries to link. /// - public Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory); + public Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory, IList linkLibraries); } } diff --git a/src/Qir/Execution/Tools/Executable/IQuantumExecutableRunner.cs b/src/Qir/Execution/Tools/Executable/IQuantumExecutableRunner.cs new file mode 100644 index 00000000000..292cb394737 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/IQuantumExecutableRunner.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal interface IQuantumExecutableRunner + { + /// + /// Runs a quantum program executable with the given arguments. + /// + /// Location of the executable to run. + /// Stream to write program output. + /// Arguments to supply the program with. + /// + Task RunExecutableAsync(FileInfo executableFile, Stream stream, string arguments); + } +} diff --git a/src/Qir/Execution/Tools/Executable/QirExecutable.cs b/src/Qir/Execution/Tools/Executable/QirExecutable.cs new file mode 100644 index 00000000000..265006d2034 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirExecutable.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Tools.Driver; +using Microsoft.Quantum.Qir.Utility; +using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + /// + /// Base for creating and running QIR-based executables. + /// + public abstract class QirExecutable : IQirExecutable + { + private const string DriverFileName = "driver"; + private const string BytecodeFileName = "qir.bc"; + + public virtual string SourceDirectoryPath => "src"; + public abstract string DriverFileExtension { get; } + + protected FileInfo ExecutableFile { get; } + + private readonly byte[] qirBytecode; + private readonly ILogger logger; + private readonly IQuantumExecutableRunner runner; + private readonly IQirDriverGenerator driverGenerator; + private readonly IQirExecutableGenerator executableGenerator; + + public QirExecutable(FileInfo executableFile, byte[] qirBytecode, IQirDriverGenerator driverGenerator, ILogger logger = null) + : this(executableFile, + qirBytecode, + logger ?? new Logger(new Clock()), + driverGenerator, + new QirExecutableGenerator(new ClangClient(logger), logger), + new QuantumExecutableRunner(logger)) + { + } + + internal QirExecutable(FileInfo executableFile, byte[] qirBytecode, ILogger logger, IQirDriverGenerator driverGenerator, IQirExecutableGenerator executableGenerator, IQuantumExecutableRunner runner) + { + ExecutableFile = executableFile; + this.qirBytecode = qirBytecode; + this.logger = logger; + this.runner = runner; + this.driverGenerator = driverGenerator; + this.executableGenerator = executableGenerator; + } + + public async Task BuildAsync(EntryPointOperation entryPoint, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory) + { + var sourceDirectory = new DirectoryInfo(SourceDirectoryPath); + if (sourceDirectory.Exists) + { + sourceDirectory.Delete(true); + } + + sourceDirectory.Create(); + logger.LogInfo($"Created source directory at {sourceDirectory.FullName}."); + + // Create driver. + var driverFileNameWithExtension = Path.ChangeExtension(DriverFileName, DriverFileExtension); + var driverFile = new FileInfo(Path.Combine(sourceDirectory.FullName, driverFileNameWithExtension)); + using (var driverFileStream = driverFile.OpenWrite()) + { + await driverGenerator.GenerateAsync(entryPoint, driverFileStream); + } + logger.LogInfo($"Created driver file at {driverFile.FullName}."); + + // Create bytecode file. + var bytecodeFile = new FileInfo(Path.Combine(sourceDirectory.FullName, BytecodeFileName)); + using (var bytecodeFileStream = bytecodeFile.OpenWrite()) + { + await bytecodeFileStream.WriteAsync(qirBytecode); + } + logger.LogInfo($"Created bytecode file at {bytecodeFile.FullName}."); + + await executableGenerator.GenerateExecutableAsync(ExecutableFile, sourceDirectory, libraryDirectory, includeDirectory, LinkLibraries); + } + + public async Task RunAsync(EntryPointOperation entryPoint, Stream output) + { + var stringArguments = driverGenerator.GetCommandLineArguments(entryPoint); + await runner.RunExecutableAsync(ExecutableFile, output, stringArguments); + } + + public abstract IList LinkLibraries { get; } + } +} diff --git a/src/Qir/Controller/Executable/QirExecutableGenerator.cs b/src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs similarity index 80% rename from src/Qir/Controller/Executable/QirExecutableGenerator.cs rename to src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs index 9445f300115..80eaf8a768a 100644 --- a/src/Qir/Controller/Executable/QirExecutableGenerator.cs +++ b/src/Qir/Execution/Tools/Executable/QirExecutableGenerator.cs @@ -1,20 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.Quantum.Qir.Utility; -namespace Microsoft.Quantum.Qir.Executable +namespace Microsoft.Quantum.Qir.Tools.Executable { - public class QirExecutableGenerator : IQirExecutableGenerator + internal class QirExecutableGenerator : IQirExecutableGenerator { - private static readonly string[] LibrariesToLink = { - "Microsoft.Quantum.Qir.Runtime", - "Microsoft.Quantum.Qir.QSharp.Foundation", - "Microsoft.Quantum.Qir.QSharp.Core" - }; private readonly IClangClient clangClient; private readonly ILogger logger; @@ -24,7 +20,7 @@ public QirExecutableGenerator(IClangClient clangClient, ILogger logger) this.logger = logger; } - public async Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory) + public async Task GenerateExecutableAsync(FileInfo executableFile, DirectoryInfo sourceDirectory, DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory, IList linkLibraries) { // Wrap in a Task.Run because FileInfo methods are not asynchronous. await Task.Run(async () => @@ -42,7 +38,7 @@ await Task.Run(async () => } var inputFiles = sourceDirectory.GetFiles().Select(fileInfo => fileInfo.FullName).ToArray(); - await clangClient.CreateExecutableAsync(inputFiles, LibrariesToLink, libraryDirectory.FullName, includeDirectory.FullName, executableFile.FullName); + await clangClient.CreateExecutableAsync(inputFiles, linkLibraries.ToArray(), libraryDirectory.FullName, includeDirectory.FullName, executableFile.FullName); }); } diff --git a/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs b/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs new file mode 100644 index 00000000000..03362d370a6 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QirFullStateExecutable.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using Microsoft.Quantum.Qir.Tools.Driver; +using Microsoft.Quantum.Qir.Utility; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + /// + /// Class to create and run QIR-based executables that use the full-state simulator. + /// + public class QirFullStateExecutable : QirExecutable + { + public QirFullStateExecutable(FileInfo executableFile, byte[] qirBytecode, ILogger logger = null) + : base(executableFile, + qirBytecode, + new QirFullStateDriverGenerator(), + logger) + { + } + + public override IList LinkLibraries => new List { + "Microsoft.Quantum.Qir.Runtime", + "Microsoft.Quantum.Qir.QSharp.Foundation", + "Microsoft.Quantum.Qir.QSharp.Core" + }; + + public override string DriverFileExtension => "cpp"; + } +} diff --git a/src/Qir/Execution/Tools/Executable/QuantumExecutableRunner.cs b/src/Qir/Execution/Tools/Executable/QuantumExecutableRunner.cs new file mode 100644 index 00000000000..987d6212799 --- /dev/null +++ b/src/Qir/Execution/Tools/Executable/QuantumExecutableRunner.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Quantum.Qir.Utility; + +namespace Microsoft.Quantum.Qir.Tools.Executable +{ + internal class QuantumExecutableRunner : IQuantumExecutableRunner + { + private readonly ILogger logger; + + public QuantumExecutableRunner(ILogger logger) + { + this.logger = logger; + } + + public async Task RunExecutableAsync(FileInfo executableFile, Stream stream, string arguments) + { + logger.LogInfo($"Invoking executable {executableFile.FullName} with the following arguments: {arguments}"); + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = executableFile.FullName, + Arguments = arguments, + RedirectStandardOutput = true, + }; + + process.EnableRaisingEvents = true; + process.Start(); + var output = await process.StandardOutput.ReadToEndAsync(); + process.WaitForExit(); + using var streamWriter = new StreamWriter(stream); + await streamWriter.WriteAsync(output); + logger.LogInfo($"Executable has finished running. Result code: {process.ExitCode}"); + } + } +} diff --git a/src/Qir/Execution/Tools/Microsoft.Quantum.Qir.Tools.csproj b/src/Qir/Execution/Tools/Microsoft.Quantum.Qir.Tools.csproj index 5ad95e9541e..06ca9571b79 100644 --- a/src/Qir/Execution/Tools/Microsoft.Quantum.Qir.Tools.csproj +++ b/src/Qir/Execution/Tools/Microsoft.Quantum.Qir.Tools.csproj @@ -11,7 +11,7 @@ - + - + diff --git a/src/Qir/Execution/Tools/QirExecutable.cs b/src/Qir/Execution/Tools/QirExecutable.cs deleted file mode 100644 index de641f4b174..00000000000 --- a/src/Qir/Execution/Tools/QirExecutable.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace Microsoft.Quantum.Qir.Tools -{ - /// - /// Base for creating and running QIR-based executables. - /// - public abstract class QirExecutable - { - private readonly EntryPointOperation EntryPointOperation; - private readonly byte[] QirBytecode; - - /// - /// Constructor for the QirExecutable class. - /// - /// Object that provides data to specify which entry-point to use for building and running a QIR-based executable. - /// QIR bytecode used to build the executable. - public QirExecutable(EntryPointOperation entryPoint, byte[] qirBytecode) - { - this.EntryPointOperation = entryPoint; - this.QirBytecode = qirBytecode; - } - - /// - /// Creates a QIR-based executable. - /// - /// Directory where the libraries to link to are located. - /// Directory where the headers needed for compilation are located. - /// File to write the executable to. - public Task BuildAsync(DirectoryInfo libraryDirectory, DirectoryInfo includeDirectory, FileInfo executable) - { - throw new NotImplementedException(); - } - - // TODO: How arguments are passed to this API will change. - public Task RunAsync(FileInfo executable, DirectoryInfo librariesDirectory, IList arguments) - { - throw new NotImplementedException(); - } - - // TODO: To be used by BuildAsync. - protected abstract Task GenerateDriverAsync(Stream driver); - - // TODO: To be used by RunAsync. - // TODO: How arguments are passed to this API will change. - protected abstract string GetCommandLineArguments(IList arguments); - - // TODO: To be used by BuildAsync. - protected abstract IList GetLinkLibraries(); - } -} diff --git a/src/Qir/Execution/Tools/QirFullStateExecutable.cs b/src/Qir/Execution/Tools/QirFullStateExecutable.cs deleted file mode 100644 index 882572810bc..00000000000 --- a/src/Qir/Execution/Tools/QirFullStateExecutable.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Quantum.QsCompiler.BondSchemas.EntryPoint; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace Microsoft.Quantum.Qir.Tools -{ - /// - /// Class to create and run QIR-based executables that use the full-state simulator. - /// - public class QirFullStateExecutable : QirExecutable - { - /// - public QirFullStateExecutable(EntryPointOperation entryPoint, byte[] qirBytecode) : - base(entryPoint, qirBytecode) - { - } - - protected override Task GenerateDriverAsync(Stream driver) - { - throw new NotImplementedException(); - } - - // TODO: How arguments are passed to this API will change. - protected override string GetCommandLineArguments(IList arguments) - { - throw new NotImplementedException(); - } - - protected override IList GetLinkLibraries() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Qir/Controller/Utility/Clock.cs b/src/Qir/Execution/Tools/Utility/Clock.cs similarity index 100% rename from src/Qir/Controller/Utility/Clock.cs rename to src/Qir/Execution/Tools/Utility/Clock.cs diff --git a/src/Qir/Controller/Utility/IClock.cs b/src/Qir/Execution/Tools/Utility/IClock.cs similarity index 100% rename from src/Qir/Controller/Utility/IClock.cs rename to src/Qir/Execution/Tools/Utility/IClock.cs diff --git a/src/Qir/Controller/Utility/ILogger.cs b/src/Qir/Execution/Tools/Utility/ILogger.cs similarity index 100% rename from src/Qir/Controller/Utility/ILogger.cs rename to src/Qir/Execution/Tools/Utility/ILogger.cs diff --git a/src/Qir/Controller/Utility/Logger.cs b/src/Qir/Execution/Tools/Utility/Logger.cs similarity index 100% rename from src/Qir/Controller/Utility/Logger.cs rename to src/Qir/Execution/Tools/Utility/Logger.cs