From e783bf8aa6f90ddb1458c7b1a78c5d0225c1496a Mon Sep 17 00:00:00 2001
From: Robert Maynard <robert.maynard@kitware.com>
Date: Tue, 30 Jun 2020 10:13:53 -0400
Subject: ISPC: Support ISPC header generation byproducts and parallel builds

---
 Auxiliary/vim/syntax/cmake.vim                |  1 +
 Help/manual/cmake-properties.7.rst            |  1 +
 Help/manual/cmake-variables.7.rst             |  1 +
 Help/prop_tgt/ISPC_HEADER_DIRECTORY.rst       | 12 ++++
 Help/variable/CMAKE_ISPC_HEADER_DIRECTORY.rst | 10 ++++
 Modules/CMakeISPCInformation.cmake            |  2 +-
 Source/cmGeneratorTarget.cxx                  | 85 ++++++++++++++++++++++++---
 Source/cmGeneratorTarget.h                    |  8 +++
 Source/cmGlobalGenerator.cxx                  |  1 +
 Source/cmGlobalNinjaGenerator.cxx             | 42 +++++++++++++
 Source/cmGlobalNinjaGenerator.h               |  6 ++
 Source/cmLocalGenerator.cxx                   | 32 ++++++++++
 Source/cmLocalGenerator.h                     |  1 +
 Source/cmMakefileTargetGenerator.cxx          | 36 +++++++++++-
 Source/cmNinjaTargetGenerator.cxx             | 37 ++++++++++++
 Source/cmRulePlaceholderExpander.cxx          |  5 ++
 Source/cmRulePlaceholderExpander.h            |  1 +
 Source/cmTarget.cxx                           |  1 +
 Tests/ISPC/CMakeLists.txt                     |  3 +-
 Tests/ISPC/Defines/CMakeLists.txt             | 12 ++--
 Tests/ISPC/Defines/main.cxx                   |  2 +-
 Tests/ISPC/ObjectLibrary/CMakeLists.txt       | 11 ++--
 Tests/ISPC/ObjectLibrary/extra.cxx            | 17 ++++++
 Tests/ISPC/ObjectLibrary/extra.ispc           | 12 ++++
 Tests/ISPC/ObjectLibrary/main.cxx             |  2 +-
 Tests/ISPC/ResponseAndDefine/CMakeLists.txt   | 28 +++++----
 Tests/ISPC/ResponseAndDefine/main.cxx         |  2 +-
 Tests/ISPC/ResponseAndDefine/simple.ispc      |  2 +-
 Tests/ISPC/StaticLibrary/CMakeLists.txt       |  7 ++-
 Tests/ISPC/StaticLibrary/main.cxx             |  2 +-
 30 files changed, 343 insertions(+), 39 deletions(-)
 create mode 100644 Help/prop_tgt/ISPC_HEADER_DIRECTORY.rst
 create mode 100644 Help/variable/CMAKE_ISPC_HEADER_DIRECTORY.rst
 create mode 100644 Tests/ISPC/ObjectLibrary/extra.cxx
 create mode 100644 Tests/ISPC/ObjectLibrary/extra.ispc

diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim
index 2d63eb0..2dd3174 100644
--- a/Auxiliary/vim/syntax/cmake.vim
+++ b/Auxiliary/vim/syntax/cmake.vim
@@ -1048,6 +1048,7 @@ syn keyword cmakeVariable contained
             \ CMAKE_HOST_UNIX
             \ CMAKE_HOST_WIN32
             \ CMAKE_IGNORE_PATH
+            \ CMAKE_ISPC_HEADER_DIRECTORY
             \ CMAKE_IMPORT_LIBRARY_PREFIX
             \ CMAKE_IMPORT_LIBRARY_SUFFIX
             \ CMAKE_INCLUDE_CURRENT_DIR
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 30b2a05..bdd2333 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -258,6 +258,7 @@ Properties on Targets
    /prop_tgt/INTERPROCEDURAL_OPTIMIZATION_CONFIG
    /prop_tgt/INTERPROCEDURAL_OPTIMIZATION
    /prop_tgt/IOS_INSTALL_COMBINED
+   /prop_tgt/ISPC_HEADER_DIRECTORY
    /prop_tgt/JOB_POOL_COMPILE
    /prop_tgt/JOB_POOL_LINK
    /prop_tgt/JOB_POOL_PRECOMPILE_HEADER
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 4ce8365..4515cc3 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -507,6 +507,7 @@ Variables for Languages
    /variable/CMAKE_Fortran_MODDIR_DEFAULT
    /variable/CMAKE_Fortran_MODDIR_FLAG
    /variable/CMAKE_Fortran_MODOUT_FLAG
+   /variable/CMAKE_ISPC_HEADER_DIRECTORY
    /variable/CMAKE_LANG_ANDROID_TOOLCHAIN_MACHINE
    /variable/CMAKE_LANG_ANDROID_TOOLCHAIN_PREFIX
    /variable/CMAKE_LANG_ANDROID_TOOLCHAIN_SUFFIX
diff --git a/Help/prop_tgt/ISPC_HEADER_DIRECTORY.rst b/Help/prop_tgt/ISPC_HEADER_DIRECTORY.rst
new file mode 100644
index 0000000..61b63fb
--- /dev/null
+++ b/Help/prop_tgt/ISPC_HEADER_DIRECTORY.rst
@@ -0,0 +1,12 @@
+ISPC_HEADER_DIRECTORY
+---------------------
+
+.. versionadded:: 3.19
+
+Specify output directory for ISPC headers provided by the target.
+
+If the target contains ISPC source files, this specifies the directory in which
+the generated headers will be placed.  When this property is not set, the
+headers will be placed a generator defined build directory. If the variable
+:variable:`CMAKE_ISPC_HEADER_DIRECTORY` is set when a target is created
+its value is used to initialize this property.
diff --git a/Help/variable/CMAKE_ISPC_HEADER_DIRECTORY.rst b/Help/variable/CMAKE_ISPC_HEADER_DIRECTORY.rst
new file mode 100644
index 0000000..a7c9cf6
--- /dev/null
+++ b/Help/variable/CMAKE_ISPC_HEADER_DIRECTORY.rst
@@ -0,0 +1,10 @@
+CMAKE_ISPC_HEADER_DIRECTORY
+----------------------------
+
+.. versionadded:: 3.19
+
+ISPC generated header output directory.
+
+This variable is used to initialize the :prop_tgt:`ISPC_HEADER_DIRECTORY`
+property on all the targets.  See the target property for additional
+information.
diff --git a/Modules/CMakeISPCInformation.cmake b/Modules/CMakeISPCInformation.cmake
index 37057d9..514036e 100644
--- a/Modules/CMakeISPCInformation.cmake
+++ b/Modules/CMakeISPCInformation.cmake
@@ -50,7 +50,7 @@ endif()
 
 if(NOT CMAKE_ISPC_COMPILE_OBJECT)
   set(CMAKE_ISPC_COMPILE_OBJECT
-    "<CMAKE_ISPC_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> --emit-obj <SOURCE>")
+    "<CMAKE_ISPC_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> --emit-obj <SOURCE> -h <ISPC_HEADER>")
 endif()
 
 set(CMAKE_ISPC_INFORMATION_LOADED 1)
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 6fc7209..9f8736a 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -1334,10 +1334,17 @@ std::string cmGeneratorTarget::EvaluateInterfaceProperty(
 
 namespace {
 
+enum class IncludeDirectoryFallBack
+{
+  BINARY,
+  OBJECT
+};
+
 std::string AddLangSpecificInterfaceIncludeDirectories(
   const cmGeneratorTarget* root, const cmGeneratorTarget* target,
   const std::string& lang, const std::string& config,
-  const std::string& propertyName, cmGeneratorExpressionDAGChecker* context)
+  const std::string& propertyName, IncludeDirectoryFallBack mode,
+  cmGeneratorExpressionDAGChecker* context)
 {
   cmGeneratorExpressionDAGChecker dag{ target->GetBacktrace(), target,
                                        propertyName, nullptr, context };
@@ -1364,7 +1371,12 @@ std::string AddLangSpecificInterfaceIncludeDirectories(
           auto* lg = dependency->GetLocalGenerator();
           std::string value = dependency->GetSafeProperty(propertyName);
           if (value.empty()) {
-            value = lg->GetCurrentBinaryDirectory();
+            if (mode == IncludeDirectoryFallBack::BINARY) {
+              value = lg->GetCurrentBinaryDirectory();
+            } else if (mode == IncludeDirectoryFallBack::OBJECT) {
+              value = cmStrCat(lg->GetCurrentBinaryDirectory(), '/',
+                               lg->GetTargetDirectory(dependency));
+            }
           }
 
           if (!directories.empty()) {
@@ -1381,7 +1393,7 @@ std::string AddLangSpecificInterfaceIncludeDirectories(
 void AddLangSpecificImplicitIncludeDirectories(
   const cmGeneratorTarget* target, const std::string& lang,
   const std::string& config, const std::string& propertyName,
-  EvaluatedTargetPropertyEntries& entries)
+  IncludeDirectoryFallBack mode, EvaluatedTargetPropertyEntries& entries)
 {
   if (const auto* libraries = target->GetLinkImplementationLibraries(config)) {
     cmGeneratorExpressionDAGChecker dag{ target->GetBacktrace(), target,
@@ -1399,12 +1411,18 @@ void AddLangSpecificImplicitIncludeDirectories(
           if (cmProp val = dependency->GetProperty(propertyName)) {
             entry.Values.emplace_back(*val);
           } else {
-            entry.Values.emplace_back(lg->GetCurrentBinaryDirectory());
+            if (mode == IncludeDirectoryFallBack::BINARY) {
+              entry.Values.emplace_back(lg->GetCurrentBinaryDirectory());
+            } else if (mode == IncludeDirectoryFallBack::OBJECT) {
+              entry.Values.emplace_back(
+                dependency->GetObjectDirectory(config));
+            }
           }
 
-          cmExpandList(AddLangSpecificInterfaceIncludeDirectories(
-                         target, dependency, lang, config, propertyName, &dag),
-                       entry.Values);
+          cmExpandList(
+            AddLangSpecificInterfaceIncludeDirectories(
+              target, dependency, lang, config, propertyName, mode, &dag),
+            entry.Values);
           entries.Entries.emplace_back(std::move(entry));
         }
       }
@@ -3439,7 +3457,27 @@ std::vector<BT<std::string>> cmGeneratorTarget::GetIncludeDirectories(
 
   if (lang == "Swift") {
     AddLangSpecificImplicitIncludeDirectories(
-      this, lang, config, "Swift_MODULE_DIRECTORY", entries);
+      this, lang, config, "Swift_MODULE_DIRECTORY",
+      IncludeDirectoryFallBack::BINARY, entries);
+  }
+
+  if (this->CanCompileSources() && (lang != "Swift" && lang != "Fortran")) {
+
+    const std::string propertyName = "ISPC_HEADER_DIRECTORY";
+
+    // If this target has ISPC sources make sure to add the header
+    // directory to other compilation units
+    if (cm::contains(this->GetAllConfigCompileLanguages(), "ISPC")) {
+      if (cmProp val = this->GetProperty(propertyName)) {
+        includes.emplace_back(*val);
+      } else {
+        includes.emplace_back(this->GetObjectDirectory(config));
+      }
+    }
+
+    AddLangSpecificImplicitIncludeDirectories(
+      this, "ISPC", config, propertyName, IncludeDirectoryFallBack::OBJECT,
+      entries);
   }
 
   AddInterfaceEntries(this, config, "INTERFACE_INCLUDE_DIRECTORIES", lang,
@@ -5933,6 +5971,37 @@ std::string cmGeneratorTarget::CreateFortranModuleDirectory(
   return mod_dir;
 }
 
+void cmGeneratorTarget::AddISPCGeneratedHeader(std::string const& header,
+                                               std::string const& config)
+{
+  std::string config_upper;
+  if (!config.empty()) {
+    config_upper = cmSystemTools::UpperCase(config);
+  }
+  auto iter = this->ISPCGeneratedHeaders.find(config_upper);
+  if (iter == this->ISPCGeneratedHeaders.end()) {
+    std::vector<std::string> headers;
+    headers.emplace_back(header);
+    this->ISPCGeneratedHeaders.insert({ config_upper, headers });
+  } else {
+    iter->second.emplace_back(header);
+  }
+}
+
+std::vector<std::string> cmGeneratorTarget::GetGeneratedISPCHeaders(
+  std::string const& config) const
+{
+  std::string config_upper;
+  if (!config.empty()) {
+    config_upper = cmSystemTools::UpperCase(config);
+  }
+  auto iter = this->ISPCGeneratedHeaders.find(config_upper);
+  if (iter == this->ISPCGeneratedHeaders.end()) {
+    return std::vector<std::string>{};
+  }
+  return iter->second;
+}
+
 std::string cmGeneratorTarget::GetFrameworkVersion() const
 {
   assert(this->GetType() != cmStateEnums::INTERFACE_LIBRARY);
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 4a03f65..680681f 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -807,6 +807,11 @@ public:
 
   const std::string& GetSourcesProperty() const;
 
+  void AddISPCGeneratedHeader(std::string const& header,
+                              std::string const& config);
+  std::vector<std::string> GetGeneratedISPCHeaders(
+    std::string const& config) const;
+
 private:
   void AddSourceCommon(const std::string& src, bool before = false);
 
@@ -985,6 +990,9 @@ private:
 
   std::unordered_set<std::string> UnityBatchedSourceFiles;
 
+  std::unordered_map<std::string, std::vector<std::string>>
+    ISPCGeneratedHeaders;
+
   bool IsLinkLookupScope(std::string const& n,
                          cmLocalGenerator const*& lg) const;
 
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index cad5d1f..0865432 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1601,6 +1601,7 @@ bool cmGlobalGenerator::AddAutomaticSources()
         continue;
       }
       lg->AddUnityBuild(gt.get());
+      lg->AddISPCDependencies(gt.get());
       // Targets that re-use a PCH are handled below.
       if (!gt->GetProperty("PRECOMPILE_HEADERS_REUSE_FROM")) {
         lg->AddPchDependencies(gt.get());
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index 2ef202d..9a87e18 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -680,6 +680,9 @@ void cmGlobalNinjaGenerator::CheckNinjaFeatures()
   this->NinjaSupportsRestatTool = !cmSystemTools::VersionCompare(
     cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
     RequiredNinjaVersionForRestatTool().c_str());
+  this->NinjaSupportsMultipleOutputs = !cmSystemTools::VersionCompare(
+    cmSystemTools::OP_LESS, this->NinjaVersion.c_str(),
+    RequiredNinjaVersionForMultipleOutputs().c_str());
 }
 
 bool cmGlobalNinjaGenerator::CheckLanguages(
@@ -688,6 +691,9 @@ bool cmGlobalNinjaGenerator::CheckLanguages(
   if (cm::contains(languages, "Fortran")) {
     return this->CheckFortran(mf);
   }
+  if (cm::contains(languages, "ISPC")) {
+    return this->CheckISPC(mf);
+  }
   if (cm::contains(languages, "Swift")) {
     const std::string architectures =
       mf->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
@@ -721,6 +727,25 @@ bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
   return false;
 }
 
+bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
+{
+  if (this->NinjaSupportsMultipleOutputs) {
+    return true;
+  }
+
+  std::ostringstream e;
+  /* clang-format off */
+  e <<
+    "The Ninja generator does not support ISPC using Ninja version\n"
+    "  " << this->NinjaVersion << "\n"
+    "due to lack of required features.  Ninja 1.10 or higher is required."
+    ;
+  /* clang-format on */
+  mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+  cmSystemTools::SetFatalErrorOccured();
+  return false;
+}
+
 void cmGlobalNinjaGenerator::EnableLanguage(
   std::vector<std::string> const& langs, cmMakefile* mf, bool optional)
 {
@@ -1127,6 +1152,21 @@ void cmGlobalNinjaGenerator::AppendTargetDepends(
     }
   } else {
     cmNinjaDeps outs;
+
+    auto computeISPCOuputs = [](cmGlobalNinjaGenerator* gg,
+                                cmGeneratorTarget const* depTarget,
+                                cmNinjaDeps& outputDeps,
+                                const std::string& targetConfig) {
+      if (depTarget->CanCompileSources()) {
+        auto headers = depTarget->GetGeneratedISPCHeaders(targetConfig);
+        if (!headers.empty()) {
+          std::transform(headers.begin(), headers.end(), headers.begin(),
+                         gg->MapToNinjaPath());
+          outputDeps.insert(outputDeps.end(), headers.begin(), headers.end());
+        }
+      }
+    };
+
     for (cmTargetDepend const& targetDep :
          this->GetTargetDirectDepends(target)) {
       if (!targetDep->IsInBuildSystem()) {
@@ -1134,8 +1174,10 @@ void cmGlobalNinjaGenerator::AppendTargetDepends(
       }
       if (targetDep.IsCross()) {
         this->AppendTargetOutputs(targetDep, outs, fileConfig, depends);
+        computeISPCOuputs(this, targetDep, outs, fileConfig);
       } else {
         this->AppendTargetOutputs(targetDep, outs, config, depends);
+        computeISPCOuputs(this, targetDep, outs, config);
       }
     }
     std::sort(outs.begin(), outs.end());
diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h
index 10f5cf6..0881ce0 100644
--- a/Source/cmGlobalNinjaGenerator.h
+++ b/Source/cmGlobalNinjaGenerator.h
@@ -370,6 +370,10 @@ public:
     return "1.10";
   }
   static std::string RequiredNinjaVersionForCleanDeadTool() { return "1.10"; }
+  static std::string RequiredNinjaVersionForMultipleOutputs()
+  {
+    return "1.10";
+  }
   bool SupportsConsolePool() const;
   bool SupportsImplicitOuts() const;
   bool SupportsManifestRestat() const;
@@ -447,6 +451,7 @@ private:
   bool CheckLanguages(std::vector<std::string> const& languages,
                       cmMakefile* mf) const override;
   bool CheckFortran(cmMakefile* mf) const;
+  bool CheckISPC(cmMakefile* mf) const;
 
   void CloseCompileCommandsStream();
 
@@ -533,6 +538,7 @@ private:
   bool NinjaSupportsRestatTool = false;
   bool NinjaSupportsUnconditionalRecompactTool = false;
   bool NinjaSupportsCleanDeadTool = false;
+  bool NinjaSupportsMultipleOutputs = false;
 
 private:
   void InitOutputPathPrefix();
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 7e35fe7..9369bd4 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -2408,6 +2408,38 @@ void cmLocalGenerator::AppendFlagEscape(std::string& flags,
     this->EscapeForShell(rawFlag, false, false, false, this->IsNinjaMulti()));
 }
 
+void cmLocalGenerator::AddISPCDependencies(cmGeneratorTarget* target)
+{
+  //
+  std::vector<std::string> configsList =
+    this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+  for (std::string const& config : configsList) {
+
+    std::string perConfigDir = target->GetObjectDirectory(config);
+    if (cmProp prop = target->GetProperty("ISPC_HEADER_DIRECTORY")) {
+      perConfigDir = cmSystemTools::CollapseFullPath(
+        cmStrCat(this->GetBinaryDirectory(), '/', *prop));
+    }
+
+    std::vector<cmSourceFile*> sources;
+    target->GetSourceFiles(sources, config);
+
+    // build up the list of ispc headers that this target is generating
+    for (cmSourceFile const* sf : sources) {
+      // Generate this object file's rule file.
+      const std::string& lang = sf->GetLanguage();
+      if (lang == "ISPC") {
+        std::string const& objectName = target->GetObjectName(sf);
+        std::string ispcSource =
+          cmSystemTools::GetFilenameWithoutLastExtension(objectName);
+
+        auto headerPath = cmStrCat(perConfigDir, '/', ispcSource, ".h");
+        target->AddISPCGeneratedHeader(headerPath, config);
+      }
+    }
+  }
+}
+
 void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
 {
   std::vector<std::string> configsList =
diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h
index 0c51a65..cf2bce1 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -133,6 +133,7 @@ public:
                            const std::vector<BT<std::string>>& newFlags) const;
   virtual void AppendFlagEscape(std::string& flags,
                                 const std::string& rawFlag) const;
+  void AddISPCDependencies(cmGeneratorTarget* target);
   void AddPchDependencies(cmGeneratorTarget* target);
   void AddUnityBuild(cmGeneratorTarget* target);
   void AppendIPOLinkerFlags(std::string& flags, cmGeneratorTarget* target,
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 854fc8b..047fee1 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -267,6 +267,7 @@ void cmMakefileTargetGenerator::WriteTargetBuildRules()
       this->ExternalObjects.push_back(objectFileName);
     }
   }
+
   std::vector<cmSourceFile const*> objectSources;
   this->GeneratorTarget->GetObjectSources(objectSources,
                                           this->GetConfigName());
@@ -524,6 +525,14 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     }
   }
 
+  if (lang != "ISPC") {
+    auto const& headers =
+      this->GeneratorTarget->GetGeneratedISPCHeaders(config);
+    if (!headers.empty()) {
+      depends.insert(depends.end(), headers.begin(), headers.end());
+    }
+  }
+
   std::string relativeObj =
     cmStrCat(this->LocalGenerator->GetHomeRelativeOutputPath(), obj);
   // Write the build rule.
@@ -551,6 +560,23 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     this->AppendFortranPreprocessFlags(flags, source);
   }
 
+  std::string ispcHeaderRelative;
+  std::string ispcHeaderForShell;
+  if (lang == "ISPC") {
+    std::string ispcSource =
+      cmSystemTools::GetFilenameWithoutLastExtension(objectName);
+
+    std::string directory = this->GeneratorTarget->GetObjectDirectory(config);
+    if (cmProp prop =
+          this->GeneratorTarget->GetProperty("ISPC_HEADER_DIRECTORY")) {
+      directory =
+        cmStrCat(this->LocalGenerator->GetBinaryDirectory(), '/', *prop);
+    }
+    ispcHeaderRelative = cmStrCat(directory, '/', ispcSource, ".h");
+    ispcHeaderForShell = this->LocalGenerator->ConvertToOutputFormat(
+      ispcHeaderRelative, cmOutputConverter::SHELL);
+  }
+
   // Add flags from source file properties.
   const std::string COMPILE_FLAGS("COMPILE_FLAGS");
   if (cmProp cflags = source.GetProperty(COMPILE_FLAGS)) {
@@ -716,6 +742,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     cmOutputConverter::SHELL);
   vars.ObjectFileDir = objectFileDir.c_str();
   vars.Flags = flags.c_str();
+  vars.ISPCHeader = ispcHeaderForShell.c_str();
 
   std::string definesString = cmStrCat("$(", lang, "_DEFINES)");
 
@@ -910,9 +937,16 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles(
     if (!evaluated_outputs.empty()) {
       // Register these as extra files to clean.
       cmExpandList(evaluated_outputs, outputs);
-      this->CleanFiles.insert(outputs.begin() + 1, outputs.end());
     }
   }
+  if (!ispcHeaderRelative
+         .empty()) { // can't move ispcHeader as vars is using it
+    outputs.emplace_back(ispcHeaderRelative);
+  }
+
+  if (outputs.size() > 1) {
+    this->CleanFiles.insert(outputs.begin() + 1, outputs.end());
+  }
 
   // Write the rule.
   this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 048dbb6..95b8ad7 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -630,6 +630,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
   vars.TargetCompilePDB = "$TARGET_COMPILE_PDB";
   vars.ObjectDir = "$OBJECT_DIR";
   vars.ObjectFileDir = "$OBJECT_FILE_DIR";
+  vars.ISPCHeader = "$ISPC_HEADER_FILE";
 
   cmMakefile* mf = this->GetMakefile();
 
@@ -1368,6 +1369,42 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
 
   objBuild.RspFile = cmStrCat(objectFileName, ".rsp");
 
+  if (language == "ISPC") {
+    std::string const& objectName =
+      this->GeneratorTarget->GetObjectName(source);
+    std::string ispcSource =
+      cmSystemTools::GetFilenameWithoutLastExtension(objectName);
+
+    std::string ispcDirectory = objectFileDir;
+    if (cmProp prop =
+          this->GeneratorTarget->GetProperty("ISPC_HEADER_DIRECTORY")) {
+      ispcDirectory = *prop;
+    }
+    ispcDirectory =
+      cmStrCat(this->LocalGenerator->GetBinaryDirectory(), '/', ispcDirectory);
+
+    std::string ispcHeader = cmStrCat(ispcDirectory, '/', ispcSource, ".h");
+    ispcHeader = this->ConvertToNinjaPath(ispcHeader);
+
+    // Make sure ninja knows what command generates the header
+    objBuild.ImplicitOuts.push_back(ispcHeader);
+
+    // Make sure ninja knows how to clean the generated header
+    this->GetGlobalGenerator()->AddAdditionalCleanFile(ispcHeader, config);
+
+    vars["ISPC_HEADER_FILE"] =
+      this->GetLocalGenerator()->ConvertToOutputFormat(
+        ispcHeader, cmOutputConverter::SHELL);
+  } else {
+    auto headers = this->GeneratorTarget->GetGeneratedISPCHeaders(config);
+    if (!headers.empty()) {
+      std::transform(headers.begin(), headers.end(), headers.begin(),
+                     MapToNinjaPath());
+      objBuild.OrderOnlyDeps.insert(objBuild.OrderOnlyDeps.end(),
+                                    headers.begin(), headers.end());
+    }
+  }
+
   if (language == "Swift") {
     this->EmitSwiftDependencyInfo(source, config);
   } else {
diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx
index 4ff81c1..6f40ec6 100644
--- a/Source/cmRulePlaceholderExpander.cxx
+++ b/Source/cmRulePlaceholderExpander.cxx
@@ -90,6 +90,11 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable(
       return replaceValues.AIXExports;
     }
   }
+  if (replaceValues.ISPCHeader) {
+    if (variable == "ISPC_HEADER") {
+      return replaceValues.ISPCHeader;
+    }
+  }
   if (replaceValues.Defines && variable == "DEFINES") {
     return replaceValues.Defines;
   }
diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h
index 09e8a3b..96e731f 100644
--- a/Source/cmRulePlaceholderExpander.h
+++ b/Source/cmRulePlaceholderExpander.h
@@ -64,6 +64,7 @@ public:
     const char* SwiftModuleName;
     const char* SwiftOutputFileMap;
     const char* SwiftSources;
+    const char* ISPCHeader;
   };
 
   // Expand rule variables in CMake of the type found in language rules
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index 0e5dfef..1105f31 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -366,6 +366,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     initProp("JOB_POOL_COMPILE");
     initProp("JOB_POOL_LINK");
     initProp("JOB_POOL_PRECOMPILE_HEADER");
+    initProp("ISPC_HEADER_DIRECTORY");
     initProp("LINK_SEARCH_START_STATIC");
     initProp("LINK_SEARCH_END_STATIC");
     initProp("Swift_LANGUAGE_VERSION");
diff --git a/Tests/ISPC/CMakeLists.txt b/Tests/ISPC/CMakeLists.txt
index c8d58fd..ca5a79b 100644
--- a/Tests/ISPC/CMakeLists.txt
+++ b/Tests/ISPC/CMakeLists.txt
@@ -6,9 +6,8 @@ macro (add_ispc_test_macro name)
     PROPERTY LABELS "ISPC")
 endmacro ()
 
-# set (ISPC_IA_TARGETS "sse2-i32x4,sse4-i32x4,avx1-i32x8,avx2-i32x8,avx512knl-i32x16,avx512skx-i32x16")
-
 add_ispc_test_macro(ISPC.Defines ISPCDefines)
 add_ispc_test_macro(ISPC.ObjectLibrary ISPCObjectLibrary)
+add_ispc_test_macro(ISPC.ResponseAndDefine ISPCResponseAndDefine)
 add_ispc_test_macro(ISPC.StaticLibrary ISPCStaticLibrary)
 add_ispc_test_macro(ISPC.TryCompile ISPCTryCompile)
diff --git a/Tests/ISPC/Defines/CMakeLists.txt b/Tests/ISPC/Defines/CMakeLists.txt
index 2c9f4c3..5155106 100644
--- a/Tests/ISPC/Defines/CMakeLists.txt
+++ b/Tests/ISPC/Defines/CMakeLists.txt
@@ -1,11 +1,15 @@
 cmake_minimum_required(VERSION 3.18)
 project(ISPCDefines CXX ISPC)
 
-set(CMAKE_ISPC_FLAGS -DM_PI=3.1415926535f [==[-DSTRUCT_DEFINE=struct{uniform int a]==])
+set(CMAKE_ISPC_FLAGS -DM_PI=3.1415926535f)
+add_compile_definitions([==[STRUCT_DEFINE=struct{uniform int a]==])
 
-add_executable(ISPCResponseFile
+add_executable(ISPCDefines
   main.cxx
   simple.ispc
   )
-set_target_properties(ISPCResponseFile PROPERTIES POSITION_INDEPENDENT_CODE ON)
-target_include_directories(ISPCResponseFile PRIVATE  "${CMAKE_CURRENT_BINARY_DIR}")
+
+set_target_properties(ISPCDefines PROPERTIES POSITION_INDEPENDENT_CODE ON)
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  set_source_files_properties(simple.ispc PROPERTIES COMPILE_OPTIONS "--arch=x86")
+endif()
diff --git a/Tests/ISPC/Defines/main.cxx b/Tests/ISPC/Defines/main.cxx
index a6b91a6..4f1c9be 100644
--- a/Tests/ISPC/Defines/main.cxx
+++ b/Tests/ISPC/Defines/main.cxx
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
-#include "simple_ispc.h"
+#include "simple.ispc.h"
 
 int main()
 {
diff --git a/Tests/ISPC/ObjectLibrary/CMakeLists.txt b/Tests/ISPC/ObjectLibrary/CMakeLists.txt
index edfbdd8..333ad66 100644
--- a/Tests/ISPC/ObjectLibrary/CMakeLists.txt
+++ b/Tests/ISPC/ObjectLibrary/CMakeLists.txt
@@ -3,12 +3,15 @@ cmake_minimum_required(VERSION 3.18)
 project(ISPCObjectLibrary CXX ISPC)
 
 set(CMAKE_NINJA_FORCE_RESPONSE_FILE ON)
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  set(CMAKE_ISPC_FLAGS "--arch=x86")
+endif()
 
-add_library(ispc_objects OBJECT simple.ispc)
+add_library(ispc_objects OBJECT simple.ispc extra.ispc)
+
+target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
 
-target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4;--arch=x86-64>")
-target_include_directories(ispc_objects INTERFACE  "${CMAKE_CURRENT_BINARY_DIR}")
 set_target_properties(ispc_objects PROPERTIES POSITION_INDEPENDENT_CODE ON)
 
-add_executable(ISPCObjectLibrary main.cxx)
+add_executable(ISPCObjectLibrary main.cxx extra.cxx)
 target_link_libraries(ISPCObjectLibrary PRIVATE ispc_objects)
diff --git a/Tests/ISPC/ObjectLibrary/extra.cxx b/Tests/ISPC/ObjectLibrary/extra.cxx
new file mode 100644
index 0000000..88ef3a7
--- /dev/null
+++ b/Tests/ISPC/ObjectLibrary/extra.cxx
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+#include "extra.ispc.h"
+
+int extra()
+{
+  float vin[16], vout[16];
+  for (int i = 0; i < 16; ++i)
+    vin[i] = i;
+
+  ispc::extra(vin, vout, 16);
+
+  for (int i = 0; i < 16; ++i)
+    printf("%d: extra(%f) = %f\n", i, vin[i], vout[i]);
+
+  return 0;
+}
diff --git a/Tests/ISPC/ObjectLibrary/extra.ispc b/Tests/ISPC/ObjectLibrary/extra.ispc
new file mode 100644
index 0000000..5a4a442
--- /dev/null
+++ b/Tests/ISPC/ObjectLibrary/extra.ispc
@@ -0,0 +1,12 @@
+
+export void extra(uniform float vin[], uniform float vout[],
+                   uniform int count) {
+    foreach (index = 0 ... count) {
+        float v = vin[index];
+        if (v < 3.)
+            v = v * v;
+        else
+            v = sqrt(v);
+        vout[index] = v;
+    }
+}
diff --git a/Tests/ISPC/ObjectLibrary/main.cxx b/Tests/ISPC/ObjectLibrary/main.cxx
index a6b91a6..4f1c9be 100644
--- a/Tests/ISPC/ObjectLibrary/main.cxx
+++ b/Tests/ISPC/ObjectLibrary/main.cxx
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
-#include "simple_ispc.h"
+#include "simple.ispc.h"
 
 int main()
 {
diff --git a/Tests/ISPC/ResponseAndDefine/CMakeLists.txt b/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
index 0dc5320..7539209 100644
--- a/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
+++ b/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
@@ -1,5 +1,5 @@
-
-project(ispc_spaces_in_path)
+cmake_minimum_required(VERSION 3.18)
+project(ispc_spaces_in_path ISPC CXX)
 
 set(CMAKE_NINJA_FORCE_RESPONSE_FILE ON)
 
@@ -10,15 +10,19 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/path with spaces/simple_include.h"
 "
 )
 
-add_executable(SpacesInPath main.cxx simple.ispc)
-set_target_properties(SpacesInPath PROPERTIES POSITION_INDEPENDENT_CODE ON)
-target_include_directories(SpacesInPath PRIVATE  "${CMAKE_CURRENT_BINARY_DIR}")
+add_executable(ISPCResponseAndDefine main.cxx simple.ispc)
+set_target_properties(ISPCResponseAndDefine PROPERTIES POSITION_INDEPENDENT_CODE ON)
+target_include_directories(ISPCResponseAndDefine PRIVATE  "${CMAKE_CURRENT_BINARY_DIR}")
+
+target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+endif()
+
 
-target_compile_options(SpacesInPath PRIVATE
-  "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4;--arch=x86-64>")
 
-target_compile_definitions(SpacesInPath PRIVATE
-  "$<$<COMPILE_LANGUAGE:ISPC>:STRUCT_DEFINE=\"struct{uniform int a;varying int b;\";M_PI=3.1415926535f>")
-target_include_directories(SpacesInPath PRIVATE
-  "$<$<COMPILE_LANGUAGE:ISPC>:\"fake path with spaces\">""
-  "$<$<COMPILE_LANGUAGE:ISPC>:\"path with spaces\">")
+target_compile_definitions(ISPCResponseAndDefine PRIVATE
+  "$<$<COMPILE_LANGUAGE:ISPC>:STRUCT_DEFINE=struct{uniform int a>;M_PI=3.14159f")
+target_include_directories(ISPCResponseAndDefine PRIVATE
+  "$<$<COMPILE_LANGUAGE:ISPC>:${CMAKE_CURRENT_BINARY_DIR}/fake path with spaces>"
+  "$<$<COMPILE_LANGUAGE:ISPC>:${CMAKE_CURRENT_BINARY_DIR}/path with spaces>")
diff --git a/Tests/ISPC/ResponseAndDefine/main.cxx b/Tests/ISPC/ResponseAndDefine/main.cxx
index a6b91a6..4f1c9be 100644
--- a/Tests/ISPC/ResponseAndDefine/main.cxx
+++ b/Tests/ISPC/ResponseAndDefine/main.cxx
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
-#include "simple_ispc.h"
+#include "simple.ispc.h"
 
 int main()
 {
diff --git a/Tests/ISPC/ResponseAndDefine/simple.ispc b/Tests/ISPC/ResponseAndDefine/simple.ispc
index 3f32c37..81fd7ca 100644
--- a/Tests/ISPC/ResponseAndDefine/simple.ispc
+++ b/Tests/ISPC/ResponseAndDefine/simple.ispc
@@ -1,5 +1,5 @@
 
-STRUCT_DEFINE};
+STRUCT_DEFINE;};
 
 #include "simple_include.h"
 
diff --git a/Tests/ISPC/StaticLibrary/CMakeLists.txt b/Tests/ISPC/StaticLibrary/CMakeLists.txt
index b057544..ebe5960 100644
--- a/Tests/ISPC/StaticLibrary/CMakeLists.txt
+++ b/Tests/ISPC/StaticLibrary/CMakeLists.txt
@@ -4,8 +4,11 @@ project(ISPCStaticLibrary CXX ISPC)
 
 add_library(ispc_objects STATIC simple.ispc)
 
-target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4;--arch=x86-64>")
-target_include_directories(ispc_objects INTERFACE  "${CMAKE_CURRENT_BINARY_DIR}")
+target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+endif()
+
 set_target_properties(ispc_objects PROPERTIES POSITION_INDEPENDENT_CODE ON)
 
 add_executable(ISPCStaticLibrary main.cxx)
diff --git a/Tests/ISPC/StaticLibrary/main.cxx b/Tests/ISPC/StaticLibrary/main.cxx
index a6b91a6..4f1c9be 100644
--- a/Tests/ISPC/StaticLibrary/main.cxx
+++ b/Tests/ISPC/StaticLibrary/main.cxx
@@ -1,6 +1,6 @@
 #include <stdio.h>
 
-#include "simple_ispc.h"
+#include "simple.ispc.h"
 
 int main()
 {
-- 
cgit v0.12