The MSVC linker /incremental:yes feature breaks the Swift runtime on Windows

I've written up a bug report about this here.

The problem is that the MSVC linker adds extra padding bytes after a data entry if incremental linking is turned on. This then throws off the Swift runtime when it tries to find out where the Swift metadata starts and where it ends because it isn't aware that the linker has inserted those extra bytes behind its back. The end result is that Swift code which tries to do anything that requires access to the metadata will crash because the padding bytes are misinterpreted as metadata.

Now a simple fix to this would be to ensure that:

a) all Swift runtime related libraries are linked with /incremental:no

b) all Swift executables and libraries that the Swift compiler generates are linked with /incremental:no

and I guess that doing this would be a good start in any case. However I'm not sure that this is really a good longterm fix and I'd prefer a fix that would allow the Swift runtime to deal with metadata sections that were generated with /incremental:yes since this option is automatically enabled if you generate a debug build. But I'm also not really sure on how to fix this properly and thus it would be helpful to hear the opinion of others before I start implementing something.

After reading through the Windows related runtime code, this is how the runtime finds its metadata stuff on Windows:

a) The compiler generates the metadata section just like on macOS / Linux but with shorter names and an extra suffix. E.g. .sw5rfst$B. The section is 16 bytes aligned.

b) The swiftest.obj file defines two extra sections per metadata section: .sw5rfst$A and .sw5rfst$C. Those sections are 8 byte aligned and they just contain a single 64bit variable each.

c) The linker merges the A, B and C sections in this order into a single .sw5rfst section with 16 byte alignment.

Now what happens when you link with /incremental:yes is that the linker adds an extra 256 0 bytes after the contents of .sw5rfst$A and .sw5rfst$C when it copies those guys into the final .sw5rfst section. It also adds 0 bytes after the contents of .sw5rfst$B but it sometimes adds way more than 256 0 bytes.

I can see two possible options to deal with this:

a) try to skip the padding bytes. This would work well for the padding bytes between .sw5rtst$A and .sw5rfst$B because the padding here is always 256 0 bytes followed alignment bytes to get to the next mod(16) == 0 address. But the distance between .sw5rfst$C and the end of the real metadata in .sw5rfst$B can be much more than 256 0 bytes. Now you could attempt to scan backwards and skip 0 bytes until you hit a non-zero, but this approach is very brittle because at least some sections may contain legitimately zeros as part of their metadata.

b) change the code that generates the metadata section in the compiler to spit out a marker at the beginning and the end of the metadata section. The linker won't add padding between those markers and the real metadata because the whole metadata blob is one data element to the linker. The runtime would then scan for those markers to find out what the real start and end of a metadata section is.

I'd prefer (b) over (a) because it would allow the runtime to find the start and end locations of its metadata without having to make assumptions about how many padding bytes the linker has inserted.

Opinions / better ideas?

Is there any annotation we can put on the symbols themselves to tell the Microsoft linker that their size is not expected to change, so they do not need padding to avoid relinking incrementally?

Good question to which I sadly don't know the answer. I've Googled about this topic yesterday for quite a while but wasn't able to find anything about controlling the incremental mode padding on a per-section basis.

I'm surprised that the MSVC linker even messes with those custom sections at all since it doesn't know what their purpose is.

@compnerd Any thoughts here?

This is how the incremental linking works unfortunately :(. We need to ensure that the other sections that are being added are marked with the same size (16-byte) as an entry. We should have the runtime skip over any 0-filled entries. That would make the runtime resilient to the incremental linking.

I'll give this a shot on Monday. My concern though is that the linker appears to add significantly more than 256 padding bytes between the $B and $C section and that the amount of padding it adds may not be a multiple of 256 which would make a skipping approach to find out where the end of the section data is potentially unreliable.

Anyway. Hopefully changing things such that the $A, $B and $C section all have the same alignment will cause the linker to insert a consistent amount of padding at the end of each section.

If all entries are of the same size, then, the padding will be a multiple of that size, and if you are skipping entries which are zeroed then, you should be fine. I had attempted to disable incremental linking since I have run into this in the past.

Finally got around to fixing this. PR is here: Implemented runtime support for MSVC incremental linking.

1 Like