Skip to content

Commit c57c44d

Browse files
committed
Make linkers that do not support multiple targets discoverable.
The link.exe cannot link for multiple architectures there is a distinct link.exe for arm86 and x86_64. The path to the specific linkers should not be added to the global search path, as when building a target we do not know the architecture until task creation time. * Only add up until the Host[x86|arm]/bin directory to the global search path. * Let the LD spec determine the correct prefix directory to find the proper link.exe * Introduce two build settings LD_MULTIARCH - A boolean indicator for multiple architecture support LD_MULTIARCH_PREFIX_MAP - A prefix directory map from architecture:prefix * Add in specfic tests for discoveredLdLinkerSpecInfo for each linkers * Add x86_64 as a supported architecture for windows. A seperate PR is inflight to add the true supported set.
1 parent 50f64fd commit c57c44d

File tree

10 files changed

+234
-61
lines changed

10 files changed

+234
-61
lines changed

Sources/SWBCore/SDKRegistry.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,14 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send
646646
tripleEnvironment = ""
647647
}
648648

649+
let archs: PropertyListItem =
650+
switch operatingSystem {
651+
case .windows:
652+
.plArray([.plString("x86_64"), .plString("aarch64")])
653+
default:
654+
.plArray([.plString(Architecture.hostStringValue ?? "unknown")])
655+
}
656+
649657
return try [
650658
"Type": .plString("SDK"),
651659
"Version": .plString(Version(ProcessInfo.processInfo.operatingSystemVersion).zeroTrimmed.description),
@@ -656,7 +664,7 @@ public final class SDKRegistry: SDKRegistryLookup, CustomStringConvertible, Send
656664
].merging(defaultProperties, uniquingKeysWith: { _, new in new })),
657665
"SupportedTargets": .plDict([
658666
operatingSystem.xcodePlatformName: .plDict([
659-
"Archs": .plArray([.plString(Architecture.hostStringValue ?? "unknown")]),
667+
"Archs": archs,
660668
"LLVMTargetTripleEnvironment": .plString(tripleEnvironment),
661669
"LLVMTargetTripleSys": .plString(operatingSystem.xcodePlatformName),
662670
"LLVMTargetTripleVendor": .plString("unknown"),

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,8 @@ public final class BuiltinMacros {
803803
public static let LD_RUNPATH_SEARCH_PATHS = BuiltinMacros.declareStringListMacro("LD_RUNPATH_SEARCH_PATHS")
804804
public static let LD_SDK_IMPORTS_FILE = BuiltinMacros.declarePathMacro("LD_SDK_IMPORTS_FILE")
805805
public static let LD_WARN_UNUSED_DYLIBS = BuiltinMacros.declareBooleanMacro("LD_WARN_UNUSED_DYLIBS")
806+
public static let LD_MULTIARCH = BuiltinMacros.declareBooleanMacro("LD_MULTIARCH")
807+
public static let LD_MULTIARCH_PREFIX_MAP = BuiltinMacros.declareStringListMacro("LD_MULTIARCH_PREFIX_MAP")
806808
public static let LEX = BuiltinMacros.declarePathMacro("LEX")
807809
public static let LEXFLAGS = BuiltinMacros.declareStringListMacro("LEXFLAGS")
808810
public static let LIBRARIAN = BuiltinMacros.declareStringMacro("LIBRARIAN")
@@ -1858,6 +1860,8 @@ public final class BuiltinMacros {
18581860
LD_RUNPATH_SEARCH_PATHS,
18591861
LD_SDK_IMPORTS_FILE,
18601862
LD_WARN_UNUSED_DYLIBS,
1863+
LD_MULTIARCH,
1864+
LD_MULTIARCH_PREFIX_MAP,
18611865
LEGACY_DEVELOPER_DIR,
18621866
LEX,
18631867
LEXFLAGS,

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,6 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
12731273
}
12741274

12751275
override public func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? {
1276-
let alternateLinker = scope.evaluate(BuiltinMacros.ALTERNATE_LINKER)
12771276
// The ALTERNATE_LINKER is the 'name' of the linker not the executable name, clang will find the linker binary based on name passed via -fuse-ld, but we need to discover
12781277
// its properties by executing the actual binary. There is a common filename when the linker is not "ld" across all platforms using "ld.<ALTERNAME_LINKER>(.exe)"
12791278
// macOS (Xcode SDK)
@@ -1309,9 +1308,43 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
13091308
//
13101309
// Note: On Linux you cannot invoke the llvm linker by the direct name for determining the version,
13111310
// you need to use ld.<ALTERNATE_LINKER>
1312-
var linkerPath = producer.hostOperatingSystem == .windows ? Path("link") : Path("ld")
1313-
if alternateLinker != "" && alternateLinker != "ld" {
1311+
let alternateLinker = scope.evaluate(BuiltinMacros.ALTERNATE_LINKER)
1312+
let isLinkerMultiarch = scope.evaluate(BuiltinMacros.LD_MULTIARCH)
1313+
1314+
var linkerPath = producer.hostOperatingSystem == .windows ? Path("ld.lld") : Path("ld")
1315+
if alternateLinker != "" && alternateLinker != "ld" && alternateLinker != "link" {
13141316
linkerPath = Path(producer.hostOperatingSystem.imageFormat.executableName(basename: "ld.\(alternateLinker)"))
1317+
} else if alternateLinker != "" {
1318+
linkerPath = Path(alternateLinker)
1319+
}
1320+
// If the linker does not support multiple architectures update the path to include a subfolder based on the prefix map
1321+
// to find the architecture specific executable.
1322+
if !isLinkerMultiarch {
1323+
let archMap = scope.evaluate(BuiltinMacros.LD_MULTIARCH_PREFIX_MAP)
1324+
let archMappings = archMap.reduce(into: [String: String]()) { mappings, map in
1325+
let split = map.components(separatedBy: ":")
1326+
if !split.isEmpty {
1327+
return mappings[split[0]] = split[1]
1328+
}
1329+
}
1330+
if archMappings.isEmpty {
1331+
delegate.error("LD_MULTIARCH is 'false', but no prefix mappings are present in LD_MULTIARCH_PREFIX_MAP")
1332+
return nil
1333+
}
1334+
// Linkers that don't support multiple architectures cannot support universal binaries, so ARCHS will
1335+
// contain the target architecture and can only be a single value.
1336+
let arch = scope.evaluate(BuiltinMacros.ARCHS)
1337+
if arch.count > 1 {
1338+
delegate.error("LD_MULTIARCH is 'false', but multiple ARCHS have been given, this is invalid")
1339+
return nil
1340+
}
1341+
if let prefix = archMappings[arch[0]] {
1342+
// Add in the target architecture prefix directory to path for search.
1343+
linkerPath = Path(prefix).join(linkerPath)
1344+
} else {
1345+
delegate.error("Could not find prefix mapping for \(arch[0]) in LD_MULTIARCH_PREFIX_MAP")
1346+
return nil
1347+
}
13151348
}
13161349
guard let toolPath = producer.executableSearchPaths.findExecutable(operatingSystem: producer.hostOperatingSystem, basename: linkerPath.str) else {
13171350
return nil

Sources/SWBCore/Specs/CoreBuildSystem.xcspec

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,11 @@ When `GENERATE_INFOPLIST_FILE` is enabled, sets the value of the [CFBundleIdenti
11481148
DefaultValue = NO;
11491149
Category = "Linking - Warnings";
11501150
},
1151+
{
1152+
Name = "LD_MULTIARCH";
1153+
Type = Boolean;
1154+
DefaultValue = YES;
1155+
},
11511156
{
11521157
Name = "LIBRARY_SEARCH_PATHS";
11531158
Type = PathList;

Sources/SWBCore/Specs/en.lproj/CoreBuildSystem.strings

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,13 @@ You cannot create a PIE from `.o` files compiled with `-mdynamic-no-pic`. Using
423423
"[LD_WARN_DUPLICATE_LIBRARIES]-name" = "Duplicate Libraries";
424424
"[LD_WARN_DUPLICATE_LIBRARIES]-description" = "Warn for linking the same library multiple times.";
425425

426+
"[LD_MULTIARCH]-name" = "Multiple Architecture Supporte Linker";
427+
"[LD_MULTIARCH]-description" = "Linker supports linking multiple target architectures.";
428+
429+
"[LD_MULTIARCH_PREFIX_MAP]-name" = "Linker subfolder architecture map";
430+
"[LD_MULTIARCH_PREFIX_MAP]-description" = "Mapping of architecture to subfolder. <arch>:<subfolder>";
431+
432+
426433
// Localization Settings
427434

428435
"[Localization]-category" = "Localization";

Sources/SWBTestSupport/CoreBasedTests.swift

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -272,53 +272,55 @@ extension CoreBasedTests {
272272
return nil
273273
}
274274
}
275-
package var linkPath: Path? {
275+
package var lldPath: Path? {
276276
get async throws {
277277
let (core, defaultToolchain) = try await coreAndToolchain()
278-
if core.hostOperatingSystem != .windows {
279-
// Most unixes have a link executable, but that is not a linker
280-
return nil
281-
}
282-
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "link") {
278+
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.lld") {
283279
return executable
284280
}
285281
for platform in core.platformRegistry.platforms {
286-
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "link") {
282+
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.lld") {
287283
return executable
288284
}
289285
}
290286
return nil
291287
}
292288
}
293-
294-
package var lldPath: Path? {
289+
package var goldPath: Path? {
295290
get async throws {
296291
let (core, defaultToolchain) = try await coreAndToolchain()
297-
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.ld") {
292+
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold") {
298293
return executable
299294
}
300295
for platform in core.platformRegistry.platforms {
301-
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.ld") {
296+
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold") {
302297
return executable
303298
}
304299
}
305300
return nil
306301
}
307302
}
303+
package func linkPath(_ targetArchitecture: String) async throws -> Path? {
304+
let (core, defaultToolchain) = try await self.coreAndToolchain()
305+
let prefixMapping = [ "x86_64": "x64", "aarch64": "arm64", "arm64": "arm64" ]
308306

309-
package var goldPath: Path? {
310-
get async throws {
311-
let (core, defaultToolchain) = try await coreAndToolchain()
312-
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold") {
307+
guard let prefix = prefixMapping[targetArchitecture] else {
308+
return nil
309+
}
310+
let linkerPath = Path(prefix).join("link").str
311+
if core.hostOperatingSystem != .windows {
312+
// Most unixes have a link executable, but that is not a linker
313+
return nil
314+
}
315+
if let executable = defaultToolchain.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: linkerPath) {
316+
return executable
317+
}
318+
for platform in core.platformRegistry.platforms {
319+
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: linkerPath) {
313320
return executable
314321
}
315-
for platform in core.platformRegistry.platforms {
316-
if let executable = platform.executableSearchPaths.findExecutable(operatingSystem: core.hostOperatingSystem, basename: "ld.gold") {
317-
return executable
318-
}
319-
}
320-
return nil
321322
}
323+
return nil
322324
}
323325
}
324326

Sources/SWBWindowsPlatform/Plugin.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ struct WindowsPlatformExtension: PlatformInfoExtension {
5959
guard let dir = try? await findLatestInstallDirectory(fs: fs) else {
6060
return []
6161
}
62+
// Note: Do not add in the target directories under the host as these will end up in the global search paths, i.e. PATH
63+
// Let the commandlinetool discovery add in the target subdirectory based on the targeted architecture.
6264
if Architecture.hostStringValue == "aarch64" {
63-
return [dir.join("bin/Hostarm64/arm64")]
65+
return [dir.join("bin/Hostarm64")]
6466
} else {
65-
return [dir.join("bin/Hostx64/x64")]
67+
return [dir.join("bin/Hostx64")]
6668
}
6769
}
6870
}

Sources/SWBWindowsPlatform/Specs/WindowsLd.xcspec

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@
5252
CommandLineFlag = "--sysroot";
5353
IsInputDependency = Yes;
5454
},
55+
{
56+
Name = LD_MULTIARCH;
57+
Type = Boolean;
58+
DefaultValue = NO;
59+
Condition = "$(ALTERNATE_LINKER) == link";
60+
},
61+
{
62+
Name = LD_MULTIARCH_PREFIX_MAP;
63+
Type = StringList;
64+
DefaultValue = "x86_64:x64 aarch64:arm64 arm64:arm64";
65+
Condition = "$(ALTERNATE_LINKER) == link";
66+
},
5567
{
5668
// No such concept
5769
Name = LD_DETERMINISTIC_MODE;

Tests/SWBBuildSystemTests/LinkerTests.swift

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ fileprivate struct LinkerTests: CoreBasedTests {
4242
"PRODUCT_NAME": "$(TARGET_NAME)",
4343
"SWIFT_VERSION": swiftVersion,
4444
"OTHER_LDFLAGS": "-not-a-real-flag",
45+
"ARCHS" : "x86_64 aarch64"
4546
])
4647
],
4748
buildPhases: [
@@ -243,25 +244,22 @@ fileprivate struct LinkerTests: CoreBasedTests {
243244
let ldLinkerPath = try await self.ldPath
244245
let lldLinkerPath = try await self.lldPath
245246
let goldLinkerPath = try await self.goldPath
246-
let linkLinkerPath = try await self.linkPath
247-
let installedLinkerPaths = [lldLinkerPath, ldLinkerPath, goldLinkerPath, linkLinkerPath].compactMap { $0 }
247+
let linkLinkerPathX86 = try await self.linkPath("x86_64")
248+
let linkLinkerPathAarch64 = try await self.linkPath("aarch64")
249+
var installedLinkerPaths = [ldLinkerPath, lldLinkerPath, goldLinkerPath, linkLinkerPathX86, linkLinkerPathAarch64].compactMap { $0 }
248250

249251
// Default Linker
250-
var parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": ""])
252+
var parameters = BuildParameters(configuration: "Debug")
251253
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
252254
results.checkTask(.matchRuleType("Ld")) { task in
253-
results.checkTaskOutput(task) { taskOutput in
254-
results.checkTaskOutput(task) { output in
255-
// Expect that one of the installed linkers is used, we are not sure which one.
256-
if runDestination == .windows && Architecture.hostStringValue == "aarch64" {
257-
withKnownIssue("'clang' picks the wrong binary for link.exe using x86 version") {
258-
// On windows aarch64 'clang' picks the wrong host architecture for link.exe, choosing "MSVC\14.41.34120\bin\Hostx86\arm64\link.exe"
259-
#expect(installedLinkerPaths.map { $0.str }.contains(where: output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains))
260-
}
261-
} else {
262-
#expect(installedLinkerPaths.map { $0.str }.contains(where: output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains))
255+
results.checkTaskOutput(task) { output in
256+
if runDestination == .windows {
257+
// clang will choose to run lld-link rather than ld.lld.exe.
258+
if let lldLinkerPath {
259+
installedLinkerPaths.append(lldLinkerPath.dirname.join("lld-link"))
263260
}
264261
}
262+
#expect(installedLinkerPaths.map { $0.str }.contains(where: output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains))
265263
}
266264
}
267265
results.checkNoDiagnostics()
@@ -286,12 +284,6 @@ fileprivate struct LinkerTests: CoreBasedTests {
286284
if let lldLinkerPath {
287285
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": "lld"])
288286
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
289-
if runDestination == .windows {
290-
// Issue: Linker cannot find dependent library
291-
results.checkError(.contains("Linker command failed with exit code 1"))
292-
results.checkError(.contains("lld-link: error: could not open 'Library.lib'"))
293-
}
294-
295287
results.checkTask(.matchRuleType("Ld")) { task in
296288
task.checkCommandLineContains(["-fuse-ld=lld"])
297289
results.checkTaskOutput(task) { output in
@@ -324,26 +316,46 @@ fileprivate struct LinkerTests: CoreBasedTests {
324316
}
325317

326318
// link.exe
327-
if let linkLinkerPath {
328-
parameters = BuildParameters(configuration: "Debug", overrides: ["ALTERNATE_LINKER": "link"])
319+
if let linkLinkerPathX86 {
320+
parameters = BuildParameters(configuration: "Debug", overrides: ["ARCHS": "x86_64", "ALTERNATE_LINKER": "link"])
329321
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
330322
results.checkTask(.matchRuleType("Ld")) { task in
331323
task.checkCommandLineContains(["-fuse-ld=link"])
332324
results.checkTaskOutput(task) { output in
333325
// Expect that the 'link' linker is called by clang
334326
if runDestination == .windows && Architecture.hostStringValue == "aarch64" {
335-
withKnownIssue("'clang' picks the wrong binary for link.exe using Hostx86 version") {
336-
// On windows aarch64 'clang' picks the wrong host architecture for link.exe, choosing "MSVC\14.41.34120\bin\Hostx86\arm64\link.exe"
337-
#expect(output.asString.replacingOccurrences(of: "Hostx86", with: "Hostarm64").contains(linkLinkerPath.str))
327+
// On windows aarch64 'clang' picks the wrong host architecture for link.exe, choosing "MSVC\14.41.34120\bin\Hostx86\x64\link.exe"
328+
withKnownIssue("'clang' picks the wrong binary for link.exe using the Hostx86 version") {
329+
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(linkLinkerPathX86.str))
338330
}
339331
} else {
340-
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(linkLinkerPath.str))
332+
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(linkLinkerPathX86.str))
341333
}
342334
}
343335
}
344336
results.checkNoDiagnostics()
345337
}
346338
}
339+
if let linkLinkerPathAarch64 {
340+
parameters = BuildParameters(configuration: "Debug", overrides: ["ARCHS": "aarch64", "ALTERNATE_LINKER": "link"])
341+
try await tester.checkBuild(parameters: parameters, runDestination: .host) { results in
342+
results.checkTask(.matchRuleType("Ld")) { task in
343+
task.checkCommandLineContains(["-fuse-ld=link"])
344+
results.checkTaskOutput(task) { output in
345+
// Expect that the 'link' linker is called by clang
346+
if runDestination == .windows && Architecture.hostStringValue == "aarch64" {
347+
// On windows aarch64 'clang' picks the wrong host architecture for link.exe, choosing "MSVC\14.41.34120\bin\Hostx86\x64\link.exe"
348+
withKnownIssue("'clang' picks the wrong binary for link.exe using the Hostx86 version") {
349+
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(linkLinkerPathAarch64.str))
350+
}
351+
} else {
352+
#expect(output.asString.replacingOccurrences(of: "\\\\", with: "\\").contains(linkLinkerPathAarch64.str))
353+
}
354+
}
355+
}
356+
results.checkNoDiagnostics()
357+
}
358+
}
347359
}
348360
}
349361
}

0 commit comments

Comments
 (0)