[lld] [lld-macho] Sort LC_LINKER_OPTIONS before processing (PR #201604)

Nuri Amari via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 4 07:56:07 PDT 2026


https://github.com/NuriAmari updated https://github.com/llvm/llvm-project/pull/201604

>From 2623d0dea95c49f3e38c80dd1ef1971f3bb066e7 Mon Sep 17 00:00:00 2001
From: Nuri Amari <nuriamari at meta.com>
Date: Wed, 3 Jun 2026 15:15:10 -0400
Subject: [PATCH] [lld-macho] Sort LC_LINKER_OPTIONS before processing

Previously https://reviews.llvm.org/D157716 brought handling of
LC_LINKER_OPTIONS closer to Apple linker behavior by processing the
options at the end after all object files have been added.

This corrects another difference in behavior, processing frameworks
before regular libraries (linked with -lFoo), and processing each group
in sorted order.

Processing a LC_LINKER_OPTIONS can trigger loads of more object files
which in turn may have more LC_LINKER_OPTIONS. We iterate this to a
fixed point, walking this graph in BFS order, processing each "level" of
the graph in the order described above. This graph traversal order
hasn't changed in this commit, only the sorting has.

The diff of the linker map produced for the included test before and
after:

```
< [  4] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libzlib.a(zlib.o)
< [  5] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Zed.framework/Zed(zed-framework.o)
< [  6] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libmlib.a(mlib.o)
< [  7] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Alpha.framework/Alpha(alpha-framework.o)
< [  8] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libalib.a(alib.o)
< [  9] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Mid.framework/Mid(mid-framework.o)
---
> [  4] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Alpha.framework/Alpha(alpha-framework.o)
> [  5] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Mid.framework/Mid(mid-framework.o)
> [  6] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/Zed.framework/Zed(zed-framework.o)
> [  7] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libalib.a(alib.o)
> [  8] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libmlib.a(mlib.o)
> [  9] /Users/nuriamari/pika26.4/build/RelWithDebInfo/Darwin-arm64/toolchain/tools/lld/test/MachO/Output/lc-linker-option-sort.ll.tmp/libzlib.a(zlib.o)
23,28c23,28
< 0x000003D0    0x00000001      [  4] _zlib
< 0x000003E0    0x00000001      [  5] _zed_framework
< 0x000003F0    0x00000001      [  6] _mlib
< 0x00000400    0x00000001      [  7] _alpha_framework
< 0x00000410    0x00000001      [  8] _alib
< 0x00000420    0x00000001      [  9] _mid_framework
---
> 0x000003D0    0x00000001      [  4] _alpha_framework
> 0x000003E0    0x00000001      [  5] _mid_framework
> 0x000003F0    0x00000001      [  6] _zed_framework
> 0x00000400    0x00000001      [  7] _alib
> 0x00000410    0x00000001      [  8] _mlib
> 0x00000420    0x00000001      [  9] _zlib
```

Apple's linker produces the same order as after.
---
 lld/MachO/Driver.cpp                          |  31 ++++-
 ...der.ll => lc-linker-option-postprocess.ll} |   4 +-
 lld/test/MachO/lc-linker-option-sort.ll       | 130 ++++++++++++++++++
 3 files changed, 157 insertions(+), 8 deletions(-)
 rename lld/test/MachO/{lc-linker-option-order.ll => lc-linker-option-postprocess.ll} (100%)
 create mode 100644 lld/test/MachO/lc-linker-option-sort.ll

diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 9a43db99f29a1..ef24a79a72e16 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -706,24 +706,43 @@ void macho::resolveLCLinkerOptions() {
     unprocessedLCLinkerOptions.clear();
 
     DeferredFiles deferred;
+    SmallVector<StringRef> frameworks;
+    SmallVector<StringRef> libraries;
+
     for (unsigned i = 0; i < LCLinkerOptions.size(); ++i) {
       StringRef arg = LCLinkerOptions[i];
       if (arg.consume_front("-l")) {
         assert(!config->ignoreAutoLinkOptions.contains(arg));
-        addLibrary(arg, /*isNeeded=*/false, /*isWeak=*/false,
-                   /*isReexport=*/false, /*isHidden=*/false,
-                   /*isExplicit=*/false, LoadType::LCLinkerOption, deferred);
+        libraries.push_back(arg);
       } else if (arg == "-framework") {
         StringRef name = LCLinkerOptions[++i];
         assert(!config->ignoreAutoLinkOptions.contains(name));
-        addFramework(name, /*isNeeded=*/false, /*isWeak=*/false,
-                     /*isReexport=*/false, /*isExplicit=*/false,
-                     LoadType::LCLinkerOption, deferred);
+        frameworks.push_back(name);
       } else {
         error(arg + " is not allowed in LC_LINKER_OPTION");
       }
     }
 
+    llvm::sort(frameworks);
+    llvm::sort(libraries);
+
+    frameworks.erase(std::unique(frameworks.begin(), frameworks.end()),
+                     frameworks.end());
+    libraries.erase(std::unique(libraries.begin(), libraries.end()),
+                    libraries.end());
+
+    for (const StringRef framework : frameworks) {
+      addFramework(framework, /*isNeeded=*/false, /*isWeak=*/false,
+                   /*isReexport=*/false, /*isExplicit=*/false,
+                   LoadType::LCLinkerOption, deferred);
+    }
+
+    for (const StringRef library : libraries) {
+      addLibrary(library, /*isNeeded=*/false, /*isWeak=*/false,
+                 /*isReexport=*/false, /*isHidden=*/false,
+                 /*isExplicit=*/false, LoadType::LCLinkerOption, deferred);
+    }
+
     for (auto &file : deferred) {
       if (loadedObjectFrameworks.contains(file.path))
         continue;
diff --git a/lld/test/MachO/lc-linker-option-order.ll b/lld/test/MachO/lc-linker-option-postprocess.ll
similarity index 100%
rename from lld/test/MachO/lc-linker-option-order.ll
rename to lld/test/MachO/lc-linker-option-postprocess.ll
index f8f7cc88c6323..f3c5e3ddda6b9 100644
--- a/lld/test/MachO/lc-linker-option-order.ll
+++ b/lld/test/MachO/lc-linker-option-postprocess.ll
@@ -34,12 +34,12 @@
 
 ; CHECK: <_foo1>:
 ; CHECK: <_bar1>:
+; CHECK: <_bar2>:
 ; CHECK: <_foo2>:
 ; CHECK: <_zoo2>:
-; CHECK: <_bar2>:
+; CHECK: <_bar3>:
 ; CHECK: <_foo3>:
 ; CHECK: <_zoo3>:
-; CHECK: <_bar3>:
 
 ;--- foo1.ll
 target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
diff --git a/lld/test/MachO/lc-linker-option-sort.ll b/lld/test/MachO/lc-linker-option-sort.ll
new file mode 100644
index 0000000000000..920005c573abf
--- /dev/null
+++ b/lld/test/MachO/lc-linker-option-sort.ll
@@ -0,0 +1,130 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+; RUN: llc -filetype=obj %t/root-a.ll -o %t/root-a.o
+; RUN: llc -filetype=obj %t/root-b.ll -o %t/root-b.o
+
+; RUN: llc -filetype=obj %t/alpha-framework.ll -o %t/alpha-framework.o
+; RUN: mkdir -p %t/Alpha.framework
+; RUN: llvm-ar rcs %t/Alpha.framework/Alpha %t/alpha-framework.o
+
+; RUN: llc -filetype=obj %t/zed-framework.ll -o %t/zed-framework.o
+; RUN: mkdir -p %t/Zed.framework
+; RUN: llvm-ar rcs %t/Zed.framework/Zed %t/zed-framework.o
+
+; RUN: llc -filetype=obj %t/mid-framework.ll -o %t/mid-framework.o
+; RUN: mkdir -p %t/Mid.framework
+; RUN: llvm-ar rcs %t/Mid.framework/Mid %t/mid-framework.o
+
+; RUN: llc -filetype=obj %t/alib.ll -o %t/alib.o
+; RUN: llvm-ar rcs %t/libalib.a %t/alib.o
+
+; RUN: llc -filetype=obj %t/zlib.ll -o %t/zlib.o
+; RUN: llvm-ar rcs %t/libzlib.a %t/zlib.o
+
+; RUN: llc -filetype=obj %t/mlib.ll -o %t/mlib.o
+; RUN: llvm-ar rcs %t/libmlib.a %t/mlib.o
+
+; RUN: %lld -dylib -lSystem -F%t -L%t %t/root-a.o %t/root-b.o \
+; RUN:   -map %t/map -o %t/out
+; RUN: FileCheck %s < %t/map
+
+;; LC_LINKER_OPTIONs are collected across object files, bucketed by kind, and
+;; processed as sorted batches. Frameworks are processed before libraries.
+; CHECK-LABEL: # Object files:
+; CHECK: root-a.o
+; CHECK: root-b.o
+; CHECK: Alpha.framework/Alpha(alpha-framework.o)
+; CHECK: Mid.framework/Mid(mid-framework.o)
+; CHECK: Zed.framework/Zed(zed-framework.o)
+; CHECK: libalib.a(alib.o)
+; CHECK: libmlib.a(mlib.o)
+; CHECK: libzlib.a(zlib.o)
+
+;--- root-a.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+!0 = !{!"-lzlib"}
+!1 = !{!"-framework", !"Zed"}
+!2 = !{!"-lmlib"}
+!llvm.linker.options = !{!0, !1, !2}
+
+define void @root_a() {
+  call void @zed_framework()
+  call void @zlib()
+  call void @mlib()
+  ret void
+}
+
+declare void @zed_framework()
+declare void @zlib()
+declare void @mlib()
+
+;--- root-b.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+!0 = !{!"-framework", !"Alpha"}
+!1 = !{!"-lalib"}
+!2 = !{!"-framework", !"Mid"}
+!llvm.linker.options = !{!0, !1, !2}
+
+define void @root_b() {
+  call void @alpha_framework()
+  call void @alib()
+  call void @mid_framework()
+  ret void
+}
+
+declare void @alpha_framework()
+declare void @alib()
+declare void @mid_framework()
+
+;--- alpha-framework.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @alpha_framework() {
+  ret void
+}
+
+;--- zed-framework.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @zed_framework() {
+  ret void
+}
+
+;--- mid-framework.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @mid_framework() {
+  ret void
+}
+
+;--- alib.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @alib() {
+  ret void
+}
+
+;--- zlib.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @zlib() {
+  ret void
+}
+
+;--- mlib.ll
+target triple = "x86_64-apple-macosx10.15.0"
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @mlib() {
+  ret void
+}



More information about the llvm-commits mailing list