-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[swift-parse-test] A parser performance check utility #69470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//===--- ASTGen.h -----------------------------------------------*- C++ -*-===// | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2023 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "swift/AST/CASTBridging.h" | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
void *_Nonnull swift_ASTGen_createQueuedDiagnostics(); | ||
void swift_ASTGen_destroyQueuedDiagnostics(void *_Nonnull queued); | ||
void swift_ASTGen_addQueuedSourceFile( | ||
void *_Nonnull queuedDiagnostics, ssize_t bufferID, | ||
void *_Nonnull sourceFile, const uint8_t *_Nonnull displayNamePtr, | ||
intptr_t displayNameLength, ssize_t parentID, ssize_t positionInParent); | ||
void swift_ASTGen_addQueuedDiagnostic( | ||
void *_Nonnull queued, const char *_Nonnull text, ptrdiff_t textLength, | ||
BridgedDiagnosticSeverity severity, const void *_Nullable sourceLoc, | ||
const void *_Nullable *_Nullable highlightRanges, | ||
ptrdiff_t numHighlightRanges); | ||
void swift_ASTGen_renderQueuedDiagnostics( | ||
void *_Nonnull queued, ssize_t contextSize, ssize_t colorize, | ||
BridgedString *_Nonnull renderedString); | ||
|
||
// FIXME: Hack because we cannot easily get to the already-parsed source | ||
// file from here. Fix this egregious oversight! | ||
void *_Nullable swift_ASTGen_parseSourceFile(const char *_Nonnull buffer, | ||
size_t bufferLength, | ||
const char *_Nonnull moduleName, | ||
const char *_Nonnull filename, | ||
void *_Nullable ctx); | ||
void swift_ASTGen_destroySourceFile(void *_Nonnull sourceFile); | ||
|
||
void *_Nullable swift_ASTGen_resolveMacroType(const void *_Nonnull macroType); | ||
void swift_ASTGen_destroyMacro(void *_Nonnull macro); | ||
|
||
void swift_ASTGen_freeBridgedString(BridgedString); | ||
|
||
void *_Nonnull swift_ASTGen_resolveExecutableMacro( | ||
const char *_Nonnull moduleName, const char *_Nonnull typeName, | ||
void *_Nonnull opaquePluginHandle); | ||
void swift_ASTGen_destroyExecutableMacro(void *_Nonnull macro); | ||
|
||
ptrdiff_t swift_ASTGen_checkMacroDefinition( | ||
void *_Nonnull diagEngine, void *_Nonnull sourceFile, | ||
const void *_Nonnull macroSourceLocation, | ||
BridgedString *_Nonnull expansionSourceOutPtr, | ||
ptrdiff_t *_Nullable *_Nonnull replacementsPtr, | ||
ptrdiff_t *_Nonnull numReplacements); | ||
void swift_ASTGen_freeExpansionReplacements( | ||
ptrdiff_t *_Nullable replacementsPtr, ptrdiff_t numReplacements); | ||
|
||
ptrdiff_t swift_ASTGen_expandFreestandingMacro( | ||
void *_Nonnull diagEngine, const void *_Nonnull macro, uint8_t externalKind, | ||
const char *_Nonnull discriminator, uint8_t rawMacroRole, | ||
void *_Nonnull sourceFile, const void *_Nullable sourceLocation, | ||
BridgedString *_Nonnull evaluatedSourceOut); | ||
|
||
ptrdiff_t swift_ASTGen_expandAttachedMacro( | ||
void *_Nonnull diagEngine, const void *_Nonnull macro, uint8_t externalKind, | ||
const char *_Nonnull discriminator, const char *_Nonnull qualifiedType, | ||
const char *_Nonnull conformances, uint8_t rawMacroRole, | ||
void *_Nonnull customAttrSourceFile, | ||
const void *_Nullable customAttrSourceLocation, | ||
void *_Nonnull declarationSourceFile, | ||
const void *_Nullable declarationSourceLocation, | ||
void *_Nullable parentDeclSourceFile, | ||
const void *_Nullable parentDeclSourceLocation, | ||
BridgedString *_Nonnull evaluatedSourceOut); | ||
|
||
bool swift_ASTGen_initializePlugin(void *_Nonnull handle, | ||
void *_Nullable diagEngine); | ||
void swift_ASTGen_deinitializePlugin(void *_Nonnull handle); | ||
bool swift_ASTGen_pluginServerLoadLibraryPlugin( | ||
void *_Nonnull handle, const char *_Nonnull libraryPath, | ||
const char *_Nonnull moduleName, BridgedString *_Nullable errorOut); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,235 @@ | ||||||||||
//===--- swift_parse_test_main.cpp ----------------------------------------===// | ||||||||||
// | ||||||||||
// This source file is part of the Swift.org open source project | ||||||||||
// | ||||||||||
// Copyright (c) 2023 Apple Inc. and the Swift project authors | ||||||||||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||||||||||
// | ||||||||||
// See https://swift.org/LICENSE.txt for license information | ||||||||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||||||||||
// | ||||||||||
//===----------------------------------------------------------------------===// | ||||||||||
// | ||||||||||
// A utility tool to measure the parser performance. | ||||||||||
// | ||||||||||
//===----------------------------------------------------------------------===// | ||||||||||
|
||||||||||
#include "swift/AST/ASTContext.h" | ||||||||||
#include "swift/Basic/LLVM.h" | ||||||||||
#include "swift/Basic/LangOptions.h" | ||||||||||
#include "swift/Bridging/ASTGen.h" | ||||||||||
#include "swift/Parse/Parser.h" | ||||||||||
#include "llvm/Support/CommandLine.h" | ||||||||||
|
||||||||||
#include <chrono> | ||||||||||
#include <ctime> | ||||||||||
|
||||||||||
using namespace swift; | ||||||||||
|
||||||||||
namespace { | ||||||||||
|
||||||||||
enum class Executor { | ||||||||||
SwiftParser, | ||||||||||
LibParse, | ||||||||||
}; | ||||||||||
|
||||||||||
enum class ExecuteOptionFlag { | ||||||||||
/// Enable body skipping | ||||||||||
SkipBodies = 1 << 0, | ||||||||||
}; | ||||||||||
using ExecuteOptions = OptionSet<ExecuteOptionFlag>; | ||||||||||
|
||||||||||
struct SwiftParseTestOptions { | ||||||||||
llvm::cl::list<Executor> Executors = llvm::cl::list<Executor>( | ||||||||||
llvm::cl::desc("Available parsers"), | ||||||||||
llvm::cl::values( | ||||||||||
clEnumValN(Executor::SwiftParser, "swift-parser", "SwiftParser"), | ||||||||||
clEnumValN(Executor::LibParse, "lib-parse", "libParse"))); | ||||||||||
|
||||||||||
llvm::cl::opt<unsigned int> Iteration = llvm::cl::opt<unsigned int>( | ||||||||||
"n", llvm::cl::desc("iteration"), llvm::cl::init(1)); | ||||||||||
Comment on lines
+49
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
|
||||||||||
|
||||||||||
llvm::cl::opt<bool> SkipBodies = llvm::cl::opt<bool>( | ||||||||||
"skip-bodies", | ||||||||||
llvm::cl::desc("skip function bodies and type members if possible")); | ||||||||||
|
||||||||||
llvm::cl::list<std::string> InputPaths = llvm::cl::list<std::string>( | ||||||||||
llvm::cl::Positional, llvm::cl::desc("input paths")); | ||||||||||
}; | ||||||||||
|
||||||||||
struct LibParseExecutor { | ||||||||||
constexpr static StringRef name = "libParse"; | ||||||||||
|
||||||||||
static void performParse(llvm::MemoryBufferRef buffer, ExecuteOptions opts) { | ||||||||||
SourceManager SM; | ||||||||||
unsigned bufferID = | ||||||||||
SM.addNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(buffer)); | ||||||||||
DiagnosticEngine diagEngine(SM); | ||||||||||
LangOptions langOpts; | ||||||||||
TypeCheckerOptions typeckOpts; | ||||||||||
SILOptions silOpts; | ||||||||||
SearchPathOptions searchPathOpts; | ||||||||||
ClangImporterOptions clangOpts; | ||||||||||
symbolgraphgen::SymbolGraphOptions symbolOpts; | ||||||||||
std::unique_ptr<ASTContext> ctx( | ||||||||||
ASTContext::get(langOpts, typeckOpts, silOpts, searchPathOpts, | ||||||||||
clangOpts, symbolOpts, SM, diagEngine)); | ||||||||||
|
||||||||||
SourceFile::ParsingOptions parseOpts; | ||||||||||
parseOpts |= SourceFile::ParsingFlags::DisablePoundIfEvaluation; | ||||||||||
if (!opts.contains(ExecuteOptionFlag::SkipBodies)) | ||||||||||
parseOpts |= SourceFile::ParsingFlags::DisableDelayedBodies; | ||||||||||
|
||||||||||
ModuleDecl *M = ModuleDecl::create(Identifier(), *ctx); | ||||||||||
SourceFile *SF = | ||||||||||
new (*ctx) SourceFile(*M, SourceFileKind::Library, bufferID, parseOpts); | ||||||||||
|
||||||||||
Parser parser(bufferID, *SF, /*SILParserState=*/nullptr); | ||||||||||
SmallVector<ASTNode> items; | ||||||||||
parser.parseTopLevelItems(items); | ||||||||||
} | ||||||||||
}; | ||||||||||
|
||||||||||
struct SwiftParserExecutor { | ||||||||||
constexpr static StringRef name = "SwiftParser"; | ||||||||||
|
||||||||||
static void performParse(llvm::MemoryBufferRef buffer, ExecuteOptions opts) { | ||||||||||
#if SWIFT_BUILD_SWIFT_SYNTAX | ||||||||||
// TODO: Implement 'ExecuteOptionFlag::SkipBodies' | ||||||||||
auto sourceFile = swift_ASTGen_parseSourceFile( | ||||||||||
buffer.getBufferStart(), buffer.getBufferSize(), /*moduleName=*/"", | ||||||||||
buffer.getBufferIdentifier().data(), /*ASTContext=*/nullptr); | ||||||||||
swift_ASTGen_destroySourceFile(sourceFile); | ||||||||||
#endif | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn’t really matter since it’s a test utility but should we error if we don’t have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do in |
||||||||||
} | ||||||||||
}; | ||||||||||
|
||||||||||
static void _loadSwiftFilesRecursively( | ||||||||||
StringRef path, | ||||||||||
SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers) { | ||||||||||
if (llvm::sys::fs::is_directory(path)) { | ||||||||||
std::error_code err; | ||||||||||
for (auto I = llvm::sys::fs::directory_iterator(path, err), | ||||||||||
E = llvm::sys::fs::directory_iterator(); | ||||||||||
I != E; I.increment(err)) { | ||||||||||
_loadSwiftFilesRecursively(I->path(), buffers); | ||||||||||
} | ||||||||||
} else if (path.endswith(".swift")) { | ||||||||||
if (auto buffer = llvm::MemoryBuffer::getFile(path)) { | ||||||||||
buffers.push_back(std::move(*buffer)); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/// Load all '.swift' files in the specified \p filePaths into \p buffers. | ||||||||||
/// If the path is a directory, this recursively collects the files in it. | ||||||||||
static void | ||||||||||
loadSources(ArrayRef<std::string> filePaths, | ||||||||||
SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers) { | ||||||||||
for (auto path : filePaths) { | ||||||||||
_loadSwiftFilesRecursively(path, buffers); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/// Measure the duration of \p body execution. | ||||||||||
template <typename Duration> | ||||||||||
static std::pair<Duration, Duration> measure(llvm::function_ref<void()> body) { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a doc comment what the first and what the second |
||||||||||
auto cStart = std::clock(); | ||||||||||
auto tStart = std::chrono::steady_clock::now(); | ||||||||||
body(); | ||||||||||
auto cEnd = std::clock(); | ||||||||||
auto tEnd = std::chrono::steady_clock::now(); | ||||||||||
|
||||||||||
auto clockMultiply = | ||||||||||
Duration::period::den / CLOCKS_PER_SEC / Duration::period::num; | ||||||||||
|
||||||||||
Duration cDuration((cEnd - cStart) * clockMultiply); | ||||||||||
return {std::chrono::duration_cast<Duration>(tEnd - tStart), | ||||||||||
std::chrono::duration_cast<Duration>(cDuration)}; | ||||||||||
} | ||||||||||
|
||||||||||
/// Perform the performance measurement using \c Executor . | ||||||||||
/// Parse all \p buffers using \c Executor , \p iteration times, and print out | ||||||||||
/// the measurement to the \c stdout. | ||||||||||
template <typename Executor> | ||||||||||
static void | ||||||||||
perform(const SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers, | ||||||||||
ExecuteOptions opts, unsigned iteration, uintmax_t totalBytes, | ||||||||||
uintmax_t totalLines) { | ||||||||||
|
||||||||||
llvm::outs() << "----\n"; | ||||||||||
llvm::outs() << "parser: " << Executor::name << "\n"; | ||||||||||
|
||||||||||
using duration_t = std::chrono::nanoseconds; | ||||||||||
auto tDuration = duration_t::zero(); | ||||||||||
auto cDuration = duration_t::zero(); | ||||||||||
|
||||||||||
for (unsigned i = 0; i < iteration; i++) { | ||||||||||
for (auto &buffer : buffers) { | ||||||||||
std::pair<duration_t, duration_t> elapsed = measure<duration_t>( | ||||||||||
[&] { Executor::performParse(buffer->getMemBufferRef(), opts); }); | ||||||||||
tDuration += elapsed.first; | ||||||||||
cDuration += elapsed.second; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
auto tDisplay = | ||||||||||
std::chrono::duration_cast<std::chrono::milliseconds>(tDuration).count(); | ||||||||||
auto cDisplay = | ||||||||||
std::chrono::duration_cast<std::chrono::milliseconds>(cDuration).count(); | ||||||||||
|
||||||||||
auto byteTPS = totalBytes * duration_t::period::den / cDuration.count(); | ||||||||||
auto lineTPS = totalLines * duration_t::period::den / cDuration.count(); | ||||||||||
|
||||||||||
llvm::outs() << llvm::format("wall clock time (ms): %8d\n", tDisplay) | ||||||||||
<< llvm::format("cpu time (ms): %8d\n", cDisplay) | ||||||||||
<< llvm::format("throughput (byte/s): %8d\n", byteTPS) | ||||||||||
<< llvm::format("throughput (line/s): %8d\n", lineTPS); | ||||||||||
Comment on lines
+186
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to specify that throughput is measured in CPU time, not wall time? |
||||||||||
} | ||||||||||
|
||||||||||
} // namespace | ||||||||||
|
||||||||||
int swift_parse_test_main(ArrayRef<const char *> args, const char *argv0, | ||||||||||
void *mainAddr) { | ||||||||||
SwiftParseTestOptions options; | ||||||||||
llvm::cl::ParseCommandLineOptions(args.size(), args.data(), | ||||||||||
"Swift parse test\n"); | ||||||||||
|
||||||||||
unsigned iteration = options.Iteration; | ||||||||||
ExecuteOptions execOptions; | ||||||||||
if (options.SkipBodies) | ||||||||||
execOptions |= ExecuteOptionFlag::SkipBodies; | ||||||||||
|
||||||||||
SmallVector<std::unique_ptr<llvm::MemoryBuffer>> buffers; | ||||||||||
loadSources(options.InputPaths, buffers); | ||||||||||
unsigned int byteCount = 0; | ||||||||||
unsigned int lineCount = 0; | ||||||||||
for (auto &buffer : buffers) { | ||||||||||
byteCount += buffer->getBufferSize(); | ||||||||||
lineCount += buffer->getBuffer().count('\n'); | ||||||||||
} | ||||||||||
|
||||||||||
llvm::outs() << llvm::format("file count: %8d\n", buffers.size()) | ||||||||||
<< llvm::format("total bytes: %8d\n", byteCount) | ||||||||||
<< llvm::format("total lines: %8d\n", lineCount) | ||||||||||
<< llvm::format("iterations: %8d\n", iteration); | ||||||||||
for (auto mode : options.Executors) { | ||||||||||
switch (mode) { | ||||||||||
case Executor::SwiftParser: | ||||||||||
#if SWIFT_BUILD_SWIFT_SYNTAX | ||||||||||
perform<SwiftParserExecutor>(buffers, execOptions, iteration, byteCount, | ||||||||||
lineCount); | ||||||||||
break; | ||||||||||
#else | ||||||||||
llvm::errs() << "error: SwiftParser is not enabled\n"; | ||||||||||
return 1; | ||||||||||
#endif | ||||||||||
case Executor::LibParse: | ||||||||||
perform<LibParseExecutor>(buffers, execOptions, iteration, byteCount, | ||||||||||
lineCount); | ||||||||||
break; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
return 0; | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we should extend this tool to do more parser tests? If it should just stay a performance measuring tool, I would name it
swift-parser-performance-test
.Also, I just want to note that swift-syntax has
swift-parser-test
(with anr
). I don’t know if it’s good that this is spelled without anr
(means we don’t have two utilities with the same name) or bad (inconsistency).