Compiling Flang with Microsoft Visual Studio

In the last Flang technical call, I offered to check whether patches work under Windows. Turns out, Flang currently does not compile at all when using MSVC. Here are my notes for making is compile using the latest msvc compiler version 19.26.28806. I don’t think we need to put effort into making it compile with older versions.

This is not a list of fixes. Some changes are not general purpose solutions, some break check-flang. However, I would gladly help working on fixing these upstream. However, with these changes f18 compiles and links successfully.

CMAKE_CXX_STANDARD=14

https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD.html

Flang’s CMakeLists.txt sets CMAKE_CXX_STANDARD=17. This is ignored when using a pre-configured llvm-prject build directory and adding flang to LLVM_ENABLED_PROJECTS. Maybe we can find a better solution such as setting CXX_STANDARD for flang targets only, but I am not sure whether the libraries are compatible when compiled for different versions of the C++ standard.

flang\include\flang\Common\idioms.h(20,1): fatal error C1189: #error: this is a C++17 program

https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/

For historic reasons, msvc never bumped the value of the __cplusplus macro.
Adding /Zc:__cplusplus to CMAKE_CXX_FLAGS fixes this.

NOTE: When saying ‘adding to CMAKE_CXX_FLAGS fixes this’ I do not suggest to add
list(APPEND CMAKE_CXX_FLAGS “…”)
into some CMakeLists.txt. A much better way is to use
add_compile_options(…)
CMAKE_CXX_FLAGS should be entirely under user control and never modified within the CMakeLists.txt.

-Wno-unused-parameter -Wno-error

lib/Optimizer/CMakeLists.txt adds these unconditionally to CMAKE_CXX_FLAGS (which, as mentioned before, should not be done). Msvc does not understand these flags.
Also, -Werror is not used by default anymore. Adding -Wno-error to selected sources also seem to defeat the purpose of -Werror.

— a/flang/lib/Optimizer/CMakeLists.txt
+++ b/flang/lib/Optimizer/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-error”)
get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)

add_flang_library(FIROptimizer

— a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -1,4 +1,3 @@
-set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -Wno-error -Wno-unused-parameter”)
get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)

add_flang_library(FortranLower

llvm\include\llvm/ADT/iterator.h(68,19): warning C4996: ‘std::iterator<IteratorCategoryT,T,DifferenceTypeT,PointerT,ReferenceT>’: warning STL4015: The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. (The header is NOT deprecated.) The C++ Standard has never required user-defined iterators to derive from std::iterator.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0174r1.html#2.1

When C++17 mode is enabled, msvc starts warning about deprecated features. Most prevalant is the warning to not derive from std::iterator, mostly in the LLVM support library. I.e. this will probably only fixed when LLVM itself bumps to C++17.
Adding /D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to CXX_CMAKE_FLAGS disables these warnings.

fatal error C1128: number of sections exceeded object file format limit: compile with /bigobj

https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/fatal-error-c1128?view=vs-2019

Some files contain more symbols than the standard PE-COFF object format allows.
Adding /bigobj to CMAKE_CXX_FLAGS switches to a new object format.
Alternatively, /Gy- could be used, but that effectively disables COMDAT folding/pruning.

INTERNAL COMPILER ERROR in ‘C:\PROGRA~2\MICROS~1\2019\COMMUN~1\VC\Tools\MSVC\1426~1.288\bin\Hostx64\x64\cl.exe’

https://developercommunity.visualstudio.com/content/problem/1067774/ice-internal-compiler-error-on-constexpr-range-bas.html

This occurs when iterating over an std::initializer_list in a constexpr constructor. As a workaround, I replaced the std::initializer_list constructor with a variadic template constructor. Unfortunately, this also requires some other changes that rely on implicit conversion of std::initializer_list literals in the source.

— a/flang/include/flang/Common/enum-set.h
+++ b/flang/include/flang/Common/enum-set.h
@@ -36,10 +36,10 @@ public:
using enumerationType = ENUM;

constexpr EnumSet() {}

  • constexpr EnumSet(const std::initializer_list &enums) {
  • for (auto x : enums) {
  • set(x);
  • }
  • explicit constexpr EnumSet(enumerationType Singletpn) { set(Singletpn); }
  • template<typename… T>
  • constexpr EnumSet(enumerationType a, T… Remaining) : EnumSet(Remaining…) {
  • set(a);
    }
    constexpr EnumSet(const EnumSet &) = default;
    constexpr EnumSet(EnumSet &&) = default;

flang\lib\Semantics\symbol.cpp(258,1): error C2668: ‘Fortran::semantics::Symbol::detailsIf’: ambiguous call to overloaded function

Msvc is confused whether to call to const or non-const qualified method. Since the calling method is const-qualified, there really should not be such an ambiguity.
The following patch the issue:

— a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -255,7 +255,7 @@ bool Symbol::CanReplaceDetails(const Details &details) const {
return has() || has();
},
[&](const DerivedTypeDetails &) {

  • auto *derived{detailsIf()};
  • auto *derived{this->detailsIf()};
    return derived && derived->isForwardReferenced();
    },
    (const auto &) { return false; },

lang\runtime\file.cpp(186,19): error C2589: ‘(’: illegal token on right side of ‘::’

https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std

windows.h defines macros min and max, conflicting with std::min. Defining NOMINMAX makes windows.h stop doing that:

— a/flang/runtime/file.cpp
+++ b/flang/runtime/file.cpp
@@ -15,6 +15,7 @@
#include <fcntl.h>
#include <stdlib.h>
#ifdef _WIN32
+#define NOMINMAX
#include <io.h>
#include <windows.h>
#else

flang\include\flang\Evaluate\expression.h(646): warning C4099: ‘Fortran::evaluate::RelationalFortran::evaluate::SomeType’: type name first seen using ‘class’ now seen using ‘struct’

When forward-declaring a class/struct, it should match struct/class with the keyword used to define the struct class.

Clang version of the same warning:
flang/include/flang/Evaluate/expression.h:646:17: warning: class template ‘Relational’ was previously declared as a struct template; this is valid, but may result in linker errors under the Microsoft C++ ABI [-Wmismatched-tags]

— a/flang/include/flang/Evaluate/expression.h
+++ b/flang/include/flang/Evaluate/expression.h
@@ -640,7 +643,7 @@ public:
FOR_EACH_INTEGER_KIND(extern template struct Relational, )
FOR_EACH_REAL_KIND(extern template struct Relational, )
FOR_EACH_CHARACTER_KIND(extern template struct Relational, )
-extern template struct Relational;
+extern template class Relational;

// Logical expressions of a kind bigger than LogicalResult
// do not include Relational<> operations as possibilities,

include\flang/Decimal/decimal.h(109): error C2765: ‘Fortran::decimal::ConvertToBinary’: an explicit specialization or instantiation of a function template cannot have any default arguments

https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-2/compiler-error-c2765?view=vs-2019

Self explaining. Msvc is definitely right here:

https://en.cppreference.com/w/cpp/language/template_specialization

Default function arguments cannot be specified in explicit specializations of function templates, member function templates, and member functions of class templates when the class is implicitly instantiated.

— a/flang/include/flang/Decimal/decimal.h
+++ b/flang/include/flang/Decimal/decimal.h
@@ -106,17 +106,17 @@ ConversionToBinaryResult ConvertToBinary(
const char *&, enum FortranRounding = RoundNearest);

extern template ConversionToBinaryResult<8> ConvertToBinary<8>(

  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    extern template ConversionToBinaryResult<11> ConvertToBinary<11>(
  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    extern template ConversionToBinaryResult<24> ConvertToBinary<24>(
  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    extern template ConversionToBinaryResult<53> ConvertToBinary<53>(
  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    extern template ConversionToBinaryResult<64> ConvertToBinary<64>(
  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    extern template ConversionToBinaryResult<113> ConvertToBinary<113>(
  • const char *&, enum FortranRounding = RoundNearest);
  • const char *&, enum FortranRounding);
    } // namespace Fortran::decimal
    extern “C” {
    #define NS(x) Fortran::decimal::x

include\flang/Evaluate/call.h(230): warning C4661: ‘std::optional<Fortran::evaluate::Constant<Fortran::evaluate::TypeFortran::common::TypeCategory::Integer,1>> Fortran::evaluate::FunctionRef<Fortran::evaluate::TypeFortran::common::TypeCategory::Integer,1>::Fold(Fortran::evaluate::FoldingContext &)’: no suitable definition provided for explicit template instantiation request

Some methods seem to not have any definitions. Seems they are never used, hence it still works.

flang\include\flang\Evaluate\constant.h(102): error C2664: ‘std::vector<Fortran::evaluate::value::Complex<Fortran::evaluate::value::Real<Fortran::evaluate::value::Integer<16,false,16,unsigned short,unsigned int>,8>>,std::allocator<Fortran::evaluate::value::Complex<Fortran::evaluate::value::Real<Fortran::evaluate::value::Integer<16,false,16,unsigned short,unsigned int>,8>>>>::vector(std::initializer_list<_Ty>,const _Alloc &)’: cannot convert argument 1 from ‘initializer list’ to ‘std::initializer_list<_Ty>’

Splitting the member initializer to the constructor body and making it more explicit works:

— a/flang/include/flang/Evaluate/constant.h
+++ b/flang/include/flang/Evaluate/constant.h
@@ -98,8 +98,11 @@ public:
template
ConstantBase(const A &x, Result res = Result{}) : result_{res}, values_{x} {}
template <typename A, typename = common::NoLvalue>

  • ConstantBase(A &&x, Result res = Result{})
  • : result_{res}, values_{std::move(x)} {}
  • ConstantBase(A &&x, Result res /= Result{}/, int a, int b, int c)
  • : result_{res} {
  • Element y = std::move(x);
  • values_.push_back(std::move(y));
  • }

flang\include\flang\Evaluate\expression.h(751): error C3200: ‘Fortran::evaluate::Expr<Fortran::evaluate::SomeKindFortran::common::TypeCategory::Logical>’: invalid template argument for template parameter ‘F’, expected a class template

Expr inside the definition of Expr may be interpreted as the current instantiation (Same reason why template parameters do not need to be repeated for declaring its constructor). Declaring a forward declaration under a different name helps:

— a/flang/include/flang/Evaluate/expression.h
+++ b/flang/include/flang/Evaluate/expression.h
@@ -736,6 +739,9 @@ public:
u;
};

+template
+using OtherExpr = Expr;