This is an archive of the discontinued LLVM Phabricator instance.

[Arm64EC] Refer to dllimport'ed functions correctly.
ClosedPublic

Authored by efriedma on Oct 18 2022, 1:47 PM.

Details

Summary

Arm64EC has two different ways to refer to dllimport'ed functions in an object file. One is using the usual __imp_ prefix, the other is using an Arm64EC-specific prefix __imp_aux_. As far as I can tell, if a function is in an x64 DLL, __imp_aux_ refers to the actual x64 address, while __imp_ points to some linker-generated code that calls the exit thunk. So __imp_aux_ is used to refer to the address in non-call contexts, while __imp_ is used for calls to avoid the indirect call checker.

There's one twist to this, though: if an object refers to a symbol using the __imp_aux_ prefix, the object file's symbol table must also contain the symbol with the usual __imp_ prefix. The symbol doesn't actually have to be used anywhere, it just has to exist; otherwise, the linker's symbol lookup in x64 import libraries doesn't work correctly. Currently, this is handled by emitting a .globl __imp_foo directive; we could try to design some better way to handle this.

One minor quirk I haven't figured out: apparently, in Arm64EC mode, MSVC prefers to use a linker-synthesized stub to call dllimport'ed functions, instead of branching directly. The linker stub appears to do the same thing that inline code would do, so not sure if it's just a code-size optimization, or if the synthesized stub can actually do something other than just load from the import table in some circumstances.

Diff Detail

Unit TestsFailed

Event Timeline

efriedma created this revision.Oct 18 2022, 1:47 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 18 2022, 1:47 PM
efriedma requested review of this revision.Oct 18 2022, 1:47 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 18 2022, 1:47 PM
efriedma edited the summary of this revision. (Show Details)Oct 18 2022, 1:47 PM
bcl5980 added a comment.EditedOct 19 2022, 1:14 AM

I am OK with this change but only 1 question:
https://godbolt.org/z/Ths69E83j
MSVC will generate both __imp_aux_fn and __imp_fn even if we only use __imp_aux_fn. But it looks based on this change we can't do that. And it will cause link error based on my local test.

Just listing the symbol in the symbol table doesn't do anything useful as far as I know. What's actually referring to __imp_importf? What do you mean by link errors?

Just listing the symbol in the symbol table doesn't do anything useful as far as I know. What's actually referring to __imp_importf? What do you mean by link errors?

Nothing refer to __imp_fn. But that is an external symbol. I guess link will use it to generate the definition of __imp_aux_fn. So, if we don't export __imp_fn, link will report:
error LNK2019: unresolved external symbol __imp_aux_fn referenced in function #p (EC Symbol)

Strange, I didn't see anything like that. Are you sure that adding the symbol to the symbol table solves the issue?

I did see a few weird issues with certain C library functions, but it turned out I could reproduce the same issue with MSVC.

Strange, I didn't see anything like that. Are you sure that adding the symbol to the symbol table solves the issue?

I did see a few weird issues with certain C library functions, but it turned out I could reproduce the same issue with MSVC.

Yeah, for example the arm64ec.exe source code is:

#include <stdio.h>

int __declspec(dllimport) fn(int a);

int main(int argc, const char* argv[])
{
    printf("result:%p\n", (void*)fn);
    return 0;
}

We will only create symbol __imp_aux_fn for now.

The x64 dll source code is:

int __declspec(dllexport) fn(int a)
{
    return a + 1;
}

The x64 dll only export the symbol __imp_fn.
So, we should export __imp_fn in arm64ec.obj I think.

The way you're describing the issue doesn't make sense. Both __imp_ and __imp_aux_ are "magic" prefixes; they only exist in object files, not linked products (dll/exe).

That said, if you link an arm64ec binary against an x64 library, link.exe does in fact appear to fail to resolve __imp_aux_fn unless __imp_fn is also in the symbol table. Note this only applies to linking against x64 libraries; linking against arm64ec libraries works fine (which is why I didn't run into issues when I was testing this). The fact that the behavior depends on how the library is compiled is so inexplicably weird that I have to call it a bug... but Microsoft probably isn't going to help us here, so we probably need to come up with a workaround.

The way you're describing the issue doesn't make sense. Both __imp_ and __imp_aux_ are "magic" prefixes; they only exist in object files, not linked products (dll/exe).

The __imp_ prefix symbol also exist in lib files. Link.exe use the lib file as input to link dll.

That said, if you link an arm64ec binary against an x64 library, link.exe does in fact appear to fail to resolve __imp_aux_fn unless __imp_fn is also in the symbol table. Note this only applies to linking against x64 libraries; linking against arm64ec libraries works fine (which is why I didn't run into issues when I was testing this). The fact that the behavior depends on how the library is compiled is so inexplicably weird that I have to call it a bug... but Microsoft probably isn't going to help us here, so we probably need to come up with a workaround.

Yeah, that is exactly what I find, ARM64EC binary link a x64 library has this issue.

efriedma updated this revision to Diff 469304.Oct 20 2022, 11:24 AM
efriedma edited the summary of this revision. (Show Details)

Updated with the symbol table quirk.

efriedma edited the summary of this revision. (Show Details)Oct 20 2022, 11:25 AM
This revision is now accepted and ready to land.Oct 20 2022, 12:59 PM
This revision was landed with ongoing or failed builds.Oct 20 2022, 3:10 PM
This revision was automatically updated to reflect the committed changes.