From 5c8c15940f040b59d9682a64ef4f626b39b2c0df Mon Sep 17 00:00:00 2001 From: Chirag Ramani Date: Tue, 16 Sep 2025 13:23:44 -0700 Subject: [PATCH 1/2] Add ir and cs instrumentation options --- Sources/SwiftDriver/Driver/Driver.swift | 89 ++- .../Jobs/DarwinToolchain+LinkerSupport.swift | 4 +- .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 3 + .../GenericUnixToolchain+LinkerSupport.swift | 2 +- .../WebAssemblyToolchain+LinkerSupport.swift | 2 +- .../Jobs/WindowsToolchain+LinkerSupport.swift | 7 +- .../SwiftDriver/Toolchains/Toolchain.swift | 26 + Sources/SwiftOptions/Options.swift | 6 + Tests/SwiftDriverTests/SwiftDriverTests.swift | 584 ++++++++++++++++++ 9 files changed, 702 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 97ecf7616..b5cc54b03 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -3307,24 +3307,87 @@ extension Driver { } } + static private func validateProfilingGenerateArgs( + _ parsedOptions: inout ParsedOptions, + diagnosticEngine: DiagnosticsEngine + ) { + let genFlags: [Option] = [ + .profileGenerate, + .irProfileGenerate, + .csProfileGenerate, + .csProfileGenerateEq, + ] + + var providedGen = genFlags.filter { parsedOptions.hasArgument($0) } + if parsedOptions.hasArgument(.csProfileGenerate), + parsedOptions.hasArgument(.csProfileGenerateEq) + { + // If both forms were specified, report a clear conflict. + diagnosticEngine.emit( + .error(Error.conflictingOptions(.csProfileGenerate, .csProfileGenerateEq)), + location: nil + ) + providedGen.removeAll { $0 == .csProfileGenerateEq } + } + + guard providedGen.count >= 2 else { return } + for i in 1.. 1 { + for i in 0..<(providedUse.count - 1) { + for j in (i + 1)..= 2 { - for i in 1.. [String] { + var args: [String] = [] + + if options.contains(.profileGenerate) || options.contains(.irProfileGenerate) { + args.append("-fprofile-generate") + } + + if options.contains(.csProfileGenerate) { + args.append("-fcs-profile-generate") + } + + if options.contains(.csProfileGenerateEq), + let path = options.getLastArgument(.csProfileGenerateEq)?.asSingle { + args.append("-fcs-profile-generate=\(path)") + } + + return args + } + + internal func needsInstrumentedProfile(from parsedOptions: inout ParsedOptions) -> Bool { + parsedOptions.contains(.profileGenerate) || + parsedOptions.contains(.irProfileGenerate) || + parsedOptions.contains(.csProfileGenerate) || + parsedOptions.contains(.csProfileGenerateEq) + } } @_spi(Testing) public enum ToolchainError: Swift.Error { diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index c3c9360fb..cd2a24a82 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -776,6 +776,9 @@ extension Option { public static let printZeroStats: Option = Option("-print-zero-stats", .flag, attributes: [.helpHidden, .frontend], helpText: "Prints all stats even if they are zero") public static let profileCoverageMapping: Option = Option("-profile-coverage-mapping", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate coverage data for use with profiled execution counts") public static let profileGenerate: Option = Option("-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect execution counts") + public static let irProfileGenerate: Option = Option("-ir-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") + public static let csProfileGenerate: Option = Option("-cs-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect context sensitive execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") + public static let csProfileGenerateEq: Option = Option("-cs-profile-generate=", .joined, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "", helpText: "Generate instrumented code to collect context sensitive execution counts into /default.profraw (overridden by LLVM_PROFILE_FILE env var)") public static let profileSampleUse: Option = Option("-profile-sample-use=", .joined, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "", helpText: "Supply sampling-based profiling data from llvm-profdata to enable profile-guided optimization") public static let profileStatsEntities: Option = Option("-profile-stats-entities", .flag, attributes: [.helpHidden, .frontend], helpText: "Profile changes to stats in -stats-output-dir, subdivided by source entity") public static let profileStatsEvents: Option = Option("-profile-stats-events", .flag, attributes: [.helpHidden, .frontend], helpText: "Profile changes to stats in -stats-output-dir") @@ -1749,6 +1752,9 @@ extension Option { Option.printZeroStats, Option.profileCoverageMapping, Option.profileGenerate, + Option.irProfileGenerate, + Option.csProfileGenerate, + Option.csProfileGenerateEq, Option.profileSampleUse, Option.profileStatsEntities, Option.profileStatsEvents, diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index 33987889f..aa02ba185 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -4649,6 +4649,11 @@ final class SwiftDriverTests: XCTestCase { $1.expect(.error(Driver.Error.missingProfilingData(try toPath("profile.profdata").name))) } + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-profile-use=profile.profdata"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .profileUse))) + $1.expect(.error(Driver.Error.missingProfilingData(try toPath("profile.profdata").name))) + } + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-profile-sample-use=profile1.profdata", "-profile-use=profile2.profdata"]) { $1.expect(.error(Driver.Error.conflictingOptions(.profileUse, .profileSampleUse))) $1.expect(.error(Driver.Error.missingProfilingData(try toPath("profile1.profdata").name))) @@ -4677,6 +4682,20 @@ final class SwiftDriverTests: XCTestCase { $1.expect(.error(Driver.Error.missingProfilingData(path.appending(component: "profile.profdata,profile2.profdata").pathString))) } } + + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-profile-generate", "-ir-profile-generate"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.profileGenerate, .irProfileGenerate))) + } + + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-cs-profile-generate"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .csProfileGenerate))) + } + + try withTemporaryDirectory { directoryPath in + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-cs-profile-generate=\(directoryPath)"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .csProfileGenerateEq))) + } + } } func testProfileSampleUseFrontendFlags() throws { @@ -4902,6 +4921,571 @@ final class SwiftDriverTests: XCTestCase { } } + func testIRProfileLinkerArgs() throws { + var envVars = ProcessEnv.block + envVars["SWIFT_DRIVER_LD_EXEC"] = ld.nativePathString(escaped: false) + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "x86_64-apple-macosx10.9", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "x86_64-apple-ios7.1-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "arm64-apple-ios7.1", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "x86_64-apple-tvos9.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "arm64-apple-tvos9.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "i386-apple-watchos2.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", "armv7k-apple-watchos2.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fprofile-generate"))) + } + + // FIXME: This will fail when run on macOS, because + // swift-autolink-extract is not present + #if os(Linux) || os(Android) || os(Windows) + for triple in ["aarch64-unknown-linux-android", "x86_64-unknown-linux-gnu"] { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-target", triple, "test.swift"]) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + if triple == "aarch64-unknown-linux-android" { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-aarch64-android.a")) + } else { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-x86_64.a")) + } + XCTAssert(plannedJobs[1].commandLine.contains { $0 == .flag("-u__llvm_profile_runtime") }) + } + #endif + + // -ir-profile-generate should add libclang_rt.profile for WebAssembly targets + try withTemporaryDirectory { resourceDir in + try localFileSystem.writeFileContents(resourceDir.appending(components: "wasi", "static-executable-args.lnk")) { + $0.send("garbage") + } + + var env = ProcessEnv.block + env["SWIFT_DRIVER_SWIFT_AUTOLINK_EXTRACT_EXEC"] = "//bin/swift-autolink-extract" + + for triple in ["wasm32-unknown-wasi", "wasm32-unknown-wasip1-threads"] { + var driver = try Driver(args: [ + "swiftc", "-ir-profile-generate", "-target", triple, "test.swift", + "-resource-dir", resourceDir.pathString + ], env: env) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-wasm32.a")) + } + } + + for explicitUseLd in [true, false] { + var args = ["swiftc", "-ir-profile-generate", "-target", "x86_64-unknown-windows-msvc", "test.swift"] + if explicitUseLd { + // Explicitly passing '-use-ld=lld' should still result in '-lld-allow-duplicate-weak'. + args.append("-use-ld=lld") + } + var driver = try Driver(args: args) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak'. + XCTAssert(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssert(linkCmds.contains([.flag("-Xlinker"), .flag("-lld-allow-duplicate-weak")])) + } + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak' even if the user requests something else. + do { + var driver = try Driver(args: ["swiftc", "-ir-profile-generate", "-use-ld=link", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + XCTAssertFalse(linkCmds.contains(.flag("-fuse-ld=link"))) + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertTrue(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + + do { + // If we're not building for profiling, don't add '-lld-allow-duplicate-weak'. + var driver = try Driver(args: ["swiftc", "-use-ld=lld", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertFalse(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + } + + func testCSProfileLinkerArgs() throws { + var envVars = ProcessEnv.block + envVars["SWIFT_DRIVER_LD_EXEC"] = ld.nativePathString(escaped: false) + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "x86_64-apple-macosx10.9", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "x86_64-apple-ios7.1-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "arm64-apple-ios7.1", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "x86_64-apple-tvos9.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "arm64-apple-tvos9.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "i386-apple-watchos2.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", "armv7k-apple-watchos2.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate"))) + } + + // FIXME: This will fail when run on macOS, because + // swift-autolink-extract is not present + #if os(Linux) || os(Android) || os(Windows) + for triple in ["aarch64-unknown-linux-android", "x86_64-unknown-linux-gnu"] { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-target", triple, "test.swift"]) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + if triple == "aarch64-unknown-linux-android" { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-aarch64-android.a")) + } else { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-x86_64.a")) + } + XCTAssert(plannedJobs[1].commandLine.contains { $0 == .flag("-u__llvm_profile_runtime") }) + } + #endif + + // -cs-profile-generate should add libclang_rt.profile for WebAssembly targets + try withTemporaryDirectory { resourceDir in + try localFileSystem.writeFileContents(resourceDir.appending(components: "wasi", "static-executable-args.lnk")) { + $0.send("garbage") + } + + var env = ProcessEnv.block + env["SWIFT_DRIVER_SWIFT_AUTOLINK_EXTRACT_EXEC"] = "//bin/swift-autolink-extract" + + for triple in ["wasm32-unknown-wasi", "wasm32-unknown-wasip1-threads"] { + var driver = try Driver(args: [ + "swiftc", "-cs-profile-generate", "-target", triple, "test.swift", + "-resource-dir", resourceDir.pathString + ], env: env) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-wasm32.a")) + } + } + + for explicitUseLd in [true, false] { + var args = ["swiftc", "-cs-profile-generate", "-target", "x86_64-unknown-windows-msvc", "test.swift"] + if explicitUseLd { + // Explicitly passing '-use-ld=lld' should still result in '-lld-allow-duplicate-weak'. + args.append("-use-ld=lld") + } + var driver = try Driver(args: args) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak'. + XCTAssert(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssert(linkCmds.contains([.flag("-Xlinker"), .flag("-lld-allow-duplicate-weak")])) + } + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak' even if the user requests something else. + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate", "-use-ld=link", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + XCTAssertFalse(linkCmds.contains(.flag("-fuse-ld=link"))) + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertTrue(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + + do { + // If we're not building for profiling, don't add '-lld-allow-duplicate-weak'. + var driver = try Driver(args: ["swiftc", "-use-ld=lld", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertFalse(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + } + + func testCSEqProfileLinkerArgs() throws { + var envVars = ProcessEnv.block + envVars["SWIFT_DRIVER_LD_EXEC"] = ld.nativePathString(escaped: false) + try withTemporaryDirectory { directoryPath in + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "x86_64-apple-macosx10.9", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "x86_64-apple-ios7.1-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "arm64-apple-ios7.1", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "x86_64-apple-tvos9.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "arm64-apple-tvos9.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "i386-apple-watchos2.0-simulator", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "armv7k-apple-watchos2.0", "test.swift"], + env: envVars) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.contains(.flag("-fcs-profile-generate=\(directoryPath)"))) + } + + // FIXME: This will fail when run on macOS, because + // swift-autolink-extract is not present + #if os(Linux) || os(Android) || os(Windows) + for triple in ["aarch64-unknown-linux-android", "x86_64-unknown-linux-gnu"] { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", triple, "test.swift"]) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + if triple == "aarch64-unknown-linux-android" { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-aarch64-android.a")) + } else { + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-x86_64.a")) + } + XCTAssert(plannedJobs[1].commandLine.contains { $0 == .flag("-u__llvm_profile_runtime") }) + } + #endif + + // -cs-profile-generate= should add libclang_rt.profile for WebAssembly targets + try withTemporaryDirectory { resourceDir in + try localFileSystem.writeFileContents(resourceDir.appending(components: "wasi", "static-executable-args.lnk")) { + $0.send("garbage") + } + + var env = ProcessEnv.block + env["SWIFT_DRIVER_SWIFT_AUTOLINK_EXTRACT_EXEC"] = "//bin/swift-autolink-extract" + + for triple in ["wasm32-unknown-wasi", "wasm32-unknown-wasip1-threads"] { + var driver = try Driver(args: [ + "swiftc", "-cs-profile-generate=\(directoryPath)", "-target", triple, "test.swift", + "-resource-dir", resourceDir.pathString + ], env: env) + let plannedJobs = try driver.planBuild().removingAutolinkExtractJobs() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + XCTAssert(plannedJobs[1].commandLine.containsPathWithBasename("libclang_rt.profile-wasm32.a")) + } + } + + for explicitUseLd in [true, false] { + var args = ["swiftc", "-cs-profile-generate=\(directoryPath)", "-target", "x86_64-unknown-windows-msvc", "test.swift"] + if explicitUseLd { + // Explicitly passing '-use-ld=lld' should still result in '-lld-allow-duplicate-weak'. + args.append("-use-ld=lld") + } + var driver = try Driver(args: args) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak'. + XCTAssert(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssert(linkCmds.contains([.flag("-Xlinker"), .flag("-lld-allow-duplicate-weak")])) + } + + // rdar://131295678 - Make sure we force the use of lld and pass + // '-lld-allow-duplicate-weak' even if the user requests something else. + do { + var driver = try Driver(args: ["swiftc", "-cs-profile-generate=\(directoryPath)", "-use-ld=link", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + + XCTAssertFalse(linkCmds.contains(.flag("-fuse-ld=link"))) + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertTrue(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + + do { + // If we're not building for profiling, don't add '-lld-allow-duplicate-weak'. + var driver = try Driver(args: ["swiftc", "-use-ld=lld", "-target", "x86_64-unknown-windows-msvc", "test.swift"]) + let plannedJobs = try driver.planBuild() + + XCTAssertEqual(plannedJobs.count, 2) + XCTAssertEqual(plannedJobs[0].kind, .compile) + + XCTAssertEqual(plannedJobs[1].kind, .link) + + let linkCmds = plannedJobs[1].commandLine + XCTAssertTrue(linkCmds.contains(.flag("-fuse-ld=lld"))) + XCTAssertFalse(linkCmds.contains(.flag("-lld-allow-duplicate-weak"))) + } + } + } + func testConditionalCompilationArgValidation() throws { try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-DFOO=BAR"]) { $1.expect(.warning("conditional compilation flags do not have values in Swift; they are either present or absent (rather than 'FOO=BAR')")) From 83ce50b5d50c76c74f1ad5d636a02fa077f1957b Mon Sep 17 00:00:00 2001 From: Chirag Ramani Date: Mon, 20 Oct 2025 10:32:53 -0700 Subject: [PATCH 2/2] Update Options.swift --- Sources/SwiftDriver/Driver/Driver.swift | 27 +++++++++++-------- .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 3 ++- .../SwiftDriver/Toolchains/Toolchain.swift | 12 ++++++--- Sources/SwiftOptions/Options.swift | 16 ++++++----- Tests/SwiftDriverTests/SwiftDriverTests.swift | 27 ++++++++++++++++++- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index b5cc54b03..0166703ae 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -3314,21 +3314,25 @@ extension Driver { let genFlags: [Option] = [ .profileGenerate, .irProfileGenerate, + .irProfileGenerateEQ, .csProfileGenerate, - .csProfileGenerateEq, + .csProfileGenerateEQ, ] + func resolveDualFormConflict(_ plain: Option, _ equalsForm: Option) { + if parsedOptions.hasArgument(plain), + parsedOptions.hasArgument(equalsForm) + { + diagnosticEngine.emit( + .error(Error.conflictingOptions(plain, equalsForm)), + location: nil + ) + providedGen.removeAll { $0 == equalsForm } + } + } var providedGen = genFlags.filter { parsedOptions.hasArgument($0) } - if parsedOptions.hasArgument(.csProfileGenerate), - parsedOptions.hasArgument(.csProfileGenerateEq) - { - // If both forms were specified, report a clear conflict. - diagnosticEngine.emit( - .error(Error.conflictingOptions(.csProfileGenerate, .csProfileGenerateEq)), - location: nil - ) - providedGen.removeAll { $0 == .csProfileGenerateEq } - } + resolveDualFormConflict(.irProfileGenerate, .irProfileGenerateEQ) + resolveDualFormConflict(.csProfileGenerate, .csProfileGenerateEQ) guard providedGen.count >= 2 else { return } for i in 1.. Bool { parsedOptions.contains(.profileGenerate) || parsedOptions.contains(.irProfileGenerate) || + parsedOptions.contains(.irProfileGenerateEQ) || parsedOptions.contains(.csProfileGenerate) || - parsedOptions.contains(.csProfileGenerateEq) + parsedOptions.contains(.csProfileGenerateEQ) } } diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index cd2a24a82..1f13dc884 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -105,6 +105,8 @@ extension Option { public static let coveragePrefixMap: Option = Option("-coverage-prefix-map", .separate, attributes: [.frontend], metaVar: "", helpText: "Remap source paths in coverage info") public static let CrossModuleOptimization: Option = Option("-cross-module-optimization", .flag, attributes: [.helpHidden, .frontend], helpText: "Perform cross-module optimization") public static let crosscheckUnqualifiedLookup: Option = Option("-crosscheck-unqualified-lookup", .flag, attributes: [.frontend, .noDriver], helpText: "Compare legacy DeclContext- to ASTScope-based unqualified name lookup (for debugging)") + public static let csProfileGenerateEQ: Option = Option("-cs-profile-generate=", .joined, attributes: [.frontend, .noInteractive], metaVar: "", helpText: "Generate instrumented code to collect context sensitive execution counts into /default.profraw (overridden by LLVM_PROFILE_FILE env var)") + public static let csProfileGenerate: Option = Option("-cs-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect context sensitive execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") public static let cxxInteropGettersSettersAsProperties: Option = Option("-cxx-interop-getters-setters-as-properties", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Import getters and setters as computed properties in Swift") public static let cxxInteropUseOpaquePointerForMoveonly: Option = Option("-cxx-interop-use-opaque-pointer-for-moveonly", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Testing flag that will be eliminated soon. Do not use.") public static let cxxInteroperabilityMode: Option = Option("-cxx-interoperability-mode=", .joined, attributes: [.frontend, .synthesizeInterface], helpText: "Enables C++ interoperability; pass 'default' to enable or 'off' to disable") @@ -643,6 +645,9 @@ extension Option { public static let swiftinterfaceCompilerVersion: Option = Option("-interface-compiler-version", .separate, attributes: [.helpHidden, .frontend], metaVar: "", helpText: "The version of the Swift compiler used to generate a .swiftinterface file") public static let internalizeAtLink: Option = Option("-internalize-at-link", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Allow internalizing public symbols and vtables at link time (assume all client code of public types is part of the same link unit, or that external symbols are explicitly requested via -exported_symbols_list)") public static let interpret: Option = Option("-interpret", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Immediate mode", group: .modes) + public static let irProfileGenerateEQ: Option = Option("-ir-profile-generate=", .joined, attributes: [.frontend, .noInteractive], metaVar: "", helpText: "Generate instrumented code to collect execution counts into /default.profraw (overridden by LLVM_PROFILE_FILE env var)") + public static let irProfileGenerate: Option = Option("-ir-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") + public static let irProfileUse: Option = Option("-ir-profile-use=", .commaJoined, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "", helpText: "Supply an IR-level PGO profdata file to enable profile-guided optimization") public static let Isystem: Option = Option("-Isystem", .separate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the system import search path") public static let I: Option = Option("-I", .joinedOrSeparate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to the import search path") public static let i: Option = Option("-i", .flag, group: .modes) @@ -776,9 +781,6 @@ extension Option { public static let printZeroStats: Option = Option("-print-zero-stats", .flag, attributes: [.helpHidden, .frontend], helpText: "Prints all stats even if they are zero") public static let profileCoverageMapping: Option = Option("-profile-coverage-mapping", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate coverage data for use with profiled execution counts") public static let profileGenerate: Option = Option("-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect execution counts") - public static let irProfileGenerate: Option = Option("-ir-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") - public static let csProfileGenerate: Option = Option("-cs-profile-generate", .flag, attributes: [.frontend, .noInteractive], helpText: "Generate instrumented code to collect context sensitive execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)") - public static let csProfileGenerateEq: Option = Option("-cs-profile-generate=", .joined, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "", helpText: "Generate instrumented code to collect context sensitive execution counts into /default.profraw (overridden by LLVM_PROFILE_FILE env var)") public static let profileSampleUse: Option = Option("-profile-sample-use=", .joined, attributes: [.frontend, .noInteractive, .argumentIsPath], metaVar: "", helpText: "Supply sampling-based profiling data from llvm-profdata to enable profile-guided optimization") public static let profileStatsEntities: Option = Option("-profile-stats-entities", .flag, attributes: [.helpHidden, .frontend], helpText: "Profile changes to stats in -stats-output-dir, subdivided by source entity") public static let profileStatsEvents: Option = Option("-profile-stats-events", .flag, attributes: [.helpHidden, .frontend], helpText: "Profile changes to stats in -stats-output-dir") @@ -1081,6 +1083,8 @@ extension Option { Option.coveragePrefixMap, Option.CrossModuleOptimization, Option.crosscheckUnqualifiedLookup, + Option.csProfileGenerateEQ, + Option.csProfileGenerate, Option.cxxInteropGettersSettersAsProperties, Option.cxxInteropUseOpaquePointerForMoveonly, Option.cxxInteroperabilityMode, @@ -1619,6 +1623,9 @@ extension Option { Option.swiftinterfaceCompilerVersion, Option.internalizeAtLink, Option.interpret, + Option.irProfileGenerateEQ, + Option.irProfileGenerate, + Option.irProfileUse, Option.Isystem, Option.I, Option.i, @@ -1752,9 +1759,6 @@ extension Option { Option.printZeroStats, Option.profileCoverageMapping, Option.profileGenerate, - Option.irProfileGenerate, - Option.csProfileGenerate, - Option.csProfileGenerateEq, Option.profileSampleUse, Option.profileStatsEntities, Option.profileStatsEvents, diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index aa02ba185..d7bd9bf56 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -4687,15 +4687,40 @@ final class SwiftDriverTests: XCTestCase { $1.expect(.error(Driver.Error.conflictingOptions(.profileGenerate, .irProfileGenerate))) } + try withTemporaryDirectory { directoryPath in + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-profile-generate", "-ir-profile-generate=\(directoryPath)"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.profileGenerate, .irProfileGenerateEQ))) + } + } + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-cs-profile-generate"]) { $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .csProfileGenerate))) } try withTemporaryDirectory { directoryPath in try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-cs-profile-generate=\(directoryPath)"]) { - $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .csProfileGenerateEq))) + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .csProfileGenerateEQ))) } } + + try withTemporaryDirectory { directoryPath in + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate=\(directoryPath)", "-cs-profile-generate=\(directoryPath)"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerateEQ, .csProfileGenerateEQ))) + } + } + + try withTemporaryDirectory { directoryPath in + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-cs-profile-generate", "-cs-profile-generate=\(directoryPath)"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.csProfileGenerate, .csProfileGenerateEQ))) + } + } + + try withTemporaryDirectory { directoryPath in + try assertDriverDiagnostics(args: ["swiftc", "foo.swift", "-ir-profile-generate", "-ir-profile-generate=\(directoryPath)"]) { + $1.expect(.error(Driver.Error.conflictingOptions(.irProfileGenerate, .irProfileGenerateEQ))) + } + } + } func testProfileSampleUseFrontendFlags() throws {