Skip to content

Commit d3edb11

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 ca41d2b commit d3edb11

File tree

8 files changed

+109
-0
lines changed

8 files changed

+109
-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/Frontend.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
#include "llvm/ADT/IntrusiveRefCntPtr.h"
4646
#include "llvm/ADT/SetVector.h"
4747
#include "llvm/Option/ArgList.h"
48+
#include "llvm/Support/BLAKE3.h"
49+
#include "llvm/Support/HashingOutputBackend.h"
4850
#include "llvm/Support/Host.h"
4951
#include "llvm/Support/MemoryBuffer.h"
5052
#include "llvm/Support/VirtualOutputBackend.h"
@@ -453,6 +455,10 @@ class CompilerInstance {
453455
/// Virtual OutputBackend.
454456
llvm::IntrusiveRefCntPtr<llvm::vfs::OutputBackend> TheOutputBackend = nullptr;
455457

458+
/// The verification output backend.
459+
using HashBackendTy = llvm::vfs::HashingOutputBackend<llvm::BLAKE3>;
460+
llvm::IntrusiveRefCntPtr<HashBackendTy> HashBackend;
461+
456462
mutable ModuleDecl *MainModule = nullptr;
457463
SerializedModuleLoaderBase *DefaultSerializedLoader = nullptr;
458464
MemoryBufferSerializedModuleLoader *MemoryBufferLoader = nullptr;
@@ -508,6 +514,8 @@ class CompilerInstance {
508514
setOutputBackend(llvm::IntrusiveRefCntPtr<llvm::vfs::OutputBackend> Backend) {
509515
TheOutputBackend = std::move(Backend);
510516
}
517+
using HashingBackendPtrTy = llvm::IntrusiveRefCntPtr<HashBackendTy>;
518+
HashingBackendPtrTy getHashingBackend() { return HashBackend; }
511519

512520
ASTContext &getASTContext() { return *Context; }
513521
const ASTContext &getASTContext() const { return *Context; }

include/swift/Frontend/FrontendOptions.h

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

495+
/// Whether to run the job twice to check determinism.
496+
bool DeterministicCheck = false;
497+
495498
/// Avoid printing actual module content into the ABI descriptor file.
496499
/// This should only be used as a workaround when emitting ABI descriptor files
497500
/// 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/Frontend/Frontend.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,18 @@ void CompilerInstance::setupOutputBackend() {
401401
Invocation.getFrontendOptions().RequestedAction))
402402
return;
403403

404+
// Setup verification backend.
405+
// Create a mirroring outputbackend to produce hash for output files.
406+
// We cannot skip disk here since swift compiler is expecting to read back
407+
// some output file in later stages.
408+
if (Invocation.getFrontendOptions().DeterministicCheck) {
409+
HashBackend = llvm::makeIntrusiveRefCnt<HashBackendTy>();
410+
TheOutputBackend = llvm::vfs::makeMirroringOutputBackend(
411+
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>(),
412+
HashBackend);
413+
return;
414+
}
415+
404416
// Default to on disk output backend.
405417
TheOutputBackend =
406418
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>();

lib/FrontendTool/FrontendTool.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@
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"
@@ -82,6 +85,7 @@
8285
#include "llvm/Support/ErrorHandling.h"
8386
#include "llvm/Support/Path.h"
8487
#include "llvm/Support/VirtualOutputBackend.h"
88+
#include "llvm/Support/VirtualOutputBackends.h"
8589
#include "llvm/Support/raw_ostream.h"
8690
#include "llvm/Support/FileSystem.h"
8791

@@ -2338,6 +2342,23 @@ int swift::performFrontend(ArrayRef<const char *> Args,
23382342
PDC.setSuppressOutput(true);
23392343
}
23402344

2345+
CompilerInstance::HashingBackendPtrTy HashBackend = nullptr;
2346+
if (Invocation.getFrontendOptions().DeterministicCheck) {
2347+
// Setup a verfication instance to run.
2348+
std::unique_ptr<CompilerInstance> VerifyInstance =
2349+
std::make_unique<CompilerInstance>();
2350+
std::string InstanceSetupError;
2351+
// This should not fail because it passed already.
2352+
(void)VerifyInstance->setup(Invocation, InstanceSetupError);
2353+
2354+
// Run the first time without observer and discard return value;
2355+
int ReturnValueTest = 0;
2356+
(void)performCompile(*VerifyInstance, ReturnValueTest,
2357+
/*observer*/ nullptr);
2358+
// Get the hashing output backend and free the compiler instance.
2359+
HashBackend = VerifyInstance->getHashingBackend();
2360+
}
2361+
23412362
int ReturnValue = 0;
23422363
bool HadError = performCompile(*Instance, ReturnValue, observer);
23432364

@@ -2352,6 +2373,44 @@ int swift::performFrontend(ArrayRef<const char *> Args,
23522373
}
23532374
}
23542375

2376+
if (Invocation.getFrontendOptions().DeterministicCheck) {
2377+
// Collect all output files.
2378+
auto ReHashBackend = Instance->getHashingBackend();
2379+
std::set<std::string> AllOutputs;
2380+
llvm::for_each(HashBackend->outputFiles(), [&](StringRef F) {
2381+
AllOutputs.insert(F.str());
2382+
});
2383+
llvm::for_each(ReHashBackend->outputFiles(), [&](StringRef F) {
2384+
AllOutputs.insert(F.str());
2385+
});
2386+
2387+
DiagnosticEngine &diags = Instance->getDiags();
2388+
for (auto &Filename : AllOutputs) {
2389+
auto O1 = HashBackend->getHashValueForFile(Filename);
2390+
if (!O1) {
2391+
diags.diagnose(SourceLoc(), diag::error_output_missing, Filename,
2392+
/*SecondRun=*/false);
2393+
HadError = true;
2394+
continue;
2395+
}
2396+
auto O2 = ReHashBackend->getHashValueForFile(Filename);
2397+
if (!O2) {
2398+
diags.diagnose(SourceLoc(), diag::error_output_missing, Filename,
2399+
/*SecondRun=*/true);
2400+
HadError = true;
2401+
continue;
2402+
}
2403+
if (*O1 != *O2) {
2404+
diags.diagnose(SourceLoc(), diag::error_nondeterministic_output,
2405+
Filename, *O1, *O2);
2406+
HadError = true;
2407+
continue;
2408+
}
2409+
diags.diagnose(SourceLoc(), diag::matching_output_produced, Filename,
2410+
*O1);
2411+
}
2412+
}
2413+
23552414
auto r = finishDiagProcessing(HadError ? 1 : ReturnValue, verifierEnabled);
23562415
if (auto *StatsReporter = Instance->getStatsReporter())
23572416
StatsReporter->noteCurrentProcessExitStatus(r);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
// RUN: %target-swift-frontend -module-name test -emit-sib -o %t/test.sib -primary-file %s -enable-swift-deterministic-check 2>&1 | %FileCheck %s --check-prefix=SIB_OUTPUT
4+
5+
/// object files are "not" deterministic because the second run going to match the mod hash and skip code generation.
6+
// 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
7+
8+
// MODULE_OUTPUT: remark: produced matching output file '{{.*}}{{/|\\}}test.swiftmodule'
9+
// SIB_OUTPUT: remark: produced matching output file '{{.*}}{{/|\\}}test.sib'
10+
// OBJECT_OUTPUT: error: output file '{{.*}}{{/|\\}}test.o' is missing from second run
11+
12+
public var x = 1

0 commit comments

Comments
 (0)