Skip to content

[LLD][COFF] Create EC alias symbols for entry points and exports #114297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 31, 2024

Conversation

cjacek
Copy link
Contributor

@cjacek cjacek commented Oct 30, 2024

On ARM64EC, a symbol may be defined in either its mangled or demangled form (or both). To ensure consistent linking for entry points and exports, define an anti-dependency symbol that binds both forms, similar to how compiler-generated code references external functions.

@llvmbot
Copy link
Member

llvmbot commented Oct 30, 2024

@llvm/pr-subscribers-platform-windows

@llvm/pr-subscribers-lld-coff

Author: Jacek Caban (cjacek)

Changes

On ARM64EC, a symbol may be defined in either its mangled or demangled form (or both). To ensure consistent linking for entry points and exports, define an anti-dependency symbol that binds both forms, similar to how compiler-generated code references external functions.


Full diff: https://github.com/llvm/llvm-project/pull/114297.diff

4 Files Affected:

  • (modified) lld/COFF/Driver.cpp (+29-8)
  • (modified) lld/COFF/Driver.h (+1-1)
  • (modified) lld/test/COFF/arm64ec-delayimport.test (+38-12)
  • (added) lld/test/COFF/arm64ec-entry-mangle.test (+129)
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 08c1476a595f64..7c66be5d3b7da9 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -415,7 +415,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
     case OPT_entry:
       if (!arg->getValue()[0])
         fatal("missing entry point symbol name");
-      ctx.config.entry = addUndefined(mangle(arg->getValue()));
+      ctx.config.entry = addUndefined(mangle(arg->getValue()), true);
       break;
     case OPT_failifmismatch:
       checkFailIfMismatch(arg->getValue(), file);
@@ -696,12 +696,33 @@ void LinkerDriver::addLibSearchPaths() {
   }
 }
 
-Symbol *LinkerDriver::addUndefined(StringRef name) {
+Symbol *LinkerDriver::addUndefined(StringRef name, bool aliasEC) {
   Symbol *b = ctx.symtab.addUndefined(name);
   if (!b->isGCRoot) {
     b->isGCRoot = true;
     ctx.config.gcroot.push_back(b);
   }
+
+  // On ARM64EC, a symbol may be defined in either its mangled or demangled form
+  // (or both). Define an anti-dependency symbol that binds both forms, similar
+  // to how compiler-generated code references external functions.
+  if (aliasEC && isArm64EC(ctx.config.machine)) {
+    if (std::optional<std::string> mangledName =
+            getArm64ECMangledFunctionName(name)) {
+      auto u = dyn_cast<Undefined>(b);
+      if (u && !u->weakAlias) {
+        Symbol *t = ctx.symtab.addUndefined(saver().save(*mangledName));
+        u->setWeakAlias(t, true);
+      }
+    } else {
+      std::optional<std::string> unmangledName =
+          getArm64ECDemangledFunctionName(name);
+      Symbol *us = ctx.symtab.addUndefined(saver().save(*unmangledName));
+      auto u = dyn_cast<Undefined>(us);
+      if (u && !u->weakAlias)
+        u->setWeakAlias(b, true);
+    }
+  }
   return b;
 }
 
@@ -2342,22 +2363,22 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     if (auto *arg = args.getLastArg(OPT_entry)) {
       if (!arg->getValue()[0])
         fatal("missing entry point symbol name");
-      config->entry = addUndefined(mangle(arg->getValue()));
+      config->entry = addUndefined(mangle(arg->getValue()), true);
     } else if (!config->entry && !config->noEntry) {
       if (args.hasArg(OPT_dll)) {
         StringRef s = (config->machine == I386) ? "__DllMainCRTStartup@12"
                                                 : "_DllMainCRTStartup";
-        config->entry = addUndefined(s);
+        config->entry = addUndefined(s, true);
       } else if (config->driverWdm) {
         // /driver:wdm implies /entry:_NtProcessStartup
-        config->entry = addUndefined(mangle("_NtProcessStartup"));
+        config->entry = addUndefined(mangle("_NtProcessStartup"), true);
       } else {
         // Windows specific -- If entry point name is not given, we need to
         // infer that from user-defined entry name.
         StringRef s = findDefaultEntry();
         if (s.empty())
           fatal("entry point must be defined");
-        config->entry = addUndefined(s);
+        config->entry = addUndefined(s, true);
         log("Entry name inferred: " + s);
       }
     }
@@ -2371,7 +2392,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
       if (config->machine == I386) {
         config->delayLoadHelper = addUndefined("___delayLoadHelper2@8");
       } else {
-        config->delayLoadHelper = addUndefined("__delayLoadHelper2");
+        config->delayLoadHelper = addUndefined("__delayLoadHelper2", true);
       }
     }
   }
@@ -2505,7 +2526,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
       for (Export &e : config->exports) {
         if (!e.forwardTo.empty())
           continue;
-        e.sym = addUndefined(e.name);
+        e.sym = addUndefined(e.name, !e.data);
         if (e.source != ExportSource::Directives)
           e.symbolName = mangleMaybe(e.sym);
       }
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 58a2ed23106243..3889feb7511c0a 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -170,7 +170,7 @@ class LinkerDriver {
 
   std::set<std::string> visitedLibs;
 
-  Symbol *addUndefined(StringRef sym);
+  Symbol *addUndefined(StringRef sym, bool aliasEC = false);
 
   void addUndefinedGlob(StringRef arg);
 
diff --git a/lld/test/COFF/arm64ec-delayimport.test b/lld/test/COFF/arm64ec-delayimport.test
index a0236d902eeaba..6797d84e088686 100644
--- a/lld/test/COFF/arm64ec-delayimport.test
+++ b/lld/test/COFF/arm64ec-delayimport.test
@@ -2,12 +2,14 @@ REQUIRES: aarch64, x86
 RUN: split-file %s %t.dir && cd %t.dir
 
 RUN: llvm-mc -filetype=obj -triple=arm64ec-windows test.s -o test.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows helper-mangled.s -o helper-mangled.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows helper-demangled.s -o helper-demangled.obj
 RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
 RUN: llvm-lib -machine:arm64ec -def:test.def -out:test-arm64ec.lib
 RUN: llvm-lib -machine:arm64ec -def:test2.def -out:test2-arm64ec.lib
 
 RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj test.obj \
-RUN:          test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
+RUN:          helper-mangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
 TESTSEC:      0x180008000 00600000 88700000 00200000 10100000
@@ -97,7 +99,7 @@ IMPORTS-NEXT:   }
 IMPORTS-NEXT: }
 
 RUN: FileCheck --check-prefix=MAP %s < out.map
-MAP:       0001:00000008       #__delayLoadHelper2        0000000180001008     test.obj
+MAP:       0001:00000008       #__delayLoadHelper2        0000000180001008     helper-mangled.obj
 MAP:       0001:00000010       #func                      0000000180001010     test-arm64ec:test.dll
 MAP-NEXT:  0001:0000001c       __impchk_func              000000018000101c     test-arm64ec:test.dll
 MAP-NEXT:  0001:00000030       #func2                     0000000180001030     test-arm64ec:test.dll
@@ -138,6 +140,21 @@ RELOC-NEXT:     Type: DIR64
 RELOC-NEXT:     Address: 0x6008
 RELOC-NEXT:   }
 
+Verify that a demangled version of __delayLoadHelper2 can be used.
+
+RUN: lld-link -machine:arm64ec -dll -noentry -out:out2.dll loadconfig-arm64ec.obj test.obj \
+RUN:          helper-demangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll
+RUN: llvm-objdump -d out2.dll | FileCheck --check-prefix=DISASM %s
+
+Verify that the mangled version of __delayLoadHelper2 can be used from a library.
+Even if an anti-dependency alias is defined by the helper, it won't appear in
+the archive index, so we need to locate it by its mangled name.
+
+RUN: llvm-lib -machine:arm64ec -out:helper.lib helper-mangled.obj
+RUN: lld-link -machine:arm64ec -dll -noentry -out:out3.dll loadconfig-arm64ec.obj test.obj \
+RUN:          helper.lib test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll
+RUN: llvm-objdump -d out3.dll | FileCheck --check-prefix=DISASM %s
+
 #--- test.s
     .section .test,"r"
     .rva __imp_func
@@ -159,16 +176,6 @@ __icall_helper_arm64ec:
     mov w0, #0
     ret
 
-    .section .text,"xr",discard,"#__delayLoadHelper2"
-    .globl "#__delayLoadHelper2"
-    .p2align 2, 0x0
-"#__delayLoadHelper2":
-    mov w0, #1
-    ret
-
-    .weak_anti_dep __delayLoadHelper2
-.set __delayLoadHelper2,"#__delayLoadHelper2"
-
     .section .hybmp$x, "yi"
     .symidx __imp_func
     .symidx func_exit_thunk
@@ -189,6 +196,25 @@ func2_exit_thunk:
     mov w0, #3
     ret
 
+#--- helper-mangled.s
+    .section .text,"xr",discard,"#__delayLoadHelper2"
+    .globl "#__delayLoadHelper2"
+    .p2align 2, 0x0
+"#__delayLoadHelper2":
+    mov w0, #1
+    ret
+
+    .weak_anti_dep __delayLoadHelper2
+.set __delayLoadHelper2,"#__delayLoadHelper2"
+
+#--- helper-demangled.s
+    .section .text,"xr",discard,__delayLoadHelper2
+    .globl __delayLoadHelper2
+    .p2align 2, 0x0
+__delayLoadHelper2:
+    mov w0, #1
+    ret
+
 #--- test.def
 NAME test.dll
 EXPORTS
diff --git a/lld/test/COFF/arm64ec-entry-mangle.test b/lld/test/COFF/arm64ec-entry-mangle.test
new file mode 100644
index 00000000000000..65283f16d02fa9
--- /dev/null
+++ b/lld/test/COFF/arm64ec-entry-mangle.test
@@ -0,0 +1,129 @@
+REQUIRES: aarch64, x86
+RUN: split-file %s %t.dir && cd %t.dir
+
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-dll-main.s -o demangled-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows mangled-dll-main.s -o mangled-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-func.s -o demangled-func.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows mangled-func.s -o mangled-func.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows ref-demangled.s -o ref-demangled.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-entry-drectve.s -o demangled-entry-drectve.obj
+RUN: llvm-mc -filetype=obj -triple=x86_64-windows demangled-dll-main.s -o x64-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
+
+RUN: llvm-lib -machine:arm64ec -out:func.lib mangled-func.obj
+RUN: llvm-lib -machine:arm64ec -out:dllmain.lib mangled-dll-main.obj
+
+Ensure that the linker recognizes the demangled version of _DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-main.dll demangled-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d demangled-main.dll | FileCheck -check-prefix=DISASM %s
+
+DISASM:      0000000180001000 <.text>:
+DISASM-NEXT: 180001000: d65f03c0     ret
+DISASM-EMPTY:
+DISASM-NEXT: Disassembly of section .hexpthk:
+DISASM-EMPTY:
+DISASM:      180002000: 48 8b c4                     movq    %rsp, %rax
+DISASM-NEXT: 180002003: 48 89 58 20                  movq    %rbx, 0x20(%rax)
+DISASM-NEXT: 180002007: 55                           pushq   %rbp
+DISASM-NEXT: 180002008: 5d                           popq    %rbp
+DISASM-NEXT: 180002009: e9 f2 ef ff ff               jmp     0x180001000 <.text>
+DISASM-NEXT: 18000200e: cc                           int3
+DISASM-NEXT: 18000200f: cc                           int3
+
+Ensure that the linker recognizes the mangled version of #_DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-dllmain.dll mangled-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d mangled-dllmain.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled version of _DllMainCRTStartup from an archive.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-lib-dllmain.dll dllmain.lib loadconfig-arm64ec.obj
+RUN: llvm-objdump -d mangled-lib-dllmain.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the demangled entry function.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-entry.dll demangled-func.obj loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d demangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function when it is referenced by its demangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry.dll mangled-func.obj loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d mangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function when it is referenced by its demangled
+name in drectve section.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry.dll mangled-func.obj loadconfig-arm64ec.obj demangled-entry-drectve.obj
+RUN: llvm-objdump -d mangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function from an archive.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-lib-entry.dll func.lib loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d mangled-lib-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the entry function when referenced by its mangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry2.dll mangled-func.obj loadconfig-arm64ec.obj "-entry:#func"
+RUN: llvm-objdump -d mangled-entry2.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the demangled exported function.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-export.dll demangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry -export:func
+RUN: llvm-objdump -d demangled-export.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced by its demangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-export.dll mangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry -export:func
+RUN: llvm-objdump -d mangled-export.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced by its mangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-export2.dll mangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry "-export:#func"
+RUN: llvm-objdump -d mangled-export2.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced
+by its mangled name and creates a demangled alias for it.
+RUN: lld-link -machine:arm64ec -dll -noentry -out:demangled-export-ref.dll mangled-func.obj \
+RUN:          ref-demangled.obj loadconfig-arm64ec.obj "-export:#func"
+RUN: llvm-objdump -d demangled-export-ref.dll | FileCheck -check-prefix=DISASM %s
+
+DISASM2:      0000000180001000 <.text>:
+DISASM2-NEXT: 180001000: d65f03c0     ret
+
+Verify that the linker emits appropriate errors for mismatched mangling.
+RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadconfig-arm64ec.obj \
+RUN:              "-entry:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
+RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadconfig-arm64ec.obj \
+RUN:              -noentry "-export:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
+FUNC-NOT-FOUND: undefined symbol: #func
+
+Verify that the linker recognizes the demangled x86_64 _DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:test.dll x64-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d test.dll | FileCheck -check-prefix=DISASM-X64 %s
+DISASM-X64:      0000000180001000 <.text>:
+DISASM-X64-NEXT: 180001000: c3                           retq
+
+#--- demangled-dll-main.s
+    .text
+    .globl _DllMainCRTStartup
+_DllMainCRTStartup:
+    ret
+
+#--- mangled-dll-main.s
+    .text
+    .globl "#_DllMainCRTStartup"
+"#_DllMainCRTStartup":
+    ret
+
+#--- demangled-func.s
+    .text
+    .globl func
+func:
+    ret
+
+#--- mangled-func.s
+    .text
+    .globl "#func"
+"#func":
+    ret
+
+#--- ref-demangled.s
+    .data
+    .rva func
+
+#--- demangled-entry-drectve.s
+	.section .drectve,"rd"
+	.ascii " -entry:func"

@llvmbot
Copy link
Member

llvmbot commented Oct 30, 2024

@llvm/pr-subscribers-lld

Author: Jacek Caban (cjacek)

Changes

On ARM64EC, a symbol may be defined in either its mangled or demangled form (or both). To ensure consistent linking for entry points and exports, define an anti-dependency symbol that binds both forms, similar to how compiler-generated code references external functions.


Full diff: https://github.com/llvm/llvm-project/pull/114297.diff

4 Files Affected:

  • (modified) lld/COFF/Driver.cpp (+29-8)
  • (modified) lld/COFF/Driver.h (+1-1)
  • (modified) lld/test/COFF/arm64ec-delayimport.test (+38-12)
  • (added) lld/test/COFF/arm64ec-entry-mangle.test (+129)
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 08c1476a595f64..7c66be5d3b7da9 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -415,7 +415,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
     case OPT_entry:
       if (!arg->getValue()[0])
         fatal("missing entry point symbol name");
-      ctx.config.entry = addUndefined(mangle(arg->getValue()));
+      ctx.config.entry = addUndefined(mangle(arg->getValue()), true);
       break;
     case OPT_failifmismatch:
       checkFailIfMismatch(arg->getValue(), file);
@@ -696,12 +696,33 @@ void LinkerDriver::addLibSearchPaths() {
   }
 }
 
-Symbol *LinkerDriver::addUndefined(StringRef name) {
+Symbol *LinkerDriver::addUndefined(StringRef name, bool aliasEC) {
   Symbol *b = ctx.symtab.addUndefined(name);
   if (!b->isGCRoot) {
     b->isGCRoot = true;
     ctx.config.gcroot.push_back(b);
   }
+
+  // On ARM64EC, a symbol may be defined in either its mangled or demangled form
+  // (or both). Define an anti-dependency symbol that binds both forms, similar
+  // to how compiler-generated code references external functions.
+  if (aliasEC && isArm64EC(ctx.config.machine)) {
+    if (std::optional<std::string> mangledName =
+            getArm64ECMangledFunctionName(name)) {
+      auto u = dyn_cast<Undefined>(b);
+      if (u && !u->weakAlias) {
+        Symbol *t = ctx.symtab.addUndefined(saver().save(*mangledName));
+        u->setWeakAlias(t, true);
+      }
+    } else {
+      std::optional<std::string> unmangledName =
+          getArm64ECDemangledFunctionName(name);
+      Symbol *us = ctx.symtab.addUndefined(saver().save(*unmangledName));
+      auto u = dyn_cast<Undefined>(us);
+      if (u && !u->weakAlias)
+        u->setWeakAlias(b, true);
+    }
+  }
   return b;
 }
 
@@ -2342,22 +2363,22 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     if (auto *arg = args.getLastArg(OPT_entry)) {
       if (!arg->getValue()[0])
         fatal("missing entry point symbol name");
-      config->entry = addUndefined(mangle(arg->getValue()));
+      config->entry = addUndefined(mangle(arg->getValue()), true);
     } else if (!config->entry && !config->noEntry) {
       if (args.hasArg(OPT_dll)) {
         StringRef s = (config->machine == I386) ? "__DllMainCRTStartup@12"
                                                 : "_DllMainCRTStartup";
-        config->entry = addUndefined(s);
+        config->entry = addUndefined(s, true);
       } else if (config->driverWdm) {
         // /driver:wdm implies /entry:_NtProcessStartup
-        config->entry = addUndefined(mangle("_NtProcessStartup"));
+        config->entry = addUndefined(mangle("_NtProcessStartup"), true);
       } else {
         // Windows specific -- If entry point name is not given, we need to
         // infer that from user-defined entry name.
         StringRef s = findDefaultEntry();
         if (s.empty())
           fatal("entry point must be defined");
-        config->entry = addUndefined(s);
+        config->entry = addUndefined(s, true);
         log("Entry name inferred: " + s);
       }
     }
@@ -2371,7 +2392,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
       if (config->machine == I386) {
         config->delayLoadHelper = addUndefined("___delayLoadHelper2@8");
       } else {
-        config->delayLoadHelper = addUndefined("__delayLoadHelper2");
+        config->delayLoadHelper = addUndefined("__delayLoadHelper2", true);
       }
     }
   }
@@ -2505,7 +2526,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
       for (Export &e : config->exports) {
         if (!e.forwardTo.empty())
           continue;
-        e.sym = addUndefined(e.name);
+        e.sym = addUndefined(e.name, !e.data);
         if (e.source != ExportSource::Directives)
           e.symbolName = mangleMaybe(e.sym);
       }
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 58a2ed23106243..3889feb7511c0a 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -170,7 +170,7 @@ class LinkerDriver {
 
   std::set<std::string> visitedLibs;
 
-  Symbol *addUndefined(StringRef sym);
+  Symbol *addUndefined(StringRef sym, bool aliasEC = false);
 
   void addUndefinedGlob(StringRef arg);
 
diff --git a/lld/test/COFF/arm64ec-delayimport.test b/lld/test/COFF/arm64ec-delayimport.test
index a0236d902eeaba..6797d84e088686 100644
--- a/lld/test/COFF/arm64ec-delayimport.test
+++ b/lld/test/COFF/arm64ec-delayimport.test
@@ -2,12 +2,14 @@ REQUIRES: aarch64, x86
 RUN: split-file %s %t.dir && cd %t.dir
 
 RUN: llvm-mc -filetype=obj -triple=arm64ec-windows test.s -o test.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows helper-mangled.s -o helper-mangled.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows helper-demangled.s -o helper-demangled.obj
 RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
 RUN: llvm-lib -machine:arm64ec -def:test.def -out:test-arm64ec.lib
 RUN: llvm-lib -machine:arm64ec -def:test2.def -out:test2-arm64ec.lib
 
 RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj test.obj \
-RUN:          test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
+RUN:          helper-mangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll -map
 
 RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
 TESTSEC:      0x180008000 00600000 88700000 00200000 10100000
@@ -97,7 +99,7 @@ IMPORTS-NEXT:   }
 IMPORTS-NEXT: }
 
 RUN: FileCheck --check-prefix=MAP %s < out.map
-MAP:       0001:00000008       #__delayLoadHelper2        0000000180001008     test.obj
+MAP:       0001:00000008       #__delayLoadHelper2        0000000180001008     helper-mangled.obj
 MAP:       0001:00000010       #func                      0000000180001010     test-arm64ec:test.dll
 MAP-NEXT:  0001:0000001c       __impchk_func              000000018000101c     test-arm64ec:test.dll
 MAP-NEXT:  0001:00000030       #func2                     0000000180001030     test-arm64ec:test.dll
@@ -138,6 +140,21 @@ RELOC-NEXT:     Type: DIR64
 RELOC-NEXT:     Address: 0x6008
 RELOC-NEXT:   }
 
+Verify that a demangled version of __delayLoadHelper2 can be used.
+
+RUN: lld-link -machine:arm64ec -dll -noentry -out:out2.dll loadconfig-arm64ec.obj test.obj \
+RUN:          helper-demangled.obj test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll
+RUN: llvm-objdump -d out2.dll | FileCheck --check-prefix=DISASM %s
+
+Verify that the mangled version of __delayLoadHelper2 can be used from a library.
+Even if an anti-dependency alias is defined by the helper, it won't appear in
+the archive index, so we need to locate it by its mangled name.
+
+RUN: llvm-lib -machine:arm64ec -out:helper.lib helper-mangled.obj
+RUN: lld-link -machine:arm64ec -dll -noentry -out:out3.dll loadconfig-arm64ec.obj test.obj \
+RUN:          helper.lib test-arm64ec.lib test2-arm64ec.lib -delayload:test.dll
+RUN: llvm-objdump -d out3.dll | FileCheck --check-prefix=DISASM %s
+
 #--- test.s
     .section .test,"r"
     .rva __imp_func
@@ -159,16 +176,6 @@ __icall_helper_arm64ec:
     mov w0, #0
     ret
 
-    .section .text,"xr",discard,"#__delayLoadHelper2"
-    .globl "#__delayLoadHelper2"
-    .p2align 2, 0x0
-"#__delayLoadHelper2":
-    mov w0, #1
-    ret
-
-    .weak_anti_dep __delayLoadHelper2
-.set __delayLoadHelper2,"#__delayLoadHelper2"
-
     .section .hybmp$x, "yi"
     .symidx __imp_func
     .symidx func_exit_thunk
@@ -189,6 +196,25 @@ func2_exit_thunk:
     mov w0, #3
     ret
 
+#--- helper-mangled.s
+    .section .text,"xr",discard,"#__delayLoadHelper2"
+    .globl "#__delayLoadHelper2"
+    .p2align 2, 0x0
+"#__delayLoadHelper2":
+    mov w0, #1
+    ret
+
+    .weak_anti_dep __delayLoadHelper2
+.set __delayLoadHelper2,"#__delayLoadHelper2"
+
+#--- helper-demangled.s
+    .section .text,"xr",discard,__delayLoadHelper2
+    .globl __delayLoadHelper2
+    .p2align 2, 0x0
+__delayLoadHelper2:
+    mov w0, #1
+    ret
+
 #--- test.def
 NAME test.dll
 EXPORTS
diff --git a/lld/test/COFF/arm64ec-entry-mangle.test b/lld/test/COFF/arm64ec-entry-mangle.test
new file mode 100644
index 00000000000000..65283f16d02fa9
--- /dev/null
+++ b/lld/test/COFF/arm64ec-entry-mangle.test
@@ -0,0 +1,129 @@
+REQUIRES: aarch64, x86
+RUN: split-file %s %t.dir && cd %t.dir
+
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-dll-main.s -o demangled-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows mangled-dll-main.s -o mangled-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-func.s -o demangled-func.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows mangled-func.s -o mangled-func.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows ref-demangled.s -o ref-demangled.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows demangled-entry-drectve.s -o demangled-entry-drectve.obj
+RUN: llvm-mc -filetype=obj -triple=x86_64-windows demangled-dll-main.s -o x64-dll-main.obj
+RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
+
+RUN: llvm-lib -machine:arm64ec -out:func.lib mangled-func.obj
+RUN: llvm-lib -machine:arm64ec -out:dllmain.lib mangled-dll-main.obj
+
+Ensure that the linker recognizes the demangled version of _DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-main.dll demangled-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d demangled-main.dll | FileCheck -check-prefix=DISASM %s
+
+DISASM:      0000000180001000 <.text>:
+DISASM-NEXT: 180001000: d65f03c0     ret
+DISASM-EMPTY:
+DISASM-NEXT: Disassembly of section .hexpthk:
+DISASM-EMPTY:
+DISASM:      180002000: 48 8b c4                     movq    %rsp, %rax
+DISASM-NEXT: 180002003: 48 89 58 20                  movq    %rbx, 0x20(%rax)
+DISASM-NEXT: 180002007: 55                           pushq   %rbp
+DISASM-NEXT: 180002008: 5d                           popq    %rbp
+DISASM-NEXT: 180002009: e9 f2 ef ff ff               jmp     0x180001000 <.text>
+DISASM-NEXT: 18000200e: cc                           int3
+DISASM-NEXT: 18000200f: cc                           int3
+
+Ensure that the linker recognizes the mangled version of #_DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-dllmain.dll mangled-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d mangled-dllmain.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled version of _DllMainCRTStartup from an archive.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-lib-dllmain.dll dllmain.lib loadconfig-arm64ec.obj
+RUN: llvm-objdump -d mangled-lib-dllmain.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the demangled entry function.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-entry.dll demangled-func.obj loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d demangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function when it is referenced by its demangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry.dll mangled-func.obj loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d mangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function when it is referenced by its demangled
+name in drectve section.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry.dll mangled-func.obj loadconfig-arm64ec.obj demangled-entry-drectve.obj
+RUN: llvm-objdump -d mangled-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled entry function from an archive.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-lib-entry.dll func.lib loadconfig-arm64ec.obj -entry:func
+RUN: llvm-objdump -d mangled-lib-entry.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the entry function when referenced by its mangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-entry2.dll mangled-func.obj loadconfig-arm64ec.obj "-entry:#func"
+RUN: llvm-objdump -d mangled-entry2.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the demangled exported function.
+RUN: lld-link -machine:arm64ec -dll -out:demangled-export.dll demangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry -export:func
+RUN: llvm-objdump -d demangled-export.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced by its demangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-export.dll mangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry -export:func
+RUN: llvm-objdump -d mangled-export.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced by its mangled name.
+RUN: lld-link -machine:arm64ec -dll -out:mangled-export2.dll mangled-func.obj \
+RUN:          loadconfig-arm64ec.obj -noentry "-export:#func"
+RUN: llvm-objdump -d mangled-export2.dll | FileCheck -check-prefix=DISASM %s
+
+Verify that the linker recognizes the mangled exported function when referenced
+by its mangled name and creates a demangled alias for it.
+RUN: lld-link -machine:arm64ec -dll -noentry -out:demangled-export-ref.dll mangled-func.obj \
+RUN:          ref-demangled.obj loadconfig-arm64ec.obj "-export:#func"
+RUN: llvm-objdump -d demangled-export-ref.dll | FileCheck -check-prefix=DISASM %s
+
+DISASM2:      0000000180001000 <.text>:
+DISASM2-NEXT: 180001000: d65f03c0     ret
+
+Verify that the linker emits appropriate errors for mismatched mangling.
+RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadconfig-arm64ec.obj \
+RUN:              "-entry:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
+RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadconfig-arm64ec.obj \
+RUN:              -noentry "-export:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
+FUNC-NOT-FOUND: undefined symbol: #func
+
+Verify that the linker recognizes the demangled x86_64 _DllMainCRTStartup.
+RUN: lld-link -machine:arm64ec -dll -out:test.dll x64-dll-main.obj loadconfig-arm64ec.obj
+RUN: llvm-objdump -d test.dll | FileCheck -check-prefix=DISASM-X64 %s
+DISASM-X64:      0000000180001000 <.text>:
+DISASM-X64-NEXT: 180001000: c3                           retq
+
+#--- demangled-dll-main.s
+    .text
+    .globl _DllMainCRTStartup
+_DllMainCRTStartup:
+    ret
+
+#--- mangled-dll-main.s
+    .text
+    .globl "#_DllMainCRTStartup"
+"#_DllMainCRTStartup":
+    ret
+
+#--- demangled-func.s
+    .text
+    .globl func
+func:
+    ret
+
+#--- mangled-func.s
+    .text
+    .globl "#func"
+"#func":
+    ret
+
+#--- ref-demangled.s
+    .data
+    .rva func
+
+#--- demangled-entry-drectve.s
+	.section .drectve,"rd"
+	.ascii " -entry:func"

@cjacek
Copy link
Contributor Author

cjacek commented Oct 30, 2024

I still have a few more fixes queued, but this is the final feature needed to link ARM64EC msvcrt.lib properly, making the linker genuinely useful.

The underlying issue is that entry points are provided by archives, which only contain the mangled name in the index. Although the object files that implement these symbols include the demangled alias, they aren’t visible until the object file is pulled in. This change creates aliases for entry point and exported function symbols to ensure they can be properly included.

This behavior is mostly compatible with what the MSVC linker does: all tests pass except the one where -entry:#func is specified. In that case, MSVC would automatically re-mangle the name and emit an undefined ##func error. That behavior didn’t seem necessary to replicate here, so I left it handling -entry the same way as -export.

Copy link
Member

@mstorsjo mstorsjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! One minor nit, but otherwise this looks quite straightforward, implementation wise.

}
} else {
std::optional<std::string> unmangledName =
getArm64ECDemangledFunctionName(name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: There's a small inconsistency here - is it "unmangled" or "demangled"? It would be more consistent if the variable and the function would use the same form :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

On ARM64EC, a symbol may be defined in either its mangled or demangled form (or both).
To ensure consistent linking for entry points and exports, define an anti-dependency
symbol that binds both forms, similar to how compiler-generated code references
external functions.
@cjacek cjacek force-pushed the arm64ec-entry-alias branch from 11cb331 to 185bd67 Compare October 31, 2024 17:02
@cjacek cjacek merged commit 4d4a43d into llvm:main Oct 31, 2024
4 of 5 checks passed
@cjacek cjacek deleted the arm64ec-entry-alias branch October 31, 2024 17:05
smallp-o-p pushed a commit to smallp-o-p/llvm-project that referenced this pull request Nov 3, 2024
…m#114297)

On ARM64EC, a symbol may be defined in either its mangled or demangled
form (or both). To ensure consistent linking for entry points and
exports, define an anti-dependency symbol that binds both forms, similar
to how compiler-generated code references external functions.
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this pull request Nov 4, 2024
…m#114297)

On ARM64EC, a symbol may be defined in either its mangled or demangled
form (or both). To ensure consistent linking for entry points and
exports, define an anti-dependency symbol that binds both forms, similar
to how compiler-generated code references external functions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants