GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 0.0% 0 / 0 / 90
Functions: 0.0% 0 / 0 / 6
Branches: 0.0% 0 / 0 / 203

test/util/BootstrapUtil.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "BootstrapUtil.h"
4
5 #include <algorithm>
6 #include <cstdlib>
7 #include <cstring>
8 #include <iostream>
9 #include <regex>
10
11 #include <SourceFile.h>
12 #include <driver/Driver.h>
13 #include <exception/CliError.h>
14 #include <exception/CompilerError.h>
15 #include <exception/LexerError.h>
16 #include <exception/LinkerError.h>
17 #include <exception/ParserError.h>
18 #include <exception/SemanticError.h>
19 #include <global/GlobalResourceManager.h>
20 #include <util/CommonUtil.h>
21 #include <util/FileUtil.h>
22
23 #include "../driver/TestDriver.h"
24 #include "TestUtil.h"
25
26 namespace spice::testing {
27
28 using namespace spice::compiler;
29
30 extern TestDriverCliOptions testDriverCliOptions;
31
32 /**
33 * Build the bootstrap compiler with the host compiler, like 'spice build src-bootstrap/main.spice' would do.
34 * On success, the path of the built executable is stored in the test driver cli options.
35 *
36 * @return Successful or not
37 */
38 ✗ bool BootstrapUtil::buildBootstrapCompiler() {
39 // Check the required environment variables
40 ✗ for (const char *envVar : {"SPICE_STD_DIR", "SPICE_BOOTSTRAP_DIR", "LLVM_LIB_DIR"}) {
41 ✗ if (std::getenv(envVar) == nullptr) {
42 ✗ std::cerr << "Building the bootstrap compiler requires the environment variable " << envVar << " to be set\n";
43 ✗ return false;
44 }
45 }
46 ✗ const std::filesystem::path mainSourceFilePath = std::filesystem::path(std::getenv("SPICE_BOOTSTRAP_DIR")) / "main.spice";
47
48 // Prepare a clean output directory
49 ✗ const std::filesystem::path outputDir = std::filesystem::absolute(PATH_BOOTSTRAP_COMPILER_ARTIFACTS);
50 ✗ std::error_code ec;
51 ✗ std::filesystem::remove_all(outputDir, ec);
52 ✗ std::filesystem::create_directories(outputDir);
53 #if OS_WINDOWS
54 std::filesystem::path executablePath = outputDir / "spice.exe";
55 #else
56 ✗ std::filesystem::path executablePath = outputDir / "spice";
57 #endif
58 ✗ executablePath.make_preferred();
59
60 ✗ std::cout << "Building the bootstrap compiler from " << mainSourceFilePath.string() << " ..." << std::endl;
61 try {
62 ✗ const std::string outputPath = executablePath.string();
63 ✗ const std::string mainSourceFile = mainSourceFilePath.string();
64 ✗ std::array<const char *, 8> argv = {
65 ✗ "spice", "build", "-O3", "-lto", "--ignore-cache", "--output", outputPath.c_str(), mainSourceFile.c_str()};
66 ✗ CliOptions cliOptions;
67 ✗ Driver driver(cliOptions);
68 ✗ if (driver.parse(argv.size(), argv.data()) != EXIT_SUCCESS)
69 ✗ return false;
70 ✗ driver.enrich();
71
72 // Run the compile pipeline, mirroring compileProject() of the host compiler
73 ✗ GlobalResourceManager resourceManager(cliOptions);
74 ✗ SourceFile *sourceFile = resourceManager.createSourceFile(nullptr, MAIN_FILE_NAME, cliOptions.mainSourceFile, false);
75 ✗ sourceFile->runFrontEnd();
76 ✗ sourceFile->runMiddleEnd();
77 ✗ sourceFile->runBackEnd();
78 ✗ resourceManager.linker.prepare();
79 ✗ resourceManager.cacheManager.linkOrRestoreExecutable(resourceManager);
80 ✗ resourceManager.linker.cleanup();
81 // The bootstrap compiler is still incomplete, so its sources emit huge amounts of unused-symbol warnings. Do not print them
82 ✗ } catch (CliError &error) {
83 ✗ std::cerr << error.what() << "\n";
84 ✗ return false;
85 ✗ } catch (LexerError &error) {
86 ✗ std::cerr << error.what() << "\n";
87 ✗ return false;
88 ✗ } catch (ParserError &error) {
89 ✗ std::cerr << error.what() << "\n";
90 ✗ return false;
91 ✗ } catch (SemanticError &error) {
92 ✗ std::cerr << error.what() << "\n";
93 ✗ return false;
94 ✗ } catch (CompilerError &error) {
95 ✗ std::cerr << error.what() << "\n";
96 ✗ return false;
97 ✗ } catch (LinkerError &error) {
98 ✗ std::cerr << error.what() << "\n";
99 ✗ return false;
100 ✗ }
101
102 ✗ if (!exists(executablePath)) {
103 ✗ std::cerr << "Building the bootstrap compiler did not produce an executable at " << executablePath.string() << "\n";
104 ✗ return false;
105 }
106 ✗ std::cout << "Built the bootstrap compiler: " << executablePath.string() << std::endl;
107 ✗ testDriverCliOptions.bootstrapCompilerPath = executablePath.string();
108 ✗ return true;
109 ✗ }
110
111 /**
112 * The bootstrap compiler has no exceptions, so it reports compile errors via a panic:
113 *
114 * Program panicked at <file>:<line>:<col>: <error message>
115 * <line> <source code line of the panic>
116 * ...
117 *
118 * Extract the error message and make it comparable to the one of the host compiler. Like the host compiler, the bootstrap
119 * compiler already prints the file paths of code locations relative to the directory of the main source file.
120 *
121 * @param output Combined stdout and stderr output of the bootstrap compiler
122 * @return Error message, if the bootstrap compiler reported an error
123 */
124 ✗ std::optional<std::string> BootstrapUtil::extractErrorMessage(const std::string &output) {
125 ✗ static const std::regex PANIC_HEADER_REGEX(R"(Program panicked at [^\n]*?:(\d+):\d+: )");
126 ✗ std::smatch match;
127 ✗ if (!std::regex_search(output, match, PANIC_HEADER_REGEX))
128 ✗ return std::nullopt;
129 ✗ const size_t messageStart = match.position(0) + match.length(0);
130
131 // The message ends where the source code snippet of the panic location begins (its line number, followed by two spaces)
132 ✗ const std::string snippetStart = "\n" + match[1].str() + " ";
133 ✗ size_t messageEnd = output.find(snippetStart, messageStart);
134 ✗ if (messageEnd == std::string::npos)
135 ✗ messageEnd = output.find('\n', messageStart);
136 ✗ std::string message =
137 ✗ output.substr(messageStart, messageEnd == std::string::npos ? std::string::npos : messageEnd - messageStart);
138
139 // Normalize path separators on Windows
140 ✗ CommonUtil::replaceAll(message, "\\", "/");
141 ✗ return message;
142 ✗ }
143
144 /**
145 * Extract the serialized AST from the '--dump-ast' console output of the bootstrap compiler
146 *
147 * @param output Output of the bootstrap compiler
148 * @return Serialized AST, if found
149 */
150 ✗ std::optional<std::string> BootstrapUtil::extractSerializedAST(const std::string &output) {
151 ✗ size_t start = output.find(BOOTSTRAP_SERIALIZED_AST_CAPTION);
152 ✗ if (start == std::string::npos)
153 ✗ return std::nullopt;
154 ✗ start += std::strlen(BOOTSTRAP_SERIALIZED_AST_CAPTION);
155 // All lines of the dot code are indented, except the first one and the closing brace
156 ✗ const size_t end = output.find("\n}", start);
157 ✗ if (end == std::string::npos)
158 ✗ return std::nullopt;
159 ✗ return output.substr(start, end + 2 - start);
160 }
161
162 /**
163 * Check if the bootstrap compiler is already able to raise the error, expected by the given error ref file
164 *
165 * @param errorRefPath Path to the error ref file
166 * @return Supported or not
167 */
168 ✗ bool BootstrapUtil::isErrorSupported(const std::filesystem::path &errorRefPath) {
169 ✗ for (const std::filesystem::path &refPath : TestUtil::expandRefPaths(errorRefPath)) {
170 ✗ if (!exists(refPath))
171 ✗ continue;
172 ✗ const std::string expectedError = FileUtil::getFileContent(refPath);
173 ✗ const auto pred = [&](const char *prefix) { return expectedError.starts_with(prefix); };
174 ✗ return std::ranges::any_of(BOOTSTRAP_SUPPORTED_ERROR_PREFIXES, pred);
175 ✗ }
176 ✗ return false;
177 }
178
179 /**
180 * Erase the dso_local markers from the given IR code. The LLVM C API, which the bootstrap compiler uses, offers no way to
181 * mark global values as dso_local, so the IR of the host and the bootstrap compiler only differs in these markers.
182 *
183 * @param irCode IR code
184 */
185 ✗ void BootstrapUtil::eraseDSOLocalMarkers(std::string &irCode) { CommonUtil::replaceAll(irCode, " dso_local ", " "); }
186
187 } // namespace spice::testing
188