This is an archive of the discontinued LLVM Phabricator instance.

[MinGW] [X86] Add stubs for references to data variables that might end up imported from a dll
ClosedPublic

Authored by mstorsjo on Aug 27 2018, 1:56 AM.

Details

Summary

Variables declared with the dllimport attribute are accessed via a stub variable named __imp_<var>. In MinGW configurations, variables that aren't declared with a dllimport attribute might still end up imported from another DLL with runtime pseudo relocs.

For x86_64, this avoids the risk that the target is out of range for a 32 bit PC relative reference, in case the target DLL is loaded further than 4 GB from the reference. It also avoids having to make the text section writable at runtime when doing the runtime fixups, which makes it worthwhile to do for i386 as well.

Add stub variables for all dso local data references where a definition of the variable isn't visible within the module, since the DLL data autoimporting might make them imported even though they are marked as dso local within LLVM.

Don't do this for variables that actually are defined within the same module, since we then know for sure that it actually is dso local.

Don't do this for references to functions, since there's no need for runtime pseudo relocations for autoimporting them; if a function from a different DLL is called without the appropriate dllimport attribute, the call just gets routed via a thunk instead.

GCC does something similar since 4.9 (when compiling with -mcmodel=medium or large; from that version, medium is the default code model for x86_64 mingw), but only for x86_64.

This patch both adds the generic support for COFF pointer stubs, and implements it for X86. Later patches implements the same for ARM and AArch64.

Diff Detail

Repository
rL LLVM

Event Timeline

mstorsjo created this revision.Aug 27 2018, 1:56 AM
rnk added a comment.Aug 27 2018, 4:33 PM

I think you need to change clang lib/CodeGen/CodeGenModule.cpp shouldAssumeDSOLocal to stop marking these things as dso_local, and then shouldAssumeDSOLocal will return false, and you can apply your MO_COFFSTUB flag in classifyGlobalReference. Hopefully then everything will make a little more sense.

lib/CodeGen/AsmPrinter/AsmPrinter.cpp
1388–1389 ↗(On Diff #162637)

I was looking for some way to unify this across architectures, but I don't see one. It looks like the MachO assembler implicitly creates stubs for you, similar to how you can use foo@GOT(%rip) and that will make a GOT stub for you on ELF.

test/CodeGen/X86/win32-ssp.ll
12 ↗(On Diff #162637)

Does mingw provide __stack_chk_guard? Should we use .refptr to access it? I think we should change TargetLoweringBase::insertSSPDeclarations to mark this global dso_local first, so that we don't introduce these extra stubs, if they are not needed.

In D51288#1215140, @rnk wrote:

I think you need to change clang lib/CodeGen/CodeGenModule.cpp shouldAssumeDSOLocal to stop marking these things as dso_local, and then shouldAssumeDSOLocal will return false, and you can apply your MO_COFFSTUB flag in classifyGlobalReference. Hopefully then everything will make a little more sense.

Right, I'll give that a try. That sounds like it would make a little more sense.

test/CodeGen/X86/win32-ssp.ll
12 ↗(On Diff #162637)

Yes, mingw has got __stack_chk_guard (as part of libssp.a or libssp.dll.a), and it can be linked either statically or dynamically (which we don't know until we see which kind of library the linker finds). So yes, it's intentional that we're using .refptr here.

In D51288#1215140, @rnk wrote:

I think you need to change clang lib/CodeGen/CodeGenModule.cpp shouldAssumeDSOLocal to stop marking these things as dso_local, and then shouldAssumeDSOLocal will return false, and you can apply your MO_COFFSTUB flag in classifyGlobalReference. Hopefully then everything will make a little more sense.

Right, I'll give that a try. That sounds like it would make a little more sense.

Ok, I started off doing that, but once I stop setting DSOLocal on all variables, how would you suggest I'd proceed next? In TargetMachine::shouldAssumeDSOLocal we've got pretty much the same code as in Clang, more or less - even if we don't have the DSOLocal flag set, we run into this block:

bool TargetMachine::shouldAssumeDSOLocal(const Module &M,
                                         const GlobalValue *GV) const {
  if (GV && GV->isDSOLocal())
    return true;

  if (GV && GV->hasDLLImportStorageClass())
    return false;
  
  // Every other GV is local on COFF.
  if (TT.isOSBinFormatCOFF() || (TT.isOSWindows() && TT.isOSBinFormatMachO()))
    return true;

So even if we remove the DSOLocal flag, we end up doing pretty much the same.

It might make sense in the long run to add the same "if (mingw) don'tAssumeEverythingIsDSOLocal();" there - however TargetMachine::shouldAssumeDSOLocal is used in varying ways in X86/ARM/AArch64, so if we change that one, we'd need all three of them to be ready at the same time.

And if I don't change TargetMachine::shouldAssumeDSOLocal quite yet, I don't really see much difference from what this patch does right now. I guess I could use !GV->isDSOLocal() for part of the condition though...

Ok, I started off doing that, but once I stop setting DSOLocal on all variables, how would you suggest I'd proceed next? In TargetMachine::shouldAssumeDSOLocal we've got pretty much the same code as in Clang, more or less - even if we don't have the DSOLocal flag set, we run into this block:

bool TargetMachine::shouldAssumeDSOLocal(const Module &M,
                                         const GlobalValue *GV) const {
  if (GV && GV->isDSOLocal())
    return true;

  if (GV && GV->hasDLLImportStorageClass())
    return false;
  
  // Every other GV is local on COFF.
  if (TT.isOSBinFormatCOFF() || (TT.isOSWindows() && TT.isOSBinFormatMachO()))
    return true;

So even if we remove the DSOLocal flag, we end up doing pretty much the same.

It might make sense in the long run to add the same "if (mingw) don'tAssumeEverythingIsDSOLocal();" there - however TargetMachine::shouldAssumeDSOLocal is used in varying ways in X86/ARM/AArch64, so if we change that one, we'd need all three of them to be ready at the same time.

And if I don't change TargetMachine::shouldAssumeDSOLocal quite yet, I don't really see much difference from what this patch does right now. I guess I could use !GV->isDSOLocal() for part of the condition though...

Well, I think that COFF-specific check in shouldAssumeDSOLocal is intended to go away at some point, once frontends have migrated over to setting dso_local appropriately. Rafael would be able to tell us what he was thinking here, but obviously he's no longer involved with LLVM. I think it would be reasonable to restrict the "if COFF then local" check only apply to non-gnu environments.

Mainly, I don't want the dso_local marker to become a lie, and I think it's nice to be able to mark specific symbols as being local so that we can avoid the .refptr stubs. A little more duplication between clang and llvm is a reasonable cost for that.

@compnerd @smeenai, do you want these .refptr stubs for x86_64-windows-itanium? It seems like they fix a number of COFF/ELF portability issues.

In D51288#1216484, @rnk wrote:

Ok, I started off doing that, but once I stop setting DSOLocal on all variables, how would you suggest I'd proceed next? In TargetMachine::shouldAssumeDSOLocal we've got pretty much the same code as in Clang, more or less - even if we don't have the DSOLocal flag set, we run into this block:

bool TargetMachine::shouldAssumeDSOLocal(const Module &M,
                                         const GlobalValue *GV) const {
  if (GV && GV->isDSOLocal())
    return true;

  if (GV && GV->hasDLLImportStorageClass())
    return false;
  
  // Every other GV is local on COFF.
  if (TT.isOSBinFormatCOFF() || (TT.isOSWindows() && TT.isOSBinFormatMachO()))
    return true;

So even if we remove the DSOLocal flag, we end up doing pretty much the same.

It might make sense in the long run to add the same "if (mingw) don'tAssumeEverythingIsDSOLocal();" there - however TargetMachine::shouldAssumeDSOLocal is used in varying ways in X86/ARM/AArch64, so if we change that one, we'd need all three of them to be ready at the same time.

And if I don't change TargetMachine::shouldAssumeDSOLocal quite yet, I don't really see much difference from what this patch does right now. I guess I could use !GV->isDSOLocal() for part of the condition though...

Well, I think that COFF-specific check in shouldAssumeDSOLocal is intended to go away at some point, once frontends have migrated over to setting dso_local appropriately. Rafael would be able to tell us what he was thinking here, but obviously he's no longer involved with LLVM. I think it would be reasonable to restrict the "if COFF then local" check only apply to non-gnu environments.

Yes, if I understood correctly from the commit history, the whole method was supposed to go away, more or less.

Mainly, I don't want the dso_local marker to become a lie, and I think it's nice to be able to mark specific symbols as being local so that we can avoid the .refptr stubs. A little more duplication between clang and llvm is a reasonable cost for that.

Yup, that makes sense. I'll see what I can do to allow omitting .refptr for things marked dso_local, without refactoring everything all at once.

mstorsjo updated this revision to Diff 162951.Aug 28 2018, 2:00 PM

Adjusted the code to only use .refptr for variables without dso_local.

rnk accepted this revision.Aug 28 2018, 2:56 PM

Thanks, I think this is ready. I guess it doesn't strictly depend on the clang change, so you can go ahead and commit.

This revision is now accepted and ready to land.Aug 28 2018, 2:56 PM
mstorsjo added inline comments.Aug 29 2018, 2:54 AM
lib/Target/X86/X86Subtarget.cpp
146 ↗(On Diff #162951)

I guess this condition also should be changed similarly, replacing GV->hasExternalLinkage() && GV->isDeclaration() with GV->isDeclarationForLinker()? It doesn't seem to change any of the existing testcases here in LLVM though.

rnk added inline comments.Aug 29 2018, 8:37 AM
lib/Target/X86/X86Subtarget.cpp
146 ↗(On Diff #162951)

I wouldn't expect it to, because by the time we get to codegen we've already discarded all available_externally definitions and turned them into declarations.

I just noticed this is x86-specific logic. Do we need COFF stubs more often on ARM, or can we change shouldAssumeDSOLocal to have this logic for all mingw targets?

mstorsjo added inline comments.Aug 29 2018, 8:49 AM
lib/Target/X86/X86Subtarget.cpp
146 ↗(On Diff #162951)

Ok - so even if it doesn't change anything, I guess it's good to have the expression match the one used in clang.

Yes, this is x86 specific - I've got later patches for arm and aarch64 in line afterwards. If we were to change shouldAssumeDSOLocal, we'd need to add all of the coffstub logic to arm and aarch64 all at once, and I'd rather do it gradually. Once they all support it, I could try to move the logic into the common shouldAssumeDSOLocal, as a NFC refactoring.

The stubs are essential on arm/aarch64, since the pseudo relocs don't support the arm instruction encodings, only plain pointers/offsets like in x86 relocs. For x86_64 the stubs fix cases when an autoimported variable gets loaded more than 4GB away from %rip at runtime. For x86_32, they avoid having to make the text section writable while fixing the relocs.

rnk added inline comments.Aug 29 2018, 9:09 AM
lib/Target/X86/X86Subtarget.cpp
146 ↗(On Diff #162951)

Gradually makes sense.

This revision was automatically updated to reflect the committed changes.