This repository has been archived by the owner on Jan 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 169
/
Copy pathCompilationLoader.cs
1258 lines (1090 loc) · 65.2 KB
/
CompilationLoader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.Quantum.QsCompiler.BuiltInRewriteSteps;
using Microsoft.Quantum.QsCompiler.CompilationBuilder;
using Microsoft.Quantum.QsCompiler.Diagnostics;
using Microsoft.Quantum.QsCompiler.ReservedKeywords;
using Microsoft.Quantum.QsCompiler.SyntaxTree;
using Microsoft.Quantum.QsCompiler.Transformations;
using Microsoft.Quantum.QsCompiler.Transformations.BasicTransformations;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using MetadataReference = Microsoft.CodeAnalysis.MetadataReference;
using OptimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel;
namespace Microsoft.Quantum.QsCompiler
{
public class CompilationLoader
{
/// <summary>
/// Given a load function that loads the content of a sequence of files from disk,
/// returns the content for all sources to compile.
/// </summary>
public delegate ImmutableDictionary<Uri, string> SourceLoader(Func<IEnumerable<string>, ImmutableDictionary<Uri, string>> loadFromDisk);
/// <summary>
/// Given a load function that loads the content of a sequence of referenced assemblies from disk,
/// returns the loaded references for the compilation.
/// </summary>
public delegate References ReferenceLoader(Func<IEnumerable<string>, References> loadFromDisk);
/// <summary>
/// If LoadAssembly is not null, it will be used to load the dlls that are search for classes defining rewrite steps.
/// </summary>
public static Func<string, Assembly>? LoadAssembly { get; set; }
/// <summary>
/// may be specified via configuration (or project) file in the future
/// </summary>
public class Configuration
{
/// <summary>
/// The name of the project. Used as assembly name in the generated dll.
/// The name of the project with a suitable extension will also be used as the name of the generated binary file.
/// </summary>
public string? ProjectName { get; set; }
/// <summary>
/// If set to true, forces all rewrite steps to execute, regardless of whether their precondition was satisfied.
/// If the precondition of a step is not satisfied, the transformation is executed but the output will be ignored,
/// and an error is generated, indicating a compilation failure.
/// </summary>
public bool ForceRewriteStepExecution { get; set; }
/// <summary>
/// If set to true, the syntax tree rewrite step that replaces all generation directives
/// for all functor specializations is executed during compilation.
/// </summary>
public bool GenerateFunctorSupport { get; set; }
/// <summary>
/// Unless this is set to true, the syntax tree rewrite step that inlines conjugations is executed during compilation.
/// </summary>
public bool SkipConjugationInlining { get; set; }
/// <summary>
/// Unless this is set to true, all unused callables are removed from the syntax tree.
/// </summary>
public bool SkipSyntaxTreeTrimming { get; set; }
/// <summary>
/// If this is set to true, the initial validation will take the specified runtime capability into account,
/// but any further compilation steps will execute as if no target specific properties were specified.
/// </summary>
public bool SkipTargetSpecificCompilation { get; set; }
/// <summary>
/// If set to true, the compiler attempts to pre-evaluate the built compilation as much as possible.
/// This is an experimental feature that will change over time.
/// </summary>
public bool AttemptFullPreEvaluation { get; set; }
/// <summary>
/// Specifies the capabilities of the target.
/// The specified capabilities determine what QIR profile to compile to.
/// </summary>
public TargetCapability? TargetCapability { get; set; }
/// <summary>
/// Specifies the list of warning numbers that should be treated as errors.
/// </summary>
public IEnumerable<int>? WarningsAsErrors { get; set; }
/// <summary>
/// Specifies whether the project to build is a Q# command line application.
/// If set to true, a warning will be raised if no entry point is defined.
/// If set to false, then defined entry points will be ignored and a warning will be raised.
/// </summary>
public bool IsExecutable { get; set; }
/// <summary>
/// Unless this is set to true, all usages of type-parameterized callables are replaced with
/// the concrete callable instantiation if an entry point is specified for the compilation.
/// Removes all type-parameterizations in the syntax tree.
/// </summary>
public bool SkipMonomorphization { get; set; }
/// <summary>
/// Indicates whether the compiler will remove lambda expressions and replace them with calls to generated callables.
/// </summary>
public bool LiftLambdaExpressions { get; set; } = true;
/// <summary>
/// If the output folder is not null,
/// documentation is generated in the specified folder based on doc comments in the source code.
/// </summary>
public string? DocumentationOutputFolder { get; set; }
/// <summary>
/// Directory where the compiled binaries will be generated.
/// No binaries will be written to disk unless this path is specified and valid.
/// </summary>
public string? BuildOutputFolder { get; set; }
/// <summary>
/// Output path for the dll containing the compiled binaries.
/// No dll will be generated unless this path is specified and valid.
/// </summary>
public string? DllOutputPath { get; set; }
/// <summary>
/// If set to true, then referenced dlls will be loaded purely based on attributes in the contained C# code.
/// Any Q# resources will be ignored.
/// </summary>
public bool LoadReferencesBasedOnGeneratedCsharp { get; set; }
/// <summary>
/// If set to true, then public types and callables declared in referenced assemblies
/// are exposed via their test name defined by the corresponding attribute.
/// </summary>
public bool ExposeReferencesViaTestNames { get; set; }
/// <summary>
/// Contains a sequence of tuples with the path to a dotnet dll containing one or more rewrite steps
/// (i.e. classes implementing IRewriteStep) and the corresponding output folder.
/// The contained rewrite steps will be executed in the defined order and priority at the end of the compilation.
/// </summary>
public IEnumerable<(string, string?)> RewriteStepAssemblies { get; set; } =
Enumerable.Empty<(string, string?)>();
/// <summary>
/// Contains a sequence of tuples with the types (classes implementing IRewriteStep) and the corresponding output folder.
/// The contained rewrite steps will be executed in the defined order and priority at the end of the compilation.
/// </summary>
public IEnumerable<(Type, string?)> RewriteStepTypes { get; set; } = Enumerable.Empty<(Type, string?)>();
/// <summary>
/// Contains a sequence of tuples with the objects (instances of IRewriteStep) and the corresponding output folder.
/// The contained rewrite steps will be executed in the defined order and priority at the end of the compilation.
/// </summary>
public IEnumerable<(IRewriteStep, string?)> RewriteStepInstances { get; set; } =
Enumerable.Empty<(IRewriteStep, string?)>();
/// <summary>
/// If set to true, the post-condition for loaded rewrite steps is checked if the corresponding verification is implemented.
/// Otherwise post-condition verifications are skipped.
/// </summary>
public bool EnableAdditionalChecks { get; set; }
/// <summary>
/// Handle to pass arbitrary constants with which to populate the corresponding dictionary for loaded rewrite steps.
/// These values will take precedence over any already existing values that the default constructor sets.
/// However, the compiler may overwrite the assembly constants defined for the Q# compilation unit in the dictionary of the loaded step.
/// The given dictionary in this configuration is left unchanged in any case.
/// </summary>
public IReadOnlyDictionary<string, string>? AssemblyConstants { get; set; }
/// <summary>
/// Paths to the assemblies that contains a syntax tree with target specific implementations for certain functions and operations.
/// The functions and operations defined in these assemblies replace the ones declared within the compilation unit.
/// If no paths are specified here or the sequence is null then this compilation step is omitted.
/// </summary>
public IEnumerable<string>? TargetPackageAssemblies { get; set; }
/// <summary>
/// Indicates whether the necessary compiler passes are executed for the compilation to be compatible with QIR generation.
/// </summary>
public bool PrepareQirGeneration { get; set; }
/// <summary>
/// Indicates whether a serialization of the syntax tree needs to be generated.
/// This is the case if either the build output folder is specified or the dll output path is specified.
/// </summary>
internal bool SerializeSyntaxTree =>
this.BuildOutputFolder != null || this.DllOutputPath != null;
/// <summary>
/// Indicates whether the compilation should be trimmed to include only the used callables.
/// This value is never true if SkipSyntaxTreeTrimming is specified.
/// </summary>
internal bool TrimTree =>
this.IsExecutable && !this.SkipSyntaxTreeTrimming;
/// <summary>
/// Indicates whether the compilation needs to be monomorphized.
/// This value is never true if SkipMonomorphization is specified.
/// </summary>
internal bool Monomorphize =>
(this.IsExecutable || this.PrepareQirGeneration) && !this.SkipMonomorphization;
/// <summary>
/// Indicates whether the compiler will remove if-statements and replace them with calls to appropriate intrinsic operations.
/// </summary>
internal bool ConvertClassicalControl =>
!this.SkipTargetSpecificCompilation && (this.TargetCapability?.ResultOpacity.Equals(ResultOpacityModule.Controlled) ?? false);
/// <summary>
/// Indicates whether any paths to assemblies have been specified that may contain target specific decompositions.
/// </summary>
internal bool LoadTargetSpecificDecompositions =>
!this.SkipTargetSpecificCompilation && this.TargetPackageAssemblies != null && this.TargetPackageAssemblies.Any();
/// <summary>
/// If the ProjectName does not have an ending "proj", appends a .qsproj ending to the project name.
/// Returns null if the project name is null.
/// </summary>
internal string? ProjectNameWithExtension =>
this.ProjectName == null ? null :
this.ProjectName.EndsWith("proj") ? this.ProjectName :
$"{this.ProjectName}.qsproj";
/// <summary>
/// If the ProjectName does have an extension ending with "proj", returns the project name without that extension.
/// Returns null if the project name is null.
/// </summary>
internal string? ProjectNameWithoutPathOrExtension =>
this.ProjectName == null ? null :
Path.GetExtension(this.ProjectName).EndsWith("proj") ? Path.GetFileNameWithoutExtension(this.ProjectName) :
Path.GetFileName(this.ProjectName);
}
/// <summary>
/// used to indicate the status of individual compilation steps
/// </summary>
public enum Status
{
/// <summary>
/// Indicates that a compilation step has not been executed.
/// </summary>
NotRun = -1,
/// <summary>
/// Indicates that a compilation step successfully executed.
/// </summary>
Succeeded = 0,
/// <summary>
/// Indicates that a compilation step executed but failed.
/// </summary>
Failed = 1,
}
[SuppressMessage(
"StyleCop.CSharp.MaintainabilityRules",
"SA1401:FieldsMustBePrivate",
Justification = "Fields are passed by reference.")]
private class ExecutionStatus
{
internal Status SourceFileLoading = Status.NotRun;
internal Status ReferenceLoading = Status.NotRun;
internal Status PluginLoading = Status.NotRun;
internal Status Validation = Status.NotRun;
internal Status TargetSpecificReplacements = Status.NotRun;
internal Status FunctorSupport = Status.NotRun;
internal Status PreEvaluation = Status.NotRun;
internal Status ConjugationInlining = Status.NotRun;
internal Status TreeTrimming = Status.NotRun;
internal Status LiftLambdaExpressions = Status.NotRun;
internal Status ConvertClassicalControl = Status.NotRun;
internal Status Monomorphization = Status.NotRun;
internal Status Documentation = Status.NotRun;
internal Status Serialization = Status.NotRun;
internal Status BinaryFormat = Status.NotRun;
internal Status DllGeneration = Status.NotRun;
internal Status CapabilityInference = Status.NotRun;
internal Status[] LoadedRewriteSteps;
internal ExecutionStatus(IEnumerable<IRewriteStep> externalRewriteSteps) =>
this.LoadedRewriteSteps = externalRewriteSteps.Select(_ => Status.NotRun).ToArray();
private bool WasSuccessful(bool run, Status code) =>
(run && code == Status.Succeeded) || (!run && code == Status.NotRun);
internal bool Success(Configuration options) =>
this.SourceFileLoading <= 0 &&
this.ReferenceLoading <= 0 &&
this.WasSuccessful(true, this.Validation) &&
this.WasSuccessful(true, this.PluginLoading) &&
this.WasSuccessful(options.IsExecutable && !options.SkipSyntaxTreeTrimming, this.TreeTrimming) &&
this.WasSuccessful(options.GenerateFunctorSupport, this.FunctorSupport) &&
this.WasSuccessful(!options.SkipConjugationInlining, this.ConjugationInlining) &&
this.WasSuccessful(options.AttemptFullPreEvaluation, this.PreEvaluation) &&
this.WasSuccessful(options.LoadTargetSpecificDecompositions, this.TargetSpecificReplacements) &&
this.WasSuccessful(options.ConvertClassicalControl, this.ConvertClassicalControl) &&
this.WasSuccessful(options.Monomorphize, this.Monomorphization) &&
this.WasSuccessful(!options.IsExecutable, this.CapabilityInference) &&
this.WasSuccessful(options.SerializeSyntaxTree, this.Serialization) &&
this.WasSuccessful(options.BuildOutputFolder != null, this.BinaryFormat) &&
this.WasSuccessful(options.DllOutputPath != null, this.DllGeneration) &&
this.LoadedRewriteSteps.All(status => this.WasSuccessful(true, status));
}
/// <summary>
/// Indicates whether all source files were loaded successfully.
/// Source file loading may not be executed if the content was preloaded using methods outside this class.
/// </summary>
public Status SourceFileLoading => this.compilationStatus.SourceFileLoading;
/// <summary>
/// Indicates whether all references were loaded successfully.
/// The loading may not be executed if all references were preloaded using methods outside this class.
/// </summary>
public Status ReferenceLoading => this.compilationStatus.ReferenceLoading;
/// <summary>
/// Indicates whether all external dlls specifying e.g. rewrite steps
/// to perform as part of the compilation have been loaded successfully.
/// The status indicates a successful execution if no such external dlls have been specified.
/// </summary>
public Status PluginLoading => this.compilationStatus.PluginLoading;
/// <summary>
/// Indicates whether the compilation unit passed the compiler validation
/// that is executed before invoking further rewrite and/or generation steps.
/// </summary>
public Status Validation => this.compilationStatus.Validation;
/// <summary>
/// Indicates whether all lambda expressions were replaced successfully with
/// calls to generated callables.
/// </summary>
public Status LiftLambdaExpressions => this.compilationStatus.LiftLambdaExpressions;
/// <summary>
/// Indicates whether any target-specific compilation steps executed successfully.
/// This includes the step to convert control flow statements when needed.
/// </summary>
public Status TargetSpecificCompilation => this.compilationStatus.ConvertClassicalControl;
/// <summary>
/// Indicates whether target specific implementations for functions and operations
/// have been used to replace the ones declared within the compilation unit.
/// This step is only executed if the specified configuration contains the path to the target package.
/// </summary>
public Status TargetSpecificReplacements => this.compilationStatus.TargetSpecificReplacements;
/// <summary>
/// Indicates whether all specializations were generated successfully.
/// This rewrite step is only executed if the corresponding configuration is specified.
/// </summary>
public Status FunctorSupport => this.compilationStatus.FunctorSupport;
/// <summary>
/// Indicates whether the pre-evaluation step executed successfully.
/// This rewrite step is only executed if the corresponding configuration is specified.
/// </summary>
public Status PreEvaluation => this.compilationStatus.PreEvaluation;
/// <summary>
/// Indicates whether all the type-parameterized callables were resolved to concrete callables.
/// This rewrite step is only executed if the corresponding configuration is specified.
/// </summary>
public Status Monomorphization => this.compilationStatus.Monomorphization;
/// <summary>
/// Indicates whether the inference of required runtime capabilities for execution completed successfully.
/// This rewrite step is only executed when compiling a library.
/// </summary>
public Status CapabilityInference => this.compilationStatus.CapabilityInference;
/// <summary>
/// Indicates whether documentation for the compilation was generated successfully.
/// This step is only executed if the corresponding configuration is specified.
/// </summary>
public Status Documentation => this.compilationStatus.Documentation;
/// <summary>
/// Indicates whether the built compilation could be serialized successfully.
/// This step is only executed if either the binary representation or a dll is emitted.
/// </summary>
public Status Serialization => this.compilationStatus.Serialization;
/// <summary>
/// Indicates whether a binary representation for the generated syntax tree has been generated successfully.
/// This step is only executed if the corresponding configuration is specified.
/// </summary>
public Status BinaryFormat => this.compilationStatus.BinaryFormat;
/// <summary>
/// Indicates whether a dll containing the compiled binary has been generated successfully.
/// This step is only executed if the corresponding configuration is specified.
/// </summary>
public Status DllGeneration => this.compilationStatus.DllGeneration;
/// <summary>
/// Indicates whether all rewrite steps with the given name and loaded from the given source executed successfully.
/// The source, if specified, is the path to the dll in which the step is specified.
/// Returns a status NotRun if no such step was found or executed.
/// Execution is considered successful if the precondition and transformation (if any) returned true.
/// </summary>
public Status LoadedRewriteStep(string name, string? source = null)
{
var uri = string.IsNullOrWhiteSpace(source) ? null : new Uri(Path.GetFullPath(source));
bool MatchesQuery(int index) => this.externalRewriteSteps[index].Name == name && (source == null || this.externalRewriteSteps[index].Origin == uri);
var statuses = this.compilationStatus.LoadedRewriteSteps.Where((s, i) => MatchesQuery(i)).ToArray();
return statuses.All(s => s == Status.Succeeded) ? Status.Succeeded : statuses.Any(s => s == Status.Failed) ? Status.Failed : Status.NotRun;
}
/// <summary>
/// Indicates the overall status of all rewrite step from external dlls.
/// The status is indicated as success if none of these steps failed.
/// </summary>
public Status AllLoadedRewriteSteps => this.compilationStatus.LoadedRewriteSteps.Any(s => s == Status.Failed) ? Status.Failed : Status.Succeeded;
/// <summary>
/// Indicates the overall success of all compilation steps.
/// The compilation is indicated as having been successful if all steps that were configured to execute completed successfully.
/// </summary>
public bool Success => this.compilationStatus.Success(this.config);
/// <summary>
/// Logger used to log all diagnostic events during compilation.
/// </summary>
private readonly ILogger? logger;
/// <summary>
/// Configuration specifying the compilation steps to execute.
/// </summary>
private readonly Configuration config;
/// <summary>
/// Used to track the status of individual compilation steps.
/// </summary>
private readonly ExecutionStatus compilationStatus;
/// <summary>
/// Contains all loaded rewrite steps found in the specified plugin DLLs,
/// the passed in types implementing IRewriteStep and instances of IRewriteStep,
/// where configurable properties such as the output folder have already been initialized to suitable values.
/// </summary>
private readonly ImmutableArray<LoadedStep> externalRewriteSteps;
/// <summary>
/// Contains all diagnostics generated upon source file and reference loading.
/// All other diagnostics can be accessed via the VerifiedCompilation.
/// </summary>
public ImmutableArray<Diagnostic> LoadDiagnostics { get; set; }
/// <summary>
/// Contains the initial compilation built by the compilation unit manager after verification.
/// </summary>
public CompilationUnitManager.Compilation? VerifiedCompilation { get; }
/// <summary>
/// Contains the built compilation including the syntax tree after executing all configured rewrite steps.
/// </summary>
public QsCompilation? CompilationOutput { get; private set; }
/// <summary>
/// Contains the absolute path where the binary representation of the generated syntax tree has been written to disk.
/// </summary>
public string? PathToCompiledBinary { get; private set; }
/// <summary>
/// Contains the absolute path where the generated dll containing the compiled binary has been written to disk.
/// </summary>
public string? DllOutputPath { get; private set; }
/// <summary>
/// Contains the full Q# syntax tree after executing all configured rewrite steps, including the content of loaded references.
/// </summary>
public IEnumerable<QsNamespace>? GeneratedSyntaxTree =>
this.CompilationOutput?.Namespaces;
/// <summary>
/// Contains the Uri and names of all rewrite steps loaded from the specified dlls
/// in the order in which they are executed.
/// </summary>
public ImmutableArray<(Uri, string)> LoadedRewriteSteps =>
this.externalRewriteSteps.Select(step => (step.Origin, step.Name)).ToImmutableArray();
/// <summary>
/// Builds the compilation for the source files and references loaded by the given loaders,
/// executing the compilation steps specified by the given options.
/// Uses the specified logger to log all diagnostic events.
/// </summary>
/// <remarks>This method waits for <see cref="System.Threading.Tasks.Task"/>s to complete and may deadlock if invoked through a <see cref="System.Threading.Tasks.Task"/>.</remarks>
public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReferences, Configuration? options = null, ILogger? logger = null)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.OverallCompilation);
// loading the content to compiler
this.logger = logger;
this.LoadDiagnostics = ImmutableArray<Diagnostic>.Empty;
this.config = options ?? new Configuration();
// When loading references is done through the generated C# a Bond deserializer is not needed.
if (!this.config.LoadReferencesBasedOnGeneratedCsharp)
{
BondSchemas.Protocols.InitializeDeserializer();
}
// When the syntax tree is not serialized a Bond serializer is not needed.
if (this.config.SerializeSyntaxTree)
{
BondSchemas.Protocols.InitializeSerializer();
}
Status rewriteStepLoading = Status.Succeeded;
this.externalRewriteSteps = ExternalRewriteStepsManager.Load(this.config, d => this.LogAndUpdateLoadDiagnostics(ref rewriteStepLoading, d), ex => this.LogAndUpdate(ref rewriteStepLoading, ex));
this.PrintLoadedRewriteSteps(this.externalRewriteSteps);
this.compilationStatus = new ExecutionStatus(this.externalRewriteSteps);
this.compilationStatus.PluginLoading = rewriteStepLoading;
PerformanceTracking.TaskStart(PerformanceTracking.Task.SourcesLoading);
var sourceFiles = loadSources(this.LoadSourceFiles);
PerformanceTracking.TaskEnd(PerformanceTracking.Task.SourcesLoading);
PerformanceTracking.TaskStart(PerformanceTracking.Task.ReferenceLoading);
var references = loadReferences(
refs => this.LoadAssemblies(
refs,
loadTestNames: this.config.ExposeReferencesViaTestNames,
ignoreDllResources: this.config.LoadReferencesBasedOnGeneratedCsharp));
PerformanceTracking.TaskEnd(PerformanceTracking.Task.ReferenceLoading);
// building the compilation
PerformanceTracking.TaskStart(PerformanceTracking.Task.Build);
this.compilationStatus.Validation = Status.Succeeded;
var files = CompilationUnitManager.InitializeFileManagers(sourceFiles, null, this.OnCompilerException); // do *not* live track (i.e. use publishing) here!
var processorArchitecture = this.config.AssemblyConstants?.GetValueOrDefault(AssemblyConstants.ProcessorArchitecture);
var buildProperties = ImmutableDictionary.CreateBuilder<string, string?>();
buildProperties.Add(MSBuildProperties.ResolvedTargetCapability, this.config.TargetCapability?.Name);
buildProperties.Add(MSBuildProperties.ResolvedQsharpOutputType, this.config.IsExecutable ? AssemblyConstants.QsharpExe : AssemblyConstants.QsharpLibrary);
buildProperties.Add(MSBuildProperties.ResolvedProcessorArchitecture, processorArchitecture);
if (this.config.WarningsAsErrors != null)
{
buildProperties.Add(MSBuildProperties.WarningsAsErrors, string.Join(";", this.config.WarningsAsErrors));
}
var compilationManager = new CompilationUnitManager(
new ProjectProperties(buildProperties),
this.OnCompilerException);
compilationManager.UpdateReferencesAsync(references);
compilationManager.AddOrUpdateSourceFilesAsync(files);
this.VerifiedCompilation = compilationManager.Build();
this.CompilationOutput = this.VerifiedCompilation?.BuiltCompilation;
compilationManager.Dispose();
foreach (var diag in this.VerifiedCompilation?.Diagnostics() ?? Enumerable.Empty<Diagnostic>())
{
this.LogAndUpdate(ref this.compilationStatus.Validation, diag);
}
if (this.config.IsExecutable && this.CompilationOutput?.EntryPoints.Length == 0)
{
if (this.config.TargetCapability?.Equals(TargetCapabilityModule.Top) ?? true)
{
this.LogAndUpdate(ref this.compilationStatus.Validation, WarningCode.MissingEntryPoint);
}
else
{
this.LogAndUpdate(ref this.compilationStatus.Validation, ErrorCode.MissingEntryPoint);
}
}
if (!Uri.TryCreate(Assembly.GetExecutingAssembly().Location, UriKind.Absolute, out Uri thisDllUri))
{
thisDllUri = new Uri(Path.GetFullPath(".", "CompilationLoader.cs"));
}
if (this.Validation == Status.Succeeded && this.config.LoadTargetSpecificDecompositions)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.ReplaceTargetSpecificImplementations);
this.ReplaceTargetSpecificImplementations(
this.config.TargetPackageAssemblies ?? Enumerable.Empty<string>(),
thisDllUri,
references.Declarations.Count);
PerformanceTracking.TaskEnd(PerformanceTracking.Task.ReplaceTargetSpecificImplementations);
}
PerformanceTracking.TaskEnd(PerformanceTracking.Task.Build);
if (this.Validation == Status.Succeeded)
{
this.RunRewriteSteps(thisDllUri);
this.GenerateOutput();
}
PerformanceTracking.TaskEnd(PerformanceTracking.Task.OverallCompilation);
}
/// <summary>
/// Builds the compilation of the specified source files and references,
/// executing the compilation steps specified by the given options.
/// Uses the specified logger to log all diagnostic events.
/// </summary>
public CompilationLoader(IEnumerable<string> sources, IEnumerable<string> references, Configuration? options = null, ILogger? logger = null)
: this(load => load(sources), load => load(references), options, logger)
{
}
/// <summary>
/// Builds the compilation of the specified source files and the loaded references returned by the given loader,
/// executing the compilation steps specified by the given options.
/// Uses the specified logger to log all diagnostic events.
/// </summary>
public CompilationLoader(IEnumerable<string> sources, ReferenceLoader loadReferences, Configuration? options = null, ILogger? logger = null)
: this(load => load(sources), loadReferences, options, logger)
{
}
/// <summary>
/// Builds the compilation of the content returned by the given loader and the specified references,
/// executing the compilation steps specified by the given options.
/// Uses the specified logger to log all diagnostic events.
/// </summary>
public CompilationLoader(SourceLoader loadSources, IEnumerable<string> references, Configuration? options = null, ILogger? logger = null)
: this(loadSources, load => load(references), options, logger)
{
}
private IEnumerable<(LoadedStep, Action<Status>)> InternalRewriteSteps(Uri executingAssembly)
{
// TODO: The dependencies for rewrite steps should be declared as part of IRewriteStep interface, and we
// should query those here.
#pragma warning disable CS0618 // Type or member is obsolete
var dependencies = BuiltIn.AllBuiltIns.Select(b => b.FullName);
#pragma warning restore CS0618 // Type or member is obsolete
var status = this.compilationStatus;
var steps = new (IRewriteStep Step, bool Enabled, Action<Status> SetStatus)[]
{
// TODO: It would be nicer to trim unused intrinsics. Currently, this is not possible due to how the
// old setup of the C# runtime works. Intrinsics that come from packages that use the new interface-based
// approach (which is the case for target packages) can be trimmed if they are unused.
(new SyntaxTreeTrimming(keepAllIntrinsics: true, dependencies), this.config.TrimTree, s => status.TreeTrimming = s),
(new LiftLambdas(), this.config.LiftLambdaExpressions, s => status.LiftLambdaExpressions = s),
(new ClassicallyControlled(), this.config.ConvertClassicalControl, s => status.ConvertClassicalControl = s),
(new FunctorGeneration(), this.config.GenerateFunctorSupport, s => status.FunctorSupport = s),
(new ConjugationInlining(), !this.config.SkipConjugationInlining, s => status.ConjugationInlining = s),
(new FullPreEvaluation(), this.config.AttemptFullPreEvaluation, s => status.PreEvaluation = s),
(new Monomorphization(monomorphizeIntrinsics: false), this.config.Monomorphize, s => status.Monomorphization = s),
(new CapabilityInference(), !this.config.IsExecutable, s => status.CapabilityInference = s),
};
return from step in steps
where step.Enabled
select (new LoadedStep(step.Step, typeof(IRewriteStep), executingAssembly), step.SetStatus);
}
private void RunRewriteSteps(Uri executingAssembly)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.RewriteSteps);
this.config.PrepareQirGeneration = this.config.PrepareQirGeneration || this.externalRewriteSteps.Any(step => step.Name == "QIR Generation");
var externalSteps = this.externalRewriteSteps.Select((step, index) =>
(step, new Action<Status>(status => this.compilationStatus.LoadedRewriteSteps[index] = status)));
var steps = this.InternalRewriteSteps(executingAssembly)
.Concat(externalSteps)
.OrderByDescending(step => step.Item1.Priority);
foreach (var (step, setStatus) in steps)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.SingleRewriteStep, step.Name);
setStatus(this.RunRewriteStep(step));
PerformanceTracking.TaskEnd(PerformanceTracking.Task.SingleRewriteStep, step.Name);
}
PerformanceTracking.TaskEnd(PerformanceTracking.Task.RewriteSteps);
}
private void GenerateOutput()
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.OutputGeneration);
using var stream = new MemoryStream();
PerformanceTracking.TaskStart(PerformanceTracking.Task.SyntaxTreeSerialization);
var serialized = this.config.SerializeSyntaxTree && this.WriteSyntaxTreeSerialization(stream);
PerformanceTracking.TaskEnd(PerformanceTracking.Task.SyntaxTreeSerialization);
if (serialized)
{
if (this.config.BuildOutputFolder is not null)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.BinaryGeneration);
this.PathToCompiledBinary = this.GenerateBinary(stream);
PerformanceTracking.TaskEnd(PerformanceTracking.Task.BinaryGeneration);
}
if (this.config.DllOutputPath is not null)
{
PerformanceTracking.TaskStart(PerformanceTracking.Task.DllGeneration);
this.DllOutputPath = this.GenerateDll(stream);
PerformanceTracking.TaskEnd(PerformanceTracking.Task.DllGeneration);
}
}
PerformanceTracking.TaskEnd(PerformanceTracking.Task.OutputGeneration);
}
// private routines used for logging and status updates
/// <summary>
/// Logs the given diagnostic and updates the status passed as reference accordingly.
/// </summary>
private void LogAndUpdate(ref Status current, Diagnostic d)
{
this.logger?.Log(d);
if (d.IsError())
{
current = Status.Failed;
}
}
/// <summary>
/// Logs the given exception and updates the status passed as reference accordingly.
/// </summary>
private void LogAndUpdate(ref Status current, Exception ex)
{
this.logger?.Log(ex);
current = Status.Failed;
}
/// <summary>
/// Logs an error with the given error code and message parameters, and updates the status passed as reference accordingly.
/// </summary>
private void LogAndUpdate(ref Status current, ErrorCode code, params string[] args)
{
this.logger?.Log(code, args);
current = Status.Failed;
}
/// <summary>
/// Logs an error with the given warning code and message parameters, and updates the status passed as reference accordingly.
/// </summary>
private void LogAndUpdate(ref Status current, WarningCode code, params string[] args)
{
this.logger?.Log(code, args);
}
/// <summary>
/// Logs the given diagnostic and updates the status passed as reference accordingly.
/// Adds the given diagnostic to the tracked load diagnostics.
/// </summary>
private void LogAndUpdateLoadDiagnostics(ref Status current, Diagnostic d)
{
this.LoadDiagnostics = this.LoadDiagnostics.Add(d);
this.LogAndUpdate(ref current, d);
}
/// <summary>
/// Logs an UnexpectedCompilerException error as well as the given exception, and updates the validation status accordingly.
/// </summary>
private void OnCompilerException(Exception ex)
{
this.LogAndUpdate(ref this.compilationStatus.Validation, ErrorCode.UnexpectedCompilerException);
this.LogAndUpdate(ref this.compilationStatus.Validation, ex);
}
/// <summary>
/// Logs the names of the given source files as Information.
/// Does nothing if the given argument is null.
/// </summary>
private void PrintResolvedFiles(IEnumerable<Uri> sourceFiles)
{
var args = sourceFiles.Any()
? sourceFiles.Select(f => f.LocalPath).ToArray()
: new string[] { "(none)" };
this.logger?.Log(InformationCode.CompilingWithSourceFiles, Enumerable.Empty<string>(), messageParam: Formatting.Indent(args).ToArray());
}
/// <summary>
/// Logs the names of the given assemblies as Information.
/// Does nothing if the given argument is null.
/// </summary>
private void PrintResolvedAssemblies(IEnumerable<string> assemblies)
{
if (assemblies == null)
{
return;
}
var args = assemblies.Any()
? assemblies.ToArray()
: new string[] { "(none)" };
this.logger?.Log(InformationCode.CompilingWithAssemblies, Enumerable.Empty<string>(), messageParam: Formatting.Indent(args).ToArray());
}
/// <summary>
/// Logs the names and origins of the given rewrite steps as Information.
/// Does nothing if the given argument is null.
/// </summary>
private void PrintLoadedRewriteSteps(IEnumerable<LoadedStep> rewriteSteps)
{
if (rewriteSteps == null)
{
return;
}
var args = rewriteSteps.Any()
? rewriteSteps.Select(step => $"{step.Name} ({step.Origin})").ToArray()
: new string[] { "(none)" };
this.logger?.Log(InformationCode.LoadedRewriteSteps, Enumerable.Empty<string>(), messageParam: Formatting.Indent(args).ToArray());
}
// private helper methods used during construction
/// <summary>
/// Attempts to load the target package assemblies with the given paths, logging diagnostics
/// when a path is invalid, or loading fails. Logs suitable diagnostics if the loaded dlls
/// contains conflicting declarations. Updates the compilation status accordingly.
/// Executes the transformation to replace target specific implementations as atomic rewrite step.
/// </summary>
private void ReplaceTargetSpecificImplementations(IEnumerable<string> paths, Uri rewriteStepOrigin, int nrReferences)
{
void LogError(ErrorCode errCode, params string[] args) => this.LogAndUpdate(ref this.compilationStatus.TargetSpecificReplacements, errCode, args);
void LogException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.TargetSpecificReplacements, ex);
(string, ImmutableArray<QsNamespace>)? LoadReferences(string path)
{
try
{
var targetDll = Path.GetFullPath(path);
if (AssemblyLoader.LoadReferencedAssembly(targetDll, out var loaded, LogException))
{
return (path, loaded.Namespaces);
}
LogError(ErrorCode.FailedToLoadTargetSpecificDecompositions, targetDll);
return null;
}
catch (Exception ex)
{
LogError(ErrorCode.InvalidPathToTargetSpecificDecompositions, path);
LogException(ex);
return null;
}
}
var natives = paths.SelectNotNull(LoadReferences).ToArray();
var combinedSuccessfully = References.CombineSyntaxTrees(out var replacements, additionalAssemblies: nrReferences, onError: LogError, natives);
if (!combinedSuccessfully)
{
LogError(ErrorCode.ConflictsInTargetSpecificDecompositions);
}
var targetSpecificDecompositions = new QsCompilation(replacements, ImmutableArray<QsQualifiedName>.Empty);
var step = new LoadedStep(new IntrinsicResolution(targetSpecificDecompositions), typeof(IRewriteStep), rewriteStepOrigin);
this.compilationStatus.TargetSpecificReplacements = this.RunRewriteStep(step);
}
/// <summary>
/// Runs the given rewrite step on the current compilation. Catches and logs any thrown exception.
/// </summary>
/// <returns>The status of the rewrite step.</returns>
private Status RunRewriteStep(LoadedStep rewriteStep)
{
if (this.CompilationOutput is not { } compilation)
{
return Status.NotRun;
}
string? GetDiagnosticsCode(DiagnosticSeverity severity) =>
rewriteStep.Name == "CSharpGeneration" && severity == DiagnosticSeverity.Error ? Errors.Code(ErrorCode.CsharpGenerationGeneratedError) :
rewriteStep.Name == "CSharpGeneration" && severity == DiagnosticSeverity.Warning ? Warnings.Code(WarningCode.CsharpGenerationGeneratedWarning) :
rewriteStep.Name == "CSharpGeneration" && severity == DiagnosticSeverity.Information ? Informations.Code(InformationCode.CsharpGenerationGeneratedInfo) :
rewriteStep.Name == "QIR Generation" && severity == DiagnosticSeverity.Error ? Errors.Code(ErrorCode.QirEmissionGeneratedError) :
rewriteStep.Name == "QIR Generation" && severity == DiagnosticSeverity.Warning ? Warnings.Code(WarningCode.QirEmissionGeneratedWarning) :
rewriteStep.Name == "QIR Generation" && severity == DiagnosticSeverity.Information ? Informations.Code(InformationCode.QirEmissionGeneratedInfo) :
null;
var messageSource = ProjectManager.MessageSource(rewriteStep.Origin);
void LogDiagnostics(ref Status status)
{
try
{
var steps = rewriteStep.GeneratedDiagnostics ?? ImmutableArray<IRewriteStep.Diagnostic>.Empty;
foreach (var diagnostic in steps)
{
this.LogAndUpdate(ref status, LoadedStep.ConvertDiagnostic(rewriteStep.Name, diagnostic, GetDiagnosticsCode));
}
}
catch
{
this.LogAndUpdate(ref status, WarningCode.RewriteStepDiagnosticsGenerationFailed, rewriteStep.Name, messageSource);
}
}
QsCompilation? newCompilation = null;
var status = Status.Succeeded;
try
{
var preconditionFailed = rewriteStep.ImplementsPreconditionVerification && !rewriteStep.PreconditionVerification(compilation);
if (preconditionFailed)
{
LogDiagnostics(ref status);
if (this.config.ForceRewriteStepExecution)
{
this.LogAndUpdate(ref status, ErrorCode.PreconditionVerificationFailed, rewriteStep.Name, messageSource);
}
else
{
this.LogAndUpdate(ref status, WarningCode.PreconditionVerificationFailed, rewriteStep.Name, messageSource);
return status;
}
}
var transformationFailed = rewriteStep.ImplementsTransformation && (!rewriteStep.Transformation(compilation, out newCompilation) || newCompilation is null);
var postconditionFailed = this.config.EnableAdditionalChecks && rewriteStep.ImplementsPostconditionVerification && !rewriteStep.PostconditionVerification(newCompilation);
LogDiagnostics(ref status);
if (transformationFailed)
{
this.LogAndUpdate(ref status, ErrorCode.RewriteStepExecutionFailed, new[] { rewriteStep.Name, messageSource });
}
if (postconditionFailed)
{
this.LogAndUpdate(ref status, ErrorCode.PostconditionVerificationFailed, new[] { rewriteStep.Name, messageSource });
}
}
catch (Exception ex)
{
this.LogAndUpdate(ref status, ex);
var isLoadException = ex is FileLoadException || ex.InnerException is FileLoadException;
if (isLoadException)
{
this.LogAndUpdate(ref status, ErrorCode.FileNotFoundDuringPluginExecution, new[] { rewriteStep.Name, messageSource });
}
else
{
this.LogAndUpdate(ref status, ErrorCode.PluginExecutionFailed, new[] { rewriteStep.Name, messageSource });
}
}
if (status == Status.Succeeded)
{
this.CompilationOutput = newCompilation;
}
return status;
}
// routines for loading from and dumping to files
/// <summary>
/// Used to load the content of the specified source files from disk.
/// Returns a dictionary mapping the file uri to its content.
/// Logs suitable diagnostics in the process and modifies the compilation status accordingly.
/// Prints all loaded files using PrintResolvedFiles.
/// </summary>
private ImmutableDictionary<Uri, string> LoadSourceFiles(IEnumerable<string> sources)
{
this.compilationStatus.SourceFileLoading = 0;
if (sources == null)
{
this.LogAndUpdate(ref this.compilationStatus.SourceFileLoading, ErrorCode.SourceFilesMissing);
}
void OnException(Exception ex) => this.LogAndUpdate(ref this.compilationStatus.SourceFileLoading, ex);
void OnDiagnostic(Diagnostic d) => this.LogAndUpdateLoadDiagnostics(ref this.compilationStatus.SourceFileLoading, d);
var sourceFiles = ProjectManager.LoadSourceFiles(sources ?? Enumerable.Empty<string>(), OnDiagnostic, OnException);
this.PrintResolvedFiles(sourceFiles.Keys);
return sourceFiles;
}
/// <summary>
/// Used to load the content of the specified assembly references from disk.
/// If loadTestNames is set to true, then public types and callables declared in referenced assemblies
/// are exposed via their test name defined by the corresponding attribute.
/// Returns the loaded content of the references.
/// Logs suitable diagnostics in the process and modifies the compilation status accordingly.
/// Prints all loaded files using PrintResolvedAssemblies.
/// </summary>
private References LoadAssemblies(IEnumerable<string> refs, bool loadTestNames, bool ignoreDllResources)
{
this.compilationStatus.ReferenceLoading = 0;
if (refs == null)
{