Skip to content

Windows: Support building stage3, and bootstrapping via MSVC #13514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 6, 2023

Conversation

kcbanner
Copy link
Contributor

@kcbanner kcbanner commented Nov 11, 2022

These are changes to support bootstrapping zig on Windows using MSVC. This closes #12703

The corresponding bootstrap script is here: ziglang/zig-bootstrap#140

Summary of changes:

  • Place the result of llvm-config --system-libs into it's own list, so it can be handled separately. On windows, the list of libraries is returned like kernel32.lib uuid.lib instead of -lkernel32 -luuid. This would cause addCMakeLibraryList to link them as object files, which would produce incorrect paths. This list now makes it's way to addCMakeSystemLibraryList which handles this case correctly.
  • Do a similar approach for clang's system libraries (which is just version.lib)
  • Place the zig1/zig2 binaries into the build dir, and not a Release/Debug subdirectory, so they can be found by subsequent steps
  • Add ZIG_ENABLE_ZSTD: This controls whether zstd is linked in build.zig. This is done via a new argument, -Ddisable-zstd. This is to support building zig when it links against and MSVC-built LLVM built with LLVM_ENABLE_ZSTD=OFF.
  • Fixup zlib path (used during bootstrapping)
  • Don't try to link libcxx when being built for msvc - this was bringing in libcxxabi which does not build under msvc due to several conflicts (and I'm not sure this is even supported) see: Unable to build stage3 on Windows: FileNotFound followed by Unable to dump stack trace: InvalidPEMagic #12703 (comment)
  • If a .def file is present in the zig cc/c++ argument list, pass it through to lld-link as -DEF:<filename>. This is to handle this case: https://reviews.llvm.org/D134165. Specifically, building libLTO.dll during bootstrapping.

I'm opening this as a draft, as I'm not not sure about one of the changes in lld.zig, specifically:

            if (target.abi == .msvc) {
                argv.appendAssumeCapacity(lib_basename);
                continue;
            }

This change was to support linking system libraries without needing to build the .lib for them (during the first zig build with msvc) however I'm not sure if this should take precedence over the if (comp.crt_files.get(lib_basename)) |crt_file| { branch.

build.zig Outdated
@@ -292,10 +293,12 @@ pub fn build(b: *Builder) !void {
artifact.addCSourceFiles(&optimized_c_sources, &[_][]const u8{ "-std=c99", "-O3" });

artifact.linkLibrary(softfloat);
artifact.linkLibCpp();
if (artifact.target.getOs().tag != .windows or artifact.target.getAbi() != .msvc) {
artifact.linkLibCpp();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we want to link against libc++ when targeting .msvc ABI? Is this circumventing Zig wanting to use its own-built libc++.a when linkLibCpp is specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was to handle this problem: #12703 (comment)

I tried just not building/linking libcxxabi at first, which gets you as far as linking (diffs I tried in the comment there).

The problem is that libc++ once built/linked under the msvc-built zig, conflicts with libcmt.lib (which comes from the msvc-built llvm that links with it):

lld-link : error : duplicate symbol: void * __cdecl operator new(unsigned __int64) [C:\cygwin64\home\kcbanner\kit\zig\build-release\stage3.vcxproj]
  >>> defined at d:\a01\_work\12\s\src\vctools\crt\vcstartup\src\heap\new_scalar.cpp:32
  >>>            libcmt.lib(new_scalar.obj)
  >>> defined at C:\cygwin64\home\kcbanner\kit\zig\lib\libcxx\src/new.cpp
  >>>            libc++.lib(new.obj)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think I was too hasty here. This might only be necessary during bootstrap - when we're linking against an msvc-built llvm (and not a zig-built one).

I'm going to attempt bootstrapping with this check in place, then removing it when building self-hosted. It might be that the solution is a build arg like -Duse-native-libcxx to gate this behavior might be the real solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this flow to use a new -Ddisable-libcpp flag passed in by the bootstrapping script when building on a *-windows-msvc host.

@@ -639,10 +644,15 @@ fn addStaticLlvmOptionsToExe(exe: *std.build.LibExeObjStep) !void {
}

exe.linkSystemLibrary("z");
exe.linkSystemLibrary("zstd");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ls zstd not supported when targeting windows-msvc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked this again, and I think removing this conditionally was actually unnecessary. The key part of the zstd change was adding the ZIG_ENABLE_ZSTD flag to the cmake script - since the initial LLVM built during bootstrapping on the host is build without ZSTD enabled (since we haven't built it for the host yet), we don't want to require it during the build:

if(ZIG_STATIC_ZSTD AND ZIG_ENABLE_ZSTD)
    list(REMOVE_ITEM LLVM_SYSTEM_LIBRARIES "-lzstd")
    find_library(ZSTD NAMES libzstd.a libzstdstatic.a zstd NAMES_PER_DIR)
    list(APPEND LLVM_LIBRARIES "${ZSTD}")
endif()

Will remove -Ddisable-zstd and the change to this function.

CMakeLists.txt Outdated
if (MSVC)
list(REMOVE_ITEM LLVM_SYSTEM_LIBRARIES "z.lib")
else()
list(REMOVE_ITEM LLVM_SYSTEM_LIBRARIES "-lz")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between LLVM_LIBRARIES and LLVM_SYSTEM_LIBRARIES? Will this change not potentially break other targets linking against system version of LLVM such as macOS?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to link statically against a system provided LLVM e.g., Homebrew provided LLVM on macOS, in which case, I am wondering if this change doesn't break it as now we are removing -lz from LLVM_SYSTEM_LIBRARIES rather than LLVM_LIBRARIES, assuming they are not the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added LLVM_SYSTEM_LIBRARIES to hold the result of llvm-config.exe --system-libs, instead of adding it to LLVM_LIBRARIES. The reason for breaking it out into it's own variable was to handle this difference:

e:\dev\zig-bootstrap\out-win\host\bin>llvm-config.exe --system-libs
psapi.lib shell32.lib ole32.lib uuid.lib advapi32.lib z.lib
$ ./llvm-config.exe  --system-libs
-lpsapi -lshell32 -lole32 -luuid -ladvapi32 -lz

The problem was that addCMakeLibraryList requires a -l prefix in order to pass the library to exe.linkSystemLibrary instead of exe.addObjectFile. There is no -l prefix in the msvc --system-libs case. What was happening was ie. uuid.lib would be passed to exe.addObjectFile which would generate a path like c:\path\to\zig\uuid.lib when linking, which would fail.

For comparison the --libfiles result on msvc vs msys2/mingw:

e:\dev\zig-bootstrap\out-win\host\bin>llvm-config.exe --libfiles
e:\dev\zig-bootstrap\out-win\host\lib\LLVMWindowsManifest.lib e:\dev\zig-bootstrap\out-win\host\lib\LLVMWindowsDriver.lib ... snip
$ ./llvm-config.exe  --libfiles
E:/dev/zig-bootstrap/out/build-llvm-host/lib/libLLVMWindowsManifest.a E:/dev/zig-bootstrap/out/build-llvm-host/lib/libLLVMWindowsDriver.a ... snip

In this case, you do want to pass the full lib path, so I couldn't just check for a .lib extension to make the distinction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your second question, I believe -lz should be in LLVM_SYSTEM_LIBARIES in the ZIG_STATIC_LLVM case on macOS, but I don't have access to a macOS system to check if -lz should up in the llvm-config --libfiles result or the llvm-config --system-libs result.

@kcbanner
Copy link
Contributor Author

Updated:

  • Remove -Ddisable-zstd, no longer needed
  • Add -Ddisable-libcpp for use when bootstrapping on an msvc host
  • Fix -ENTRY being passed twice if it was specified explicitly and -nostdlib was present.
  • Add support for /pdb, /version, /implib, and /subsystem as linker args (passed by CMake): https://github.com/Kitware/CMake/blob/master/Modules/Platform/Windows-Clang.cmake#L77
  • If nostdlib is passed, also set ensure_libcpp_on_non_freestanding = false. Previously, using zig c++ could result in a scenario where libc wasn't linked but libcpp was.

I also investigated the ability to build an *-windows-msvc target, and as part of that required adding support for the way that CMake calls us in this case. Specifically, the Windows-Clang.cmake platform code uses some of the link.exe style arguments. However, it doesn't seem possible to fully bootstrap this target yet due to the following issue:

The cmake platform code for Windows-Clang (which is selected when it detects your using Clang with an MSVC target) passes -Xclang --dependent-lib=libcmt to the compile step when it builds all it's test programs to check for compiler options. This causes clang to put directives in the .o file to tell the linker what libs to link. But, it then passes -nostartfiles -nostdlib to the linker ling (which is zig cc here). With the current mapping in zig of -nostdlib resulting in -NODEFAULTLIB being sent to lld-link, this means these directives get ignored. This means that for all the cmake compile programs that use say, main as an entry point, will fail to compile. I tried circumvented this by passing /ENTRY:main to lld-link instead if this is detected, but then you run into cases like llvm's cmake build checking for the compress2 symbol from zlib. It does this by compiling and linking a test program links z.lib, which requires malloc/free from libcmt.lib. Since -NODEFAULTLIB is passed, the directives in the test program's .o aren't followed and these test programs all fail with linker errors.

So, building -msvc isn't possible yet - but it sounds like that once we stop using lld-link, that we could support this case with our own COFF linker. With that being said, some of the changes I made during this investigation might be useful that point, so I included them.

@kcbanner kcbanner force-pushed the windows_build_fixes branch from 9c2a548 to 9194831 Compare November 14, 2022 19:41
@kcbanner kcbanner marked this pull request as ready for review November 14, 2022 19:45
@kcbanner kcbanner force-pushed the windows_build_fixes branch from 9194831 to ce58f23 Compare November 25, 2022 00:45
@kcbanner kcbanner force-pushed the windows_build_fixes branch from ce58f23 to c43d345 Compare December 2, 2022 23:27
@kubkon
Copy link
Member

kubkon commented Dec 9, 2022

Would you mind rebasing and fixing conflicts?

@kcbanner kcbanner force-pushed the windows_build_fixes branch from c43d345 to b1482f8 Compare December 9, 2022 18:47
@kcbanner
Copy link
Contributor Author

kcbanner commented Dec 9, 2022

Will do, however I'm going to convert this back to a draft until I resolve the CBE issues with MSVC. This was ready to go until those changes landed.

@kcbanner kcbanner marked this pull request as draft December 9, 2022 18:48
@kcbanner kcbanner force-pushed the windows_build_fixes branch 3 times, most recently from 903dd83 to fa89cc6 Compare December 19, 2022 07:06
@kcbanner kcbanner force-pushed the windows_build_fixes branch 2 times, most recently from 83d95c6 to b70d439 Compare December 28, 2022 07:32
@kcbanner kcbanner marked this pull request as ready for review December 30, 2022 07:28
@kcbanner kcbanner force-pushed the windows_build_fixes branch from b70d439 to b803f32 Compare January 1, 2023 23:00
Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Place the result of llvm-config --system-libs into it's own list, so it can be handled separately. On windows, the list of libraries is returned like kernel32.lib uuid.lib instead of -lkernel32 -luuid. This would cause addCMakeLibraryList to link them as object files, which would produce incorrect paths. This list now makes it's way to addCMakeSystemLibraryList which handles this case correctly.

Wouldn't the same possibilities also affect the main llvm/clang/lld libraries? For example, LLVMCore.lib rather than -lLLVMCore.

It seems like rather than changing all this stuff, the logic inside parseConfigH and/or addCMakeLibraryList could be improved to handle both -lfoo and foo.lib.

Actually, I'm not sure what the problem is with exe.addObjectFile("foo.lib"). I don't see this new addCMakeSystemLibraryList function doing anything new.

CMakeLists.txt Outdated
Comment on lines 94 to 95
set(ZIG_ENABLE_ZSTD on CACHE BOOL "Enable linking zstd")
set(ZIG_ENABLE_LIBCPP on CACHE BOOL "Enable linking libcpp")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to avoid adding these if possible. It looks like ZIG_ENABLE_LIBCPP is unused and can simply be deleted.

As for ZIG_ENABLE_ZSTD, it will confuse package maintainers and users into believing that Zig depends on zstd, which is not the case. In reality, it is a transitive dependency that we only have to care about when LLVM is built statically, and is built with zstd enabled. In such case, the user must pass -DZIG_STATIC_ZSTD=ON. In all other cases, the user can leave the defaults as-is.

I don't see the purpose of an additional option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of ZIG_ENABLE_ZSTD was to get around ZIG_STATIC enabling ZIG_STATIC_ZSTD, which would cause a find_library search, which would then fail in cases where it didn't exist (like building zig for the host during bootstrapping).

I changed the way that the defaults were set to allow ZIG_STATIC_ZSTD to be set explicitly off when ZIG_STATIC is enabled. The build.bat script will set this to OFF explicitly when building for the host.

@kcbanner kcbanner force-pushed the windows_build_fixes branch from b803f32 to 4939d23 Compare January 3, 2023 07:52
@kcbanner
Copy link
Contributor Author

kcbanner commented Jan 3, 2023

Wouldn't the same possibilities also affect the main llvm/clang/lld libraries? For example, LLVMCore.lib rather than -lLLVMCore.

It seems like rather than changing all this stuff, the logic inside parseConfigH and/or addCMakeLibraryList could be improved to handle both -lfoo and foo.lib.

Actually, I'm not sure what the problem is with exe.addObjectFile("foo.lib"). I don't see this new addCMakeSystemLibraryList function doing anything new.

The problem was that libraries such as ntdll.lib (as returned by llvm-config --system-libs) being passed to exe.addObjectFile would result in the linker being passed the incorrect absolute path c:\cygwin64\home\kcbanner\kit\zig\ntdll.lib, for example. The --libs result from llvm-config already provided absolute paths to the .lib, so this wasn't an issue there.

I could instead have addCMakeLibraryList assume a lib name like ntdll.lib with no absolute path prefix is a system lib and call linkSystemLibrary instead. Then I could remove the *_SYSTEM_LIBRARIES lists that I added and use a single list for everything.

@kcbanner kcbanner force-pushed the windows_build_fixes branch 2 times, most recently from 945fb64 to e4c4731 Compare January 4, 2023 15:50
Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really excellent work. The only problem I see is a couple missing cache hash lines (potentially would cause a subsequent compilation that added these new linker args to think it did not need to re-run the linker).

With those added, this is ready to land.

- add support for passing through .def files to the linker,
  required for building libLTO.dll in LLVM
- fixup libcpp linking conditionals
- add option to skip linking zstd for use in bootstrapping (when
  building against an LLVM with LLVM_ENABLE_ZSTD=OFF)
Using zig cc with CMake on Windows was failing during compiler
detection. -nostdinc was causing the crt not to be linked, and Coff/lld.zig
assumed that wWinMainCRTStartup would be present in this case.

-nostdlib did not prevent the default behaviour of linking libc++ when
zig c++ was used. This caused libc++ to be built when CMake ran
ABI detection using zig c++, which fails as libcxxabi cannot compile
under MSVC.

- Change the behaviour of COFF -nostdinc to set /entry to the function that the
default CRT method for the specified subsystem would have called.
- Fix -ENTRY being passed twice if it was specified explicitly and -nostdlib was present.
- Add support for /pdb, /version, /implib, and /subsystem as linker args (passed by CMake)
- Remove -Ddisable-zstd, no longer needed
- Add -Ddisable-libcpp for use when bootstrapping on msvc
- Remove ZIG_ENABLE_ZSTD in favour of allowing ZIG_STATIC_ZSTD to be toggled off explicitly when ZIG_STATIC is on
- Remove ZIG_ENABLE_LIBCPP (now unused)
- Revert the addition of CLANG_SYSTEM_LIBARIES and LLVM_SYSTEM_LIBRARIES
- Change addCMakeLibraryList to parse non-absolute path .lib dependencies as system libraries
@kcbanner kcbanner force-pushed the windows_build_fixes branch from e4c4731 to 7fe6247 Compare January 5, 2023 02:49
@kcbanner
Copy link
Contributor Author

kcbanner commented Jan 5, 2023

Thanks! Hash inputs have been added.

@andrewrk andrewrk merged commit c28c38d into ziglang:master Jan 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to build stage3 on Windows: FileNotFound followed by Unable to dump stack trace: InvalidPEMagic
3 participants