SwiftPM Windows: Link issues during build

I'm in the process of trying to get the package manager to build natively on Windows, and have run into linking issues in the bootstrap stage:

--- bootstrap: note: building stage1
Linking S:\b\spm\.build\.bootstrap\bin\swift-run
Linking S:\b\spm\.build\.bootstrap\bin\swift-build
Linking S:\b\spm\.build\.bootstrap\bin\swift-test
Linking S:\b\spm\.build\.bootstrap\bin\swift-package
 "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview\\VC\\Tools\\MSVC\\14.22.27706\\bin\\HostX64\\x64\\link.exe" "-out:S:\\b\\spm\\.build\\.bootstrap\\bin\\swift-run" "-libpath:S:\\b\\swift\\lib\\swift\\windows/x86_64" "-libpath:S:\\b\\foundation" "-libpath:S:\\b\\libdispatch" "-libpath:S:\\b\\libdispatch\\src" "-libpath:S:\\b\\sqlite" "-libpath:S:\\b\\llbuild\\lib" -nologo "S:\\b\\swift\\lib\\swift\\windows\\x86_64\\swiftrt.obj" "S:\\b\\spm\\.build\\.bootstrap\\swift-run.build\\main.o" "S:\\b\\spm\\.build\\.bootstrap\\lib\\Commands.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\Workspace.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\Xcodeproj.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\Build.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\PackageGraph.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\SourceControl.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\PackageLoading.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\SPMLLBuild.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\PackageModel.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\SPMUtility.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\Basic.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\SPMLibc.lib" "S:\\b\\spm\\.build\\.bootstrap\\lib\\clibc.lib" BlocksRuntime.lib
   Creating library S:\b\spm\.build\.bootstrap\bin\swift-run.lib and object S:\b\spm\.build\.bootstrap\bin\swift-run.exp
main.o : error LNK2019: unresolved external symbol __imp_$s8Commands12SwiftRunToolCMa referenced in function main
main.o : error LNK2019: unresolved external symbol __imp_$s8Commands12SwiftRunToolC4argsACSaySSG_tcfC referenced in function main

Looking at the Commands.lib file that should contain the relevant symbols, I see that they appear both as external imports and as definitions:

3CC 00000AD0 SECT1  notype ()    External     | $s8Commands12SwiftRunToolCMa
3CD 00001228 SECT8F notype       External     | $s8Commands12SwiftRunToolCAA0D4NameAAWP

40A 00000000 UNDEF  notype       External     | __imp_$s8Commands12SwiftRunToolCMa
40B 00000000 UNDEF  notype       External     | __imp_$s8Commands12SwiftRunToolCAA0D4NameAAWP

It seems like the object files are getting built with DLLImport storage for other files within their own module; I don't know if that's how it should be or not.

I've merged in all the pending Windows-related PRs for SwiftPM and llbuild on GitHub.

@compnerd, any idea what's going wrong here?

(By the way, the build process for Swift on Windows has gotten so much smoother since last I tried - great work!)

On a related topic, is there currently any supported way to specify that a module should be linked statically, or is everything from an external module assumed to use DLL linkage?

The DLL storage issue is known (and I think I have an idea on how to solve it) - it is being tracked as SR-9138. Are you sure that swift-run is linking against Commands.lib? No, currently static linking doesn't work on Windows. I think that after SR-9138 is resolved, it should be easy to extend that to support it.

Glad to hear that the build process is getting better!

swift-run is definitely linking against Commands.lib – or at the very least, Commands.lib is on the invocation to link.exe. I suspect SR-9138 may be the culprit, since Commands.lib appears to have unresolved symbols to itself, unless I'm interpreting it wrong.

As one part of managing the DLL storage: for Swift modules, would it make sense to have e.g. a -static-library flag that's stored in e.g. the .swiftmodule (if there isn't one already)? Given that, we could determine whether to use DLLImportStorageClass or the default storage class for the declaration based on the source module's definition and whether the declaration comes from the current module.

If that sounds a reasonable approach, I can try implementing it.

Yes, the extension would require -static be passed during compilation (alternatively, I think that @jrose preferred the -emit-archive spelling). This was something which @Dave_Lee said he was interested in implementing, but, I don' think that he has gotten around to it.

Okay, I'm working on a patch that at least deals with the DLL storage parts of static linking.

In terms of SR-9138, I think SILFunction::isDefinition may be all we need – it already gets passed into getIRLinkage, so we can just disable DLLImport storage if isDefinition is true.

Draft PR is up, although it still needs both actual usage testing and tests: Add '-emit-archive' option to mark modules as being part of static libraries by troughton · Pull Request #25088 · apple/swift · GitHub. It builds on macOS; I'll try it on Windows tomorrow.

The -emit-archive option should probably do a lot more than just change the linkage settings, but I'm not sure what actually needs to change on platforms other than Windows, beyond just a .a extension rather than .dylib/.so.

When generating a static library, the linker is not invoked. Rather than invoking clang++ as the linker driver, ar needs to be invoked. Additionally, we should assume a modern ar, preferably llvm-ar. This can generate the index at the same time as building the archive and should therefore be invoked as:

ar crs library.lib objecs…

This would require a change to the driver to add support for the new job type and the action.

Is there any reason not to invoke clang++ though? All the reasoning about not needing to deal with platform linkers still stands.

@jrose - because the clang driver does not know how to generate static libraries, using that to generate static libraries doesn't really work.

I…did not realize that was a thing still. Okay.

There's a potential complication here: there are times in which files from different modules will be linked together statically (so they should be treated as coming from static modules), but we don't actually want a static archive for each module. SwiftPM builds packages this way – it generates a list of output object files that other modules link against.

I can think of a couple of possible solutions: we could add another swiftc flag like -comprises-static-archive that does the same thing as -emit-archive except skips producing the .a/.lib, or we could modify SwiftPM so it builds static libraries for each module.

Hrm, I guess that'd be a potential reason to prefer -emit-library -static, because then we get -c -static "automatically".

@jrose - yeah, compilers are difficult.

On Linux, invoking the driver with -static will generate an ELF file (relocated) that will be useless as a static library (assuming that you do -nostdlib -nodefaultlib -nostartfiles which will basically avoid the C runtime support that is needed).

The same really occurs on Windows.

In fact, even on Darwin, ld64 will create a statically linked library that will not be a static library (that is, it does not depend on the loader).

This is why all the build systems handle "linking" static libraries specially by invoking the archiver. The only difference is that Darwin and the BSDs use a BSD style archive, while Linux uses GNU (which is close enough to the AT&T Unix archiver so as to be portable to Windows).

What happens with linker flags when building a static library? Are they all dropped, or passed along to the archiver somehow?

I might try to stage this in two parts - getting -static in with the DLL linkage fixed first, and then dealing with -emit-library -static separately, since for the first I have a pretty good idea of what to do but for the second I’m fairly lost.

I also might not be the best person to implement -emit-library -static – I don’t think I’ve ever built a static library directly on the command line, and although I’m willing to give it a try with detailed instructions it may be easier for someone more knowledgeable to just implement it themself.

Referring to the original topic: my approach doesn't quite work since the module getting passed to getIRLinkage appears to be the current module rather than the source module.

For people knowledgeable about IRGen (cc @John_McCall):
Given a LinkEntity for a symbol such as $sSSs25LosslessStringConvertiblesWP, is there any way to map that symbol back to the source Swift ModuleDecl from within IRGen, with the purpose being to check if the module was compiled with the -static flag? ForDefinition_t tells us whether the declaration came from the current module, but not what the source module was, while entity.getDecl()->getModuleContext() works for DeclKind LinkEntitys but not TypeKind ones.

I would very much like SIL declarations to carry their defining module (for shared modules, this would be the current module, i.e. it might change after deserialization/linking). I think that, if you had that, you would be able to derive a defining module from an arbitrary LinkEntity; if that module is a native Swift module (as opposed to e.g. an imported C module), I don't see why it shouldn't carry information like whether it was built with -static. But we'd need that first step, and we don't have it right now.

1 Like

Hmm. In the short term, can you think of a way we could detect whether a LinkEntity should be given DLLImport or default linkage? For example, would introducing a new SILLinkage:PublicDLLExport case be a possibility, and would that actually carry through to the LinkEntity? SILLinkage appears to be inferred in a few cases rather than deserialised, but if those cases are for C declarations and C declarations already carry e.g. __declspec(dllimport) that may not be an issue.

Otherwise, @compnerd, what would be your thoughts on exclusively supporting static linking on Windows (besides perhaps hardcoding in DLLImport support for the stdlib, Dispatch, and Foundation)?

@Torust, what @John_McCall suggested is what I had in mind for the changes for the IRGen.

I would be strongly against that. The linker can deal with the fact that the function given import DLL storage is actually local and adjust the referenced symbol. However, it is not possible to use a mix of static and dynamically linked modules. You end up with multiple definitions of the standard library and parts of the C and C++ runtimes which is going to break in odd manners (e.g. cross-domain free). Everything can switch over to dynamic linking for the time being IMO. Darwin is also trending towards this after ABI stability. I think it is better to restructure the swift-package-manager than to change to static linking.

2 Likes

To make sure I understand correctly, you’re saying a situation where:

  • The C stdlib is dynamically linked
  • The swift stdlib is dynamically linked
  • Foundation is dynamically linked
  • Application non-executable modules are built as archives and statically linked to the final executable

would not work? Because that was what I meant to suggest, although I can see how that may not have been clear. If that wouldn’t work I have some rearchitecting to do.

I would be against changing SwiftPM – I don’t want to bundle ~20 DLLs with my application.