zig icon indicating copy to clipboard operation
zig copied to clipboard

Proposal: Free Sema from worrying about link inputs

Open alexrp opened this issue 7 months ago • 4 comments

In a nutshell, remove all of this code.

I think it's really bizarre that comptime evaluation of Zig code can have any effect whatsoever on the compilation's link inputs. This should just not be Sema's job IMO. We like explicitness in Zig, so it's better for users to be explicit about their link inputs on the CLI or in build.zig anyway.

cc @andrewrk @mlugg

alexrp avatar May 23 '25 09:05 alexrp

extern "libname" is very useful in wasm, where it's used to refer to different namespaces in the import object

silversquirl avatar May 23 '25 09:05 silversquirl

I agree that extern "libname" should not be able to implicitly add link inputs. I've always found this to be a weird feature of Zig, and entirely agree that this just doesn't make sense to be so implicit or discovered so late in the compiler pipeline. I think the fact that extern "c" needs to be an exception to the implicit-link rule (because linking libc has global effects on the ZCU, so we can't wait until halfway through analysis to discover that we are linking it!) also backs this up.

I'm not sure of the historical intention of this feature, but the only thing I can imagine is that it was considered handy for Zig to be able to link only "needed" inputs based on comptime evaluation. If that is an accurate characterisation of the feature's motivation, I'd consider it a pretty flaky argument. Comptime evaluation is ultimately always deciding what to do based on its CLI inputs (target, build options module, etc), which are usually from the build system. For instance, target and build options (in the std.Build.addOptions sense) are known in the build script; you can just branch on them there to figure out what to link! In fact, you almost certainly are already doing that in many cases. So specifying the link inputs in the build script should realistically be trivially easy for users in almost all cases.

Regarding the existence of extern "libname" syntax in general: from silversquirl's comment above, it sounds like there might be reasonable use cases for it? I could also see an argument that it improves readability to specify the name even if not necessary (provided the compiler actually checks it in unambiguous cases?). I'm not familiar enough with the use cases to have a very useful opinion here, but it sounds like removing the syntax altogether might have more clear negative consequences, so I'd suggest that it should at least be split into a separate proposal.

EDIT

It sounds like Alex and I both misunderstood this language feature slightly! We thought that it lets you implicitly link any system library, but it's actually limited to linking implibs for a mingw-provided list of system DLLs when targeting Windows.

My reaction to receiving this information was approximately as follows: timestamped youtube link

We should definitely try to avoid having language features which are specialized to a single target. This language feature having a different meaning when targeting Windows as it does on any other target seems very confusing to me.

I don't see any reason to treat Windows specially here: it seems fine, and much better even, to require the user to pass -ld3d9 or whatever if they want to link a particular system library. I can see how that could be particularly annoying on Windows, because (when not linking libc) lots of basic functionality requires linking to random system libraries like ntdll. To solve this, I would suggest we do the same thing as we do for e.g. macOS: always implicitly link libraries which are necessary to interact with the system and definitely always present. On macOS, this is just libc (I'm assuming we have other targets with this rule too, my target knowledge is terrible); for Windows, that's ntdll and maybe some other "basic" DLLs (related: #1840). Perhaps Windows is our only current target with this rule for anything other than libc, but I think this seems like a reasonable way to keep the experience of targeting Windows relatively smooth without dual-purposing extern "libname" syntax.

Regardless of what happens, documentation definitely needs to cover the extern "libname" syntax and explain what it actually does; I think it's telling that Alex and I were both wrong about what it does despite knowing a huge amount about Zig!

mlugg avatar May 23 '25 10:05 mlugg

Some notes:

The extern "libname" syntax is nice and concise when writing code specifically for targets/object formats that support them, notably Windows PE (import tables) and Wasm (module imports) but probably some others as well. It would be a tiny bit annoying if the syntax was removed and the functionality relegated to @extern(..., .{ .library_name = "libname" }) since it's a bit noisier to read and write, but it wouldn't be the end of the world. @extern is the only way you can specify linkage and is_dll_import (which I still don't understand exactly what it does), so you could make a case for it being odd that the library name gets a dedicated syntax but not strong/weak linkage.

Regardless of whether the syntax stays or goes, what extern "libname"/@extern(..., .{ .library_name = "libname" }) is supposed to do definitely needs to be documented and codified better in the language spec. I played around with it for a bit on Windows and the current behavior is unintuitive and arguably broken. If I have the libraries a.dll and b.dll available to me and I declare both extern "a" foo() void and extern "b" foo() void in my Zig code, only one of the libraries will be linked, in my case a.dll, and the extern "b" foo will be resolve to the function in a.dll. Furthermore, if I also include a static library/object file/C source file in my build-exe invocation that exports a symbol with the same name, the extern functions might even be resolved to the symbol exported by this input instead of one of the DLLs! I would expect that an extern "libname" symbol can only ever resolve to something that can be added to a runtime import table.

For posterity I'll repeat what I said in the related discussion on Zulip: regarding having the compiler implicitly generate import libraries and adding them as linker inputs whenever Sema analyzes extern "libname", I agree that that is probably bad and that would be better/more in line with Zig's philosophy to require users to explicitly add e.g. -luser32 to their command line invocation if they need anything but the most core Windows libraries. As for which libraries might be considered "core", ntdll.dll, kernel32.dll and kernelbase.dll are always loaded and available to Windows processes regardless of whether or not the executable/DLL explicitly links those libraries (exercise for the reader: verify this behavior yourself by walking std.os.windows.peb().Ldr.InMemoryOrderModuleList in an executable with an empty import table). Given that behavior in combination with the accepted #1840 it seems sensible and fair that those three would be the exceptions that are "magic" and always implicitly linked.

castholm avatar May 23 '25 21:05 castholm

I've retracted the extern "libname" ... portion of the proposal. We can figure out what to do with that separately.

alexrp avatar May 29 '25 14:05 alexrp

We will keep the errors telling you to link libc, libc++, etc. But the logic that adds Windows import libraries as link inputs will be deleted.

alexrp avatar Jun 10 '25 21:06 alexrp

Is requiring every program that uses e.g. std.net.tcpConnectToAddress to special case windows in their build script an intended consequence of this change?

ypsvlq avatar Jul 13 '25 13:07 ypsvlq

That's the current thinking, yes. We'll see how it goes when 0.15.0 is out; if it's too disruptive, one option we might consider is to expand the list of linked-by-default DLLs to include all DLLs that the standard library's abstractions might depend on.

alexrp avatar Jul 13 '25 19:07 alexrp

expand the list of linked-by-default DLLs

I would find it very surprising to be linking extra dlls that my program isn't using. There is a cost to linking unneeded libraries (I don't know what that cost is, but it is nonzero). I know people will complain about losing functionality that used to be there, but as long as the error is easily actionable (tells you what lib is missing, and how to add it), I think it's acceptable to avoid the unnecessary cost.

Another option is to go ahead and add implicit libraries for anything in std (if std is used), but provide a means to remove one or all implicit libs from the linking process.

Paul-Dempsey avatar Aug 03 '25 14:08 Paul-Dempsey

#24707

alexrp avatar Aug 05 '25 20:08 alexrp