From 001ca0ae739dc188a528b7ae3e552f5ef05cb5e5 Mon Sep 17 00:00:00 2001 From: Marc Auberer Date: Mon, 26 Aug 2024 23:07:14 +0200 Subject: [PATCH] Use topology sort for determining source file checking order --- src/SourceFile.cpp | 49 ++++++++++++++-------------- src/SourceFile.h | 2 -- src/global/GlobalResourceManager.cpp | 32 +++++++++++++++++- src/global/GlobalResourceManager.h | 4 ++- src/typechecker/TypeChecker.cpp | 3 +- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/SourceFile.cpp b/src/SourceFile.cpp index add401dbf..0cd38f018 100644 --- a/src/SourceFile.cpp +++ b/src/SourceFile.cpp @@ -238,8 +238,15 @@ void SourceFile::runTypeChecker() { // NOLINT(misc-no-recursion) // We need two runs here due to generics. // The first run to determine all concrete substantiations of potentially generic elements runTypeCheckerPre(); // Visit dependency tree from bottom to top + + // Compute best order of the source files to perform type checking + resourceManager.enqueueSourceFilesForTypeChecking(); // The second run to ensure, also generic scopes are type-checked properly - runTypeCheckerPost(); // Visit dependency tree from top to bottom + while (!resourceManager.sourceFileVisitQueue.empty()) { + SourceFile *sourceFile = resourceManager.sourceFileVisitQueue.front(); + sourceFile->runTypeCheckerPost(); + resourceManager.sourceFileVisitQueue.pop(); + } } void SourceFile::runTypeCheckerPre() { // NOLINT(misc-no-recursion) @@ -265,7 +272,7 @@ void SourceFile::runTypeCheckerPre() { // NOLINT(misc-no-recursion) void SourceFile::runTypeCheckerPost() { // NOLINT(misc-no-recursion) // Skip if restored from cache, this stage has already been done or not all dependants finished type checking - if (restoredFromCache || !haveAllDependantsBeenTypeChecked()) + if (restoredFromCache) return; Timer timer(&compilerOutput.times.typeCheckerPost); @@ -273,22 +280,10 @@ void SourceFile::runTypeCheckerPost() { // NOLINT(misc-no-recursion) // Start type-checking loop. The type-checker can request a re-execution. The max number of type-checker runs is limited TypeChecker typeChecker(resourceManager, this, TC_MODE_POST); - unsigned short typeCheckerRuns = 0; - do { - typeCheckerRuns++; - totalTypeCheckerRuns++; - reVisitRequested = false; - - // Type-check the current file first. Multiple times, if requested - timer.resume(); - typeChecker.visit(ast); - timer.pause(); - - // Then type-check all dependencies - for (SourceFile *sourceFile : dependencies | std::views::values) - sourceFile->runTypeCheckerPost(); - } while (reVisitRequested); + typeChecker.visit(ast); + totalTypeCheckerRuns++; + // Check if there are soft errors and print them checkForSoftErrors(); // Check if all dyn variables were type-inferred successfully @@ -296,7 +291,7 @@ void SourceFile::runTypeCheckerPost() { // NOLINT(misc-no-recursion) previousStage = TYPE_CHECKER_POST; timer.stop(); - printStatusMessage("Type Checker Post", IO_AST, IO_AST, compilerOutput.times.typeCheckerPost, typeCheckerRuns); + printStatusMessage("Type Checker Post", IO_AST, IO_AST, compilerOutput.times.typeCheckerPost, totalTypeCheckerRuns); // Save the JSON version in the compiler output if (cliOptions.dumpSettings.dumpSymbolTable || cliOptions.testMode) @@ -545,8 +540,16 @@ void SourceFile::runFrontEnd() { // NOLINT(misc-no-recursion) void SourceFile::runMiddleEnd() { runTypeCheckerPre(); CHECK_ABORT_FLAG_V() - runTypeCheckerPost(); - CHECK_ABORT_FLAG_V() + + // Compute best order of the source files to perform type checking + resourceManager.enqueueSourceFilesForTypeChecking(); + // Visit the source files in this order + while (!resourceManager.sourceFileVisitQueue.empty()) { + SourceFile *sourceFile = resourceManager.sourceFileVisitQueue.front(); + sourceFile->runTypeCheckerPost(); + CHECK_ABORT_FLAG_V() + resourceManager.sourceFileVisitQueue.pop(); + } } void SourceFile::runBackEnd() { // NOLINT(misc-no-recursion) @@ -716,10 +719,6 @@ bool SourceFile::isRT(RuntimeModule runtimeModule) const { return exportedNameRegistry.at(topLevelName).targetEntry->scope == globalScope.get(); } -bool SourceFile::haveAllDependantsBeenTypeChecked() const { - return std::ranges::all_of(dependants, [](const SourceFile *dependant) { return dependant->totalTypeCheckerRuns >= 1; }); -} - /** * Acquire all publicly visible symbols from the imported source file and put them in the name registry of the current one. * But only do that for the symbols that are actually defined in the imported source file. Do not allow transitive dependencies. @@ -823,7 +822,7 @@ void SourceFile::printStatusMessage(const char *stage, const CompileStageIOType outputStr << compilerStageIoTypeName[in] << " --> " << compilerStageIoTypeName[out]; outputStr << " (" << std::to_string(stageRuntime) << " ms"; if (stageRuns > 0) - outputStr << "; " << std::to_string(stageRuns) << " run(s)"; + outputStr << "; Run " << std::to_string(stageRuns); outputStr << ")\n"; // Print std::cout << outputStr.str(); diff --git a/src/SourceFile.h b/src/SourceFile.h index a655edac2..5cf311327 100644 --- a/src/SourceFile.h +++ b/src/SourceFile.h @@ -166,7 +166,6 @@ class SourceFile { bool alwaysKeepSymbolsOnNameCollision = false; bool ignoreWarnings = false; bool restoredFromCache = false; - bool reVisitRequested = false; CompileStageType previousStage = NONE; SourceFileAntlrCtx antlrCtx; CompilerOutput compilerOutput; @@ -192,7 +191,6 @@ class SourceFile { uint8_t totalTypeCheckerRuns = 0; // Private methods - bool haveAllDependantsBeenTypeChecked() const; void mergeNameRegistries(const SourceFile &importedSourceFile, const std::string &importName); void dumpOutput(const std::string &content, const std::string &caption, const std::string &fileSuffix) const; void visualizerPreamble(std::stringstream &output) const; diff --git a/src/global/GlobalResourceManager.cpp b/src/global/GlobalResourceManager.cpp index a19736ea9..7ef5d327c 100644 --- a/src/global/GlobalResourceManager.cpp +++ b/src/global/GlobalResourceManager.cpp @@ -61,7 +61,7 @@ GlobalResourceManager::~GlobalResourceManager() { SourceFile *GlobalResourceManager::createSourceFile(SourceFile *parent, const std::string &depName, const std::filesystem::path &path, bool isStdFile) { // Check if the source file was already added (e.g. by another source file that imports it) - const std::string filePathStr = std::filesystem::weakly_canonical(std::filesystem::absolute(path)).string(); + const std::string filePathStr = weakly_canonical(absolute(path)).string(); // Create the new source file if it does not exist yet if (!sourceFiles.contains(filePathStr)) @@ -70,6 +70,36 @@ SourceFile *GlobalResourceManager::createSourceFile(SourceFile *parent, const st return sourceFiles.at(filePathStr).get(); } +void GlobalResourceManager::enqueueSourceFilesForTypeChecking() { + assert(sourceFileVisitQueue.empty()); + std::stack stack; + std::unordered_set visited; + + // Call the recursive helper function to store topological sort starting from all files + for (auto &[name, sourceFile] : sourceFiles) + if (!visited.contains(sourceFile->name)) + topologicalSortHelper(sourceFile.get(), visited, stack); + + while (!stack.empty()) { + sourceFileVisitQueue.push(stack.top()); + stack.pop(); + } +} + +void GlobalResourceManager::topologicalSortHelper(SourceFile *file, std::unordered_set &visited, + std::stack &stack) { + // Mark the current node as visited + visited.insert(file->name); + + // Recur for all the dependencies of this source file + for (auto &[name, sourceFile] : file->dependencies) + if (!visited.contains(name)) + topologicalSortHelper(sourceFile, visited, stack); + + // Push the current source file to the stack + stack.push(file); +} + uint64_t GlobalResourceManager::getNextCustomTypeId() { return nextCustomTypeId++; } size_t GlobalResourceManager::getTotalLineCount() const { diff --git a/src/global/GlobalResourceManager.h b/src/global/GlobalResourceManager.h index 7a39e2452..7c15ea477 100644 --- a/src/global/GlobalResourceManager.h +++ b/src/global/GlobalResourceManager.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -36,6 +35,8 @@ class GlobalResourceManager { // Public methods SourceFile *createSourceFile(SourceFile *parent, const std::string &depName, const std::filesystem::path &path, bool isStdFile); + void enqueueSourceFilesForTypeChecking(); + static void topologicalSortHelper(SourceFile* file, std::unordered_set& visited, std::stack& stack); uint64_t getNextCustomTypeId(); size_t getTotalLineCount() const; @@ -55,6 +56,7 @@ class GlobalResourceManager { RuntimeModuleManager runtimeModuleManager; Timer totalTimer; ErrorManager errorManager; + std::queue sourceFileVisitQueue; // Used for queueing type checking in the correct order bool abortCompilation = false; private: diff --git a/src/typechecker/TypeChecker.cpp b/src/typechecker/TypeChecker.cpp index 3be3e7490..4dfb60dca 100644 --- a/src/typechecker/TypeChecker.cpp +++ b/src/typechecker/TypeChecker.cpp @@ -2667,8 +2667,9 @@ std::vector &TypeChecker::getOpFctPointers(ASTNode *node) cons * @param fct Function to check */ void TypeChecker::requestRevisitIfRequired(const Function *fct) const { + // Push source file to the back of the processing queue if (fct && !fct->alreadyTypeChecked && !fct->entry->scope->isImportedBy(rootScope)) - fct->entry->scope->sourceFile->reVisitRequested = true; + resourceManager.sourceFileVisitQueue.push(fct->entry->scope->sourceFile); } /**