From f4be19725f48e6e09a885238f3cf4fb5ac8ce889 Mon Sep 17 00:00:00 2001 From: Steven Wu Date: Mon, 17 Mar 2025 13:42:42 -0700 Subject: [PATCH] Support driver flag `-enable-deterministic-check` Add support for `-enable-deterministic-check` after promoting that to a driver flag. This allows running the command twice and comparing the output and make sure they are the same. --- .../SwiftDriver/Jobs/FrontendJobHelpers.swift | 9 ++++ Sources/SwiftOptions/Options.swift | 6 ++- .../SwiftDriverTests/CachingBuildTests.swift | 44 +++++++++++++++++++ Tests/SwiftDriverTests/SwiftDriverTests.swift | 11 +++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index 2d79a0ccc..934570f82 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -505,6 +505,15 @@ extension Driver { } } + if parsedOptions.contains(.enableDeterministicCheck), + isFrontendArgSupported(.enableDeterministicCheck) { + commandLine.appendFlag(.enableDeterministicCheck) + commandLine.appendFlag(.alwaysCompileOutputFiles) + if enableCaching { + commandLine.appendFlag(.cacheDisableReplay) + } + } + // Pass along -no-verify-emitted-module-interface only if it's effective. // Assume verification by default as we want to know only when the user skips // the verification. diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index ba9f958f2..3eaf6a0e8 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -431,7 +431,7 @@ extension Option { public static let enableDeserializationRecovery: Option = Option("-enable-deserialization-recovery", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Attempt to recover from missing xrefs (etc) in swiftmodules") public static let enableDeserializationSafety: Option = Option("-enable-deserialization-safety", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Avoid reading potentially unsafe decls in swiftmodules") public static let enableDestroyHoisting: Option = Option("-enable-destroy-hoisting=", .joined, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "true|false", helpText: "Whether to enable destroy hoisting") - public static let enableDeterministicCheck: Option = Option("-enable-deterministic-check", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Check compiler output determinism by running it twice") + public static let enableDeterministicCheck: Option = Option("-enable-deterministic-check", .flag, attributes: [.frontend, .doesNotAffectIncrementalBuild, .cacheInvariant], helpText: "Check compiler output determinism by running it twice") public static let enableDynamicReplacementChaining: Option = Option("-enable-dynamic-replacement-chaining", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable chaining of dynamic replacements") public static let enableEmitGenericClassRoTList: Option = Option("-enable-emit-generic-class-ro_t-list", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable emission of a section with references to class_ro_t of generic class patterns") public static let enableEmitTypeMallocForCoroFrame: Option = Option("-enable-emit-type-malloc-for-coro-frame", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Enable emitting typed malloc for coroutine frame allocation") @@ -564,6 +564,7 @@ extension Option { public static let forcePublicLinkage: Option = Option("-force-public-linkage", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Force public linkage for private symbols. Used by LLDB.") public static let forceSingleFrontendInvocation: Option = Option("-force-single-frontend-invocation", .flag, alias: Option.wholeModuleOptimization, attributes: [.helpHidden, .frontend, .noInteractive]) public static let forceStructTypeLayouts: Option = Option("-force-struct-type-layouts", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Force type layout based lowering for structs") + public static let formalCxxInteroperabilityMode: Option = Option("-formal-cxx-interoperability-mode=", .joined, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "|off", helpText: "What version of C++ interoperability a textual interface was originally generated with") public static let framework: Option = Option("-framework", .separate, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Specifies a framework which should be linked against", group: .linkerOption) public static let frontendParseableOutput: Option = Option("-frontend-parseable-output", .flag, attributes: [.frontend, .noDriver, .cacheInvariant], helpText: "Emit textual output in a parseable format") public static let Fsystem: Option = Option("-Fsystem", .separate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to system framework search path") @@ -848,7 +849,7 @@ extension Option { public static let staticExecutable: Option = Option("-static-executable", .flag, helpText: "Statically link the executable") public static let staticStdlib: Option = Option("-static-stdlib", .flag, attributes: [.doesNotAffectIncrementalBuild], helpText: "Statically link the Swift standard library") public static let `static`: Option = Option("-static", .flag, attributes: [.frontend, .noInteractive, .moduleInterface], helpText: "Make this module statically linkable and make the output of -emit-library a static library.") - public static let statsOutputDir: Option = Option("-stats-output-dir", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath], helpText: "Directory to write unified compilation-statistics files to") + public static let statsOutputDir: Option = Option("-stats-output-dir", .separate, attributes: [.helpHidden, .frontend, .argumentIsPath, .cacheInvariant], helpText: "Directory to write unified compilation-statistics files to") public static let strictConcurrency: Option = Option("-strict-concurrency=", .joined, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Specify the how strict concurrency checking will be. The value may be 'minimal' (most 'Sendable' checking is disabled), 'targeted' ('Sendable' checking is enabled in code that uses the concurrency model, or 'complete' ('Sendable' and other checking is enabled for all code in the module)") public static let strictImplicitModuleContext: Option = Option("-strict-implicit-module-context", .flag, attributes: [.helpHidden, .frontend], helpText: "Enable the strict forwarding of compilation context to downstream implicit module dependencies") public static let strictMemorySafety: Option = Option("-strict-memory-safety", .flag, attributes: [.frontend, .synthesizeInterface], helpText: "Enable strict memory safety checking") @@ -1508,6 +1509,7 @@ extension Option { Option.forcePublicLinkage, Option.forceSingleFrontendInvocation, Option.forceStructTypeLayouts, + Option.formalCxxInteroperabilityMode, Option.framework, Option.frontendParseableOutput, Option.Fsystem, diff --git a/Tests/SwiftDriverTests/CachingBuildTests.swift b/Tests/SwiftDriverTests/CachingBuildTests.swift index 192b8deba..e9052428d 100644 --- a/Tests/SwiftDriverTests/CachingBuildTests.swift +++ b/Tests/SwiftDriverTests/CachingBuildTests.swift @@ -1043,6 +1043,50 @@ final class CachingBuildTests: XCTestCase { } } + func testDeterministicCheck() throws { + try withTemporaryDirectory { path in + try localFileSystem.changeCurrentWorkingDirectory(to: path) + let moduleCachePath = path.appending(component: "ModuleCache") + let casPath = path.appending(component: "cas") + try localFileSystem.createDirectory(moduleCachePath) + let main = path.appending(component: "testCachingBuild.swift") + let mainFileContent = "import C;" + try localFileSystem.writeFileContents(main) { + $0.send(mainFileContent) + } + let cHeadersPath: AbsolutePath = + try testInputsPath.appending(component: "ExplicitModuleBuilds") + .appending(component: "CHeaders") + let swiftModuleInterfacesPath: AbsolutePath = + try testInputsPath.appending(component: "ExplicitModuleBuilds") + .appending(component: "Swift") + let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? [] + let bridgingHeaderpath: AbsolutePath = + cHeadersPath.appending(component: "Bridging.h") + var driver = try Driver(args: ["swiftc", + "-I", cHeadersPath.nativePathString(escaped: true), + "-I", swiftModuleInterfacesPath.nativePathString(escaped: true), + "-explicit-module-build", "-enable-deterministic-check", + "-module-cache-path", moduleCachePath.nativePathString(escaped: true), + "-cache-compile-job", "-cas-path", casPath.nativePathString(escaped: true), + "-import-objc-header", bridgingHeaderpath.nativePathString(escaped: true), + "-working-directory", path.nativePathString(escaped: true), + main.nativePathString(escaped: true)] + sdkArgumentsForTesting, + interModuleDependencyOracle: dependencyOracle) + let jobs = try driver.planBuild() + jobs.forEach { job in + guard job.kind == .compile else { + return + } + XCTAssertJobInvocationMatches(job, + .flag("-enable-deterministic-check"), + .flag("-always-compile-output-files"), + .flag("-cache-disable-replay")) + } + } + + } + func testCASManagement() throws { try withTemporaryDirectory { path in let casPath = path.appending(component: "cas") diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index f14bb7375..11f1dd58a 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -7069,6 +7069,17 @@ final class SwiftDriverTests: XCTestCase { } } + func testDeterministicCheck() throws { + do { + var driver = try Driver(args: ["swiftc", "-enable-deterministic-check", "foo.swift", + "-import-objc-header", "foo.h", "-enable-bridging-pch"]) + let plannedJobs = try driver.planBuild() + // Check bridging header compilation command and main module command. + XCTAssertJobInvocationMatches(plannedJobs[0], .flag("-enable-deterministic-check"), .flag("-always-compile-output-files")) + XCTAssertJobInvocationMatches(plannedJobs[1], .flag("-enable-deterministic-check"), .flag("-always-compile-output-files")) + } + } + func testWarnConcurrency() throws { do { var driver = try Driver(args: ["swiftc", "-warn-concurrency", "foo.swift"])