From b43d105c1f22541e9d942f32af04acf731f89792 Mon Sep 17 00:00:00 2001
From: Willi Mutschler <willi@mutschler.eu>
Date: Mon, 16 Oct 2023 17:47:57 +0200
Subject: [PATCH] Backporting provisions and clang support for Apple Silicon
 (arm64)

- provisions for Apple Silicon package
(backported fe8aaf44fdef66ea965f976e585e1daf48194562)

- ModelTree::findGccOnMacOS() now returns a std::filesystem::path
(backported bae04fa89954ae0f4594e9fe8d38073b3e0ae51f)

- use_dll: under Windows, append MinGW location to the PATH variable only once [By the way, move the macOS+Octave environment variable initializations to the
same place, for consistency.]
(backported bff80c0eaf39ba5c05be92900bbf09b16c7f9452)

- macOS: use clang if GCC is not available for use_dll [Related to Dynare/dynare#1893 and Dynare/dynare#1894]
(backported 958b0a380072c2c650fe9effa0f9b4001f5bd4f7)
---
 src/ModelTree.cc | 64 ++++++++++++++++++++++++++++--------------------
 src/ModelTree.hh |  7 +++---
 2 files changed, 42 insertions(+), 29 deletions(-)

diff --git a/src/ModelTree.cc b/src/ModelTree.cc
index d1c3ca01..0b206ff7 100644
--- a/src/ModelTree.cc
+++ b/src/ModelTree.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2003-2021 Dynare Team
+ * Copyright © 2003-2023 Dynare Team
  *
  * This file is part of Dynare.
  *
@@ -1903,6 +1903,8 @@ ModelTree::matlab_arch(const string &mexext)
     }
   else if (mexext == "mexmaci64")
     return "maci64";
+  else if (mexext == "mexmaca64")
+    return "maca64";
   else
     {
       cerr << "ERROR: 'mexext' option to preprocessor incorrectly set, needed with 'use_dll'" << endl;
@@ -1911,29 +1913,36 @@ ModelTree::matlab_arch(const string &mexext)
 }
 
 #ifdef __APPLE__
-string
-ModelTree::findGccOnMacos()
+
+pair<filesystem::path, bool>
+ModelTree::findCompilerOnMacos(const string &mexext)
 {
+  /* Try to find gcc, otherwise use Apple’s clang compiler.
+     Homebrew binaries are located in /usr/local/bin/ on x86_64 systems and in
+     /opt/homebrew/bin/ on arm64 systems.
+     Apple’s clang is located both in /usr/bin/gcc and /usr/bin/clang, it
+     automatically selects x86_64 or arm64 depending on the compile-time
+     environment. */
   const string macos_gcc_version {"13"};
-  char dynare_preprocessor_path[PATH_MAX];
-  uint32_t size = PATH_MAX;
-  string local_gcc_path;
-  if (_NSGetExecutablePath(dynare_preprocessor_path, &size) == 0)
-    {
-      string s = dynare_preprocessor_path;
-      local_gcc_path = s.substr(0, s.find_last_of("/")) + "/../.brew/bin/gcc-" + macos_gcc_version;
-    }
 
-  if (filesystem::exists(local_gcc_path))
-    return local_gcc_path;
-  else if (string global_gcc_path = "/usr/local/bin/gcc-" + macos_gcc_version;
-           filesystem::exists(global_gcc_path))
-    return global_gcc_path;
+  if (filesystem::path global_gcc_path {"/usr/local/bin/gcc-" + macos_gcc_version};
+      exists(global_gcc_path) && mexext == "mexmaci64")
+    return { global_gcc_path, false };
+  else if (filesystem::path global_gcc_path {"/opt/homebrew/bin/gcc-" + macos_gcc_version};
+           exists(global_gcc_path) && mexext == "mexmaca64")
+    return { global_gcc_path, false };
+  else if (filesystem::path global_clang_path {"/usr/bin/clang"}; exists(global_clang_path))
+    return { global_clang_path, true };
   else
     {
       cerr << "ERROR: You must install gcc-" << macos_gcc_version
            << " on your system before using the `use_dll` option of Dynare. "
-           << "If using MATLAB, you can do this via the Dynare installation package. If using Octave, you should run `brew install gcc-" << macos_gcc_version << "` in a terminal." << endl;
+           << "You should install Homebrew";
+      if (mexext == "mexmaca64")
+        cerr << " for arm64";
+      else if (mexext == "mexmaci64")
+        cerr << " for x86_64";
+      cerr << " and run `brew install gcc-" << macos_gcc_version << "` in a terminal." << endl;
       exit(EXIT_FAILURE);
     }
 }
@@ -1942,11 +1951,13 @@ ModelTree::findGccOnMacos()
 void
 ModelTree::compileMEX(const string &basename, const string &funcname, const string &mexext, const vector<filesystem::path> &src_files, const filesystem::path &matlabroot, const filesystem::path &dynareroot) const
 {
-  const string opt_flags = "-O3 -g0 --param ira-max-conflict-table-size=1 -fno-forward-propagate -fno-gcse -fno-dce -fno-dse -fno-tree-fre -fno-tree-pre -fno-tree-cselim -fno-tree-dse -fno-tree-dce -fno-tree-pta -fno-gcse-after-reload";
+  const string gcc_opt_flags { "-O3 -g0 --param ira-max-conflict-table-size=1 -fno-forward-propagate -fno-gcse -fno-dce -fno-dse -fno-tree-fre -fno-tree-pre -fno-tree-cselim -fno-tree-dse -fno-tree-dce -fno-tree-pta -fno-gcse-after-reload" };
+  const string clang_opt_flags { "-O3 -g0 --param ira-max-conflict-table-size=1 -Wno-unused-command-line-argument" };
 
   filesystem::path compiler;
   ostringstream flags;
   string libs;
+  bool is_clang {false};
 
   if (matlabroot.empty())
     {
@@ -1960,16 +1971,17 @@ ModelTree::compileMEX(const string &basename, const string &funcname, const stri
       compiler = matlabroot / "bin" / "mkoctfile";
       flags << "--mex";
 #ifdef __APPLE__
-      /* On macOS, enforce GCC, otherwise Clang will be used, and it does not
-         accept our custom optimization flags (see dynare#1797) */
-      string gcc_path = findGccOnMacos();
-      if (setenv("CC", gcc_path.c_str(), 1) != 0)
+      /* On macOS, with Octave, enforce our compiler. In particular this is
+         necessary if we’ve selected GCC; otherwise Clang will be used, and
+         it does not accept the same optimization flags (see dynare#1797) */
+      auto [compiler_path, is_clang] { findCompilerOnMacos(mexext) };
+      if (setenv("CC", compiler_path.c_str(), 1) != 0)
         {
           cerr << "Can't set CC environment variable" << endl;
           exit(EXIT_FAILURE);
         }
       // We also define CXX, because that is used for linking
-      if (setenv("CXX", gcc_path.c_str(), 1) != 0)
+      if (setenv("CXX", compiler_path.c_str(), 1) != 0)
         {
           cerr << "Can't set CXX environment variable" << endl;
           exit(EXIT_FAILURE);
@@ -2011,9 +2023,9 @@ ModelTree::compileMEX(const string &basename, const string &funcname, const stri
             }
         }
 #ifdef __APPLE__
-      else if (mexext == "mexmaci64")
+      else if (mexext == "mexmaci64" || mexext == "mexmaca64")
         {
-          compiler = findGccOnMacos();
+          tie(compiler, is_clang) = findCompilerOnMacos(mexext);
           flags << " -fno-common -Wl,-twolevel_namespace -undefined error -bundle";
           libs += " -lm";
         }
@@ -2049,7 +2061,7 @@ ModelTree::compileMEX(const string &basename, const string &funcname, const stri
       cmd << user_set_compiler << " ";
 
   if (user_set_subst_flags.empty())
-    cmd << opt_flags << " " << flags.str() << " ";
+    cmd << (is_clang ? clang_opt_flags : gcc_opt_flags) << " " << flags.str() << " ";
   else
     cmd << user_set_subst_flags << " ";
 
diff --git a/src/ModelTree.hh b/src/ModelTree.hh
index 0c54edf9..bcf3da0b 100644
--- a/src/ModelTree.hh
+++ b/src/ModelTree.hh
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2003-2021 Dynare Team
+ * Copyright © 2003-2023 Dynare Team
  *
  * This file is part of Dynare.
  *
@@ -399,8 +399,9 @@ private:
   //! Returns the name of the MATLAB architecture given the extension used for MEX files
   static string matlab_arch(const string &mexext);
 #ifdef __APPLE__
-  //! Finds a suitable GCC compiler on macOS
-  static string findGccOnMacos();
+  /* Finds a suitable compiler on macOS.
+     The boolean is false if this is GCC and true if this is Clang */
+  static pair<filesystem::path, bool> findCompilerOnMacos(const string &mexext);
 #endif
   //! Compiles a MEX file
   void compileMEX(const string &basename, const string &funcname, const string &mexext, const vector<filesystem::path> &src_files, const filesystem::path &matlabroot, const filesystem::path &dynareroot) const;
-- 
GitLab