Skip to content

Commit d940dbe

Browse files
Add an option to verify the deterministic output of swift compiler
Add option to test swift compiler determinism by running the job twice and compare the output produced.
1 parent b96c260 commit d940dbe

File tree

6 files changed

+103
-0
lines changed

6 files changed

+103
-0
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,16 @@ REMARK(interface_file_backup_used,none,
449449

450450
WARNING(warn_flag_deprecated,none, "flag '%0' is deprecated", (StringRef))
451451

452+
// Output deterministic check
453+
ERROR(error_nondeterministic_output,none,
454+
"output file '%0' is not deterministic: hash value '%1' vs '%2'",
455+
(StringRef, StringRef, StringRef))
456+
ERROR(error_output_missing,none,
457+
"output file '%0' is missing from %select{first|second}1 run",
458+
(StringRef, /*SecondRun=*/bool))
459+
REMARK(matching_output_produced,none,
460+
"produced matching output file '%0': hash '%1'", (StringRef, StringRef))
461+
452462
// Dependency Verifier Diagnostics
453463
ERROR(missing_member_dependency,none,
454464
"expected "

include/swift/Frontend/FrontendOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ class FrontendOptions {
489489
/// to encode the actual paths into the .swiftmodule file.
490490
PathObfuscator serializedPathObfuscator;
491491

492+
/// Whether to run the job twice to check determinism.
493+
bool DeterministicCheck = false;
494+
492495
/// Avoid printing actual module content into the ABI descriptor file.
493496
/// This should only be used as a workaround when emitting ABI descriptor files
494497
/// crashes the compiler.

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,10 @@ def enable_emit_generic_class_ro_t_list :
11351135
HelpText<"Enable emission of a section with references to class_ro_t of "
11361136
"generic class patterns">;
11371137

1138+
def enable_swift_deterministic_check :
1139+
Flag<["-"], "enable-swift-deterministic-check">,
1140+
HelpText<"Check swift compiler output determinisim by run it twice">;
1141+
11381142
def experimental_spi_only_imports :
11391143
Flag<["-"], "experimental-spi-only-imports">,
11401144
HelpText<"Enable use of @_spiOnly imports">;

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ bool ArgsToFrontendOptionsConverter::convert(
338338
Opts.serializedPathObfuscator.addMapping(SplitMap.first, SplitMap.second);
339339
}
340340
Opts.emptyABIDescriptor = Args.hasArg(OPT_empty_abi_descriptor);
341+
Opts.DeterministicCheck = Args.hasArg(OPT_enable_swift_deterministic_check);
341342
return false;
342343
}
343344

lib/FrontendTool/FrontendTool.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,23 @@
7171

7272
#include "clang/Lex/Preprocessor.h"
7373

74+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
75+
#include "llvm/ADT/STLExtras.h"
7476
#include "llvm/ADT/Statistic.h"
77+
#include "llvm/ADT/StringExtras.h"
7578
#include "llvm/ADT/StringMap.h"
7679
#include "llvm/IR/LLVMContext.h"
7780
#include "llvm/IR/Module.h"
7881
#include "llvm/IRReader/IRReader.h"
7982
#include "llvm/Option/Option.h"
8083
#include "llvm/Option/OptTable.h"
84+
#include "llvm/Support/BLAKE3.h"
8185
#include "llvm/Support/Error.h"
8286
#include "llvm/Support/ErrorHandling.h"
87+
#include "llvm/Support/HashingOutputBackend.h"
8388
#include "llvm/Support/Path.h"
8489
#include "llvm/Support/VirtualOutputBackend.h"
90+
#include "llvm/Support/VirtualOutputBackends.h"
8591
#include "llvm/Support/raw_ostream.h"
8692
#include "llvm/Support/FileSystem.h"
8793

@@ -2345,6 +2351,38 @@ int swift::performFrontend(ArrayRef<const char *> Args,
23452351
PDC.setSuppressOutput(true);
23462352
}
23472353

2354+
using HashBackendTy = llvm::vfs::HashingOutputBackend<llvm::BLAKE3>;
2355+
llvm::IntrusiveRefCntPtr<HashBackendTy> HashBackend, ReHashBackend;
2356+
int ReturnValueTest = 0;
2357+
if (Invocation.getFrontendOptions().DeterministicCheck) {
2358+
std::unique_ptr<CompilerInstance> VerifyInstance =
2359+
std::make_unique<CompilerInstance>();
2360+
std::string InstanceSetupError;
2361+
// This should not fail because it passed already.
2362+
(void)VerifyInstance->setup(Invocation, InstanceSetupError);
2363+
2364+
// Create a mirroring outputbackend to produce hash for output files.
2365+
// We cannot skip disk here since swift compiler is expecting to read back
2366+
// some output file in later stages.
2367+
// TODO: Implement diagnostics determinism check. We didn't setup any diag
2368+
// consumer currently.
2369+
HashBackend = llvm::makeIntrusiveRefCnt<HashBackendTy>();
2370+
auto VerifyBackend = llvm::vfs::makeMirroringOutputBackend(
2371+
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>(),
2372+
HashBackend);
2373+
VerifyInstance->setOutputBackend(VerifyBackend);
2374+
// Run the first time without observer and discard return value;
2375+
(void)performCompile(*VerifyInstance, ReturnValueTest,
2376+
/*observer*/ nullptr);
2377+
2378+
// Mirror the output to disk on second run.
2379+
ReHashBackend = llvm::makeIntrusiveRefCnt<HashBackendTy>();
2380+
auto FinalBackend = llvm::vfs::makeMirroringOutputBackend(
2381+
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>(),
2382+
ReHashBackend);
2383+
Instance->setOutputBackend(FinalBackend);
2384+
}
2385+
23482386
int ReturnValue = 0;
23492387
bool HadError = performCompile(*Instance, ReturnValue, observer);
23502388

@@ -2359,6 +2397,43 @@ int swift::performFrontend(ArrayRef<const char *> Args,
23592397
}
23602398
}
23612399

2400+
if (Invocation.getFrontendOptions().DeterministicCheck) {
2401+
// Collect all output files.
2402+
std::set<std::string> AllOutputs;
2403+
llvm::for_each(HashBackend->outputFiles(), [&](StringRef F) {
2404+
AllOutputs.insert(F.str());
2405+
});
2406+
llvm::for_each(ReHashBackend->outputFiles(), [&](StringRef F) {
2407+
AllOutputs.insert(F.str());
2408+
});
2409+
2410+
DiagnosticEngine &diags = Instance->getDiags();
2411+
for (auto &Filename : AllOutputs) {
2412+
auto O1 = HashBackend->getHashValueForFile(Filename);
2413+
if (!O1) {
2414+
diags.diagnose(SourceLoc(), diag::error_output_missing, Filename,
2415+
/*SecondRun=*/false);
2416+
HadError = true;
2417+
continue;
2418+
}
2419+
auto O2 = ReHashBackend->getHashValueForFile(Filename);
2420+
if (!O2) {
2421+
diags.diagnose(SourceLoc(), diag::error_output_missing, Filename,
2422+
/*SecondRun=*/true);
2423+
HadError = true;
2424+
continue;
2425+
}
2426+
if (*O1 != *O2) {
2427+
diags.diagnose(SourceLoc(), diag::error_nondeterministic_output,
2428+
Filename, *O1, *O2);
2429+
HadError = true;
2430+
continue;
2431+
}
2432+
diags.diagnose(SourceLoc(), diag::matching_output_produced, Filename,
2433+
*O1);
2434+
}
2435+
}
2436+
23622437
auto r = finishDiagProcessing(HadError ? 1 : ReturnValue, verifierEnabled);
23632438
if (auto *StatsReporter = Instance->getStatsReporter())
23642439
StatsReporter->noteCurrentProcessExitStatus(r);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -module-name test -emit-module -o %t/test.swiftmodule -primary-file %s -enable-swift-deterministic-check 2>&1 | %FileCheck %s --check-prefix=MODULE_OUTPUT
3+
4+
/// object files are "not" deterministic because the second run going to match the mod hash and skip code generation.
5+
// RUN: not %target-swift-frontend -module-name test -c -o %t/test.o -primary-file %s -enable-swift-deterministic-check 2>&1 | %FileCheck %s --check-prefix=OBJECT_OUTPUT
6+
7+
// MODULE_OUTPUT: remark: produced matching output file
8+
// OBJECT_OUTPUT: error: output file '{{.*}}{{/|\\}}test.o' is missing from second run
9+
10+
public var x = 1

0 commit comments

Comments
 (0)