Skip to content

Commit c22ef1a

Browse files
Handle LLVM LTO option to perform thin and full link-time-optimization
1 parent 1e58c23 commit c22ef1a

11 files changed

+106
-13
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ public struct Driver {
151151
/// The type of the primary output generated by the compiler.
152152
public let compilerOutputType: FileType?
153153

154+
/// The type of the link-time-optimization we expect to perform.
155+
public let lto: LTOKind?
156+
154157
/// The type of the primary output generated by the linker.
155158
public let linkerOutputType: LinkOutputType?
156159

@@ -362,6 +365,7 @@ public struct Driver {
362365
self.allSourcesFileList = nil
363366
}
364367

368+
self.lto = Self.ltoKind(&parsedOptions)
365369
// Figure out the primary outputs from the driver.
366370
(self.compilerOutputType, self.linkerOutputType) = Self.determinePrimaryOutputs(&parsedOptions, driverKind: driverKind, diagnosticsEngine: diagnosticEngine)
367371

@@ -575,6 +579,15 @@ extension Driver {
575579
}
576580
}
577581

582+
extension Driver {
583+
private static func ltoKind(_ parsedOptions: inout ParsedOptions) -> LTOKind? {
584+
guard let arg = parsedOptions.getLastArgument(.lto)?.asSingle else {
585+
return nil
586+
}
587+
return LTOKind(rawValue: arg)
588+
}
589+
}
590+
578591
// MARK: - Response files.
579592
extension Driver {
580593
/// Tokenize a single line in a response file.
@@ -1020,6 +1033,7 @@ extension Driver {
10201033
// By default, the driver does not link its output. However, this will be updated below.
10211034
var compilerOutputType: FileType? = (driverKind == .interactive ? nil : .object)
10221035
var linkerOutputType: LinkOutputType? = nil
1036+
let objectLikeFileType: FileType = parsedOptions.getLastArgument(.lto) != nil ? .llvmBitcode : .object
10231037

10241038
if let outputOption = parsedOptions.getLast(in: .modes) {
10251039
switch outputOption.option {
@@ -1028,11 +1042,11 @@ extension Driver {
10281042
diagnosticsEngine.emit(.error_static_emit_executable_disallowed)
10291043
}
10301044
linkerOutputType = .executable
1031-
compilerOutputType = .object
1045+
compilerOutputType = objectLikeFileType
10321046

10331047
case .emitLibrary:
10341048
linkerOutputType = parsedOptions.hasArgument(.static) ? .staticLibrary : .dynamicLibrary
1035-
compilerOutputType = .object
1049+
compilerOutputType = objectLikeFileType
10361050

10371051
case .emitObject, .c:
10381052
compilerOutputType = .object

Sources/SwiftDriver/Driver/LinkKind.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ public enum LinkOutputType {
2121
/// A static library (e.g., .a or .lib)
2222
case staticLibrary
2323
}
24+
25+
/// Describes the kind of link-time-optimization we expect to perform.
26+
public enum LTOKind: String, Hashable {
27+
/// Perform LLVM ThinLTO.
28+
case llvmThin = "llvm-thin"
29+
/// Perform LLVM full LTO.
30+
case llvmFull = "llvm-full"
31+
}

Sources/SwiftDriver/Jobs/AutolinkExtractJob.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension Driver {
1717
// pull the info we need from the .o files directly and pass them as an
1818
// argument input file to the linker.
1919
// FIXME: Also handle Cygwin and MinGW
20-
guard inputs.count > 0 && targetTriple.objectFormat == .elf else {
20+
guard inputs.count > 0 && targetTriple.objectFormat == .elf && lto == nil else {
2121
return nil
2222
}
2323

Sources/SwiftDriver/Jobs/DarwinToolchain+LinkerSupport.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import TSCUtility
1414
import SwiftOptions
1515

1616
extension DarwinToolchain {
17-
internal func findARCLiteLibPath() throws -> AbsolutePath? {
17+
internal func findXcodeClangLibPath(_ additionalPath: String) throws -> AbsolutePath? {
1818
let path = try getToolPath(.swiftCompiler)
1919
.parentDirectory // 'swift'
2020
.parentDirectory // 'bin'
21-
.appending(components: "lib", "arc")
21+
.appending(components: "lib", additionalPath)
2222

2323
if fileSystem.exists(path) { return path }
2424

@@ -28,11 +28,22 @@ extension DarwinToolchain {
2828
return clangPath
2929
.parentDirectory // 'clang'
3030
.parentDirectory // 'bin'
31-
.appending(components: "lib", "arc")
31+
.appending(components: "lib", additionalPath)
3232
}
3333
return nil
3434
}
3535

36+
internal func findARCLiteLibPath() throws -> AbsolutePath? {
37+
return try findXcodeClangLibPath("arc")
38+
}
39+
40+
internal func addLTOLibArgs(to commandLine: inout [Job.ArgTemplate]) throws {
41+
if let path = try findXcodeClangLibPath("libLTO.dylib") {
42+
commandLine.appendFlag("-lto_library")
43+
commandLine.appendPath(path)
44+
}
45+
}
46+
3647
func addLinkRuntimeLibraryRPath(
3748
to commandLine: inout [Job.ArgTemplate],
3849
parsedOptions: inout ParsedOptions,
@@ -183,6 +194,7 @@ extension DarwinToolchain {
183194
inputs: [TypedVirtualPath],
184195
outputFile: VirtualPath,
185196
shouldUseInputFileList: Bool,
197+
lto: LTOKind?,
186198
sdkPath: String?,
187199
sanitizers: Set<Sanitizer>,
188200
targetInfo: FrontendTargetInfo
@@ -260,7 +272,7 @@ extension DarwinToolchain {
260272
try commandLine.appendAllArguments(.Xlinker, from: &parsedOptions)
261273

262274
case .staticLibrary:
263-
linkerTool = .staticLinker
275+
linkerTool = .staticLinker(lto)
264276
commandLine.appendFlag(.static)
265277
}
266278

@@ -269,6 +281,9 @@ extension DarwinToolchain {
269281
parsedOptions: &parsedOptions,
270282
targetTriple: targetTriple
271283
)
284+
285+
try addLTOLibArgs(to: &commandLine)
286+
272287
let targetVariantTriple = targetInfo.targetVariant?.triple
273288
addDeploymentTargetArgs(
274289
to: &commandLine,
@@ -320,6 +335,8 @@ extension DarwinToolchain {
320335
inputModules.append(input.file)
321336
} else if input.type == .object {
322337
inputPaths.append(input.file)
338+
} else if input.type == .llvmBitcode {
339+
inputPaths.append(input.file)
323340
}
324341
}
325342
commandLine.appendPath(.fileList(path, .list(inputPaths)))
@@ -337,6 +354,8 @@ extension DarwinToolchain {
337354
return [.flag("-add_ast_path"), .path(path.file)]
338355
} else if path.type == .object {
339356
return [.path(path.file)]
357+
} else if path.type == .llvmBitcode {
358+
return [.path(path.file)]
340359
} else {
341360
return []
342361
}

Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ extension Driver {
177177
try commandLine.appendLast(.localizationPath, from: &parsedOptions)
178178
try commandLine.appendLast(.requireExplicitAvailability, from: &parsedOptions)
179179
try commandLine.appendLast(.requireExplicitAvailabilityTarget, from: &parsedOptions)
180+
try commandLine.appendLast(.lto, from: &parsedOptions)
180181
try commandLine.appendAll(.D, from: &parsedOptions)
181182
try commandLine.appendAll(.sanitizeEQ, from: &parsedOptions)
182183
try commandLine.appendAll(.debugPrefixMap, from: &parsedOptions)

Sources/SwiftDriver/Jobs/GenericUnixToolchain+LinkerSupport.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extension GenericUnixToolchain {
5050
inputs: [TypedVirtualPath],
5151
outputFile: VirtualPath,
5252
shouldUseInputFileList: Bool,
53+
lto: LTOKind?,
5354
sdkPath: String?,
5455
sanitizers: Set<Sanitizer>,
5556
targetInfo: FrontendTargetInfo
@@ -70,6 +71,8 @@ extension GenericUnixToolchain {
7071
var linker: String?
7172
if let arg = parsedOptions.getLastArgument(.useLd) {
7273
linker = arg.asSingle
74+
} else if lto != nil {
75+
linker = "lld"
7376
} else {
7477
linker = defaultLinker(for: targetTriple)
7578
}
@@ -247,6 +250,15 @@ extension GenericUnixToolchain {
247250
commandLine.appendFlag("-u__llvm_profile_runtime")
248251
}
249252

253+
if let lto = lto {
254+
switch lto {
255+
case .llvmFull:
256+
commandLine.appendFlag("-flto=full")
257+
case .llvmThin:
258+
commandLine.appendFlag("-flto=thin")
259+
}
260+
}
261+
250262
// Run clang++ in verbose mode if "-v" is set
251263
try commandLine.appendLast(.v, from: &parsedOptions)
252264

@@ -268,7 +280,7 @@ extension GenericUnixToolchain {
268280
commandLine.appendPath(outputFile)
269281

270282
commandLine.append(contentsOf: inputs.map { .path($0.file) })
271-
return try getToolPath(.staticLinker)
283+
return try getToolPath(.staticLinker(lto))
272284
}
273285

274286
}

Sources/SwiftDriver/Jobs/LinkJob.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ extension Driver {
4545
inputs: inputs,
4646
outputFile: outputFile,
4747
shouldUseInputFileList: shouldUseInputFileList,
48+
lto: lto,
4849
sdkPath: sdkPath,
4950
sanitizers: enabledSanitizers,
5051
targetInfo: frontendTargetInfo

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ extension Driver {
4343
switch jobOutput.type {
4444
case .object, .autolink:
4545
linkerInputs.append(jobOutput)
46-
46+
case .llvmBitcode where lto == .llvmThin || lto == .llvmFull:
47+
linkerInputs.append(jobOutput)
4748
case .swiftModule:
4849
moduleInputsFromJobOutputs.append(jobOutput)
4950

@@ -160,7 +161,7 @@ extension Driver {
160161
addJobOutputs(jobOutputs)
161162
}
162163

163-
case .object, .autolink:
164+
case .object, .autolink, .llvmBitcode:
164165
if linkerOutputType != nil {
165166
linkerInputs.append(input)
166167
} else {

Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ public final class GenericUnixToolchain: Toolchain {
5454
switch tool {
5555
case .swiftCompiler:
5656
return try lookup(executable: "swift-frontend")
57-
case .staticLinker:
57+
case .staticLinker(nil):
5858
return try lookup(executable: "ar")
59+
case .staticLinker(.llvmFull),
60+
.staticLinker(.llvmThin):
61+
return try lookup(executable: "llvm-ar")
5962
case .dynamicLinker:
6063
// FIXME: This needs to look in the tools_directory first.
6164
return try lookup(executable: "clang")

Sources/SwiftDriver/Toolchains/Toolchain.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import Foundation
1313
import TSCBasic
1414
import SwiftOptions
1515

16-
public enum Tool {
16+
public enum Tool: Hashable {
1717
case swiftCompiler
18-
case staticLinker
18+
case staticLinker(LTOKind?)
1919
case dynamicLinker
2020
case clang
2121
case swiftAutolinkExtract
@@ -66,6 +66,7 @@ public protocol Toolchain {
6666
inputs: [TypedVirtualPath],
6767
outputFile: VirtualPath,
6868
shouldUseInputFileList: Bool,
69+
lto: LTOKind?,
6970
sdkPath: String?,
7071
sanitizers: Set<Sanitizer>,
7172
targetInfo: FrontendTargetInfo

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ final class SwiftDriverTests: XCTestCase {
248248
let driver3 = try Driver(args: ["swiftc", "-static", "foo.swift", "-emit-library"])
249249
XCTAssertEqual(driver3.compilerOutputType, .object)
250250
XCTAssertEqual(driver3.linkerOutputType, .staticLibrary)
251+
252+
let driver4 = try Driver(args: ["swiftc", "-lto=llvm-thin", "foo.swift", "-emit-library"])
253+
XCTAssertEqual(driver4.compilerOutputType, .llvmBitcode)
254+
let driver5 = try Driver(args: ["swiftc", "-lto=llvm-full", "foo.swift", "-emit-library"])
255+
XCTAssertEqual(driver5.compilerOutputType, .llvmBitcode)
251256
}
252257

253258
func testPrimaryOutputKindsDiagnostics() throws {
@@ -851,6 +856,34 @@ final class SwiftDriverTests: XCTestCase {
851856
XCTAssertFalse(cmd.contains(.flag("-dylib")))
852857
XCTAssertFalse(cmd.contains(.flag("-shared")))
853858
}
859+
860+
do {
861+
// lto linking
862+
var driver1 = try Driver(args: commonArgs + ["-emit-executable", "-target", "x86_64-apple-macosx10.15", "-lto=llvm-thin"], env: env)
863+
let plannedJobs1 = try driver1.planBuild()
864+
XCTAssertFalse(plannedJobs1.contains(where: { $0.kind == .autolinkExtract }))
865+
let linkJob1 = plannedJobs1.first(where: { $0.kind == .link })
866+
XCTAssertTrue(linkJob1?.tool.name.contains("ld"))
867+
XCTAssertTrue(linkJob1?.commandLine.contains(.flag("-lto_library")))
868+
869+
var driver2 = try Driver(args: commonArgs + ["-emit-executable", "-target", "x86_64-unknown-linux", "-lto=llvm-thin"], env: env)
870+
let plannedJobs2 = try driver2.planBuild()
871+
XCTAssertFalse(plannedJobs2.contains(where: { $0.kind == .autolinkExtract }))
872+
let linkJob2 = plannedJobs2.first(where: { $0.kind == .link })
873+
XCTAssertTrue(linkJob2?.tool.name.contains("clang"))
874+
XCTAssertTrue(linkJob2?.commandLine.contains(.flag("-flto=thin")))
875+
876+
var driver3 = try Driver(args: commonArgs + ["-emit-executable", "-target", "x86_64-unknown-linux", "-lto=llvm-full"], env: env)
877+
let plannedJobs3 = try driver3.planBuild()
878+
XCTAssertFalse(plannedJobs3.contains(where: { $0.kind == .autolinkExtract }))
879+
880+
let compileJob3 = try XCTUnwrap(plannedJobs3.first(where: { $0.kind == .compile }))
881+
XCTAssertTrue(compileJob3.outputs.contains { $0.file.basename.hasSuffix(".bc") })
882+
883+
let linkJob3 = try XCTUnwrap(plannedJobs3.first(where: { $0.kind == .link }))
884+
XCTAssertTrue(linkJob3.tool.name.contains("clang"))
885+
XCTAssertTrue(linkJob3.commandLine.contains(.flag("-flto=full")))
886+
}
854887

855888
#if os(macOS)
856889
// dsymutil won't be found on Linux.

0 commit comments

Comments
 (0)