1 /// Copyright: Copyright (c) 2017-2022 Andrey Penechko
2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
3 /// Authors: Andrey Penechko
4 module cli;
5 
6 import std.stdio;
7 import std.file : exists;
8 import std.path : absolutePath, extension, baseName, setExtension;
9 import vox.all;
10 
11 enum WindowsSubsystemCli : ushort {
12 	CUI,
13 	GUI
14 }
15 
16 int runCli(string[] args)
17 {
18 	import std.getopt;
19 
20 	auto time1 = currTime;
21 	auto startInitTime = currTime;
22 
23 	Driver driver;
24 	WindowsSubsystemCli subSystem;
25 
26 	bool printTime;
27 	bool printMem;
28 	bool printStats;
29 	bool checkOnly;
30 	string outputFilename;
31 	string outputTarget = TARGET_OS_STRING[driver.context.hostOS];
32 	string filterFuncName;
33 	string targetHelp;
34 
35 	version(Windows) {
36 		targetHelp = "Choose target. [windows-x64(default), linux-x64, macos-x64]";
37 	}
38 	else version(linux) {
39 		targetHelp = "Choose target. [windows-x64, linux-x64(default), macos-x64]";
40 	}
41 	else version(OSX) {
42 		targetHelp = "Choose target. [windows-x64, linux-x64, macos-x64(default)]";
43 	}
44 	else static assert(false, "Unnhandled OS");
45 
46 	bool printHelp;
47 	GetoptResult optResult;
48 
49 	// Look for tool usage
50 	if (args.length > 1)
51 	{
52 		switch(args[1])
53 		{
54 			case "pdb-dump":
55 				if (args.length == 1) {
56 					writeln("Usage: vox pdb-dump file.pdb");
57 					return 1;
58 				}
59 				string filename = absolutePath(args[2]);
60 				if (!exists(filename))
61 				{
62 					writefln("File `%s` not found", absolutePath(filename));
63 					return 1;
64 				}
65 				import vox.be.debug_info.pdb;
66 				PdbReader.fromFile(filename);
67 				return 0;
68 
69 			default:
70 				break; // regular compiler invocation
71 		}
72 	}
73 
74 	// Regular compiler invocation
75 	retry_parse_opts:
76 	try
77 	{
78 		// GC
79 		optResult = getopt(
80 			args,
81 			"of", "Write output to file.", &outputFilename,
82 			"target", targetHelp, &outputTarget,
83 			"subsystem", "Select windows subsystem. [CUI(default), GUI]", &subSystem,
84 			"check-only", "Disable backend passes, leaving only error checking", &checkOnly,
85 			"bundle", "Emit .har file containing all of the input files and CLI arguments. No other output will be generated", &driver.context.bundleInputs,
86 
87 			"no-dce", "Disable Dead Code Elimination", &driver.context.disableDCE,
88 			"no-inline", "Disable Inlining", &driver.context.disableInline,
89 
90 			"print-time", "Print time of compilation.", &printTime,
91 			"print-mem", "Print memory consumtion.", &printMem,
92 			"print-stats", "Print general statistics", &printStats,
93 
94 			"print-source", "Print source code.", &driver.context.printSource,
95 			"print-lexemes", "Print lexemes.", &driver.context.printLexemes,
96 			"print-ast-fresh", "Print AST after parsing.", &driver.context.printAstFresh,
97 			"print-ast-sema", "Print AST after semantic analisys.", &driver.context.printAstSema,
98 			"print-ir", "Print IR after AST to IR pass.", &driver.context.printIr,
99 			"print-ir-opt-each", "Print IR after each optimization pass.", &driver.context.printIrOptEach,
100 			"print-ir-opt", "Print IR after all optimization passes.", &driver.context.printIrOpt,
101 			"print-ir-lower-each", "Print IR after each lowering pass.", &driver.context.printIrLowerEach,
102 			"print-ir-lower", "Print IR after all lowering passes.", &driver.context.printIrLower,
103 			"print-lir", "Print Print LIR after IR to LIR pass.", &driver.context.printLir,
104 			"print-liveness", "Print liveness analisys info.", &driver.context.printLiveIntervals,
105 			"print-lir-ra", "Print LIR after register allocation pass.", &driver.context.printLirRA,
106 			"print-stack-layout", "Print stack layout.", &driver.context.printStackLayout,
107 			"print-code-hex", "Print code hex.", &driver.context.printCodeHex,
108 			"print-symbols", "Print symbols.", &driver.context.printSymbols,
109 			"print-filter", "Print only info about <function name>.", &filterFuncName,
110 			"print-error-trace", "Print stack trace for every error", &driver.context.printTraceOnError,
111 		);
112 	}
113 	catch(GetOptException e)
114 	{
115 		import std.algorithm.mutation : remove;
116 
117 		writeln(e.msg);
118 		printHelp = true;
119 		args = args.remove(1);
120 		goto retry_parse_opts;
121 	}
122 
123 	switch(outputTarget) {
124 		case "windows-x64": driver.context.targetOs = TargetOs.windows; break;
125 		case "linux-x64":   driver.context.targetOs = TargetOs.linux;   break;
126 		case "macos-x64":   driver.context.targetOs = TargetOs.macos;   break;
127 		//case "linux-arm64": driver.context.targetOs = TargetOs.linux; driver.context.targetArch = TargetArch.arm64; break;
128 		default:
129 			writefln("Unknown target: %s", outputTarget);
130 			printHelp = true;
131 	}
132 
133 	if (args.length < 2) printHelp = true;
134 	if (args.length > 0) args = args[1..$]; // skip program name
135 
136 	if (optResult.helpWanted) printHelp = true;
137 
138 	if (printHelp)
139 	{
140 		writeln("Usage: vox [tool] [options]... [.vx|.har]...");
141 		writeln(" tools:");
142 		writeln("   pdb-dump - dumps the contents of a .pdb file");
143 		writeln(" options:");
144 
145 		size_t maxShortLength;
146 		size_t maxLongLength;
147 		foreach (it; optResult.options)
148 		{
149 			maxShortLength = max(maxShortLength, it.optShort.length);
150 			maxLongLength = max(maxLongLength, it.optLong.length);
151 		}
152 
153 		foreach (it; optResult.options)
154 		{
155 			writefln("%-*s %-*s  %s", maxShortLength, it.optShort, maxLongLength, it.optLong, it.help);
156 		}
157 
158 		return 0;
159 	}
160 
161 	final switch (subSystem) {
162 		case WindowsSubsystemCli.CUI: driver.context.windowsSubsystem = WindowsSubsystem.WINDOWS_CUI; break;
163 		case WindowsSubsystemCli.GUI: driver.context.windowsSubsystem = WindowsSubsystem.WINDOWS_GUI; break;
164 	}
165 
166 	string[] filenames = args;
167 
168 	// Select passes
169 	auto passes = exePasses;
170 	if (checkOnly) passes = frontendOnlyPasses; // Disable backend for check-only
171 	if (driver.context.bundleInputs) passes = bundlePasses;
172 
173 	driver.context.buildType = BuildType.exe;
174 
175 	driver.initialize(passes);
176 	driver.beginCompilation();
177 
178 	// Set outputFilename
179 	if (outputFilename) driver.context.outputFilename = outputFilename;
180 	else {
181 		string ext;
182 		if (driver.context.targetOs == TargetOs.windows) ext = ".exe";
183 		driver.context.outputFilename = filenames[0].baseName.setExtension(ext); // GC
184 	}
185 	if (driver.context.bundleInputs) {
186 		driver.context.outputFilename = driver.context.outputFilename.setExtension(".har");
187 	}
188 
189 	if (filterFuncName) driver.context.setDumpFilter(filterFuncName);
190 
191 	auto times = PerPassTimeMeasurements(1, driver.passes);
192 	auto endInitTime = currTime;
193 
194 	try
195 	{
196 		// Add files to the compilation
197 		foreach(filename; filenames)
198 		{
199 			string ext = filename.extension;
200 			switch(ext)
201 			{
202 				case ".har":
203 					if (!exists(filename))
204 					{
205 						driver.context.error("File `%s` not found", absolutePath(filename));
206 						break;
207 					}
208 
209 					auto file = File(filename, "r");
210 					char[] sourceBuffer = driver.context.sourceBuffer.voidPut(file.size);
211 					char[] harData = file.rawRead(sourceBuffer);
212 					file.close();
213 
214 					void onHarFile(SourceFileInfo fileInfo)
215 					{
216 						if (fileInfo.name == "<args>") {
217 							// skip
218 						} else {
219 							driver.addModule(fileInfo);
220 						}
221 					}
222 
223 					parseHar(driver.context, filename, harData, &onHarFile);
224 					break;
225 				default:
226 					driver.addModule(SourceFileInfo(filename));
227 					break;
228 			}
229 		}
230 
231 		scope(exit) {
232 			// In case of an error write the output
233 			if (driver.context.bundleInputs) write_bundle(driver.context);
234 		}
235 
236 		driver.compile();
237 	}
238 	catch(CompilationException e) {
239 		writeln(driver.context.sink.text);
240 		if (e.isICE) {
241 			writeln(e);
242 		}
243 		return 1;
244 	}
245 	catch(Throwable t) {
246 		writeln(driver.context.sink.text);
247 		writeln(t);
248 		return 1;
249 	}
250 
251 	TextSink sink;
252 	if (printMem) driver.context.printMemSize(sink);
253 
254 	auto startReleaseTime = currTime;
255 		// releasing memory is not necessary when running in standalone mode
256 		driver.releaseMemory;
257 	auto endReleaseTime = currTime;
258 
259 	auto time2 = currTime;
260 	Duration duration = time2-time1;
261 
262 	times.onIteration(0, duration);
263 
264 	if (printMem) write(cast(string)sink.data.data);
265 
266 	if (printStats) {
267 		writefln("Lexed %s lines", driver.context.numLinesLexed);
268 	}
269 
270 	if (printTime) {
271 		times.print;
272 	}
273 
274 	if (printStats || printTime) {
275 		writefln("Finished in %ss, init %ss, release %ss",
276 			scaledNumberFmt(duration),
277 			scaledNumberFmt(endInitTime-startInitTime),
278 			scaledNumberFmt(endReleaseTime-startReleaseTime));
279 	}
280 
281 	return 0;
282 }