|
| 1 | +//===--- swift_parse_test_main.cpp ----------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2023 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | +// |
| 13 | +// A utility tool to measure the parser performance. |
| 14 | +// |
| 15 | +//===----------------------------------------------------------------------===// |
| 16 | + |
| 17 | +#include "swift/AST/ASTContext.h" |
| 18 | +#include "swift/Basic/LLVM.h" |
| 19 | +#include "swift/Basic/LangOptions.h" |
| 20 | +#include "swift/Bridging/ASTGen.h" |
| 21 | +#include "swift/Parse/Parser.h" |
| 22 | +#include "llvm/Support/CommandLine.h" |
| 23 | + |
| 24 | +#include <chrono> |
| 25 | +#include <ctime> |
| 26 | + |
| 27 | +using namespace swift; |
| 28 | + |
| 29 | +namespace { |
| 30 | + |
| 31 | +enum class Executor { |
| 32 | + SwiftParser, |
| 33 | + LibParse, |
| 34 | +}; |
| 35 | + |
| 36 | +enum class ExecuteOptionFlag { |
| 37 | + /// Enable body skipping |
| 38 | + SkipBodies = 1 << 0, |
| 39 | +}; |
| 40 | +using ExecuteOptions = OptionSet<ExecuteOptionFlag>; |
| 41 | + |
| 42 | +struct SwiftParseTestOptions { |
| 43 | + llvm::cl::list<Executor> Executors = llvm::cl::list<Executor>( |
| 44 | + llvm::cl::desc("Available parsers"), |
| 45 | + llvm::cl::values( |
| 46 | + clEnumValN(Executor::SwiftParser, "swift-parser", "SwiftParser"), |
| 47 | + clEnumValN(Executor::LibParse, "lib-parse", "libParse"))); |
| 48 | + |
| 49 | + llvm::cl::opt<unsigned int> Iteration = llvm::cl::opt<unsigned int>( |
| 50 | + "n", llvm::cl::desc("iteration"), llvm::cl::init(1)); |
| 51 | + |
| 52 | + llvm::cl::opt<bool> SkipBodies = llvm::cl::opt<bool>( |
| 53 | + "skip-bodies", |
| 54 | + llvm::cl::desc("skip function bodies and type members if possible")); |
| 55 | + |
| 56 | + llvm::cl::list<std::string> InputPaths = llvm::cl::list<std::string>( |
| 57 | + llvm::cl::Positional, llvm::cl::desc("input paths")); |
| 58 | +}; |
| 59 | + |
| 60 | +struct LibParseExecutor { |
| 61 | + constexpr static StringRef name = "libParse"; |
| 62 | + |
| 63 | + static void performParse(llvm::MemoryBufferRef buffer, ExecuteOptions opts) { |
| 64 | + SourceManager SM; |
| 65 | + unsigned bufferID = |
| 66 | + SM.addNewSourceBuffer(llvm::MemoryBuffer::getMemBuffer(buffer)); |
| 67 | + DiagnosticEngine diagEngine(SM); |
| 68 | + LangOptions langOpts; |
| 69 | + TypeCheckerOptions typeckOpts; |
| 70 | + SILOptions silOpts; |
| 71 | + SearchPathOptions searchPathOpts; |
| 72 | + ClangImporterOptions clangOpts; |
| 73 | + symbolgraphgen::SymbolGraphOptions symbolOpts; |
| 74 | + std::unique_ptr<ASTContext> ctx( |
| 75 | + ASTContext::get(langOpts, typeckOpts, silOpts, searchPathOpts, |
| 76 | + clangOpts, symbolOpts, SM, diagEngine)); |
| 77 | + |
| 78 | + SourceFile::ParsingOptions parseOpts; |
| 79 | + parseOpts |= SourceFile::ParsingFlags::DisablePoundIfEvaluation; |
| 80 | + if (!opts.contains(ExecuteOptionFlag::SkipBodies)) |
| 81 | + parseOpts |= SourceFile::ParsingFlags::DisableDelayedBodies; |
| 82 | + |
| 83 | + ModuleDecl *M = ModuleDecl::create(Identifier(), *ctx); |
| 84 | + SourceFile *SF = |
| 85 | + new (*ctx) SourceFile(*M, SourceFileKind::Library, bufferID, parseOpts); |
| 86 | + |
| 87 | + Parser parser(bufferID, *SF, /*SILParserState=*/nullptr); |
| 88 | + SmallVector<ASTNode> items; |
| 89 | + parser.parseTopLevelItems(items); |
| 90 | + } |
| 91 | +}; |
| 92 | + |
| 93 | +struct SwiftParserExecutor { |
| 94 | + constexpr static StringRef name = "SwiftParser"; |
| 95 | + |
| 96 | + static void performParse(llvm::MemoryBufferRef buffer, ExecuteOptions opts) { |
| 97 | +#if SWIFT_BUILD_SWIFT_SYNTAX |
| 98 | + // TODO: Implement 'ExecuteOptionFlag::SkipBodies' |
| 99 | + auto sourceFile = swift_ASTGen_parseSourceFile( |
| 100 | + buffer.getBufferStart(), buffer.getBufferSize(), /*moduleName=*/"", |
| 101 | + buffer.getBufferIdentifier().data(), /*ASTContext=*/nullptr); |
| 102 | + swift_ASTGen_destroySourceFile(sourceFile); |
| 103 | +#endif |
| 104 | + } |
| 105 | +}; |
| 106 | + |
| 107 | +static void _loadSwiftFilesRecursively( |
| 108 | + StringRef path, |
| 109 | + SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers) { |
| 110 | + if (llvm::sys::fs::is_directory(path)) { |
| 111 | + std::error_code err; |
| 112 | + for (auto I = llvm::sys::fs::directory_iterator(path, err), |
| 113 | + E = llvm::sys::fs::directory_iterator(); |
| 114 | + I != E; I.increment(err)) { |
| 115 | + _loadSwiftFilesRecursively(I->path(), buffers); |
| 116 | + } |
| 117 | + } else if (path.endswith(".swift")) { |
| 118 | + if (auto buffer = llvm::MemoryBuffer::getFile(path)) { |
| 119 | + buffers.push_back(std::move(*buffer)); |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +/// Load all '.swift' files in the specified \p filePaths into \p buffers. |
| 125 | +/// If the path is a directory, this recursively collects the files in it. |
| 126 | +static void |
| 127 | +loadSources(ArrayRef<std::string> filePaths, |
| 128 | + SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers) { |
| 129 | + for (auto path : filePaths) { |
| 130 | + _loadSwiftFilesRecursively(path, buffers); |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +/// Measure the duration of \p body execution. |
| 135 | +template <typename Duration> |
| 136 | +static std::pair<Duration, Duration> measure(llvm::function_ref<void()> body) { |
| 137 | + auto cStart = std::clock(); |
| 138 | + auto tStart = std::chrono::steady_clock::now(); |
| 139 | + body(); |
| 140 | + auto cEnd = std::clock(); |
| 141 | + auto tEnd = std::chrono::steady_clock::now(); |
| 142 | + |
| 143 | + auto clockMultiply = |
| 144 | + Duration::period::den / CLOCKS_PER_SEC / Duration::period::num; |
| 145 | + |
| 146 | + Duration cDuration((cEnd - cStart) * clockMultiply); |
| 147 | + return {std::chrono::duration_cast<Duration>(tEnd - tStart), |
| 148 | + std::chrono::duration_cast<Duration>(cDuration)}; |
| 149 | +} |
| 150 | + |
| 151 | +/// Perform the performance measurement using \c Executor . |
| 152 | +/// Parse all \p buffers using \c Executor , \p iteration times, and print out |
| 153 | +/// the measurement to the \c stdout. |
| 154 | +template <typename Executor> |
| 155 | +static void |
| 156 | +perform(const SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> &buffers, |
| 157 | + ExecuteOptions opts, unsigned iteration, uintmax_t totalBytes, |
| 158 | + uintmax_t totalLines) { |
| 159 | + |
| 160 | + llvm::outs() << "----\n"; |
| 161 | + llvm::outs() << "parser: " << Executor::name << "\n"; |
| 162 | + |
| 163 | + using duration_t = std::chrono::nanoseconds; |
| 164 | + auto tDuration = duration_t::zero(); |
| 165 | + auto cDuration = duration_t::zero(); |
| 166 | + |
| 167 | + for (unsigned i = 0; i < iteration; i++) { |
| 168 | + for (auto &buffer : buffers) { |
| 169 | + std::pair<duration_t, duration_t> elapsed = measure<duration_t>( |
| 170 | + [&] { Executor::performParse(buffer->getMemBufferRef(), opts); }); |
| 171 | + tDuration += elapsed.first; |
| 172 | + cDuration += elapsed.second; |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + auto tDisplay = |
| 177 | + std::chrono::duration_cast<std::chrono::milliseconds>(tDuration).count(); |
| 178 | + auto cDisplay = |
| 179 | + std::chrono::duration_cast<std::chrono::milliseconds>(cDuration).count(); |
| 180 | + |
| 181 | + auto byteTPS = totalBytes * duration_t::period::den / cDuration.count(); |
| 182 | + auto lineTPS = totalLines * duration_t::period::den / cDuration.count(); |
| 183 | + |
| 184 | + llvm::outs() << llvm::format("wall clock time (ms): %8d\n", tDisplay) |
| 185 | + << llvm::format("cpu time (ms): %8d\n", cDisplay) |
| 186 | + << llvm::format("throughput (byte/s): %8d\n", byteTPS) |
| 187 | + << llvm::format("throughput (line/s): %8d\n", lineTPS); |
| 188 | +} |
| 189 | + |
| 190 | +} // namespace |
| 191 | + |
| 192 | +int swift_parse_test_main(ArrayRef<const char *> args, const char *argv0, |
| 193 | + void *mainAddr) { |
| 194 | + SwiftParseTestOptions options; |
| 195 | + llvm::cl::ParseCommandLineOptions(args.size(), args.data(), |
| 196 | + "Swift parse test\n"); |
| 197 | + |
| 198 | + unsigned iteration = options.Iteration; |
| 199 | + ExecuteOptions execOptions; |
| 200 | + if (options.SkipBodies) |
| 201 | + execOptions |= ExecuteOptionFlag::SkipBodies; |
| 202 | + |
| 203 | + SmallVector<std::unique_ptr<llvm::MemoryBuffer>> buffers; |
| 204 | + loadSources(options.InputPaths, buffers); |
| 205 | + unsigned int byteCount = 0; |
| 206 | + unsigned int lineCount = 0; |
| 207 | + for (auto &buffer : buffers) { |
| 208 | + byteCount += buffer->getBufferSize(); |
| 209 | + lineCount += buffer->getBuffer().count('\n'); |
| 210 | + } |
| 211 | + |
| 212 | + llvm::outs() << llvm::format("file count: %8d\n", buffers.size()) |
| 213 | + << llvm::format("total bytes: %8d\n", byteCount) |
| 214 | + << llvm::format("total lines: %8d\n", lineCount) |
| 215 | + << llvm::format("iterations: %8d\n", iteration); |
| 216 | + for (auto mode : options.Executors) { |
| 217 | + switch (mode) { |
| 218 | + case Executor::SwiftParser: |
| 219 | +#if SWIFT_BUILD_SWIFT_SYNTAX |
| 220 | + perform<SwiftParserExecutor>(buffers, execOptions, iteration, byteCount, |
| 221 | + lineCount); |
| 222 | + break; |
| 223 | +#else |
| 224 | + llvm::errs() << "error: SwiftParser is not enabled\n"; |
| 225 | + return 1; |
| 226 | +#endif |
| 227 | + case Executor::LibParse: |
| 228 | + perform<LibParseExecutor>(buffers, execOptions, iteration, byteCount, |
| 229 | + lineCount); |
| 230 | + break; |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + return 0; |
| 235 | +} |
0 commit comments