generator.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. 
  2. #include "generator.h"
  3. using namespace dsr;
  4. static uint64_t checksum(const ReadableString& text) {
  5. uint64_t a = 0x8C2A03D4;
  6. uint64_t b = 0xF42B1583;
  7. uint64_t c = 0xA6815E74;
  8. uint64_t d = 0;
  9. for (int i = 0; i < string_length(text); i++) {
  10. a = (b * c + ((i * 3756 + 2654) & 58043)) & 0xFFFFFFFF;
  11. b = (231 + text[i] * (a & 154) + c * 867 + 28294061) & 0xFFFFFFFF;
  12. c = (a ^ b ^ (text[i] * 1543217521)) & 0xFFFFFFFF;
  13. d = d ^ (a << 32) ^ b ^ (c << 16);
  14. }
  15. return d;
  16. }
  17. static uint64_t checksum(const Buffer& buffer) {
  18. SafePointer<uint8_t> data = buffer_getSafeData<uint8_t>(buffer, "checksum input buffer");
  19. uint64_t a = 0x8C2A03D4;
  20. uint64_t b = 0xF42B1583;
  21. uint64_t c = 0xA6815E74;
  22. uint64_t d = 0;
  23. for (int i = 0; i < buffer_getSize(buffer); i++) {
  24. a = (b * c + ((i * 3756 + 2654) & 58043)) & 0xFFFFFFFF;
  25. b = (231 + data[i] * (a & 154) + c * 867 + 28294061) & 0xFFFFFFFF;
  26. c = (a ^ b ^ (data[i] * 1543217521)) & 0xFFFFFFFF;
  27. d = d ^ (a << 32) ^ b ^ (c << 16);
  28. }
  29. return d;
  30. }
  31. static int64_t findDependency(ProjectContext &context, const ReadableString& findPath);
  32. static void resolveConnection(Connection &connection);
  33. static void resolveDependency(Dependency &dependency);
  34. static String findSourceFile(const ReadableString& headerPath, bool acceptC, bool acceptCpp);
  35. static void flushToken(List<String> &target, String &currentToken);
  36. static void tokenize(List<String> &target, const ReadableString& line);
  37. static void interpretPreprocessing(ProjectContext &context, int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber);
  38. static void analyzeCode(ProjectContext &context, int64_t parentIndex, String content, const ReadableString &parentFolder);
  39. static int64_t findDependency(ProjectContext &context, const ReadableString& findPath) {
  40. for (int d = 0; d < context.dependencies.length(); d++) {
  41. if (string_match(context.dependencies[d].path, findPath)) {
  42. return d;
  43. }
  44. }
  45. return -1;
  46. }
  47. static void resolveConnection(ProjectContext &context, Connection &connection) {
  48. connection.dependencyIndex = findDependency(context, connection.path);
  49. }
  50. static void resolveDependency(ProjectContext &context, Dependency &dependency) {
  51. for (int l = 0; l < dependency.links.length(); l++) {
  52. resolveConnection(context, dependency.links[l]);
  53. }
  54. for (int i = 0; i < dependency.includes.length(); i++) {
  55. resolveConnection(context, dependency.includes[i]);
  56. }
  57. }
  58. void resolveDependencies(ProjectContext &context) {
  59. for (int d = 0; d < context.dependencies.length(); d++) {
  60. resolveDependency(context, context.dependencies[d]);
  61. }
  62. }
  63. static String findSourceFile(const ReadableString& headerPath, bool acceptC, bool acceptCpp) {
  64. int lastDotIndex = string_findLast(headerPath, U'.');
  65. if (lastDotIndex != -1) {
  66. ReadableString extensionlessPath = string_removeOuterWhiteSpace(string_before(headerPath, lastDotIndex));
  67. String cPath = extensionlessPath + U".c";
  68. String cppPath = extensionlessPath + U".cpp";
  69. if (acceptC && file_getEntryType(cPath) == EntryType::File) {
  70. return cPath;
  71. } else if (acceptCpp && file_getEntryType(cppPath) == EntryType::File) {
  72. return cppPath;
  73. }
  74. }
  75. return U"";
  76. }
  77. static void flushToken(List<String> &target, String &currentToken) {
  78. if (string_length(currentToken) > 0) {
  79. target.push(currentToken);
  80. currentToken = U"";
  81. }
  82. }
  83. static void tokenize(List<String> &target, const ReadableString& line) {
  84. String currentToken;
  85. for (int i = 0; i < string_length(line); i++) {
  86. DsrChar c = line[i];
  87. DsrChar nextC = line[i + 1];
  88. if (c == U'#' && nextC == U'#') {
  89. // Appending tokens using ##
  90. i++;
  91. } else if (c == U'#' || c == U'(' || c == U')' || c == U'[' || c == U']' || c == U'{' || c == U'}') {
  92. // Atomic token of a single character
  93. flushToken(target, currentToken);
  94. string_appendChar(currentToken, c);
  95. flushToken(target, currentToken);
  96. } else if (c == U' ' || c == U'\t') {
  97. // Whitespace
  98. flushToken(target, currentToken);
  99. } else {
  100. string_appendChar(currentToken, c);
  101. }
  102. }
  103. flushToken(target, currentToken);
  104. }
  105. static void interpretPreprocessing(ProjectContext &context, int64_t parentIndex, const List<String> &tokens, const ReadableString &parentFolder, int64_t lineNumber) {
  106. if (tokens.length() >= 3) {
  107. if (string_match(tokens[1], U"include")) {
  108. if (tokens[2][0] == U'\"') {
  109. String relativePath = string_unmangleQuote(tokens[2]);
  110. String absolutePath = file_getTheoreticalAbsolutePath(relativePath, parentFolder, LOCAL_PATH_SYNTAX);
  111. context.dependencies[parentIndex].includes.pushConstruct(absolutePath, lineNumber);
  112. analyzeFromFile(context, absolutePath);
  113. }
  114. }
  115. }
  116. }
  117. static void analyzeCode(ProjectContext &context, int64_t parentIndex, String content, const ReadableString &parentFolder) {
  118. List<String> tokens;
  119. bool continuingLine = false;
  120. int64_t lineNumber = 0;
  121. string_split_callback(content, U'\n', true, [&parentIndex, &parentFolder, &tokens, &continuingLine, &lineNumber, &context](ReadableString line) {
  122. lineNumber++;
  123. if (line[0] == U'#' || continuingLine) {
  124. tokenize(tokens, line);
  125. // Continuing pre-processing line using \ at the end.
  126. continuingLine = line[string_length(line) - 1] == U'\\';
  127. } else {
  128. continuingLine = false;
  129. }
  130. if (!continuingLine && tokens.length() > 0) {
  131. interpretPreprocessing(context, parentIndex, tokens, parentFolder, lineNumber);
  132. tokens.clear();
  133. }
  134. });
  135. }
  136. void analyzeFromFile(ProjectContext &context, const ReadableString& absolutePath) {
  137. if (findDependency(context, absolutePath) != -1) {
  138. // Already analyzed the current entry. Abort to prevent duplicate dependencies.
  139. return;
  140. }
  141. int lastDotIndex = string_findLast(absolutePath, U'.');
  142. if (lastDotIndex != -1) {
  143. Extension extension = extensionFromString(string_after(absolutePath, lastDotIndex));
  144. if (extension != Extension::Unknown) {
  145. // The old length will be the new dependency's index.
  146. int64_t parentIndex = context.dependencies.length();
  147. // Get the file's binary content.
  148. Buffer fileBuffer = file_loadBuffer(absolutePath);
  149. // Get the checksum
  150. uint64_t contentChecksum = checksum(fileBuffer);
  151. context.dependencies.pushConstruct(absolutePath, extension, contentChecksum);
  152. if (extension == Extension::H || extension == Extension::Hpp) {
  153. // The current file is a header, so look for an implementation with the corresponding name.
  154. String sourcePath = findSourceFile(absolutePath, extension == Extension::H, true);
  155. // If found:
  156. if (string_length(sourcePath) > 0) {
  157. // Remember that anything using the header will have to link with the implementation.
  158. context.dependencies[parentIndex].links.pushConstruct(sourcePath);
  159. // Look for included headers in the implementation file.
  160. analyzeFromFile(context, sourcePath);
  161. }
  162. }
  163. // Interpret the file's content.
  164. analyzeCode(context, parentIndex, string_loadFromMemory(fileBuffer), file_getRelativeParentFolder(absolutePath));
  165. }
  166. }
  167. }
  168. static void debugPrintDependencyList(const List<Connection> &connnections, const ReadableString verb) {
  169. for (int c = 0; c < connnections.length(); c++) {
  170. int64_t lineNumber = connnections[c].lineNumber;
  171. if (lineNumber != -1) {
  172. printText(U" @", lineNumber, U"\t");
  173. } else {
  174. printText(U" \t");
  175. }
  176. printText(U" ", verb, U" ", file_getPathlessName(connnections[c].path), U"\n");
  177. }
  178. }
  179. void printDependencies(ProjectContext &context) {
  180. for (int d = 0; d < context.dependencies.length(); d++) {
  181. printText(U"* ", file_getPathlessName(context.dependencies[d].path), U"\n");
  182. debugPrintDependencyList(context.dependencies[d].includes, U"including");
  183. debugPrintDependencyList(context.dependencies[d].links, U"linking");
  184. }
  185. }
  186. static void script_printMessage(ScriptTarget &output, const ReadableString message) {
  187. if (output.language == ScriptLanguage::Batch) {
  188. string_append(output.generatedCode, U"echo ", message, U"\n");
  189. } else if (output.language == ScriptLanguage::Bash) {
  190. string_append(output.generatedCode, U"echo ", message, U"\n");
  191. }
  192. }
  193. static void script_executeLocalBinary(ScriptTarget &output, const ReadableString code) {
  194. if (output.language == ScriptLanguage::Batch) {
  195. string_append(output.generatedCode, code, ".exe\n");
  196. } else if (output.language == ScriptLanguage::Bash) {
  197. string_append(output.generatedCode, file_combinePaths(U".", code), U";\n");
  198. }
  199. }
  200. static void traverserHeaderChecksums(ProjectContext &context, uint64_t &target, int64_t dependencyIndex) {
  201. // Use checksums from headers
  202. for (int h = 0; h < context.dependencies[dependencyIndex].includes.length(); h++) {
  203. int64_t includedIndex = context.dependencies[dependencyIndex].includes[h].dependencyIndex;
  204. if (!context.dependencies[includedIndex].visited) {
  205. //printText(U" traverserHeaderChecksums(context, ", includedIndex, U") ", context.dependencies[includedIndex].path, "\n");
  206. // Bitwise exclusive or is both order independent and entropy preserving for non-repeated content.
  207. target = target ^ context.dependencies[includedIndex].contentChecksum;
  208. // Just have to make sure that the same checksum is not used twice.
  209. context.dependencies[includedIndex].visited = true;
  210. // Use checksums from headers recursively
  211. traverserHeaderChecksums(context, target, includedIndex);
  212. }
  213. }
  214. }
  215. static uint64_t getCombinedChecksum(ProjectContext &context, int64_t dependencyIndex) {
  216. //printText(U"getCombinedChecksum(context, ", dependencyIndex, U") ", context.dependencies[dependencyIndex].path, "\n");
  217. for (int d = 0; d < context.dependencies.length(); d++) {
  218. context.dependencies[d].visited = false;
  219. }
  220. context.dependencies[dependencyIndex].visited = true;
  221. uint64_t result = context.dependencies[dependencyIndex].contentChecksum;
  222. traverserHeaderChecksums(context, result, dependencyIndex);
  223. return result;
  224. }
  225. struct SourceObject {
  226. uint64_t identityChecksum = 0; // Identification number for the object's name.
  227. uint64_t combinedChecksum = 0; // Combined content of the source file and all included headers recursively.
  228. String sourcePath, objectPath;
  229. SourceObject(ProjectContext &context, const ReadableString& sourcePath, const ReadableString& tempFolder, const ReadableString& identity, int64_t dependencyIndex)
  230. : identityChecksum(checksum(identity)), combinedChecksum(getCombinedChecksum(context, dependencyIndex)), sourcePath(sourcePath) {
  231. // By making the content checksum a part of the name, one can switch back to an older version without having to recompile everything again.
  232. // Just need to clean the temporary folder once in a while because old versions can take a lot of space.
  233. this->objectPath = file_combinePaths(tempFolder, string_combine(U"dfpsr_", this->identityChecksum, U"_", this->combinedChecksum, U".o"));
  234. }
  235. };
  236. void generateCompilationScript(ScriptTarget &output, ProjectContext &context, const Machine &settings, ReadableString programPath) {
  237. // Convert lists of linker and compiler flags into strings.
  238. // TODO: Give a warning if two contradictory flags are used, such as optimization levels and language versions.
  239. // TODO: Make sure that no spaces are inside of the flags, because that can mess up detection of pre-existing and contradictory arguments.
  240. String compilerFlags;
  241. for (int i = 0; i < settings.compilerFlags.length(); i++) {
  242. string_append(compilerFlags, " ", settings.compilerFlags[i]);
  243. }
  244. String linkerFlags;
  245. for (int i = 0; i < settings.linkerFlags.length(); i++) {
  246. string_append(linkerFlags, " -l", settings.linkerFlags[i]);
  247. }
  248. printText(U"Generating build instructions for ", programPath, U" using settings:\n");
  249. printText(U" Compiler flags:", compilerFlags, U"\n");
  250. printText(U" Linker flags:", linkerFlags, U"\n");
  251. for (int v = 0; v < settings.variables.length(); v++) {
  252. printText(U" * ", settings.variables[v].key, U" = ", settings.variables[v].value);
  253. if (settings.variables[v].inherited) {
  254. printText(U" (inherited input)");
  255. }
  256. printText(U"\n");
  257. }
  258. // The compiler is often a global alias, so the user must supply either an alias or an absolute path.
  259. ReadableString compilerName = getFlag(settings, U"Compiler", U"g++"); // Assume g++ as the compiler if not specified.
  260. ReadableString compileFrom = getFlag(settings, U"CompileFrom", U"");
  261. // Check if the build system was asked to run the compiler from a specific folder.
  262. bool changePath = (string_length(compileFrom) > 0);
  263. if (changePath) {
  264. printText(U"Using ", compilerName, " as the compiler executed from ", compileFrom, ".\n");
  265. } else {
  266. printText(U"Using ", compilerName, " as the compiler from the current directory.\n");
  267. }
  268. // TODO: Warn if -DNDEBUG, -DDEBUG, or optimization levels are given directly.
  269. // Using the variables instead is both more flexible by accepting input arguments
  270. // and keeping the same format to better reuse compiled objects.
  271. if (getFlagAsInteger(settings, U"Debug")) {
  272. printText(U"Building with debug mode.\n");
  273. string_append(compilerFlags, " -DDEBUG");
  274. } else {
  275. printText(U"Building with release mode.\n");
  276. string_append(compilerFlags, " -DNDEBUG");
  277. }
  278. if (getFlagAsInteger(settings, U"StaticRuntime")) {
  279. if (getFlagAsInteger(settings, U"Windows")) {
  280. printText(U"Building with static runtime. Your application's binary will be bigger but can run without needing any installer.\n");
  281. string_append(compilerFlags, " -static -static-libgcc -static-libstdc++");
  282. string_append(linkerFlags, " -static -static-libgcc -static-libstdc++");
  283. } else {
  284. printText(U"The target platform does not support static linking of runtime. But don't worry about bundling any runtimes, because it comes with most of the Posix compliant operating systems.\n");
  285. }
  286. } else {
  287. printText(U"Building with dynamic runtime. Don't forget to bundle the C and C++ runtimes for systems that don't have it pre-installed.\n");
  288. }
  289. ReadableString optimizationLevel = getFlag(settings, U"Optimization", U"2");
  290. printText(U"Building with optimization level ", optimizationLevel, U".\n");
  291. string_append(compilerFlags, " -O", optimizationLevel);
  292. List<SourceObject> sourceObjects;
  293. bool hasSourceCode = false;
  294. bool needCppCompiler = false;
  295. for (int d = 0; d < context.dependencies.length(); d++) {
  296. Extension extension = context.dependencies[d].extension;
  297. if (extension == Extension::Cpp) {
  298. needCppCompiler = true;
  299. }
  300. if (extension == Extension::C || extension == Extension::Cpp) {
  301. // Dependency paths are already absolute from the recursive search.
  302. String sourcePath = context.dependencies[d].path;
  303. String identity = string_combine(sourcePath, compilerFlags);
  304. sourceObjects.pushConstruct(context, sourcePath, output.tempPath, identity, d);
  305. if (file_getEntryType(sourcePath) != EntryType::File) {
  306. throwError(U"The source file ", sourcePath, U" could not be found!\n");
  307. } else {
  308. hasSourceCode = true;
  309. }
  310. }
  311. }
  312. if (hasSourceCode) {
  313. // TODO: Give a warning if a known C compiler incapable of handling C++ is given C++ source code when needCppCompiler is true.
  314. if (changePath) {
  315. // Go into the requested folder.
  316. if (output.language == ScriptLanguage::Batch) {
  317. string_append(output.generatedCode, "pushd ", compileFrom, "\n");
  318. } else if (output.language == ScriptLanguage::Bash) {
  319. string_append(output.generatedCode, U"(cd ", compileFrom, ";\n");
  320. }
  321. }
  322. String allObjects;
  323. for (int i = 0; i < sourceObjects.length(); i++) {
  324. if (output.language == ScriptLanguage::Batch) {
  325. string_append(output.generatedCode, U"if exist ", sourceObjects[i].objectPath, U" (\n");
  326. } else if (output.language == ScriptLanguage::Bash) {
  327. string_append(output.generatedCode, U"if [ -e \"", sourceObjects[i].objectPath, U"\" ]; then\n");
  328. }
  329. script_printMessage(output, string_combine(U"Reusing ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U"."));
  330. if (output.language == ScriptLanguage::Batch) {
  331. string_append(output.generatedCode, U") else (\n");
  332. } else if (output.language == ScriptLanguage::Bash) {
  333. string_append(output.generatedCode, U"else\n");
  334. }
  335. script_printMessage(output, string_combine(U"Compiling ", sourceObjects[i].sourcePath, U" ID:", sourceObjects[i].identityChecksum, U" with \"", compilerFlags, U"\"."));
  336. string_append(output.generatedCode, compilerName, compilerFlags, U" -c ", sourceObjects[i].sourcePath, U" -o ", sourceObjects[i].objectPath, U"\n");
  337. if (output.language == ScriptLanguage::Batch) {
  338. string_append(output.generatedCode, ")\n");
  339. } else if (output.language == ScriptLanguage::Bash) {
  340. string_append(output.generatedCode, U"fi\n");
  341. }
  342. // Remember each object name for linking.
  343. string_append(allObjects, U" ", sourceObjects[i].objectPath);
  344. }
  345. script_printMessage(output, string_combine(U"Linking with \"", linkerFlags, U"\"."));
  346. string_append(output.generatedCode, compilerName, allObjects, linkerFlags, U" -o ", programPath, U"\n");
  347. if (changePath) {
  348. // Get back to the previous folder.
  349. if (output.language == ScriptLanguage::Batch) {
  350. string_append(output.generatedCode, "popd\n");
  351. } else if (output.language == ScriptLanguage::Bash) {
  352. string_append(output.generatedCode, U")\n");
  353. }
  354. }
  355. script_printMessage(output, U"Done building.");
  356. if (getFlagAsInteger(settings, U"Supressed")) {
  357. script_printMessage(output, string_combine(U"Execution of ", programPath, U" was supressed using the Supressed flag."));
  358. } else {
  359. script_printMessage(output, string_combine(U"Starting ", programPath));
  360. script_executeLocalBinary(output, programPath);
  361. script_printMessage(output, U"The program terminated.");
  362. }
  363. } else {
  364. printText("Filed to find any source code to compile.\n");
  365. }
  366. }