Index: clang/include/clang/Basic/TargetInfo.h =================================================================== --- clang/include/clang/Basic/TargetInfo.h +++ clang/include/clang/Basic/TargetInfo.h @@ -1092,6 +1092,22 @@ /// either; the entire thing is pretty badly mangled. virtual bool hasProtectedVisibility() const { return true; } + /// Does this target aim for semantic compatibility with + /// Microsoft C++ code using dllimport/export attributes? + virtual bool shouldDLLImportComdatSymbols() const { + return getTriple().isWindowsMSVCEnvironment() || + getTriple().isWindowsItaniumEnvironment() || getTriple().isPS4CPU(); + } + + // Does this target have PS4 specific dllimport/export handling? + virtual bool hasPS4DLLImportExport() const { + return getTriple().isPS4CPU() || + // Windows Itanium support allows for testing the SCEI flavour of + // dllimport/export handling on a Windows system. + (getTriple().isWindowsItaniumEnvironment() && + getTriple().getVendor() == llvm::Triple::SCEI); + } + /// An optional hook that targets can implement to perform semantic /// checking on attribute((section("foo"))) specifiers. /// Index: clang/lib/AST/RecordLayoutBuilder.cpp =================================================================== --- clang/lib/AST/RecordLayoutBuilder.cpp +++ clang/lib/AST/RecordLayoutBuilder.cpp @@ -2277,7 +2277,8 @@ // If the key function is dllimport but the class isn't, then the class has // no key function. The DLL that exports the key function won't export the // vtable in this case. - if (MD->hasAttr() && !RD->hasAttr()) + if (MD->hasAttr() && !RD->hasAttr() && + !Context.getTargetInfo().hasPS4DLLImportExport()) return nullptr; // We found it. Index: clang/lib/CodeGen/ItaniumCXXABI.cpp =================================================================== --- clang/lib/CodeGen/ItaniumCXXABI.cpp +++ clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1815,6 +1815,29 @@ /*InRangeIndex=*/1); } +// Check whether all the non-inline virtual methods for the class have the +// specified attribute. +template +static bool CXXRecordAllNonInlineVirtualsHaveAttr(const CXXRecordDecl *RD) { + bool FoundNonInlineVirtualMethodWithAttr = false; + for (const auto *D : RD->noload_decls()) { + if (const auto *FD = dyn_cast(D)) { + if (!FD->isVirtualAsWritten() || FD->isInlineSpecified() || + FD->doesThisDeclarationHaveABody()) + continue; + if (!D->hasAttr()) + return false; + FoundNonInlineVirtualMethodWithAttr = true; + } + } + + // We didn't find any non-inline virtual methods missing the attribute. We + // will return true when we found at least one non-inline virtual with the + // attribute. (This lets our caller know that the attribute needs to be + // propagated up to the vtable.) + return FoundNonInlineVirtualMethodWithAttr; +} + llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructorWithVTT( CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, const CXXRecordDecl *NearestVBase) { @@ -1870,6 +1893,24 @@ getContext().toCharUnitsFromBits(PAlign).getQuantity()); VTable->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + // In MS C++ if you have a class with virtual functions in which you are using + // selective member import/export, then all virtual functions must be exported + // unless they are inline, otherwise a link error will result. To match this + // behavior, for such classes, we dllimport the vtable if it is defined + // externally and all the non-inline virtual methods are marked dllimport, and + // we dllexport the vtable if it is defined in this TU and all the non-inline + // virtual methods are marked dllexport. + if (CGM.getTarget().hasPS4DLLImportExport()) { + if ((!RD->hasAttr()) && (!RD->hasAttr())) { + if (CGM.getVTables().isVTableExternal(RD)) { + if (CXXRecordAllNonInlineVirtualsHaveAttr(RD)) + VTable->setDLLStorageClass(llvm::GlobalValue::DLLImportStorageClass); + } else { + if (CXXRecordAllNonInlineVirtualsHaveAttr(RD)) + VTable->setDLLStorageClass(llvm::GlobalValue::DLLExportStorageClass); + } + } + } CGM.setGVProperties(VTable, RD); return VTable; @@ -2345,13 +2386,14 @@ } // Create the guard variable with a zero-initializer. - // Just absorb linkage and visibility from the guarded variable. + // Just absorb linkage, visibility and dll storage class from the guarded variable. guard = new llvm::GlobalVariable(CGM.getModule(), guardTy, false, var->getLinkage(), llvm::ConstantInt::get(guardTy, 0), guardName.str()); guard->setDSOLocal(var->isDSOLocal()); guard->setVisibility(var->getVisibility()); + guard->setDLLStorageClass(var->getDLLStorageClass()); // If the variable is thread-local, so is its guard variable. guard->setThreadLocalMode(var->getThreadLocalMode()); guard->setAlignment(guardAlignment.getAsAlign()); @@ -3026,6 +3068,14 @@ Name); const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); CGM.setGVProperties(GV, RD); + // Import the typeinfo symbol when all non-inline virtual methods are + // imported. + if (CGM.getTarget().hasPS4DLLImportExport()) { + if (RD && CXXRecordAllNonInlineVirtualsHaveAttr(RD)) { + GV->setDLLStorageClass(llvm::GlobalVariable::DLLImportStorageClass); + CGM.setDSOLocal(GV); + } + } } return llvm::ConstantExpr::getBitCast(GV, CGM.Int8PtrTy); @@ -3200,11 +3250,14 @@ if (CGM.getTriple().isWindowsGNUEnvironment()) return false; - if (CGM.getVTables().isVTableExternal(RD)) + if (CGM.getVTables().isVTableExternal(RD)) { + if (CGM.getTarget().hasPS4DLLImportExport()) + return true; + return IsDLLImport && !CGM.getTriple().isWindowsItaniumEnvironment() ? false : true; - + } if (IsDLLImport) return true; } @@ -3656,6 +3709,18 @@ new llvm::GlobalVariable(M, Init->getType(), /*isConstant=*/true, Linkage, Init, Name); + // Export the typeinfo in the same circumstances as the vtable is exported. + auto GVDLLStorageClass = DLLStorageClass; + if (CGM.getTarget().hasPS4DLLImportExport()) { + if (const RecordType *RecordTy = dyn_cast(Ty)) { + const CXXRecordDecl *RD = cast(RecordTy->getDecl()); + if (RD->hasAttr() || + CXXRecordAllNonInlineVirtualsHaveAttr(RD)) { + GVDLLStorageClass = llvm::GlobalVariable::DLLExportStorageClass; + } + } + } + // If there's already an old global variable, replace it with the new one. if (OldGV) { GV->takeName(OldGV); @@ -3694,7 +3759,9 @@ CGM.setDSOLocal(GV); TypeName->setDLLStorageClass(DLLStorageClass); - GV->setDLLStorageClass(DLLStorageClass); + GV->setDLLStorageClass(CGM.getTarget().hasPS4DLLImportExport() + ? GVDLLStorageClass + : DLLStorageClass); TypeName->setPartition(CGM.getCodeGenOpts().SymbolPartition); GV->setPartition(CGM.getCodeGenOpts().SymbolPartition); Index: libcxx/include/CMakeLists.txt =================================================================== --- libcxx/include/CMakeLists.txt +++ libcxx/include/CMakeLists.txt @@ -229,14 +229,14 @@ add_library(cxx-headers INTERFACE) add_dependencies(cxx-headers generate-cxx-headers ${LIBCXX_CXX_ABI_HEADER_TARGET}) # TODO: Use target_include_directories once we figure out why that breaks the runtimes build - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC" AND NOT ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL "GNU") target_compile_options(cxx-headers INTERFACE /I "${output_dir}") else() target_compile_options(cxx-headers INTERFACE -I "${output_dir}") endif() # Make sure the generated __config_site header is included when we build the library. - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC" AND NOT ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL "GNU") target_compile_options(cxx-headers INTERFACE /FI "${LIBCXX_BINARY_DIR}/__config_site") else() target_compile_options(cxx-headers INTERFACE -include "${LIBCXX_BINARY_DIR}/__config_site") Index: libcxx/include/support/win32/locale_win32.h =================================================================== --- libcxx/include/support/win32/locale_win32.h +++ libcxx/include/support/win32/locale_win32.h @@ -12,9 +12,29 @@ #include <__config> #include -#include // _locale_t +#include // _locale_t #include <__nullptr> + +#define _X_ALL LC_ALL +#define _X_COLLATE LC_COLLATE +#define _X_CTYPE LC_CTYPE +#define _X_MONETARY LC_MONETARY +#define _X_NUMERIC LC_NUMERIC +#define _X_TIME LC_TIME +#define _X_MAX LC_MAX +#define _X_MESSAGES 6 +#define _NCAT (_X_MESSAGES + 1) /* maximum + 1 */ + + #define _CATMASK(n) ((1 << (n)) >> 1) + #define _M_COLLATE _CATMASK(_X_COLLATE) + #define _M_CTYPE _CATMASK(_X_CTYPE) + #define _M_MONETARY _CATMASK(_X_MONETARY) + #define _M_NUMERIC _CATMASK(_X_NUMERIC) + #define _M_TIME _CATMASK(_X_TIME) + #define _M_MESSAGES _CATMASK(_X_MESSAGES) + #define _M_ALL (_CATMASK(_NCAT) - 1) + #define LC_COLLATE_MASK _M_COLLATE #define LC_CTYPE_MASK _M_CTYPE #define LC_MONETARY_MASK _M_MONETARY Index: libcxx/src/CMakeLists.txt =================================================================== --- libcxx/src/CMakeLists.txt +++ libcxx/src/CMakeLists.txt @@ -76,8 +76,12 @@ list(APPEND LIBCXX_SOURCES support/win32/locale_win32.cpp support/win32/support.cpp - support/win32/thread_win32.cpp ) +if (LIBCXX_ENABLE_THREADS) + list(APPEND LIBCXX_SOURCES + support/win32/thread_win32.cpp) +endif() + elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "SunOS") list(APPEND LIBCXX_SOURCES support/solaris/mbsnrtowcs.inc @@ -208,6 +212,8 @@ if (LIBCXX_STATICALLY_LINK_ABI_IN_SHARED_LIBRARY) if (APPLE) target_link_libraries(cxx_shared PRIVATE "-Wl,-force_load" "${LIBCXX_CXX_STATIC_ABI_LIBRARY}") + elseif (MSVC OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") + target_link_libraries(cxx_shared PRIVATE "-Wl,/WHOLEARCHIVE:${LIBCXX_CXX_STATIC_ABI_LIBRARY}${CMAKE_STATIC_LIBRARY_SUFFIX}") else() target_link_libraries(cxx_shared PRIVATE "-Wl,--whole-archive,-Bstatic" "${LIBCXX_CXX_STATIC_ABI_LIBRARY}" "-Wl,-Bdynamic,--no-whole-archive") endif() @@ -283,6 +289,9 @@ endif() if (TARGET "${LIBCXX_CXX_STATIC_ABI_LIBRARY}" OR HAVE_LIBCXXABI) set(MERGE_ARCHIVES_ABI_TARGET "$") + elseif (MSVC OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") + set(MERGE_ARCHIVES_ABI_TARGET + "${LIBCXX_CXX_STATIC_ABI_LIBRARY}${CMAKE_STATIC_LIBRARY_SUFFIX}") else() set(MERGE_ARCHIVES_ABI_TARGET "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBCXX_CXX_STATIC_ABI_LIBRARY}${CMAKE_STATIC_LIBRARY_SUFFIX}") Index: libcxxabi/CMakeLists.txt =================================================================== --- libcxxabi/CMakeLists.txt +++ libcxxabi/CMakeLists.txt @@ -443,8 +443,21 @@ # pre-C++17. add_definitions(-D_LIBCPP_ENABLE_CXX17_REMOVED_UNEXPECTED_FUNCTIONS) -if (MSVC) +if (MSVC OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") add_definitions(-D_CRT_SECURE_NO_WARNINGS) + if (LIBCXX_ENABLE_SHARED AND LIBCXX_ENABLE_STATIC_ABI_LIBRARY) + # Building libcxxabi statically, but intending for it to be statically + # linked into a shared libcxx; copy this block of compiler flags from + # libc++'s' CMakeLists.txt. Unless these flags are normalized link errors are emitted + # when trying to link the libc++ dll, for example: + # lld-link: error: /failifmismatch: mismatch detected for '_CRT_STDIO_ISO_WIDE_SPECIFIERS': + # >>> src/CMakeFiles/cxx_shared.dir/support/win32/support.cpp.obj has value 1 + # >>> c++abi.lib(cxa_handlers.cpp.obj) has value 0 + add_definitions(-D_ALLOW_MSC_VER_MISMATCH) + add_definitions(-D_ALLOW_ITERATOR_DEBUG_LEVEL_MISMATCH) + add_definitions(-D_CRTBLD) + add_definitions(-D_CRT_STDIO_ISO_WIDE_SPECIFIERS) + endif() endif() # Define LIBCXXABI_USE_LLVM_UNWINDER for conditional compilation. Index: libcxxabi/include/__cxxabi_config.h =================================================================== --- libcxxabi/include/__cxxabi_config.h +++ libcxxabi/include/__cxxabi_config.h @@ -25,7 +25,7 @@ # endif #elif defined(__GNUC__) # define _LIBCXXABI_COMPILER_GCC -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) || defined(_WINDOWS_ITANIUM) # define _LIBCXXABI_COMPILER_MSVC #elif defined(__IBMCPP__) # define _LIBCXXABI_COMPILER_IBM Index: libcxxabi/src/cxa_guard_impl.h =================================================================== --- libcxxabi/src/cxa_guard_impl.h +++ libcxxabi/src/cxa_guard_impl.h @@ -39,7 +39,9 @@ #include "__cxxabi_config.h" #include "include/atomic_support.h" +#if !defined(_WIN32) #include +#endif #if defined(__has_include) # if __has_include() # include Index: libunwind/CMakeLists.txt =================================================================== --- libunwind/CMakeLists.txt +++ libunwind/CMakeLists.txt @@ -69,6 +69,7 @@ option(LIBUNWIND_INCLUDE_DOCS "Build the libunwind documentation." ${LLVM_INCLUDE_DOCS}) option(LIBUNWIND_IS_BAREMETAL "Build libunwind for baremetal targets." OFF) option(LIBUNWIND_USE_FRAME_HEADER_CACHE "Cache frame headers for unwinding. Requires locking dl_iterate_phdr." OFF) +option(LIBUNWIND_NO_LIBRARIES "Don't add any libraries to the link line." OFF) set(LIBUNWIND_LIBDIR_SUFFIX "${LLVM_LIBDIR_SUFFIX}" CACHE STRING "Define suffix of library directory name (32/64)") Index: libunwind/src/CMakeLists.txt =================================================================== --- libunwind/src/CMakeLists.txt +++ libunwind/src/CMakeLists.txt @@ -66,17 +66,19 @@ ${LIBUNWIND_ASM_SOURCES}) # Generate library list. -add_library_flags_if(LIBUNWIND_HAS_C_LIB c) -if (LIBUNWIND_USE_COMPILER_RT) - add_library_flags("${LIBUNWIND_BUILTINS_LIBRARY}") -else() - add_library_flags_if(LIBUNWIND_HAS_GCC_S_LIB gcc_s) - add_library_flags_if(LIBUNWIND_HAS_GCC_LIB gcc) -endif() -add_library_flags_if(LIBUNWIND_HAS_DL_LIB dl) -if (LIBUNWIND_ENABLE_THREADS) - add_library_flags_if(LIBUNWIND_HAS_PTHREAD_LIB pthread) - add_compile_flags_if(LIBUNWIND_WEAK_PTHREAD_LIB -DLIBUNWIND_USE_WEAK_PTHREAD=1) +if (NOT LIBUNWIND_NO_LIBRARIES) + add_library_flags_if(LIBUNWIND_HAS_C_LIB c) + if (LIBUNWIND_USE_COMPILER_RT) + add_library_flags("${LIBUNWIND_BUILTINS_LIBRARY}") + else() + add_library_flags_if(LIBUNWIND_HAS_GCC_S_LIB gcc_s) + add_library_flags_if(LIBUNWIND_HAS_GCC_LIB gcc) + endif() + add_library_flags_if(LIBUNWIND_HAS_DL_LIB dl) + if (LIBUNWIND_ENABLE_THREADS) + add_library_flags_if(LIBUNWIND_HAS_PTHREAD_LIB pthread) + add_compile_flags_if(LIBUNWIND_WEAK_PTHREAD_LIB -DLIBUNWIND_USE_WEAK_PTHREAD=1) + endif() endif() # Setup flags. Index: wi-test/lit.cfg =================================================================== --- /dev/null +++ wi-test/lit.cfg @@ -0,0 +1,10 @@ +import os, platform, sys, lit.formats + +# name: The name of this test suite. +config.name = 'wi-test' + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.ShTest(execute_external=False) + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = ['.py'] Index: wi-test/test/dll_class/foo.h =================================================================== --- /dev/null +++ wi-test/test/dll_class/foo.h @@ -0,0 +1,13 @@ +#if EXPORTING + #define IMPORT_EXPORT __declspec(dllexport) +#else + #define IMPORT_EXPORT __declspec(dllimport) +#endif + +struct Foo +{ + virtual int TypeId() const { return 1; } + IMPORT_EXPORT static int mCounter; + IMPORT_EXPORT virtual int Func(); + IMPORT_EXPORT virtual void AnotherFunc(); +}; Index: wi-test/test/dll_class/main.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_class/main.cpp @@ -0,0 +1,46 @@ +#include + +// EXPORTING == 0 => we are importing in this module +#define EXPORTING 0 +#include "foo.h" + +extern void doImportedWork(); +void doImportedWork() +{ + Foo f; + printf("doImportedWork(), mCounter = %d\n", Foo::mCounter); + ++Foo::mCounter; + (void) f.Func(); // Foo::Func() is imported + f.AnotherFunc(); // Foo::AnotherFunc() is imported +} + +#include +int load_and_run_plugin() +{ + HMODULE module = LoadLibraryA("test.py.tmp_plugin.dll"); + typedef int (*PluginMainT)(); + PluginMainT pstart = (PluginMainT)GetProcAddress(module, "module_start"); + if (!pstart) { + printf("Failure to load DLL or to find 'module_start' in it.\n"); + printf("load module result: 0x%08x\n", (unsigned int) module); + return -1; + } + int res = pstart(); + printf("plugin module: %u\n", (unsigned int) module); + printf("plugin pstart: %u\n", (unsigned int) pstart); + printf("Successfully loaded and ran plugin.\n"); + doImportedWork(); + printf("Plugin returned: %d\n", res); + return 0; +} + +int main() +{ + printf("app_main_start\n"); + if (load_and_run_plugin() == 0) + printf("{PASS}\n"); + else + printf("{FAIL}\n"); + printf("app_main_end\n"); + return 0; +} Index: wi-test/test/dll_class/plugin.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_class/plugin.cpp @@ -0,0 +1,43 @@ +#include + +#define EXPORTING 1 +#include "foo.h" + +// This is a vitual method, exported from here. It is declared as follows in "foo.h": +// IMPORT_EXPORT virtual int Func(); +// +// This is the key function, so the vtable of 'Foo' is defined in this TU. +int Foo::Func() +{ + mCounter += 28; + printf("Foo::Func(), mCounter = %d\n", mCounter); + return mCounter; +} + +// This is a vitual method, exported from here. It is declared as follows in "foo.h": +// IMPORT_EXPORT virtual void AnotherFunc(); +void Foo::AnotherFunc() +{ + printf("Foo::AnotherFunc(), mCounter = %d\n", mCounter); +} + +IMPORT_EXPORT int Foo::mCounter = 1; + +int plugin_main() +{ + Foo f; + Foo::mCounter += 11; + printf("plugin_main(), mCounter = %d\n", Foo::mCounter); + ++Foo::mCounter; + return f.TypeId() + Foo::mCounter; +} + + +extern "C" +__declspec(dllexport) +int module_start(size_t unused_a, const void* unused_b) +{ + printf("module_start\n"); + int i = plugin_main(); + return i + 3; +} Index: wi-test/test/dll_class/test.py =================================================================== --- /dev/null +++ wi-test/test/dll_class/test.py @@ -0,0 +1,12 @@ +## Check that we can export the member functions of a class + +# RUN: %{clang-wi} plugin.cpp -Wl,/dll -o %t_plugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast +# RUN: %{clang-wi} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -o %t_main_wi.exe +# RUN: %t_main_wi.exe | FileCheck %s + +# RUN: %{clang-wi-scei} plugin.cpp -Wl,/dll -o %t_plugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast +# RUN: %{clang-wi-scei} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -o %t_main_scei.exe +# RUN: %t_main_scei.exe | FileCheck %s + +# CHECK: Plugin returned: 17 +# CHECK: {PASS} Index: wi-test/test/dll_guard/foo.h =================================================================== --- /dev/null +++ wi-test/test/dll_guard/foo.h @@ -0,0 +1,20 @@ +#if EXPORTING + #define IMPORT_EXPORT __declspec(dllexport) +#else + #define IMPORT_EXPORT __declspec(dllimport) +#endif + +struct foo +{ + IMPORT_EXPORT foo() {}; + + IMPORT_EXPORT static foo& Get() + { + static foo Singleton; + Singleton.d++; + return Singleton; + } + + int d = 10; +}; + Index: wi-test/test/dll_guard/main.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_guard/main.cpp @@ -0,0 +1,41 @@ +#include + +// EXPORTING == 0 => we are importing in this module +#define EXPORTING 0 +#include "foo.h" + +#include + +int load_and_run_plugin() +{ + HMODULE module = LoadLibraryA("test.py.tmp_plugin.dll"); + typedef int (*PluginMainT)(); + PluginMainT pstart = (PluginMainT)GetProcAddress(module, "module_start"); + if (!pstart) { + printf("Failure to load DLL or to find 'module_start' in it.\n"); + printf("load module result: 0x%08x\n", (unsigned int) module); + return -1; + } + int res = pstart(); + printf("plugin module: %u\n", (unsigned int) module); + printf("plugin pstart: %u\n", (unsigned int) pstart); + printf("Successfully loaded and ran plugin.\n"); + printf("Plugin returned: %d\n", res); + return 0; +} + +int main() +{ + printf("app_main_start\n"); + if (load_and_run_plugin() != 0) { + printf("{FAIL}\n"); + } + else { + if (12 == foo::Get().d) + printf("{PASS}\n"); + else + printf("{FAIL}\n"); + } + printf("app_main_end\n"); + return 0; +} Index: wi-test/test/dll_guard/plugin.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_guard/plugin.cpp @@ -0,0 +1,12 @@ +#include + +#define EXPORTING 1 +#include "foo.h" + +extern "C" +__declspec(dllexport) +int module_start(size_t unused_a, const void* unused_b) +{ + printf("module_start\n"); + return foo::Get().d; +} Index: wi-test/test/dll_guard/test.py =================================================================== --- /dev/null +++ wi-test/test/dll_guard/test.py @@ -0,0 +1,9 @@ +## Check that we export guard variables correctly. + +# RUN: %{clang-wi} plugin.cpp -Wl,/dll -o %t_plugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast +# RUN: %{clang-wi} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -o %t_main.exe -O3 +# RUN: %t_main.exe | FileCheck %s + +# CHECK: Plugin returned: 11 +# CHECK: {PASS} + Index: wi-test/test/dll_typeinfo/foo.h =================================================================== --- /dev/null +++ wi-test/test/dll_typeinfo/foo.h @@ -0,0 +1,19 @@ +#if EXPORTING + #define IMPORT_EXPORT __declspec(dllexport) +#else + #define IMPORT_EXPORT __declspec(dllimport) +#endif + +struct Base +{ + virtual int getVersion() const { return 1; } + IMPORT_EXPORT virtual void keyFunc(); +}; + +// Derive a class from 'Base', just so we can have interesting typeinfo +struct Derived : public Base +{ + int mUnused; +}; + +extern IMPORT_EXPORT void obscuro(); Index: wi-test/test/dll_typeinfo/main.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_typeinfo/main.cpp @@ -0,0 +1,56 @@ +#include +#include + +#define EXPORTING 0 +#include "foo.h" + +#define EXPECTED_RESULT 17 + +Base *basePtr; + +extern void doImportedWork(); +void doImportedWork() +{ + Base b; + Derived d; + printf("doImportedWork() invoked.\n"); + b.keyFunc(); // Base::keyFunc() is imported + + basePtr = &b; // address of a 'Base' + obscuro(); // don't let the optimizer figure out the typeid().name() + printf("Base typeid: '%s'\n", typeid(*basePtr).name()); + + basePtr = &d; // address of a 'Derived' + obscuro(); // don't let the optimizer figure out the typeid().name() + printf("Derived typeid: '%s'\n", typeid(*basePtr).name()); +} + +#include +int load_and_run_plugin() +{ + HMODULE module = LoadLibraryA("test.py.tmp_plugin.dll"); + typedef int (*PluginMainT)(); + PluginMainT pstart = (PluginMainT)GetProcAddress(module, "module_start"); + if (!pstart) { + printf("Failure to load DLL or to find 'module_start' in it.\n"); + printf("load module result: 0x%08x\n", (unsigned int) module); + return -1; + } + int res = pstart(); + printf("plugin module: %u\n", (unsigned int) module); + printf("plugin pstart: %u\n", (unsigned int) pstart); + printf("Successfully loaded and ran plugin.\n"); + doImportedWork(); + printf("Plugin returned: %d\n", res); + return res; +} + +int main() +{ + printf("begin main()\n"); + if (load_and_run_plugin() == EXPECTED_RESULT) + printf("{PASS}\n"); + else + printf("{FAIL}\n"); + return 0; +} Index: wi-test/test/dll_typeinfo/plugin.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_typeinfo/plugin.cpp @@ -0,0 +1,30 @@ +#include + +#define EXPORTING 1 +#include "foo.h" + +// This is the key function, so the vtable of 'Base' is defined in this TU. +void Base::keyFunc() +{ + printf("Base::keyFunc() invoked.\n"); +} + +int plugin_main() +{ + Base b; + printf("plugin_main() invoked.\n"); + return 16 + b.getVersion(); +} + +extern "C" +IMPORT_EXPORT int module_start(size_t unused_a, const void* unused_b) +{ + printf("module_start\n"); + return plugin_main(); +} + +// A function to call to prevent the optimizer from seeing too much. That is, +// to keep the optimizer from optimizing-away RTTI calls in the other module. +IMPORT_EXPORT void obscuro() +{ +} Index: wi-test/test/dll_typeinfo/test.py =================================================================== --- /dev/null +++ wi-test/test/dll_typeinfo/test.py @@ -0,0 +1,80 @@ +# This test-case is for a program with more than one "module", which contains a class +# referenced in the multiple modules, and where the typeinfo symbol for RTTI, and the +# vtable, are only defined in one place (because of the "key function" concept of the +# Itanium C++ ABI rules). +# +# For this to link, the typeinfo symbol and vtable must be exported from the module that +# contains their definitions, and imported into the module that uses them but does not +# have the definitions. The user can express this by either: + +# (a) declaring the class as a whole as '__declspec(dllexport)'/'__declspec(dllimport)', or +# +# (b) declaring all non-inline virtual methods of the class as +# '__declspec(dllexport)'/'__declspec(dllimport)'. +# ______________________________________________________________________ +# +# For this test-case, we have the following 'Base' and 'Derived' class: +# +# #if EXPORTING +# #define IMPORT_EXPORT __declspec(dllexport) +# #else +# #define IMPORT_EXPORT __declspec(dllimport) +# #endif +# +# struct Base +# { +# virtual int getVersion() const { return 1; } +# IMPORT_EXPORT virtual void keyFunc(); +# }; +# +# // Derive a class from 'Base', just so we can have interesting typeinfo +# struct Derived : public Base +# { +# int mUnused; +# }; +# +# This class is compiled into two separate modules: an exporting module (compiled with +# EXPORTING defined as 1), and an importing module (compiled with EXPORTING defined as 0). +# ______________________________________________________________________ +# +# The test-case prints the string returned by the typeid().name() method: +# +# Base *basePtr; +# +# extern void doImportedWork(); +# void doImportedWork() +# { +# Base b; +# Derived d; +# printf("doImportedWork() invoked.\n"); +# b.keyFunc(); // Base::keyFunc() is imported +# +# basePtr = &b; // address of a 'Base' +# obscuro(); // don't let the optimizer figure out the typeid().name() +# printf("Base typeid: '%s'\n", typeid(*basePtr).name()); +# +# basePtr = &d; // address of a 'Derived' +# obscuro(); // don't let the optimizer figure out the typeid().name() +# printf("Derived typeid: '%s'\n", typeid(*basePtr).name()); +# } +# +# The form of that string is implementation-dependent. To make the test somewhat flexible, +# we assume that the string returned by the name() method will contain "Base" for the class +# named 'Base', and "Derived" for the class named 'Derived'. This will be the case on most +# platforms. + +# RUN: %{clang-wi} plugin.cpp -Wl,/dll -o %t_plugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -frtti +# RUN: %{clang-wi} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -frtti -o %t_main_wi.exe 2>&1 +# RUN: %t_main_wi.exe | FileCheck %s + +# RUN: %{clang-wi-scei} plugin.cpp -Wl,/dll -o %t_plugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -frtti +# RUN: %{clang-wi-scei} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -frtti -o %t_main_scei.exe 2>&1 +# RUN: %t_main_scei.exe | FileCheck %s + +# CHECK: Successfully loaded and ran plugin. +# CHECK-NEXT: doImportedWork() invoked. +# CHECK-NEXT: Base::keyFunc() invoked. +# CHECK-NEXT: Base typeid: '{{.*}}Base{{.*}}' +# CHECK-NEXT: Derived typeid: '{{.*}}Derived{{.*}}' +# CHECK-NEXT: Plugin returned: 17 +# CHECK-NEXT: {PASS} Index: wi-test/test/dll_vtable_missing/foo.h =================================================================== --- /dev/null +++ wi-test/test/dll_vtable_missing/foo.h @@ -0,0 +1,33 @@ +#if EXPORTING + #define IMPORT_EXPORT __declspec(dllexport) +#else + #define IMPORT_EXPORT __declspec(dllimport) +#endif + +// Since all out-of-line virtual member functions of the following class are not +// imported/exported, Microsoft will fail to link this. Specifically, with AnotherFunc() +// being being virtual, it's referenced in the vtable. The definition appears in one +// place ("plugin.obj"/"plugin.o", which goes into "plugin.dll"/"plugin.prx" in this +// test-case). With the Microsoft vtable model, the vtable will be in COMDAT everywhere +// it is referenced. So since it's referenced in "main.obj"/"main.o", to be linked into +// "test.exe"/"test.elf", and there will be a reference to AnotherFunc() in the vtable +// in "main.obj", but AnotherFunc() is not exported from its definition in "plugin.obj", so +// there will be a link-time failure when liking "test.exe" by Microsoft. We want to +// make sure we get a link-time failure with the Windwos Itanium toolchain. +struct Foo +{ + virtual int TypeId() const { return 1; } + IMPORT_EXPORT static int mCounter; + +#if CASE1 + // AnotherFunc() not being exported should cause a link-time failure, as described above. + /* IMPORT_EXPORT */ virtual void AnotherFunc(); +#endif + + IMPORT_EXPORT virtual int Func(); + +#if CASE2 + // AnotherFunc() not being exported should cause a link-time failure, as described above. + /* IMPORT_EXPORT */ virtual void AnotherFunc(); +#endif +}; Index: wi-test/test/dll_vtable_missing/main.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_vtable_missing/main.cpp @@ -0,0 +1,46 @@ +#include + +// EXPORTING == 0 => we are importing in this module +#define EXPORTING 0 +#include "foo.h" + +extern void doImportedWork(); +void doImportedWork() +{ + Foo f; // constructor call will reference vtable of 'Foo' + printf("doImportedWork(), mCounter = %d\n", Foo::mCounter); + ++Foo::mCounter; + (void) f.Func(); // Foo::Func() is imported + // f.AnotherFunc(); // Foo::AnotherFunc() is not imported -- don't call it +} + +#include +int load_and_run_plugin() +{ + HMODULE module = LoadLibraryA("test.py.tmp_plugin.dll"); + typedef int (*PluginMainT)(); + PluginMainT pstart = (PluginMainT)GetProcAddress(module, "module_start"); + if (!pstart) { + printf("Failure to load DLL or to find 'module_start' in it.\n"); + printf("load module result: 0x%08x\n", (unsigned int) module); + return -1; + } + int res = pstart(); + printf("plugin module: %u\n", (unsigned int) module); + printf("plugin pstart: %u\n", (unsigned int) pstart); + printf("Successfully loaded and ran plugin.\n"); + doImportedWork(); + printf("Plugin returned: %d\n", res); + return 0; +} + +int main() +{ + printf("app_main_start\n"); + if (load_and_run_plugin() == 0) + printf("{PASS}\n"); + else + printf("{FAIL}\n"); + printf("app_main_end\n"); + return 0; +} Index: wi-test/test/dll_vtable_missing/plugin.cpp =================================================================== --- /dev/null +++ wi-test/test/dll_vtable_missing/plugin.cpp @@ -0,0 +1,43 @@ +#include + +#define EXPORTING 1 +#include "foo.h" + +// This is a vitual method, but not exported from here. It is declared as follows in "foo.h": +// virtual void AnotherFunc(); +// Consequently, the vtable of 'Foo' will not be exported either. This will cause an unresolved +// refence to 'Foo::AnotherFunc()' for the Microsoft toolchain). +void Foo::AnotherFunc() +{ + printf("Foo::AnotherFunc(), mCounter = %d\n", mCounter); +} + +// This is a vitual method, exported from here. It is declared as follows in "foo.h": +// IMPORT_EXPORT virtual int Func(); +// This is the key function, so the vtable of 'Foo' is defined in this TU. +int Foo::Func() +{ + mCounter += 28; + printf("Foo::Func(), mCounter = %d\n", mCounter); + return mCounter; +} + +IMPORT_EXPORT int Foo::mCounter = 1; + +int plugin_main() +{ + Foo f; + Foo::mCounter += 11; + printf("plugin_main(), mCounter = %d\n", Foo::mCounter); + ++Foo::mCounter; + return f.TypeId() + Foo::mCounter; +} + +extern "C" +__declspec(dllexport) +int module_start(size_t unused_a, const void* unused_b) +{ + printf("module_start\n"); + int i = plugin_main(); + return i + 3; +} Index: wi-test/test/dll_vtable_missing/test.py =================================================================== --- /dev/null +++ wi-test/test/dll_vtable_missing/test.py @@ -0,0 +1,21 @@ +## Test that the link fails if some, but not all, of the +## non-inline member functions of a class are marked as +## dllimport/export. VC++ fails in this cirumstance. + +# RUN: %{clang-wi} plugin.cpp -Wl,/dll -o %tplugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -DCASE1 +# RUN: not %{clang-wi} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -DCASE1 2>&1 | FileCheck %s --check-prefix=WI-CASE1 + +# RUN: %{clang-wi-scei} plugin.cpp -Wl,/dll -o %tplugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -DCASE1 +# RUN: not %{clang-wi-scei} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -DCASE1 2>&1 | FileCheck %s --check-prefix=SCEI-CASE1 + +# RUN: %{clang-wi} plugin.cpp -Wl,/dll -o %tplugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -DCASE2 +# RUN: not %{clang-wi} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -DCASE2 2>&1 | FileCheck %s --check-prefix=WI-CASE2 + +# RUN: %{clang-wi-scei} plugin.cpp -Wl,/dll -o %tplugin.dll -Wl,/IMPLIB:%t_plugin.lib -Wno-pointer-to-int-cast -DCASE2 +# RUN: not %{clang-wi-scei} main.cpp -l%t_plugin.lib -Wno-pointer-to-int-cast -DCASE2 2>&1 | FileCheck %s --check-prefix=SCEI-CASE2 + +# WI-CASE1: error: undefined symbol: vtable for Foo +# SCEI-CASE1: error: undefined symbol: vtable for Foo + +# WI-CASE2: error: undefined symbol: Foo::AnotherFunc() +# SCEI-CASE2: error: undefined symbol: vtable for Foo Index: wi-test/test/hello/test.py =================================================================== --- /dev/null +++ wi-test/test/hello/test.py @@ -0,0 +1,6 @@ +## Simple C++ hello world smoke test + +# RUN: %{clang-wi} hello.cpp -o %t.exe +# RUN: %t.exe | FileCheck %s + +# CHECK: Hello Index: wi-test/test/typeinfo_vtable_ptr/main.cpp =================================================================== --- /dev/null +++ wi-test/test/typeinfo_vtable_ptr/main.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +struct B1 {}; +struct D1 : B1 {}; + +struct B2 { virtual void foo() {} }; +struct D2 : B2 {}; + +int main() { + { + D1 d1; + auto & td1 = typeid(d1); + std::cout << td1.name() << '\n'; + B1& b1 = d1; + auto & tb1 = typeid(b1); + std::cout << tb1.name() << '\n'; + } + { + D2 d2; + auto & td2 = typeid(d2); + std::cout << td2.name() << '\n'; + B2& b2 = d2; + auto & tb2 = typeid(b2); + std::cout << tb2.name() << '\n'; + } +} Index: wi-test/test/typeinfo_vtable_ptr/test.py =================================================================== --- /dev/null +++ wi-test/test/typeinfo_vtable_ptr/test.py @@ -0,0 +1,16 @@ +# This testcase tries to exercise typeinfo in the hope of +# exposing a bug due to the uninitialised vtable ptrs in +# the case of the references to e.g. vtables for +# __cxxabiv1::__class_type_info from typeinto objects. +# See _pei386_runtime_relocator which handles the runtime +# component of the autoimporting scheme used for mingw and +# comments in https://reviews.llvm.org/D43184 and +# https://reviews.llvm.org/D89518 for more. + +# RUN: %{clang-wi} main.cpp -Wno-pointer-to-int-cast -frtti -fexceptions -o %t_main.exe 2>&1 +# RUN: %t_main.exe | FileCheck %s + +# CHECK: {{.*}}D1{{.*}} +# CHECK-NEXT: {{.*}}B1{{.*}} +# CHECK-NEXT: {{.*}}D2{{.*}} +# CHECK-NEXT: {{.*}}D2{{.*}} Index: wi.py =================================================================== --- /dev/null +++ wi.py @@ -0,0 +1,568 @@ +#!/usr/bin/env python3 + +# Build a windows-itanium environment: +# We require libunwind, libcxx-abi, libcxx and the MS ucrt. +# +# This script is windows specific; however, a similar process +# could be used to cross-compile from linux. +# +# The process *should be* to first build a window-itanium clang +# and then do a stand-alone compilation of each of the +# libraries using that compiler. However, this doesn't +# work (currently) due to the lack of a suitable driver for +# windows-itanium (in the clang driver code). Instead we build +# an msvc targeting clang and then we invoke that clang but force +# the triple to windows-itanium using the -cc1 layer -triple option. +# +# This script automates a build of the required components using cmake +# and ninja. Build products are left in the source tree for simplicity. The +# result is a "wi" directory containing the toolchain binaries, include +# files and libraries. Due to the lack of a suitable windows-itanium +# driver this script creates a .bat file in the toolchain bin directory +# named "wi.bat" which assists in driving the windows-itanium compiler. +# +# usage: +# 1. Ensure pre-requisites are available as described in "pre-requisites:". +# 2. cd into a git workspace and apply the patch from D88124. +# 3. Manually fix up "locale_win32.h" (see notes in the patch). +# 4. Run "wi.py" to build. +# +# Note: The "--clean-all" option can be supplied to force a rebuild. +# +# pre-requisites: +# This script assumes it's run from within an llvm git checkout (tested +# using: 8a73aa8c4c3ee82e7d650c6a957937713a4a9ab4) with the patch from +# https://reviews.llvm.org/D88124 applied. +# +# Ninja and cmake must be on your path. Tested with: +# - Ninga version v1.10.1, binaries from https://github.com/ninja-build/ninja/releases +# - cmake version 3.18.2, binary zip from https://cmake.org/download/ +# +# Note: We need a version of cmake that is high enough to have: +# https://gitlab.kitware.com/cmake/cmake/-/issues/16439 +# +# The build needs to be able to find the installed windows sdk. I tested +# the script with an sdk installed via VS2017. The following include files +# and library locations were added when running vcvarsall.bat on my system: +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\ATLMFC\\include +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\include +# C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\ucrt +# C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\shared +# C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\um +# C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\winrt +# C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.17763.0\\cppwinrt +# +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\ATLMFC\\lib\\x64 +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\lib\\x64 +# C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\ucrt\\x64 +# C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.17763.0\\um\\x64 +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\ATLMFC\\lib\\x64 +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\lib\\x64 +# C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC\\Tools\\MSVC\\14.16.27023\\lib\\x86\\store\\references +# C:\\Program Files (x86)\\Windows Kits\\10\\UnionMetadata\\10.0.17763.0 +# C:\\Program Files (x86)\\Windows Kits\\10\\References\\10.0.17763.0 +# C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319 +# +# Notes on inter-dependencies: +# libunwind can be built as a dll and is not dependent on other projects. +# +# In libcxx/include/exception the "class _LIBCPP_TYPE_VIS exception_ptr" is declared. This has these three +# functions +# exception_ptr(const exception_ptr&) _NOEXCEPT; +# exception_ptr& operator=(const exception_ptr&) _NOEXCEPT; +# ~exception_ptr() _NOEXCEPT; +# +# This header is included when building stdlib_exception.cpp in libcxxabi. The definitions of the above three +# functions are in libcxx not libcxxabi. Therefore, there is a circular dependency and you get a link error when +# trying to link libcxxabi into a DLL. Maybe this works with other itanium toolchains as they would generate +# ELF output and the missing symbols would become references to be resolved later by the dynamic linker? We +# tackle this by statically linking libcxxabi into libcxx when building the libcxx DLL. +# See: https://reviews.llvm.org/D90021 for a discussion on this way of building libcxx. +# + +import os +import shutil +import stat +import subprocess +import sys +import winreg + +curr_dir = os.getcwd() + +def do_exit(exit_code): + print('Build {}'.format('failed' if exit_code != 0 else 'succeeded')) + exit(exit_code) + +def remove_file(file): + if os.path.exists(file): + os.remove(file) + +def remove_readonly(func, path, _): + "Clear the readonly bit and reattempt the removal" + os.chmod(path, stat.S_IWRITE) + func(path) + +def remove_dir(dir): + if os.path.exists(dir): + shutil.rmtree(dir, onerror = remove_readonly) + +def run_cmd(cmds, bat_file, input=None): + with open('{}'.format(bat_file), 'w') as tf: + for cmd in cmds: + tf.write('{}\n@if %ERRORLEVEL% NEQ 0 exit %ERRORLEVEL%\n\n'.format(cmd)); + tf.write('exit %ERRORLEVEL%\n') + tf.flush() + + proc = subprocess.Popen(['cmd', '/c', tf.name], stdin = subprocess.PIPE) + proc.communicate(input.encode() if input else None) + if proc.returncode != 0: + do_exit(proc.returncode) + +vcvars_cmd_line = None +def get_vcvars_cmd(): + global vcvars_cmd_line + if not vcvars_cmd_line: + try: + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7') as key: + path = winreg.QueryValueEx(key, r'15.0') + vcvars_cmd_line = '@call "' + path[0] + r'VC\Auxiliary\Build\vcvarsall.bat" amd64' + except: + vcvars_cmd_line = r'@echo "Warning: could not find vcvarsall.bat"' + return vcvars_cmd_line + +build_dirs_name = 'build-wi' + +toolchain_build_path = os.path.join(curr_dir, build_dirs_name) + +install_path = os.path.join(curr_dir, 'wi') + +toolchain_bin_path = os.path.join(install_path, 'bin') + +def build_clang_and_lld(): + ''' + Build a windows-msvc (default triple) toolchain + with support for the windows-itanium triple. See comments + on the lack of a suitable windows-itanium driver elsewhere. + ''' + if not os.path.exists(toolchain_build_path): + os.mkdir(toolchain_build_path) + + cd_build_dir = '@cd {}'.format(toolchain_build_path) + + cmake_cmd = ' cmake' + cmake_cmd += ' -GNinja' + cmake_cmd += ' -DCMAKE_CXX_COMPILER=cl' + cmake_cmd += ' -DCMAKE_C_COMPILER=cl' + cmake_cmd += ' -DCMAKE_MAKE_PROGRAM=ninja' + cmake_cmd += ' -DLLVM_TARGETS_TO_BUILD:STRING=X86' + cmake_cmd += ' -DLLVM_OPTIMIZED_TABLEGEN:BOOL=ON' + cmake_cmd += ' -DLLVM_ENABLE_ASSERTIONS=ON' + cmake_cmd += ' -DLLVM_INCLUDE_EXAMPLES=OFF' + cmake_cmd += ' -DLLVM_ENABLE_TIMESTAMPS=OFF' + cmake_cmd += ' -DLLVM_VERSION_SUFFIX=' + cmake_cmd += ' -DLLVM_BUILD_RUNTIME=OFF' + cmake_cmd += ' -DLLVM_APPEND_VC_REV=OFF' + cmake_cmd += ' -DLLVM_ENABLE_PROJECTS="clang;lld"' + cmake_cmd += ' -DCMAKE_BUILD_TYPE=Release' + cmake_cmd += ' -DCLANG_ENABLE_STATIC_ANALYZER=OFF' + cmake_cmd += ' -DCLANG_ENABLE_ARCMT=OFF' + cmake_cmd += ' ../llvm' + cmake_cmd += ' -DCMAKE_INSTALL_PREFIX=\"{}\"'.format(install_path) + + run_cmd([get_vcvars_cmd(), + cd_build_dir, + cmake_cmd, + 'ninja -v', + 'ninja install'], + 'toolchain.bat') + +ar_bin = os.path.join(toolchain_bin_path, 'llvm-ar') +ranlib_bin = os.path.join(toolchain_bin_path, 'llvm-ranlib') + +unwind_build_dir = os.path.join('libunwind', build_dirs_name) + +llvm_dir = os.path.join(curr_dir, 'llvm') + +install_libs_path = os.path.join(install_path, 'lib') +install_bin_path = os.path.join(install_path, 'bin') + +# EXPLAIN: Use -Xclang -triple rather than -target (see other comments about the lack of a suitable windows-itanium driver.) +common_clang_options = ' -Xclang -triple -Xclang x86_64-unknown-windows-itanium' + +# EXPLAIN: My understanding is that we shouldn't need to set any _LIBCPP_* flags when +# building libcxxabi. Unfortunately, I found that if you don't set this then you get +# compiler errors for "missing refstring" because refstring is declared in libcxx/include/stdexcept +# but the declaration is guarded by: #ifndef _LIBCPP_ABI_VCRUNTIME and _LIBCPP_ABI_VCRUNTIME is on. This +# seemed the best way to force it off. It also seems right as it makes sense to set this for windows-itanium. +common_clang_options += ' -D_LIBCPP_ABI_FORCE_ITANIUM' + +# EXPLAIN: We rather crudely specify linker options even when doing compilation. +# suppress warnings about this. +common_clang_options += ' -Qunused-arguments' + +# EXPLAIN: Use -fsjlj-exceptions for now! +# TODO: Get DWARF zero cost exceptions working properly! +common_clang_options += ' -fsjlj-exceptions' + +# EXPLAIN: Lack of a recognised environment and driver for windows itanium +# causes cofiguration problems with #defines being set inappropriately +# in places. Use this define to work around these problems. +common_clang_options += ' -D_WINDOWS_ITANIUM' + +clang_builtins_path = os.path.join(toolchain_build_path, 'lib', 'clang', '12.0.0', 'include') + +def build_unwind(): + if not os.path.exists(unwind_build_dir): + os.mkdir(unwind_build_dir) + + if not os.path.exists(install_path): + os.mkdir(install_path) + + cd_build_dir = '@cd {}'.format(unwind_build_dir) + + run_cmake = 'cmake' + run_cmake += ' --debug-trycompile' + run_cmake += ' -G \"Ninja\"' + run_cmake += ' -DCMAKE_MAKE_PROGRAM=ninja' + run_cmake += ' -DCMAKE_BUILD_TYPE=Release' + run_cmake += ' -DCMAKE_INSTALL_PREFIX=\"{}\"'.format(install_path) + run_cmake += ' -DCMAKE_C_COMPILER=clang' + run_cmake += ' -DCMAKE_CXX_COMPILER=clang' + run_cmake += ' -DCMAKE_CROSSCOMPILING=ON' + run_cmake += ' -DCMAKE_SYSTEM_NAME=Windows' + run_cmake += ' -DCMAKE_C_COMPILER_WORKS=ON' + run_cmake += ' -DCMAKE_CXX_COMPILER_WORKS=ON' + run_cmake += ' -DLLVM_PATH=\"{}\"'.format(llvm_dir) + run_cmake += ' -DLLVM_COMPILER_CHECKED=ON' + run_cmake += ' -DCMAKE_AR=\"{}\"'.format(ar_bin) + run_cmake += ' -DCMAKE_RANLIB=\"{}\"'.format(ranlib_bin) + run_cmake += ' -DCXX_SUPPORTS_CXX11=ON' + run_cmake += ' -DCXX_SUPPORTS_CXX_STD=ON' + run_cmake += ' -DLIBUNWIND_USE_COMPILER_RT=OFF' + run_cmake += ' -DLIBUNWIND_ENABLE_THREADS=OFF' + run_cmake += ' -DLIBUNWIND_ENABLE_SHARED=ON' + run_cmake += ' -DLIBUNWIND_ENABLE_STATIC=OFF' + run_cmake += ' -DLIBUNWIND_ENABLE_CROSS_UNWINDING=OFF' + run_cmake += ' -DLIBUNWIND_INSTALL_LIBRARY=ON' + + # EXPLAIN: I added this flag to the cmake files to prevent cmake adding + # the following libraries: -lc.lib -lgcc_s.lib -lgcc.lib -ldl.lib. I presume + # that building for windows is just not a thing that libunwind really supports? + # Can we upstream something similar? + run_cmake += ' -DLIBUNWIND_NO_LIBRARIES=ON' + + # EXPLAN: Adding these two libraries fixes the following error during linking: + # lld-link: warning: undefined symbol: fprintf + # >>> referenced by src/CMakeFiles/unwind_shared.dir/Unwind-sjlj.c.obj:(_Unwind_SjLj_RaiseException) + # >>> referenced 33 more times + libunwind_c_flags = ' -lmsvcrt -llegacy_stdio_definitions' + + # EXPLAN: We don't want the inline stdio functions as they will casuse multiple + # defintiion errors when the same symbols are pulled in from "legacy_stdio_definitions" + libunwind_c_flags += ' -D_NO_CRT_STDIO_INLINE' + + run_cmake += ' -DCMAKE_CXX_FLAGS=\"-Wno-dll-attribute-on-redeclaration {} {}\"'.format(common_clang_options, libunwind_c_flags) + run_cmake += ' -DCMAKE_C_FLAGS=\"-Wno-dll-attribute-on-redeclaration {} {}\"'.format(common_clang_options, libunwind_c_flags) + + # EXPLAIN: Don't run the linker on compiler check + run_cmake += ' -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY' + run_cmake += ' ..' + + run_cmd([get_vcvars_cmd(), + 'set PATH={};%PATH%'.format(toolchain_bin_path), + cd_build_dir, + run_cmake, + 'ninja -v', + 'ninja install'], + 'libunwind.bat') + + # TODO: Why doesn't "ninja install" put the dll + # into the correct install location? Currently, it + # doesn't so we manually do this here: + if not os.path.exists(install_bin_path): + os.mkdir(install_bin_path) + built_dll = os.path.join(unwind_build_dir, 'lib', 'unwind.dll') + installed_dll = os.path.join(install_bin_path, 'unwind.dll') + shutil.copyfile(built_dll, installed_dll) + +cxx_build_dir = os.path.join('libcxx', build_dirs_name) + +def build_cxx(): + if not os.path.exists(cxx_build_dir): + os.mkdir(cxx_build_dir) + + cd_build_dir = '@cd {}'.format(cxx_build_dir) + + run_cmake = 'cmake' + # EXPLAIN: --debug-trycompile leaves around the files and dirs that cmake + # uses to decide which features are supported. This process can easily go + # awry due to setting incorrect flags here and leaving this stuff around + # allows for investigation. + run_cmake += ' --debug-trycompile' + run_cmake += ' -G \"Ninja\"' + run_cmake += ' -DCMAKE_MAKE_PROGRAM=ninja' + run_cmake += ' -DCMAKE_BUILD_TYPE=Release' + run_cmake += ' -DCMAKE_INSTALL_PREFIX=\"{}\"'.format(install_path) + run_cmake += ' -DCMAKE_C_COMPILER=clang' + run_cmake += ' -DCMAKE_CXX_COMPILER=clang' + run_cmake += ' -DCMAKE_CROSSCOMPILING=ON' + run_cmake += ' -DCMAKE_SYSTEM_NAME=Windows' + run_cmake += ' -DCMAKE_C_COMPILER_WORKS=ON' + run_cmake += ' -DCMAKE_CXX_COMPILER_WORKS=ON' + run_cmake += ' -DLLVM_PATH=\"{}\"'.format(llvm_dir) + run_cmake += ' -DLLVM_COMPILER_CHECKED=ON' + run_cmake += ' -DCMAKE_AR=\"{}\"'.format(ar_bin) + run_cmake += ' -DCMAKE_RANLIB=\"{}\"'.format(ranlib_bin) + run_cmake += ' -DLIBCXX_INSTALL_HEADERS=ON' + run_cmake += ' -DLIBCXX_ENABLE_SHARED=ON' + run_cmake += ' -DLIBCXX_ENABLE_STATIC=OFF' + run_cmake += ' -DLIBCXX_ENABLE_EXCEPTIONS=ON' + + # EXPLAIN: for windows-itanium we want to use the MS supplied runtime. + run_cmake += ' -DLIBCXX_USE_COMPILER_RT=OFF' + + # EXPLAIN: turn threads off for now - hopefully simplifies the build. + # TODO: enable threads + run_cmake += ' -DLIBCXX_ENABLE_THREADS=OFF' + + # EXPLAIN: _LIBCPP_HAS_THREAD_API_WIN32 can't be set if -DLIBCXXABI_ENABLE_THREADS" is "OFF" + # run_cmake += ' -DLIBCXX_HAS_WIN32_THREAD_API=ON' + + run_cmake += ' -DLIBCXX_ENABLE_MONOTONIC_CLOCK=ON' + run_cmake += ' -DLIBCXX_SUPPORTS_STD_EQ_CXX11_FLAG=ON' + run_cmake += ' -DLIBCXX_HAVE_CXX_ATOMICS_WITHOUT_LIB=ON' + run_cmake += ' -DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=OFF' + run_cmake += ' -DLIBCXX_ENABLE_FILESYSTEM=OFF' + run_cmake += ' -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON' + run_cmake += ' -DLIBCXX_CXX_ABI=libcxxabi' + run_cmake += ' -DLIBCXX_CXX_ABI_INCLUDE_PATHS=../../libcxxabi/include' + run_cmake += ' -DLIBCXX_CXX_ABI_LIBRARY_PATH=../../libcxxabi/{}/lib'.format(build_dirs_name) + run_cmake += ' -DLIBCXX_LIBDIR_SUFFIX=""' + run_cmake += ' -DLIBCXX_INCLUDE_TESTS=OFF' + run_cmake += ' -DLIBCXX_ENABLE_ABI_LINKER_SCRIPT=OFF' + + # EXPLAIN: Try to remove any dependency on the VC runtime - we + # need libc++abi to supply the C++ runtime. + run_cmake += ' -DLIBCXX_NO_VCRUNTIME=ON' + + # EXPLAIN: As we are statically linking against libcxxabi we need + # to link against the unwind import library to resolve + # unwind references from the libcxxabi objects. + libcxx_c_flags = ' {}'.format(os.path.join(install_path, 'lib', 'unwind.lib')) + + # EXPLAIN: force this to be undefined to prevent the inclusion of sys/time that MS doesn't provide. + libcxx_c_flags += ' -UCLOCK_REALTIME' + + libcxx_c_flags += ' -fuse-ld=link' + + run_cmake += ' -DCMAKE_CXX_FLAGS=\"{} {}\"'.format(common_clang_options, libcxx_c_flags) + run_cmake += ' -DCMAKE_C_FLAGS=\"{} {}\"'.format(common_clang_options, libcxx_c_flags) + + # EXPLAIN: Don't run the linker on CMAKE compiler check. + run_cmake += ' -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY' + + run_cmake += ' ..' + + run_cmd(['set PATH={};%PATH%'.format(toolchain_bin_path), + cd_build_dir, + run_cmake, + 'ninja -v', + 'ninja install'], + 'libcxx.bat') + +cxxabi_build_dir = os.path.join('libcxxabi', build_dirs_name) + +def build_cxxabi(): + if not os.path.exists(cxxabi_build_dir): + os.mkdir(cxxabi_build_dir) + + cd_build_dir = '@cd {}'.format(cxxabi_build_dir) + + run_cmake = 'cmake' + run_cmake += ' --debug-trycompile' + run_cmake += ' -G \"Ninja\"' + run_cmake += ' -DCMAKE_MAKE_PROGRAM=ninja' + run_cmake += ' -DCMAKE_BUILD_TYPE=Release' + run_cmake += ' -DCMAKE_INSTALL_PREFIX=\"{}\"'.format(install_path) + run_cmake += ' -DCMAKE_C_COMPILER=clang' + run_cmake += ' -DCMAKE_CXX_COMPILER=clang' + run_cmake += ' -DCMAKE_CROSSCOMPILING=ON' + run_cmake += ' -DCMAKE_SYSTEM_NAME=Windows' + run_cmake += ' -DCMAKE_C_COMPILER_WORKS=ON' + run_cmake += ' -DCMAKE_CXX_COMPILER_WORKS=ON' + run_cmake += ' -DLLVM_PATH=\"{}\"'.format(llvm_dir) + run_cmake += ' -DLLVM_COMPILER_CHECKED=ON' + run_cmake += ' -DCMAKE_AR=\"{}\"'.format(ar_bin) + run_cmake += ' -DCMAKE_RANLIB=\"{}\"'.format(ranlib_bin) + run_cmake += ' -DLIBCXXABI_ENABLE_EXCEPTIONS=ON' + + # EXPLAIN: we want to use the MS CRT. + run_cmake += ' -DLIBUNWIND_USE_COMPILER_RT=OFF' + + # EXPLAIN: turn threads off - hopefully simplifies the build. + # TODO: enable threads + run_cmake += ' -DLIBCXXABI_ENABLE_THREADS=OFF' + + run_cmake += ' -DLIBCXXABI_ENABLE_SHARED=OFF' + run_cmake += ' -DLIBCXXABI_ENABLE_STATIC=ON' + + # EXPLAIN: Add visibility annotations as if building a dll. + # This cmake option does not exist upstream. See + # comments in code changes for details. + run_cmake += ' -DLIBCXX_ENABLE_SHARED=ON' + run_cmake += ' -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON' + + run_cmake += ' -DLIBCXXABI_LIBCXX_INCLUDES=../../libcxx/include' + run_cmake += ' -DLIBCXXABI_LIBDIR_SUFFIX=""' + run_cmake += ' -DLIBCXXABI_ENABLE_NEW_DELETE_DEFINITIONS=ON' + run_cmake += ' -DCXX_SUPPORTS_CXX_STD=ON' + + # EXPLAIN: Unless we force this off then cxxabi will contain + # references to threading symbols that will not be defined + # by libc++ e.g: + # lld-link: error: undefined symbol: std::__1::__libcpp_thread_yield() + # >>> referenced by c++abi.lib(cxa_default_handlers.cpp.obj):(std::__1::__libcpp_timed_backoff_policy::operator()(std::__1::chrono::duration >) const) + # >>> referenced by c++abi.lib(cxa_demangle.cpp.obj) + # >>> referenced by c++abi.lib(cxa_exception_storage.cpp.obj) + libcxxabi_c_flags = ' -D_LIBCPP_HAS_NO_THREADS' + + libcxxabi_c_flags += ' -fuse-ld=link' + + # EXPLAIN: Use -Xclang -triple rather than -target to avoid: clang: error: invalid linker name in argument '-fuse-ld=lld-link' + run_cmake += ' -DCMAKE_CXX_FLAGS=\"{} {}\"'.format(libcxxabi_c_flags, common_clang_options) + run_cmake += ' -DCMAKE_C_FLAGS=\"{} {}\"'.format(libcxxabi_c_flags, common_clang_options) + + run_cmake += ' ..' + + run_cmd(['set PATH={};%PATH%'.format(toolchain_bin_path), + cd_build_dir, + run_cmake, + 'ninja -v'], + 'libcxxabi.bat') + +def clang_wi_args(triple): + return [ + os.path.join(toolchain_bin_path, 'clang.exe') + ,'-fuse-ld=lld-link' + ,'-Xclang', '-triple', '-Xclang', triple + ,'-fsjlj-exceptions' + ,'-isystem', os.path.join(install_path, 'include') + ,'-isystem', os.path.join(install_path, 'include', 'c++', 'v1') + ,'-lkernel32' + ,'-luser32' + ,'-lgdi32' + ,'-lwinspool' + ,'-lshell32' + ,'-lole32' + ,'-loleaut32' + ,'-luuid' + ,'-lcomdlg32' + ,'-ladvapi32' + ,'-loldnames' + ,os.path.join(install_path, 'lib', 'c++.lib') + ,os.path.join(install_path, 'lib', 'unwind.lib') + ,'-D_LIBCPP_HAS_NO_THREADS' + ,'-Wl,/nodefaultlib' + ,'-lmsvcrt' + ,'-lucrt' + ,'-lvcruntime' + # EXPLAIN: Use autoimporting to resolve references to + # "vtable for cxxabiv1::class_type_info" from typeinfos. The symptom was linking errors like: + # lld-link: error: undefined symbol: vtable for cxxabiv1::class_type_info + # referenced by plugin-8ae1e2.o:(typeinfo for Foo) + # The dll_class test case will fail without this. + # Have to use mingw here as using -auto-import diagnoses a problem: + # "lld-link: error: automatic dllimport of _ZTVN10__cxxabiv117__class_type_infoE in plugin-f2d5e0.o requires pseudo relocations" + # This allows for linking but code that actually uses such fields will not work + # as they these will not be fixed up at runtime: see _pei386_runtime_relocator which + # handles the runtime component of the autoimporting scheme used for mingw. + # See: https://reviews.llvm.org/D43184 and comments in https://reviews.llvm.org/D89518 for more. + # TODO: Address this limitation. + ,'-Wl,-lldmingw'] + +def write_driver_script(): + script = get_vcvars_cmd() + script += '\nset PATH={};%PATH%\n'.format(os.path.join(install_path, 'bin')) + for a in clang_wi_args('x86_64-unknown-windows-itanium'): + script += ' {} ^\n'.format(a) + script += ' %*' + + with open('{}'.format(os.path.join(install_path, 'bin', 'clang-wi.bat')), 'w') as file: + file.write(script) + +def write_lit_cfg(): + cmdline = '' + for a in clang_wi_args('x86_64-unknown-windows-itanium'): + cmdline += '\"{}\" '.format(a.replace('\\','\\\\')) + + scei_cmdline = '' + for a in clang_wi_args('x86_64-scei-windows-itanium'): + scei_cmdline += '\"{}\" '.format(a.replace('\\','\\\\')) + + with open('{}'.format('wi-test/lit.site.cfg.py'), 'w') as cfg: + cfg.write(r''' +config.substitutions.append(('%{{clang-wi}}',r'{}')) +config.substitutions.append(('%{{clang-wi-scei}}',r'{}')) + +import sys +import lit.llvm +# lit.llvm.initialize(lit_config, config) + +# Let the main config do the real work. +lit_config.load_config(config, "./lit.cfg") +'''.format(cmdline, scei_cmdline)) + +def run_tests(action): + run_cmd([get_vcvars_cmd(), + 'set PATH={};%PATH%'.format(toolchain_bin_path), + 'cd wi-test', + 'python ..\\llvm\\utils\\lit\\lit.py --path ..\\build-wi\\bin\\ test'], + '{}.bat'.format(action)) + +if '--clean-libs' in sys.argv or '--clean-all' in sys.argv: + remove_file('toolchain.bat') + remove_file('libunwind.bat') + remove_file('libcxxabi.bat') + remove_file('libcxx.bat') + remove_file('test.bat') + remove_file('clean.bat') + remove_file(os.path.join(install_libs_path, 'unwind.lib')) + remove_file(os.path.join(install_bin_path, 'unwind.dll')) + remove_file(os.path.join(install_libs_path, 'c++.lib')) + remove_file(os.path.join(install_bin_path, 'c++.dll')) + remove_dir(unwind_build_dir) + remove_dir(cxxabi_build_dir) + remove_dir(cxx_build_dir) + run_tests('clean') + +if '--clean-all' in sys.argv: + remove_dir(toolchain_build_path) + remove_dir(install_path) + +if '--debug' in sys.argv: + # EXPLAIN: produce more debugging output from the linker in the log files. + common_clang_options += ' -Xlinker /verbose' + + # EXPLAIN: Log which include files were pulled in. + common_clang_options += ' -H' + + # EXPLAIN: Save the preprocessed output files next to the object files + # Note: I have seen this option cause comdat errors to be reported. + # It seems that source -> assembly -> obj may report comdat + # errors (when assembling) that are not reported doing source-> obj. + # Example: + # src/CMakeFiles/cxx_shared.dir\locale.s:107958:2: error: section '.xdata$' is already linkonce + # .linkonce discard + # TODO: Investigate these comdat errors. + # Note: Comdat errors only occur for the libc++ build. Building the Uwind + # and c++abi libs doesn't show this problem. + common_clang_options += ' -save-temps=obj' + +build_clang_and_lld() +build_unwind() +build_cxxabi() +build_cxx() +write_driver_script() +write_lit_cfg() +run_tests('test') +do_exit(0)