Skip to content

Commit a04d98e

Browse files
authored
Merge pull request #269 from kateinoigakukun/katei/llvm-lto-driver
Handle LLVM LTO option to perform thin and full link-time-optimization
2 parents 5940330 + 88b7a84 commit a04d98e

11 files changed

+122
-13
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public struct Driver {
165165
/// The type of the primary output generated by the compiler.
166166
@_spi(Testing) public let compilerOutputType: FileType?
167167

168+
/// The type of the link-time-optimization we expect to perform.
169+
@_spi(Testing) public let lto: LTOKind?
170+
168171
/// The type of the primary output generated by the linker.
169172
@_spi(Testing) public let linkerOutputType: LinkOutputType?
170173

@@ -376,6 +379,7 @@ public struct Driver {
376379
self.allSourcesFileList = nil
377380
}
378381

382+
self.lto = Self.ltoKind(&parsedOptions, diagnosticsEngine: diagnosticsEngine)
379383
// Figure out the primary outputs from the driver.
380384
(self.compilerOutputType, self.linkerOutputType) = Self.determinePrimaryOutputs(&parsedOptions, driverKind: driverKind, diagnosticsEngine: diagnosticEngine)
381385

@@ -589,6 +593,18 @@ extension Driver {
589593
}
590594
}
591595

596+
extension Driver {
597+
private static func ltoKind(_ parsedOptions: inout ParsedOptions,
598+
diagnosticsEngine: DiagnosticsEngine) -> LTOKind? {
599+
guard let arg = parsedOptions.getLastArgument(.lto)?.asSingle else { return nil }
600+
guard let kind = LTOKind(rawValue: arg) else {
601+
diagnosticsEngine.emit(.error_invalid_arg_value(arg: .lto, value: arg))
602+
return nil
603+
}
604+
return kind
605+
}
606+
}
607+
592608
// MARK: - Response files.
593609
extension Driver {
594610
/// Tokenize a single line in a response file.
@@ -1034,6 +1050,7 @@ extension Driver {
10341050
// By default, the driver does not link its output. However, this will be updated below.
10351051
var compilerOutputType: FileType? = (driverKind == .interactive ? nil : .object)
10361052
var linkerOutputType: LinkOutputType? = nil
1053+
let objectLikeFileType: FileType = parsedOptions.getLastArgument(.lto) != nil ? .llvmBitcode : .object
10371054

10381055
if let outputOption = parsedOptions.getLast(in: .modes) {
10391056
switch outputOption.option {
@@ -1042,11 +1059,11 @@ extension Driver {
10421059
diagnosticsEngine.emit(.error_static_emit_executable_disallowed)
10431060
}
10441061
linkerOutputType = .executable
1045-
compilerOutputType = .object
1062+
compilerOutputType = objectLikeFileType
10461063

10471064
case .emitLibrary:
10481065
linkerOutputType = parsedOptions.hasArgument(.static) ? .staticLibrary : .dynamicLibrary
1049-
compilerOutputType = .object
1066+
compilerOutputType = objectLikeFileType
10501067

10511068
case .emitObject, .c:
10521069
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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ extension Driver {
103103
func addLinkerInput(_ li: TypedVirtualPath) { linkerInputs.append(li) }
104104

105105
var moduleInputs = [TypedVirtualPath]()
106+
let acceptBitcodeAsLinkerInput = lto == .llvmThin || lto == .llvmFull
106107
func addModuleInput(_ mi: TypedVirtualPath) { moduleInputs.append(mi) }
107108
var moduleInputsFromJobOutputs = [TypedVirtualPath]()
108109
func addModuleInputFromJobOutputs(_ mis: TypedVirtualPath) {
@@ -113,7 +114,8 @@ extension Driver {
113114
switch jobOutput.type {
114115
case .object, .autolink:
115116
addLinkerInput(jobOutput)
116-
117+
case .llvmBitcode where acceptBitcodeAsLinkerInput:
118+
addLinkerInput(jobOutput)
117119
case .swiftModule:
118120
addModuleInputFromJobOutputs(jobOutput)
119121

@@ -245,7 +247,7 @@ extension Driver {
245247
addJob(job)
246248
}
247249

248-
case .object, .autolink:
250+
case .object, .autolink, .llvmBitcode:
249251
if linkerOutputType != nil {
250252
addLinkerInput(input)
251253
} else {

Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ import TSCBasic
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 enum Tool {
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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ final class SwiftDriverTests: XCTestCase {
249249
let driver3 = try Driver(args: ["swiftc", "-static", "foo.swift", "-emit-library"])
250250
XCTAssertEqual(driver3.compilerOutputType, .object)
251251
XCTAssertEqual(driver3.linkerOutputType, .staticLibrary)
252+
253+
let driver4 = try Driver(args: ["swiftc", "-lto=llvm-thin", "foo.swift", "-emit-library"])
254+
XCTAssertEqual(driver4.compilerOutputType, .llvmBitcode)
255+
let driver5 = try Driver(args: ["swiftc", "-lto=llvm-full", "foo.swift", "-emit-library"])
256+
XCTAssertEqual(driver5.compilerOutputType, .llvmBitcode)
252257
}
253258

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

856889
#if os(macOS)
857890
// dsymutil won't be found on Linux.
@@ -2437,6 +2470,18 @@ final class SwiftDriverTests: XCTestCase {
24372470
}
24382471
}
24392472

2473+
func testLTOOption() throws {
2474+
XCTAssertEqual(try Driver(args: ["swiftc"]).lto, nil)
2475+
2476+
XCTAssertEqual(try Driver(args: ["swiftc", "-lto=llvm-thin"]).lto, .llvmThin)
2477+
2478+
XCTAssertEqual(try Driver(args: ["swiftc", "-lto=llvm-full"]).lto, .llvmFull)
2479+
2480+
try assertDriverDiagnostics(args: ["swiftc", "-lto=nop"]) { driver, verify in
2481+
verify.expect(.error("invalid value 'nop' in '-lto='"))
2482+
}
2483+
}
2484+
24402485
func testScanDependenciesOption() throws {
24412486
do {
24422487
var driver = try Driver(args: ["swiftc", "-scan-dependencies", "foo.swift"])

0 commit comments

Comments
 (0)