context: // Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
  
  using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.Collections.Immutable;
  using System.Diagnostics;
  using System.Linq;
  using System.Threading;
  using System.Threading.Tasks;
  using Microsoft.CodeAnalysis.CodeGen;
  using Microsoft.CodeAnalysis.CSharp.Emit;
  using Microsoft.CodeAnalysis.CSharp.Symbols;
  using Microsoft.CodeAnalysis.CSharp.Syntax;
  using Microsoft.CodeAnalysis.Diagnostics;
  using Microsoft.CodeAnalysis.Emit;
  using Microsoft.CodeAnalysis.Instrumentation;
  using Roslyn.Utilities;
  
  namespace Microsoft.CodeAnalysis.CSharp
  {
      internal sealed class MethodCompiler : CSharpSymbolVisitor<TypeCompilationState, object>
      {
          // The moduleBeingBuilt can be null. This indicates that we are compiling the method bodies solely
          // for the purpose of getting diagnostics.
          private readonly CSharpCompilation compilation;
          private readonly bool generateDebugInfo;
          private readonly CancellationToken cancellationToken;
          private readonly DiagnosticBag diagnostics;
          private readonly bool hasDeclarationErrors;
          private readonly NamespaceScopeBuilder namespaceScopeBuilder;
          private readonly PEModuleBuilder moduleBeingBuiltOpt;
          private readonly Predicate<Symbol> filterOpt; // if not null, limit analysis to specific symbols
          private readonly DebugDocumentProvider debugDocumentProvider;
  
          //
          // MethodCompiler employs concurrency by following flattened fork/join pattern.
          //
          // For every item that we want to compile in parallel a new task is forked.
          // compileTaskQueue is used to track and observe all the tasks. 
          // Once compileTaskQueue is empty, we know that there are no more tasks (and no more can be created)
          // and that means we are done compiling. WaitForWorkers ensures this condition.
          //
          // Note that while tasks may fork more tasks (nested types, lambdas, whatever else that may introduce more types),
          // we do not want any child/parent relationship between spawned tasks and their creators. 
          // Creator has no real dependencies on the completion of its children and should finish and release any resources
          // as soon as it can regardless of the tasks it may have spawned.
          //
          // Stack is used so that the wait would observe the most recently added task and have 
          // more chances to do inlined execution.
          private ConcurrentStack<Task> compilerTasks;
  
          // This field tracks whether any bound method body had hasErrors set or whether any constant field had a bad value.
          // We track it so that we can abort emission in the event that an error occurs without a corresponding diagnostic
          // (e.g. if this module depends on a bad type or constant from another module).
          // CONSIDER: instead of storing a flag, we could track the first member symbol with an error (to improve the diagnostic).
  
          // NOTE: once the flag is set to true, it should never go back to false!!!
          // Do not use this as a shortcircuiting for stages that might produce diagnostics.
          // That would make diagnostics to depend on the random order in which methods are compiled.
          private bool globalHasErrors;
  
          private void SetGlobalErrorIfTrue(bool arg)
          {
              //NOTE: this is not a volatile write
              //      for correctness we need only single threaded consistency.
              //      Within a single task - if we have got an error it may not be safe to continue with some lowerings.
              //      It is ok if other tasks will see the change after some delay or does not observe at all.
              //      Such races are unavoidable and will just result in performing some work that is safe to do
              //      but may no longer be needed.
              //      The final Join of compiling tasks cannot happen without interlocked operations and that 
              //      will ensure that any write of the flag is globally visible.
              if (arg)
              {
                  globalHasErrors = true;
              }
          }
  
          // Internal for testing only.
          internal MethodCompiler(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, bool generateDebugInfo, bool hasDeclarationErrors,
              DiagnosticBag diagnostics, Predicate<Symbol> filterOpt, CancellationToken cancellationToken)
          {
              Debug.Assert(compilation != null);
              Debug.Assert(diagnostics != null);
  
              this.compilation = compilation;
              this.moduleBeingBuiltOpt = moduleBeingBuiltOpt;
              this.generateDebugInfo = generateDebugInfo;
              this.cancellationToken = cancellationToken;
              this.diagnostics = diagnostics;
              this.filterOpt = filterOpt;
  
              this.hasDeclarationErrors = hasDeclarationErrors;
              SetGlobalErrorIfTrue(hasDeclarationErrors);
  
              if (generateDebugInfo)
              {
                  this.debugDocumentProvider = (path, basePath) => moduleBeingBuiltOpt.GetOrAddDebugDocument(path, basePath, CreateDebugDocumentForFile);
                  this.namespaceScopeBuilder = new NamespaceScopeBuilder(compilation);
              }
          }
  
          public static void CompileMethodBodies(
              CSharpCompilation compilation,
              PEModuleBuilder moduleBeingBuiltOpt,
              bool generateDebugInfo,
              bool hasDeclarationErrors,
              DiagnosticBag diagnostics,
              Predicate<Symbol> filterOpt,
              CancellationToken cancellationToken)
          {
              Debug.Assert(compilation != null);
              Debug.Assert(diagnostics != null);
  
              using (Logger.LogBlock(FunctionId.CSharp_Compiler_CompileMethodBodies, message: compilation.AssemblyName, cancellationToken: cancellationToken))
              {
                  if (compilation.PreviousSubmission != null)
                  {
                      // In case there is a previous submission, we should ensure 
                      // it has already created anonymous type/delegates templates
  
                      // NOTE: if there are any errors, we will pick up what was created anyway
                      compilation.PreviousSubmission.EnsureAnonymousTypeTemplates(cancellationToken);
  
                      // TODO: revise to use a loop instead of a recursion
                  }
  
                  MethodCompiler methodCompiler = new MethodCompiler(
                      compilation,
                      moduleBeingBuiltOpt,
                      generateDebugInfo,
                      hasDeclarationErrors,
                      diagnostics,
                      filterOpt,
                      cancellationToken);
  
                  if (compilation.Options.ConcurrentBuild)
                  {
                      methodCompiler.compilerTasks = new ConcurrentStack<Task>();
                  }
  
                  // directly traverse global namespace (no point to defer this to async)
                  methodCompiler.CompileNamespace(compilation.SourceModule.GlobalNamespace);
                  methodCompiler.WaitForWorkers();
  
                  // compile additional and anonymous types if any
                  if (moduleBeingBuiltOpt != null)
                  {
                      var additionalTypes = moduleBeingBuiltOpt.GetAdditionalTopLevelTypes();
                      if (!additionalTypes.IsEmpty)
                      {
                          methodCompiler.CompileSynthesizedMethods(additionalTypes, diagnostics);
                      }
  
                      // By this time we have processed all types reachable from module's global namespace
                      compilation.AnonymousTypeManager.AssignTemplatesNamesAndCompile(methodCompiler, moduleBeingBuiltOpt, diagnostics);
                      methodCompiler.WaitForWorkers();
  
                      var privateImplClass = moduleBeingBuiltOpt.PrivateImplClass;
                      if (privateImplClass != null)
                      {
                          // all threads that were adding methods must be finished now, we can freeze the class:
                          privateImplClass.Freeze();
  
                          methodCompiler.CompileSynthesizedMethods(privateImplClass, diagnostics);
                      }
                  }
  
                  // If we are trying to emit and there's an error without a corresponding diagnostic (e.g. because
                  // we depend on an invalid type or constant from another module), then explicitly add a diagnostic.
                  // This diagnostic is not very helpful to the user, but it will prevent us from emitting an invalid
                  // module or crashing.
                  if (moduleBeingBuiltOpt != null && methodCompiler.globalHasErrors && !diagnostics.HasAnyErrors() && !hasDeclarationErrors)
                  {
                      diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, NoLocation.Singleton, ((Cci.INamedEntity)moduleBeingBuiltOpt).Name);
                  }
  
                  diagnostics.AddRange(compilation.AdditionalCodegenWarnings);
  
                  // we can get unused field warnings only if compiling whole compilation.
                  if (filterOpt == null)
                  {
                      WarnUnusedFields(compilation, diagnostics, cancellationToken);
                  }
  
                  MethodSymbol entryPoint = GetEntryPoint(compilation, moduleBeingBuiltOpt, hasDeclarationErrors, diagnostics, cancellationToken);
                  if (moduleBeingBuiltOpt != null)
                  {
                      moduleBeingBuiltOpt.SetEntryPoint(entryPoint);
                  }
              }
          }
  
          internal static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuilt, bool hasDeclarationErrors, DiagnosticBag diagnostics, CancellationToken cancellationToken)
          {
              CSharpCompilationOptions options = compilation.Options;
              if (!options.OutputKind.IsApplication())
              {
                  Debug.Assert(compilation.GetEntryPointAndDiagnostics(default(CancellationToken)) == null);
                  return compilation.IsSubmission
                      ? DefineScriptEntryPoint(compilation, moduleBeingBuilt, compilation.GetSubmissionReturnType(), hasDeclarationErrors, diagnostics)
                      : null;
              }
  
              Debug.Assert(!compilation.IsSubmission);
  
              CSharpCompilation.EntryPoint entryPoint = compilation.GetEntryPointAndDiagnostics(cancellationToken);
              Debug.Assert(entryPoint != null);
              Debug.Assert(!entryPoint.Diagnostics.IsDefault);
  
              diagnostics.AddRange(entryPoint.Diagnostics);
  
              if ((object)compilation.ScriptClass != null)
              {
                  Debug.Assert((object)entryPoint.MethodSymbol == null);
                  return DefineScriptEntryPoint(compilation, moduleBeingBuilt, compilation.GetSpecialType(SpecialType.System_Void), hasDeclarationErrors, diagnostics);
              }
  
              Debug.Assert((object)entryPoint.MethodSymbol != null || entryPoint.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty);
              return entryPoint.MethodSymbol;
          }
  
          internal static MethodSymbol DefineScriptEntryPoint(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuilt, TypeSymbol returnType, bool hasDeclarationErrors, DiagnosticBag diagnostics)
          {
              var scriptEntryPoint = new SynthesizedEntryPointSymbol(compilation.ScriptClass, returnType, diagnostics);
              if (moduleBeingBuilt != null && !hasDeclarationErrors && !diagnostics.HasAnyErrors())
              {
                  var compilationState = new TypeCompilationState(compilation.ScriptClass, compilation, moduleBeingBuilt);
                  var body = scriptEntryPoint.CreateBody();
  
                  var emittedBody = GenerateMethodBody(
                      compilationState,
                      scriptEntryPoint,
                      body,
                      stateMachineTypeOpt: null,
                      diagnostics: diagnostics,
                      optimize: compilation.Options.Optimize,
                      debugDocumentProvider: null,
                      namespaceScopes: default(ImmutableArray<Cci.NamespaceScope>));
  
                  moduleBeingBuilt.SetMethodBody(scriptEntryPoint, emittedBody);
                  moduleBeingBuilt.AddSynthesizedDefinition(compilation.ScriptClass, scriptEntryPoint);
              }
  
              return scriptEntryPoint;
          }
  
          private void WaitForWorkers()
          {
              var tasks = this.compilerTasks;
              if (tasks == null)
              {
                  return;
              }
  
              Task curTask;
              while (tasks.TryPop(out curTask))
              {
                  curTask.GetAwaiter().GetResult();
              }
          }
  
          private static void WarnUnusedFields(CSharpCompilation compilation, DiagnosticBag diagnostics, CancellationToken cancellationToken)
          {
              SourceAssemblySymbol assembly = (SourceAssemblySymbol)compilation.Assembly;
              diagnostics.AddRange(assembly.GetUnusedFieldWarnings(cancellationToken));
          }
  
          public override object VisitNamespace(NamespaceSymbol symbol, TypeCompilationState arg)
          {
              if (!PassesFilter(this.filterOpt, symbol))
              {
                  return null;
              }
  
              arg = null; // do not use compilation state of outer type.
              cancellationToken.ThrowIfCancellationRequested();
  
              if (compilation.Options.ConcurrentBuild)
              {
                  Task worker = CompileNamespaceAsTask(symbol);
                  compilerTasks.Push(worker);
              }
              else
              {
                  CompileNamespace(symbol);
              }
  
              return null;
          }
  
          private Task CompileNamespaceAsTask(NamespaceSymbol symbol)
          {
              return Task.Run(() =>
                  {
                      try
                      {
                          CompileNamespace(symbol);
                          return;
                      }
                      catch (Exception e)
                      {
                          if (CompilerFatalError.ReportUnlessCanceled(e))
                          {
                              throw ExceptionUtilities.Unreachable;
                          }
                      }
                  }, this.cancellationToken);
          }
  
          private void CompileNamespace(NamespaceSymbol symbol)
          {
  
gt:             foreach (var s in symbol.GetMembersUnordered())