Skip to content

Commit f7dca4d

Browse files
committed
Swift: trap output rework
Firstly, this change reworks how inter-process races are resolved. Moreover some responsability reorganization has led to merging `TrapArena` and `TrapOutput` again into a `TrapDomain` class. A `TargetFile` class is introduced, that is successfully created only for the first process that starts processing a given trap output file. From then on `TargetFile` simply wraps around `<<` stream operations, dumping them to a temporary file. When `TargetFile::commit` is called, the temporary file is moved on to the actual target trap file. Processes that lose the race can now just ignore the unneeded extraction and go on, while previously all processes would carry out all extractions overwriting each other at the end. Some of the file system logic contained in `SwiftExtractor.cpp` has been moved to this class, and two TODOs are solved: * introducing a better inter process file collision avoidance strategy * better error handling for trap output operations: if unable to write to the trap file (or carry out other basic file operations), we just abort. The changes to `ExprVisitor` and `StmtVisitor` are due to wanting to hide the raw `TrapDomain::createLabel` from them, and bring more funcionality under the generic caching/dispatching mechanism.
1 parent 2aaedac commit f7dca4d

File tree

16 files changed

+238
-156
lines changed

16 files changed

+238
-156
lines changed

swift/codegen/schema.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ AppliedPropertyWrapperExpr:
319319
_extends: Expr
320320

321321
Argument:
322+
_extends: Locatable
322323
label: string
323324
_children:
324325
expr: Expr

swift/extractor/SwiftExtractor.cpp

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
#include <llvm/Support/Path.h>
1717

1818
#include "swift/extractor/trap/generated/TrapClasses.h"
19-
#include "swift/extractor/trap/TrapOutput.h"
19+
#include "swift/extractor/trap/TrapDomain.h"
2020
#include "swift/extractor/visitors/SwiftVisitor.h"
21+
#include "swift/extractor/infra/TargetFile.h"
2122

2223
using namespace codeql;
2324

@@ -52,7 +53,7 @@ static void archiveFile(const SwiftExtractorConfiguration& config, swift::Source
5253
}
5354
}
5455

55-
static std::string getTrapFilename(swift::ModuleDecl& module, swift::SourceFile* primaryFile) {
56+
static std::string getFilename(swift::ModuleDecl& module, swift::SourceFile* primaryFile) {
5657
if (primaryFile) {
5758
return primaryFile->getFilename().str();
5859
}
@@ -80,56 +81,39 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config,
8081
swift::CompilerInstance& compiler,
8182
swift::ModuleDecl& module,
8283
swift::SourceFile* primaryFile = nullptr) {
84+
auto filename = getFilename(module, primaryFile);
85+
8386
// The extractor can be called several times from different processes with
84-
// the same input file(s)
85-
// We are using PID to avoid concurrent access
86-
// TODO: find a more robust approach to avoid collisions?
87-
auto name = getTrapFilename(module, primaryFile);
88-
llvm::StringRef filename(name);
89-
std::string tempTrapName = filename.str() + '.' + std::to_string(getpid()) + ".trap";
90-
llvm::SmallString<PATH_MAX> tempTrapPath(config.getTempTrapDir());
91-
llvm::sys::path::append(tempTrapPath, tempTrapName);
92-
93-
llvm::StringRef tempTrapParent = llvm::sys::path::parent_path(tempTrapPath);
94-
if (std::error_code ec = llvm::sys::fs::create_directories(tempTrapParent)) {
95-
std::cerr << "Cannot create temp trap directory '" << tempTrapParent.str()
96-
<< "': " << ec.message() << "\n";
87+
// the same input file(s). Using `TargetFile` the first process will win, and the following
88+
// will just skip the work
89+
TargetFile trapStream{filename + ".trap", config.trapDir, config.getTempTrapDir()};
90+
if (!trapStream.good()) {
91+
// another process arrived first, nothing to do for us
9792
return;
9893
}
9994

100-
std::ofstream trapStream(tempTrapPath.str().str());
101-
if (!trapStream) {
102-
std::error_code ec;
103-
ec.assign(errno, std::generic_category());
104-
std::cerr << "Cannot create temp trap file '" << tempTrapPath.str().str()
105-
<< "': " << ec.message() << "\n";
106-
return;
107-
}
10895
trapStream << "/* extractor-args:\n";
109-
for (auto opt : config.frontendOptions) {
96+
for (const auto& opt : config.frontendOptions) {
11097
trapStream << " " << std::quoted(opt) << " \\\n";
11198
}
11299
trapStream << "\n*/\n";
113100

114101
trapStream << "/* swift-frontend-args:\n";
115-
for (auto opt : config.patchedFrontendOptions) {
102+
for (const auto& opt : config.patchedFrontendOptions) {
116103
trapStream << " " << std::quoted(opt) << " \\\n";
117104
}
118105
trapStream << "\n*/\n";
119106

120-
TrapOutput trap{trapStream};
121-
TrapArena arena{};
107+
TrapDomain trap{trapStream};
122108

123109
// TODO: move default location emission elsewhere, possibly in a separate global trap file
124-
auto unknownFileLabel = arena.allocateLabel<FileTag>();
125110
// the following cannot conflict with actual files as those have an absolute path starting with /
126-
trap.assignKey(unknownFileLabel, "unknown");
111+
auto unknownFileLabel = trap.createLabel<FileTag>("unknown");
112+
auto unknownLocationLabel = trap.createLabel<LocationTag>("unknown");
127113
trap.emit(FilesTrap{unknownFileLabel});
128-
auto unknownLocationLabel = arena.allocateLabel<LocationTag>();
129-
trap.assignKey(unknownLocationLabel, "unknown");
130114
trap.emit(LocationsTrap{unknownLocationLabel, unknownFileLabel});
131115

132-
SwiftVisitor visitor(compiler.getSourceMgr(), arena, trap, module, primaryFile);
116+
SwiftVisitor visitor(compiler.getSourceMgr(), trap, module, primaryFile);
133117
auto topLevelDecls = getTopLevelDecls(module, primaryFile);
134118
for (auto decl : topLevelDecls) {
135119
visitor.extract(decl);
@@ -142,28 +126,10 @@ static void extractDeclarations(const SwiftExtractorConfiguration& config,
142126
// fact that the file was extracted
143127
llvm::SmallString<PATH_MAX> name(filename);
144128
llvm::sys::fs::make_absolute(name);
145-
auto fileLabel = arena.allocateLabel<FileTag>();
146-
trap.assignKey(fileLabel, name.str().str());
129+
auto fileLabel = trap.createLabel<FileTag>(name.str().str());
147130
trap.emit(FilesTrap{fileLabel, name.str().str()});
148131
}
149-
150-
// TODO: Pick a better name to avoid collisions
151-
std::string trapName = filename.str() + ".trap";
152-
llvm::SmallString<PATH_MAX> trapPath(config.trapDir);
153-
llvm::sys::path::append(trapPath, trapName);
154-
155-
llvm::StringRef trapParent = llvm::sys::path::parent_path(trapPath);
156-
if (std::error_code ec = llvm::sys::fs::create_directories(trapParent)) {
157-
std::cerr << "Cannot create trap directory '" << trapParent.str() << "': " << ec.message()
158-
<< "\n";
159-
return;
160-
}
161-
162-
// TODO: The last process wins. Should we do better than that?
163-
if (std::error_code ec = llvm::sys::fs::rename(tempTrapPath, trapPath)) {
164-
std::cerr << "Cannot rename temp trap file '" << tempTrapPath.str().str() << "' -> '"
165-
<< trapPath.str().str() << "': " << ec.message() << "\n";
166-
}
132+
trapStream.commit();
167133
}
168134

169135
static std::unordered_set<std::string> collectInputFilenames(swift::CompilerInstance& compiler) {

swift/extractor/infra/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ load("//swift:rules.bzl", "swift_cc_library")
22

33
swift_cc_library(
44
name = "infra",
5+
srcs = glob(["*.cpp"]),
56
hdrs = glob(["*.h"]),
67
visibility = ["//swift:__subpackages__"],
78
deps = [
89
"//swift/extractor/trap",
10+
"//swift/tools/prebuilt:swift-llvm-support",
911
],
1012
)

swift/extractor/infra/SwiftDispatcher.h

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
#include <swift/Basic/SourceManager.h>
55
#include <llvm/Support/FileSystem.h>
66

7-
#include "swift/extractor/trap/TrapArena.h"
87
#include "swift/extractor/trap/TrapLabelStore.h"
9-
#include "swift/extractor/trap/TrapOutput.h"
8+
#include "swift/extractor/trap/TrapDomain.h"
109
#include "swift/extractor/infra/SwiftTagTraits.h"
1110
#include "swift/extractor/trap/generated/TrapClasses.h"
1211

@@ -22,12 +21,10 @@ class SwiftDispatcher {
2221
// all references and pointers passed as parameters to this constructor are supposed to outlive
2322
// the SwiftDispatcher
2423
SwiftDispatcher(const swift::SourceManager& sourceManager,
25-
TrapArena& arena,
26-
TrapOutput& trap,
24+
TrapDomain& trap,
2725
swift::ModuleDecl& currentModule,
2826
swift::SourceFile* currentPrimarySourceFile = nullptr)
2927
: sourceManager{sourceManager},
30-
arena{arena},
3128
trap{trap},
3229
currentModule{currentModule},
3330
currentPrimarySourceFile{currentPrimarySourceFile} {}
@@ -77,6 +74,7 @@ class SwiftDispatcher {
7774
}
7875
waitingForNewLabel = e;
7976
visit(e);
77+
// TODO when everything is moved to structured C++ classes, this should be moved to createEntry
8078
if (auto l = store.get(e)) {
8179
if constexpr (!std::is_base_of_v<swift::TypeBase, E>) {
8280
attachLocation(e, *l);
@@ -95,13 +93,17 @@ class SwiftDispatcher {
9593
return fetchLabelFromUnion<AstNodeTag>(node);
9694
}
9795

96+
TrapLabel<ConditionElementTag> fetchLabel(const swift::StmtConditionElement& element) {
97+
return fetchLabel(&element);
98+
}
99+
98100
// Due to the lazy emission approach, we must assign a label to a corresponding AST node before
99101
// it actually gets emitted to handle recursive cases such as recursive calls, or recursive type
100102
// declarations
101103
template <typename E, typename... Args>
102104
TrapLabelOf<E> assignNewLabel(E* e, Args&&... args) {
103105
assert(waitingForNewLabel == Store::Handle{e} && "assignNewLabel called on wrong entity");
104-
auto label = createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
106+
auto label = trap.createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
105107
store.insert(e, label);
106108
waitingForNewLabel = std::monostate{};
107109
return label;
@@ -118,18 +120,13 @@ class SwiftDispatcher {
118120
return TrapClassOf<E>{assignNewLabel(&e, std::forward<Args>(args)...)};
119121
}
120122

121-
template <typename Tag>
122-
TrapLabel<Tag> createLabel() {
123-
auto ret = arena.allocateLabel<Tag>();
124-
trap.assignStar(ret);
125-
return ret;
126-
}
127-
128-
template <typename Tag, typename... Args>
129-
TrapLabel<Tag> createLabel(Args&&... args) {
130-
auto ret = arena.allocateLabel<Tag>();
131-
trap.assignKey(ret, std::forward<Args>(args)...);
132-
return ret;
123+
// used to create a new entry for entities that should not be cached
124+
// an example is swift::Argument, that are created on the fly and thus have no stable pointer
125+
template <typename E, typename... Args, std::enable_if_t<!std::is_pointer_v<E>>* = nullptr>
126+
auto createUncachedEntry(const E& e, Args&&... args) {
127+
auto label = trap.createLabel<TrapTagOf<E>>(std::forward<Args>(args)...);
128+
attachLocation(&e, label);
129+
return TrapClassOf<E>{label};
133130
}
134131

135132
template <typename Locatable>
@@ -213,6 +210,7 @@ class SwiftDispatcher {
213210
using Store = TrapLabelStore<swift::Decl,
214211
swift::Stmt,
215212
swift::StmtCondition,
213+
swift::StmtConditionElement,
216214
swift::CaseLabelItem,
217215
swift::Expr,
218216
swift::Pattern,
@@ -227,13 +225,13 @@ class SwiftDispatcher {
227225
return;
228226
}
229227
std::string filepath = getFilepath(start);
230-
auto fileLabel = createLabel<FileTag>(filepath);
228+
auto fileLabel = trap.createLabel<FileTag>(filepath);
231229
// TODO: do not emit duplicate trap entries for Files
232230
trap.emit(FilesTrap{fileLabel, filepath});
233231
auto [startLine, startColumn] = sourceManager.getLineAndColumnInBuffer(start);
234232
auto [endLine, endColumn] = sourceManager.getLineAndColumnInBuffer(end);
235-
auto locLabel = createLabel<LocationTag>('{', fileLabel, "}:", startLine, ':', startColumn, ':',
236-
endLine, ':', endColumn);
233+
auto locLabel = trap.createLabel<LocationTag>('{', fileLabel, "}:", startLine, ':', startColumn,
234+
':', endLine, ':', endColumn);
237235
trap.emit(LocationsTrap{locLabel, fileLabel, startLine, startColumn, endLine, endColumn});
238236
trap.emit(LocatableLocationsTrap{locatableLabel, locLabel});
239237
}
@@ -275,16 +273,16 @@ class SwiftDispatcher {
275273
// which are to be introduced in follow-up PRs
276274
virtual void visit(swift::Decl* decl) = 0;
277275
virtual void visit(swift::Stmt* stmt) = 0;
278-
virtual void visit(swift::StmtCondition* cond) = 0;
276+
virtual void visit(const swift::StmtCondition* cond) = 0;
277+
virtual void visit(const swift::StmtConditionElement* cond) = 0;
279278
virtual void visit(swift::CaseLabelItem* item) = 0;
280279
virtual void visit(swift::Expr* expr) = 0;
281280
virtual void visit(swift::Pattern* pattern) = 0;
282281
virtual void visit(swift::TypeRepr* type) = 0;
283282
virtual void visit(swift::TypeBase* type) = 0;
284283

285284
const swift::SourceManager& sourceManager;
286-
TrapArena& arena;
287-
TrapOutput& trap;
285+
TrapDomain& trap;
288286
Store store;
289287
Store::Handle waitingForNewLabel{std::monostate{}};
290288
swift::ModuleDecl& currentModule;

swift/extractor/infra/SwiftTagTraits.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ using SILBoxTypeReprTag = SilBoxTypeReprTag;
3636

3737
MAP_TAG(Stmt);
3838
MAP_TAG(StmtCondition);
39+
MAP_TYPE_TO_TAG(StmtConditionElement, ConditionElementTag);
3940
MAP_TAG(CaseLabelItem);
4041
#define ABSTRACT_STMT(CLASS, PARENT) MAP_SUBTAG(CLASS##Stmt, PARENT)
4142
#define STMT(CLASS, PARENT) ABSTRACT_STMT(CLASS, PARENT)
4243
#include <swift/AST/StmtNodes.def>
4344

4445
MAP_TAG(Expr);
46+
MAP_TAG(Argument);
4547
#define ABSTRACT_EXPR(CLASS, PARENT) MAP_SUBTAG(CLASS##Expr, PARENT)
4648
#define EXPR(CLASS, PARENT) ABSTRACT_EXPR(CLASS, PARENT)
4749
#include <swift/AST/ExprNodes.def>

swift/extractor/infra/TargetFile.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "swift/extractor/infra/TargetFile.h"
2+
3+
#include <llvm/Support/FileSystem.h>
4+
#include <llvm/Support/Path.h>
5+
6+
namespace codeql {
7+
namespace {
8+
[[noreturn]] void error(const char* action, const std::string& arg, std::error_code ec) {
9+
std::cerr << "Unable to " << action << ": " << arg << " (" << ec.message() << ")\n";
10+
std::abort();
11+
}
12+
13+
[[noreturn]] void error(const char* action, const std::string& arg) {
14+
error(action, arg, {errno, std::system_category()});
15+
}
16+
17+
void ensureParentDir(const std::string& path) {
18+
auto parent = llvm::sys::path::parent_path(path);
19+
if (auto ec = llvm::sys::fs::create_directories(parent)) {
20+
error("create directory", parent.str(), ec);
21+
}
22+
}
23+
24+
std::string initPath(std::string_view target, std::string_view dir) {
25+
std::string ret{dir};
26+
assert(!target.empty() && "target must be a non-empty path");
27+
if (target[0] != '/') {
28+
ret += '/';
29+
}
30+
ret.append(target);
31+
ensureParentDir(ret);
32+
return ret;
33+
}
34+
} // namespace
35+
36+
TargetFile::TargetFile(std::string_view target,
37+
std::string_view targetDir,
38+
std::string_view workingDir)
39+
: workingPath{initPath(target, workingDir)}, targetPath{initPath(target, targetDir)} {
40+
errno = 0;
41+
// since C++17 "x" mode opens with O_EXCL (fails if file already exists)
42+
if (auto f = std::fopen(targetPath.c_str(), "wx")) {
43+
std::fclose(f);
44+
out.open(workingPath);
45+
checkOutput("open file for writing");
46+
} else {
47+
if (errno != EEXIST) {
48+
error("open file for writing", targetPath);
49+
}
50+
// else we just lost the race, do nothing (good() will return false to signal this)
51+
}
52+
}
53+
54+
bool TargetFile::good() const {
55+
return out && out.is_open();
56+
}
57+
58+
void TargetFile::commit() {
59+
assert(good());
60+
out.close();
61+
errno = 0;
62+
if (std::rename(workingPath.c_str(), targetPath.c_str()) != 0) {
63+
error("rename file", targetPath);
64+
}
65+
}
66+
67+
void TargetFile::checkOutput(const char* action) {
68+
if (!out) {
69+
error(action, workingPath);
70+
}
71+
}
72+
73+
} // namespace codeql

swift/extractor/infra/TargetFile.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <fstream>
5+
#include <iostream>
6+
#include <cstdio>
7+
#include <cerrno>
8+
#include <system_error>
9+
#include <sstream>
10+
11+
namespace codeql {
12+
13+
// Only the first process trying to open an `TargetFile` for a given `target` is allowed to do
14+
// so, all others will have an instance with `good() == false` and failing on any other operation.
15+
// The content streamed to the `TargetFile` is written to `workingDir/target`, and is moved onto
16+
// `targetDir/target` only when `commit()` is called
17+
class TargetFile {
18+
std::string workingPath;
19+
std::string targetPath;
20+
std::ofstream out;
21+
22+
public:
23+
TargetFile(std::string_view target, std::string_view targetDir, std::string_view workingDir);
24+
25+
bool good() const;
26+
void commit();
27+
28+
template <typename T>
29+
TargetFile& operator<<(T&& value) {
30+
errno = 0;
31+
out << value;
32+
checkOutput("write to file");
33+
return *this;
34+
}
35+
36+
private:
37+
void checkOutput(const char* action);
38+
};
39+
40+
} // namespace codeql

0 commit comments

Comments
 (0)