Skip to content

[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

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions include/swift/Bridging/ASTGen.h
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
3 changes: 2 additions & 1 deletion include/swift/Driver/Driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ class Driver {
SymbolGraph, // swift-symbolgraph
APIExtract, // swift-api-extract
APIDigester, // swift-api-digester
CacheTool // swift-cache-tool
CacheTool, // swift-cache-tool
ParseTest, // swift-parse-test
};

class InputInfoMap;
Expand Down
2 changes: 2 additions & 0 deletions lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ void Driver::parseDriverKind(ArrayRef<const char *> Args) {
.Case("swift-api-extract", DriverKind::APIExtract)
.Case("swift-api-digester", DriverKind::APIDigester)
.Case("swift-cache-tool", DriverKind::CacheTool)
.Case("swift-parse-test", DriverKind::ParseTest)
Copy link
Member

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 an r). I don’t know if it’s good that this is spelled without an r (means we don’t have two utilities with the same name) or bad (inconsistency).

.Default(llvm::None);

if (Kind.has_value())
Expand Down Expand Up @@ -3577,6 +3578,7 @@ void Driver::printHelp(bool ShowHidden) const {
case DriverKind::APIExtract:
case DriverKind::APIDigester:
case DriverKind::CacheTool:
case DriverKind::ParseTest:
ExcludedFlagsBitmask |= options::NoBatchOption;
break;
}
Expand Down
10 changes: 9 additions & 1 deletion lib/DriverTool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ set(driver_sources_and_options
swift_cache_tool_main.cpp
swift_indent_main.cpp
swift_symbolgraph_extract_main.cpp
swift_api_extract_main.cpp)
swift_api_extract_main.cpp
swift_parse_test_main.cpp)

set(driver_common_libs
swiftAPIDigester
Expand All @@ -34,6 +35,13 @@ if(NOT SWIFT_BUILT_STANDALONE)
add_dependencies(swiftDriverTool clang-resource-headers)
endif()

if (SWIFT_BUILD_SWIFT_SYNTAX)
target_compile_definitions(swiftDriverTool
PRIVATE
SWIFT_BUILD_SWIFT_SYNTAX
)
endif()

set_swift_llvm_is_available(swiftDriverTool)

set(LLVM_TARGET_DEFINITIONS SwiftCacheToolOptions.td)
Expand Down
7 changes: 7 additions & 0 deletions lib/DriverTool/driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ extern int swift_api_extract_main(ArrayRef<const char *> Args,
extern int swift_cache_tool_main(ArrayRef<const char *> Args, const char *Argv0,
void *MainAddr);

/// Run 'swift-parse-test'
extern int swift_parse_test_main(ArrayRef<const char *> Args, const char *Argv0,
void *MainAddr);

/// Determine if the given invocation should run as a "subcommand".
///
/// Examples of "subcommands" are 'swift build' or 'swift test', which are
Expand Down Expand Up @@ -379,6 +383,9 @@ static int run_driver(StringRef ExecName,
return swift_cache_tool_main(
TheDriver.getArgsWithoutProgramNameAndDriverMode(argv), argv[0],
(void *)(intptr_t)getExecutablePath);
case Driver::DriverKind::ParseTest:
return swift_parse_test_main(argv, argv[0],
(void *)(intptr_t)getExecutablePath);
default:
break;
}
Expand Down
235 changes: 235 additions & 0 deletions lib/DriverTool/swift_parse_test_main.cpp
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

Suggested change
llvm::cl::opt<unsigned int> Iteration = llvm::cl::opt<unsigned int>(
"n", llvm::cl::desc("iteration"), llvm::cl::init(1));
llvm::cl::opt<unsigned int> Iterations = llvm::cl::opt<unsigned int>(
"n", llvm::cl::desc("iteration"), llvm::cl::init(1));


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
Copy link
Member

Choose a reason for hiding this comment

The 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 SWIFT_BUILD_SWIFT_SYNTAX?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do in swift_parse_test_main, I also made this mistake when I first went through it. An alternative design would be to have these methods return an Error, which would mean the SWIFT_BUILD_SWIFT_SYNTAX check in swift_parse_test_main could be removed for something more generic.

}
};

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) {
Copy link
Member

Choose a reason for hiding this comment

The 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 Duration is?

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
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
Loading