From 80a30f504abf780a5c79e0a25e23e63ad21daa59 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 15 Nov 2023 10:30:59 +0100 Subject: [PATCH] Add initial support for Precompiled Header Files (PCH) in MSVC compilation --- Source/FlaxEngine.pch.h | 25 +++++ Source/Tools/Flax.Build/Build/EngineModule.cs | 4 + .../Build/NativeCpp/BuildOptions.cs | 21 ++++ .../Build/NativeCpp/Builder.NativeCpp.cs | 13 +++ .../Build/NativeCpp/CompileEnvironment.cs | 20 +++- .../Build/NativeCpp/CompileOutput.cs | 5 + .../Platforms/Windows/WindowsToolchainBase.cs | 98 ++++++++++++++++++- 7 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 Source/FlaxEngine.pch.h diff --git a/Source/FlaxEngine.pch.h b/Source/FlaxEngine.pch.h new file mode 100644 index 000000000..0a73fc464 --- /dev/null +++ b/Source/FlaxEngine.pch.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +// Include common engine headers +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/StringUtils.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/Guid.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Log.h" +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Serialization/SerializationFwd.h" diff --git a/Source/Tools/Flax.Build/Build/EngineModule.cs b/Source/Tools/Flax.Build/Build/EngineModule.cs index 5d84761fb..8ee4050f9 100644 --- a/Source/Tools/Flax.Build/Build/EngineModule.cs +++ b/Source/Tools/Flax.Build/Build/EngineModule.cs @@ -35,6 +35,10 @@ namespace Flax.Build options.ScriptingAPI.Defines.Add("FLAX_GAME"); } + // Use custom precompiled header file for the engine to boost compilation time + options.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.CreateManual; + options.CompileEnv.PrecompiledHeaderSource = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, "Source/FlaxEngine.pch.h")); + BinaryModuleName = "FlaxEngine"; options.ScriptingAPI.Defines.Add("FLAX"); options.ScriptingAPI.Defines.Add("FLAX_ASSERTIONS"); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index e161024b4..05871a331 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -23,6 +23,27 @@ namespace Flax.Build.NativeCpp GenerateProject = 1, } + /// + /// Precompiled Headers Files (PCH) usage modes. + /// + public enum PrecompiledHeaderFileUsage + { + /// + /// Precompiled Headers Files (PCH) feature is disabled. + /// + None, + + /// + /// Enables creation and usage of the header file. The input source PCH will be precompiled and included. + /// + CreateManual, + + /// + /// Enables usage of the header file. The input source PCH will be included in the build (assuming it exists). + /// + UseManual, + } + /// /// The nullable context type used with reference types (C#). /// diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 225e46ba7..232b52ba0 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -32,6 +32,7 @@ namespace Flax.Build public IGrouping[] BinaryModules; public BuildTargetInfo BuildInfo; public Dictionary ReferenceBuilds = new Dictionary(); + public Dictionary PrecompiledHeaderFiles = new(); public BuildTargetBinaryModuleInfo FinReferenceBuildModule(string name) { @@ -484,6 +485,13 @@ namespace Flax.Build } } + // If the PCH was already created (eg. by other engine module) then simply reuse the same file + if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && buildData.PrecompiledHeaderFiles.TryGetValue(moduleOptions.CompileEnv.PrecompiledHeaderSource, out var pch)) + { + moduleOptions.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.UseManual; + moduleOptions.CompileEnv.PrecompiledHeaderFile = pch; + } + // Compile all source files var compilationOutput = buildData.Toolchain.CompileCppFiles(buildData.Graph, moduleOptions, cppFiles, moduleOptions.OutputFolder); foreach (var e in compilationOutput.ObjectFiles) @@ -493,6 +501,11 @@ namespace Flax.Build // TODO: find better way to add generated doc files to the target linker (module exports the output doc files?) buildData.TargetOptions.LinkEnv.DocumentationFiles.AddRange(compilationOutput.DocumentationFiles); } + if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && !string.IsNullOrEmpty(compilationOutput.PrecompiledHeaderFile)) + { + // Cache PCH file to be used by other modules that reference the same file + buildData.PrecompiledHeaderFiles.Add(moduleOptions.CompileEnv.PrecompiledHeaderSource, compilationOutput.PrecompiledHeaderFile); + } if (buildData.Target.LinkType != TargetLinkType.Monolithic) { diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs index 72d0380e4..ad418019b 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs @@ -162,6 +162,21 @@ namespace Flax.Build.NativeCpp /// public readonly HashSet CustomArgs = new HashSet(); + /// + /// The Precompiled Header File (PCH) usage. + /// + public PrecompiledHeaderFileUsage PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.None; + + /// + /// The Precompiled Header File (PCH) binary path. Null if not created. + /// + public string PrecompiledHeaderFile; + + /// + /// The Precompiled Header File (PCH) source path. Null if not provided. + /// + public string PrecompiledHeaderSource; + /// public object Clone() { @@ -184,7 +199,10 @@ namespace Flax.Build.NativeCpp StringPooling = StringPooling, IntrinsicFunctions = IntrinsicFunctions, BufferSecurityCheck = BufferSecurityCheck, - TreatWarningsAsErrors = TreatWarningsAsErrors + TreatWarningsAsErrors = TreatWarningsAsErrors, + PrecompiledHeaderUsage = PrecompiledHeaderUsage, + PrecompiledHeaderFile = PrecompiledHeaderFile, + PrecompiledHeaderSource = PrecompiledHeaderSource, }; clone.PreprocessorDefinitions.AddRange(PreprocessorDefinitions); clone.IncludePaths.AddRange(IncludePaths); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs index ea75fa51c..bdef3a531 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs @@ -23,5 +23,10 @@ namespace Flax.Build.NativeCpp /// The result documentation files. /// public readonly List DocumentationFiles = new List(); + + /// + /// The result precompiled header file (PCH) created during compilation. Can be used in other compilations (as shared). + /// + public string PrecompiledHeaderFile; } } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index e7dbaac19..877d4c389 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -430,6 +430,7 @@ namespace Flax.Build.Platforms var commonArgs = new List(); commonArgs.AddRange(options.CompileEnv.CustomArgs); SetupCompileCppFilesArgs(graph, options, commonArgs); + var useSeparatePdb = true; //compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.None; { // Suppress Startup Banner commonArgs.Add("/nologo"); @@ -490,7 +491,10 @@ namespace Flax.Build.Platforms if (compileEnvironment.DebugInformation) { // Debug Information Format - commonArgs.Add("/Zi"); + if (useSeparatePdb) + commonArgs.Add("/Zi"); + else + commonArgs.Add("/Z7"); // Enhance Optimized Debugging commonArgs.Add("/Zo"); @@ -611,8 +615,65 @@ namespace Flax.Build.Platforms AddIncludePath(commonArgs, includePath); } - // Compile all C++ files var args = new List(); + + // Create precompiled header + string pchFile = null, pchSource = null; + if (compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.UseManual) + { + pchFile = compileEnvironment.PrecompiledHeaderFile; + pchSource = compileEnvironment.PrecompiledHeaderSource; + } + else if (compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual) + { + // Use intermediate cpp file that includes the PCH path but also contains compiler info to properly recompile when it's modified + pchSource = compileEnvironment.PrecompiledHeaderSource; + var pchFilName = Path.GetFileName(pchSource); + var pchSourceFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "cpp")); + var contents = Bindings.BindingsGenerator.GetStringBuilder(); + contents.AppendLine("// This code was auto-generated. Do not modify it."); + contents.Append("// Compiler: ").AppendLine(_compilerPath); + contents.Append("#include \"").Append(pchSource).AppendLine("\""); + Utilities.WriteFileIfChanged(pchSourceFile, contents.ToString()); + Bindings.BindingsGenerator.PutStringBuilder(contents); + + // Compile intermediate cpp file into actual PCH (and obj+pdb files) + pchFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "pch")); + if (pchFile.EndsWith(".pch.pch")) + pchFile = pchFile.Substring(0, pchFile.Length - 4); + var pchPdbFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "pdb")); + var pchObjFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "obj")); + var task = graph.Add(); + task.PrerequisiteFiles.Add(pchSourceFile); + task.PrerequisiteFiles.Add(pchSource); + task.PrerequisiteFiles.AddRange(IncludesCache.FindAllIncludedFiles(pchSource)); + task.ProducedFiles.Add(pchFile); + task.ProducedFiles.Add(pchObjFile); + args.AddRange(commonArgs); + args.Add(string.Format("/Yc\"{0}\"", pchSource)); + args.Add(string.Format("/Fp\"{0}\"", pchFile)); + args.Add(string.Format("/Fd\"{0}\"", pchPdbFile)); + args.Add(string.Format("/Fo\"{0}\"", pchObjFile)); + args.Add("/FS"); + args.Add(string.Format("\"{0}\"", pchSourceFile)); + task.WorkingDirectory = options.WorkingDirectory; + task.CommandPath = _compilerPath; + task.CommandArguments = string.Join(" ", args); + task.Cost = int.MaxValue; // Run it before any other tasks + + // Setup outputs + output.PrecompiledHeaderFile = pchFile; + output.ObjectFiles.Add(pchObjFile); + } + if (pchFile != null) + { + // Include PCH file + commonArgs.Add(string.Format("/FI\"{0}\"", pchSource)); + commonArgs.Add(string.Format("/Yu\"{0}\"", pchSource)); + commonArgs.Add(string.Format("/Fp\"{0}\"", pchFile)); + } + + // Compile all C++ files foreach (var sourceFile in sourceFiles) { var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile); @@ -625,9 +686,25 @@ namespace Flax.Build.Platforms if (compileEnvironment.DebugInformation) { // Program Database File Name - var pdbFile = Path.Combine(outputPath, sourceFilename + ".pdb"); - args.Add(string.Format("/Fd\"{0}\"", pdbFile)); - output.DebugDataFiles.Add(pdbFile); + string pdbFile = null; + if (pchFile != null) + { + // When using PCH we need to share the same PDB file that was used when building PCH + pdbFile = pchFile + ".pdb"; + + // Turn on sync for file access to prevent issues when compiling on multiple threads at once + if (useSeparatePdb) + args.Add("/FS"); + } + else if (useSeparatePdb) + { + pdbFile = Path.Combine(outputPath, sourceFilename + ".pdb"); + } + if (pdbFile != null) + { + args.Add(string.Format("/Fd\"{0}\"", pdbFile)); + output.DebugDataFiles.Add(pdbFile); + } } if (compileEnvironment.GenerateDocumentation) @@ -650,6 +727,10 @@ namespace Flax.Build.Platforms // Request included files to exist var includes = IncludesCache.FindAllIncludedFiles(sourceFile); task.PrerequisiteFiles.AddRange(includes); + if (pchFile != null) + { + task.PrerequisiteFiles.Add(pchFile); + } // Compile task.WorkingDirectory = options.WorkingDirectory; @@ -853,6 +934,13 @@ namespace Flax.Build.Platforms task.PrerequisiteFiles.AddRange(linkEnvironment.InputFiles); foreach (var file in linkEnvironment.InputFiles) { + if (file.EndsWith(".pch", StringComparison.OrdinalIgnoreCase)) + { + // PCH file + args.Add(string.Format("/Yu:\"{0}\"", file)); + continue; + } + args.Add(string.Format("\"{0}\"", file)); }