MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include <cstdlib>
2
3#include <sstream>
4#include <string>
5
6#include <fe/cli.h>
7#include <fe/sys.h>
8#include <fe/term.h>
9
10#include <mim/config.h>
11#include <mim/driver.h>
12#include <mim/flags.h>
13#include <mim/phase.h>
14#include <mim/plugin.h>
15
16#include <mim/ast/parser.h>
17#include <mim/phase/optimize.h>
18
19using namespace mim;
20using namespace std::literals;
21
22namespace {
23
24enum Emit { AST, Dot, H, PY, Md, Mim, NestDot, Profile, Num_Emits };
25
26/// Everything the command line configures that neither Flags nor fe::CodeDiag already holds.
27struct Opts {
28 std::string input;
29 std::vector<std::string> plugins, search_paths, import_paths, prefix_paths, plugin_args;
30 std::array<Out, Num_Emits> outs;
31 DotConfig dot;
32 bool no_opt = false;
33};
34
35void emit_help(fe::Cli& cli, Driver& driver, const std::vector<std::string>& plugins, bool md) {
36 for (auto&& plugin : plugins) // a plugin declares its `-X` arguments in its shared library
37 driver.load(plugin);
38
39 if (!driver.known_args().empty()) {
40 cli.section(md ? "Plugin Arguments {#xargs}" : "Plugin Arguments");
41
42 for (const auto& [plugin, args] : driver.known_args()) {
43 auto rows = fe::Cli::Rows();
44 for (const auto& arg : args)
45 rows.emplace_back(arg.syntax, arg.descr);
46 // The Markdown links back to the plugin's page and carries the anchor that page links to.
47 auto title = md ? std::format("[{0}](@ref {0}) {{#xarg_{0}}}", plugin) : plugin;
48 cli.section(std::move(title), "Argument", std::move(rows));
49 }
50 }
51 if (!driver.known_envs().empty()) {
52 cli.section(md ? "Plugin Environment Variables {#envs}" : "Plugin Environment Variables");
53
54 for (const auto& [plugin, envs] : driver.known_envs()) {
55 auto rows = fe::Cli::Rows();
56 for (const auto& env : envs)
57 rows.emplace_back(env.name, env.descr);
58 // Ditto.
59 auto title = md ? std::format("[{0}](@ref {0}) {{#env_{0}}}", plugin) : std::string(plugin);
60 cli.section(std::move(title), "Variable", std::move(rows));
61 }
62 }
63
64 if (md)
65 cli.markdown(std::cout);
66 else
67 std::cout << cli;
68}
69
70void emit_profile(Driver& driver, std::ostream& os) {
71 switch (driver.flags().profile) {
72 case Flags::Profile::Summary: driver.profiler().summary(os); break;
73 case Flags::Profile::Tree: driver.profiler().tree(os); break;
74 case Flags::Profile::Trace: driver.profiler().chrome_trace(os); break;
75 case Flags::Profile::None: break;
76 }
77}
78
79/// Parses `Opts::input` into @p driver's World, optimizes it, and emits whatever `--output-*` asked for.
80int compile(Driver& driver, Opts& opts) {
81 auto& world = driver.world();
82 auto& outs = opts.outs;
83
84 try {
85 auto name = fs::path(opts.input).filename().replace_extension().string();
86 world.set(name);
87
88 auto ast = ast::AST(world);
89 auto parser = ast::Parser(ast);
90 // Outputs are buffered until binding succeeded, so an error leaves no partial file behind.
91 auto md = std::ostringstream();
92 auto file = parser.import_main(opts.input, opts.plugins, outs[Md].name().empty() ? nullptr : &md);
93
94 if (!file) {
95 ast.error().ack(); // prefer the parser's own diagnostic, if it recorded one
96 fe::throwf("could not read file `{}`", opts.input);
97 }
98
99 auto ast_out = std::ostringstream();
100 if (!outs[AST].name().empty()) {
101 auto tab = fe::Tab::spaces();
102 file->stream(tab, ast_out);
103 }
104
105 file->bind(ast);
106 ast.error().ack();
107 if (auto s = outs[Md].os()) *s << md.str();
108
109 if (auto s = outs[AST].os()) {
110 *s << ast_out.str();
111 // Md is emitted while parsing, so nothing else needs a World.
112 auto only_parse
113 = opts.plugin_args.empty() && std::ranges::none_of(std::views::iota(0, int(Num_Emits)), [&](int i) {
114 return i != AST && i != Md && !outs[i].name().empty();
115 });
116 if (only_parse) return EXIT_SUCCESS;
117 }
118
119 if (!outs[H].name().empty() || !outs[PY].name().empty()) {
120 auto plugin = world.sym(name);
121 if (auto h = outs[H].os()) ast.bootstrap(plugin, *h);
122 if (auto py = outs[PY].os()) ast.bootstrap_py(plugin, *py);
123 return EXIT_SUCCESS;
124 }
125
126 file->compile(ast);
127 std::exception_ptr opt_error;
128 if (opts.no_opt)
129 driver.move_to_library(world);
130 else
131 try {
132 optimize(world);
133 } catch (...) {
134 opt_error = std::current_exception(); // still emit the world the failing phase saw
135 }
136
137 if (auto s = outs[Dot].os()) world.dot(*s, opts.dot);
138 if (auto s = outs[Mim].os()) world.dump(*s);
139 if (auto s = outs[NestDot].os()) mim::Nest(world).dot(*s, opts.dot);
140 if (auto s = outs[Profile].os()) emit_profile(driver, *s);
141 if (opt_error) std::rethrow_exception(opt_error);
142 } catch (const Error::Bail& e) {
143 std::cerr << e;
144 return EXIT_FAILURE;
145 }
146
147 return EXIT_SUCCESS;
148}
149
150} // namespace
151
152int main(int argc, char** argv) {
153 fe::term::resolve_mode(); // colors in std::format-ed output depend on Auto being resolved up front
154 Driver driver; // outlives the handlers below: an Error's Locs point into its SrcMap
155
156 try {
157 bool show_help = false;
158 bool show_help_md = false;
159 bool show_version = false;
160 bool list_search_paths = false;
161 int verbose = 0;
162 Opts opts;
163 auto& flags = driver.flags();
164 auto& diag = driver.diag();
165 auto inc_verbose = [&](bool) { ++verbose; };
166#ifdef MIM_ENABLE_CHECKS
167 std::vector<uint32_t> breakpoints, watchpoints;
168#endif
169
170 auto loc_style = [&](const std::string& t) -> std::string {
171 // clang-format off
172 if (t == "full" ) diag.loc_style = fe::Loc::Style::Full;
173 else if (t == "rowcol") diag.loc_style = fe::Loc::Style::RowCol;
174 else if (t == "row" ) diag.loc_style = fe::Loc::Style::Row;
175 else if (t == "msvc" ) diag.loc_style = fe::Loc::Style::MSVC;
176 else return std::format("'{}' is not a location style", t);
177 // clang-format on
178 return {};
179 };
180
181 auto profile = [&](const std::string& t) -> std::string {
182 // clang-format off
183 if (t == "summary") flags.profile = Flags::Profile::Summary;
184 else if (t == "tree" ) flags.profile = Flags::Profile::Tree;
185 else if (t == "trace" ) flags.profile = Flags::Profile::Trace;
186 else return std::format("'{}' is not a profile mode", t);
187 // clang-format on
188 return {};
189 };
190
191 // clang-format off
192 auto cli = fe::Cli("mim", "MimIR is my Intermediate Representation.")
193 .arg(opts.input, "file", "Input file.")
194 .help(show_help)
195 .opt(show_help_md , "" , "" , "--help-md" , "Displays this help as Markdown and exits.")
196 .opt(show_version , "" , "-v", "--version" , "Displays version info and exits.")
197 .opt(list_search_paths , "" , "-l", "--list-search-paths" , "Lists the search paths in order and exits.")
198 .opt(opts.plugins , "plugin" , "-p", "--plugin" , "Dynamically loads a plugin; `foo/bar` finds `libmim_bar` in each search path's `foo`.")
199 .opt(opts.search_paths , "path" , "-P", "--plugin-path" , "Path to search for plugins; also searched for imports.")
200 .opt(opts.import_paths , "path" , "-I", "--import-path" , "Path to search for imports.")
201 .opt(opts.prefix_paths , "path" , "-R", "--prefix-path" , "Install prefix/root to derive plugin, import, and runtime directories from.")
202 .opt(opts.plugin_args , "plugin:arg", "-X", "--plugin-arg" , "Passes an argument to a plugin/phase, e.g. `-X ll:o=output.ll`. Repeatable.")
203 .opt(flags.force_load , "" , "" , "--force-load" , "Loads plugins even on version mismatch.")
204 .opt(flags.bootstrap , "" , "" , "--bootstrap" , "Bootstrap mode: only read Mim AST, don't compile to MimIR.")
205 .opt(inc_verbose , "" , "-V", "--verbose" , "Raises the log level from error to warn, info, verbose, debug, trace; repeatable.").cardinality(0, 5)
206 .grp("Output")
207 .opt(flags.ascii , "" , "-a", "--ascii" , "Uses ASCII alternatives in output instead of UTF-8.")
208 .opt(opts.outs[AST].name() , "file" , "" , "--output-ast" , "Emits the AST of the input.")
209 .opt(opts.outs[Dot].name() , "file" , "" , "--output-dot" , "Emits the Mim program as a MimIR graph using Graphviz' DOT language.")
210 .opt(opts.outs[H].name() , "file" , "" , "--output-h" , "Emits a header file to be used to interface with a plugin in C++.")
211 .opt(opts.outs[Md].name() , "file" , "" , "--output-md" , "Emits the input formatted as Markdown.")
212 .opt(opts.outs[Mim].name() , "file" , "-o", "--output-mim" , "Emits the Mim program again.")
213 .opt(opts.outs[NestDot].name() , "file" , "" , "--output-nest" , "Emits the program's nesting tree using Graphviz' DOT language.")
214 .opt(opts.outs[PY].name() , "file" , "" , "--output-py" , "Emits a Python enum to be used to interface with a plugin in Python.")
215 .grp("DOT Output")
216 .opt(opts.dot.dark , "" , "" , "--dot-dark" , "Drops the white background of the DOT output and lightens its edges, so that it embeds into a dark backdrop.")
217 .opt(opts.dot.default_filter , "" , "" , "--dot-default-filter" , "Always shows a lambda's filter in DOT output - even if it is the default one (`ff` for continuations, `tt` for direct-style functions).")
218 .opt(opts.dot.follow_types , "" , "" , "--dot-follow-types" , "Follows type dependencies in DOT output.")
219 .opt(opts.dot.inline_consts , "" , "" , "--dot-inline-consts" , "Wires up literals, axioms, etc. with normal edges in DOT output instead of detaching them into a separate row; useful for small graphs.")
220 .opt(opts.dot.lean_labels , "" , "" , "--dot-lean-labels" , "Labels a DOT node `<node_name> <unique_name>` instead of the HTML table; this drops the per-op ports, so an edge no longer docks under the op it stands for.")
221 .opt(opts.dot.no_tooltip , "" , "" , "--dot-no-tooltip" , "Omits the per-node tooltip from DOT output; it accounts for roughly two fifths of the file.")
222 .opt(opts.dot.show_hidden , "" , "" , "--dot-show-hidden" , "Renders otherwise-transparent detached edges in DOT output - back-edges from a Var to its binder, shared literals/axioms, and type edges - in a subtle gray.")
223 .grp("Mim Output")
224 .opt(flags.mim_typed_let , "" , "" , "--mim-typed-let" , "Ascribes the type of each `let`-bound name; it is inferable from the right-hand side and hence elided by default.")
225 .grp("Diagnostics")
226 .opt(diag.gutter , "width" , "" , "--gutter" , "Width of a diagnostic's line-number column.")
227 .opt(loc_style , "style" , "" , "--loc-style" , "How a diagnostic spells out a source location: `full` (`path:row:col-row:col`), `rowcol` (`path:row:col`), `row` (`path:row`), or `msvc` (`path(row,col)`).")
228 .opt(diag.max_errors , "num" , "" , "--max-errors" , "Maximum number of errors to report before dropping the rest; 0 reports all of them.")
229 .opt(diag.max_rows , "num" , "" , "--max-rows" , "Maximum number of rows a diagnostic's snippet renders before eliding its middle; 0 elides nothing.")
230 .opt(diag.no_snippet , "" , "" , "--no-snippet" , "Does not render the offending source line and caret underneath a diagnostic.")
231 .opt(diag.werror , "" , "" , "--werror" , "Treats warnings as errors.")
232 .grp("Profiling")
233 .opt(opts.outs[Profile].name() , "file" , "" , "--output-profile" , "Where to write the profiling information; defaults to stdout and implies --profile trace, if no `<mode>` is given.")
234 .opt(profile , "mode" , "" , "--profile" , "Measures how long each phase takes; `<mode>` is `summary`, `tree`, or `trace` (`chrome://tracing` compatible).")
235 .grp("Optimization")
236 .opt(flags.aggressive_lam_spec , "" , "" , "--aggr-lam-spec" , "Overrides LamSpec behavior to follow recursive calls.")
237 .opt(flags.max_fp_iters , "num" , "" , "--max-fp-iters" , "Maximum number of fixed-point iterations before a phase errors out; guards against non-monotone analyses.")
238 .opt(flags.pe_fuel , "num" , "" , "--pe-fuel" , "Maximum nesting depth of partial evaluations before erroring out; guards against non-terminating specialization.")
239 .opt(opts.no_opt , "" , "" , "--no-opt" , "Elaborates the input but skips the optimization pipeline, so no backend runs; `--output-*` still works.")
240 .opt(flags.scalarize_threshold , "threshold" , "" , "--scalarize-threshold" , "MimIR will not scalarize packs/arrays with a number of elements greater than or equal this threshold.")
241#ifdef MIM_ENABLE_CHECKS
242 .grp("Developer Options")
243 .opt(breakpoints , "gid" , "-b", "--break" , "Triggers a breakpoint when a node with this global id is created.")
244 .opt(flags.break_on_alpha , "" , "" , "--break-on-alpha" , "Triggers a breakpoint as soon as two expressions turn out not to be alpha-equivalent.")
245 .opt(flags.break_on_error , "" , "" , "--break-on-error" , "Triggers a breakpoint on an error log.")
246 .opt(flags.break_on_warn , "" , "" , "--break-on-warn" , "Triggers a breakpoint on a warning log.")
247 .opt(flags.reeval_breakpoints , "" , "" , "--reeval-breakpoints" , "Triggers a breakpoint even upon unifying a node that has already been built.")
248 .opt(flags.trace_gids , "" , "" , "--trace-gids" , "Outputs gids during `World::unify`/`insert`.")
249 .opt(watchpoints , "gid" , "-w", "--watch" , "Triggers a breakpoint when a node with this global id is set.")
250#endif
251 .section("Environment Variables", "Variable", {
252 {"MIM_PLUGIN_PATH", std::format("{}-separated list of plugin search paths, searched after those given via `-P`.", fe::sys::Path_Sep_Word)},
253 {"MIM_IMPORT_PATH", std::format("{}-separated list of import search paths, searched after those given via `-I`.", fe::sys::Path_Sep_Word)},
254 {"MIM_PREFIX_PATH", std::format("{}-separated list of install prefixes, searched after those given via `--prefix-path`.", fe::sys::Path_Sep_Word)},
255 {"NO_COLOR" , "Disables colored output if set to a non-empty value; wins over the two below."},
256 {"CLICOLOR_FORCE" , "Forces colored output if set to a non-empty value other than 0."},
257 {"CLICOLOR" , "Disables colored output if set to 0."},
258 })
259 .epilog(R"(Every output option accepts "-" to write to stdout.)");
260 // clang-format on
261
262 if (auto err = cli.parse(argc, argv)) fe::throwf<std::invalid_argument>("{}", *err);
263
264 // Resolved here and not in the handlers, so that the order of the two profiling options does not matter.
265 auto& profile_file = opts.outs[Profile].name();
266 if (flags.profile == Flags::Profile::None && !profile_file.empty()) flags.profile = Flags::Profile::Trace;
267 if (flags.profile != Flags::Profile::None && profile_file.empty()) profile_file = "-";
268
269 for (auto&& path : opts.search_paths)
270 driver.add_plugin_path(path);
271
272 for (auto&& path : opts.import_paths)
273 driver.add_import_path(path);
274
275 for (auto&& path : opts.prefix_paths)
276 driver.add_prefix_path(path);
277
278 if (show_help || show_help_md) {
279 emit_help(cli, driver, opts.plugins, show_help_md);
280 return EXIT_SUCCESS;
281 }
282
283 if (show_version) {
284 std::cout << "mim " << driver.version() << std::endl;
285 return EXIT_SUCCESS;
286 }
287
288 for (auto&& pa : opts.plugin_args) {
289 auto pos = pa.find(':');
290 if (pos == std::string::npos)
291 fe::throwf<std::invalid_argument>("`--plugin-arg` expects `<plugin>:<arg>`, got `{}`", pa);
292 driver.add_arg(std::string_view(pa).substr(0, pos), pa.substr(pos + 1));
293 }
294
295 if (list_search_paths) {
296 auto list = [](std::string_view kind, const fe::Vector<fs::path>& paths) {
297 std::cout << kind << ':' << std::endl;
298 for (auto&& path : paths)
299 if (!path.empty()) std::cout << " " << path << std::endl;
300 };
301 list("plugins", driver.plugin_paths());
302 list("imports", driver.import_paths());
303 list("runtimes", driver.rt_paths());
304 return EXIT_SUCCESS;
305 }
306
307 driver.log().set(&std::cerr).set((fe::Log::Level)verbose);
308#ifdef MIM_ENABLE_CHECKS
309 driver.log().break_on_error = flags.break_on_error;
310 driver.log().break_on_warn = flags.break_on_warn;
311 for (auto b : breakpoints)
312 driver.world().breakpoint(b);
313 for (auto w : watchpoints)
314 driver.world().watchpoint(w);
315#endif
316
317 if (opts.input.empty()) fe::throwf<std::invalid_argument>("no input given");
318
319 return compile(driver, opts);
320 } catch (const std::exception& e) {
321 driver.error().report();
322 std::println(std::cerr, "{}", e.what());
323 return EXIT_FAILURE;
324 } catch (...) {
325 driver.error().report();
326 std::println(std::cerr, "error: unknown exception");
327 return EXIT_FAILURE;
328 }
329}
Some "global" variables needed all over the place.
Definition driver.h:64
const auto & known_envs() const
The PluginEnvs each loaded Plugin declares, in load order; only for listing them, see PluginEnv.
Definition driver.h:231
void add_prefix_path(fs::path path)
Definition driver.h:137
void load(std::string_view name, Loc loc={})
Definition driver.cpp:150
void add_import_path(fs::path path)
Definition driver.h:136
const Version & version() const
MimIR Version.
Definition driver.h:84
const auto & known_args() const
The PluginArgs each loaded Plugin declares, in load order; only for listing them, see PluginArg.
Definition driver.h:228
World & world()
Definition driver.h:83
fe::Vector< fs::path > import_paths() const
Where ast::Parser looks for <name>.mim; plugin directories are included, as a plugin ships both halve...
Definition driver.cpp:110
void add_arg(std::string_view plugin, std::string arg)
Definition driver.h:221
fe::Vector< fs::path > rt_paths() const
Where a backend looks for its runtime modules.
Definition driver.cpp:124
Flags & flags()
Definition driver.h:77
void move_to_library(World &world)
Moves everything of world into library() and imports only its externals back.
Definition driver.cpp:141
void add_plugin_path(fs::path path)
Definition driver.h:135
fe::Vector< fs::path > plugin_paths() const
Where Driver::load looks for libmim_<name>.
Definition driver.cpp:100
fe::Profiler & profiler()
Definition driver.h:81
fe::Log & log()
Definition driver.h:79
Builds a nesting tree for all mutables/binders.
Definition nest.h:25
void dot(std::ostream &os, DotConfig cfg={}) const
Definition dot.cpp:281
void watchpoint(u32 gid)
Trigger breakpoint in your debugger when Def::setting a Def with this gid.
Definition world.cpp:1254
void breakpoint(u32 gid)
Trigger breakpoint in your debugger when creating a Def with this gid.
Definition world.cpp:1253
Owns the arena all AST nodes live in as well as the AnnexInfos of all plugins.
Definition ast.h:107
Parses Mim code as AST.
Definition parser.h:30
int main(int argc, char **argv)
Definition main.cpp:152
Definition Mim.cmake:1
Definition ast.h:16
Definition ast.h:16
bool follow_types
Follow Def::type() dependencies.
Definition def.h:243
bool inline_consts
Wire up literals, axioms, etc. with normal edges instead of detaching them.
Definition def.h:244
void optimize(World &)
Runs _compile or _default_compile, if available (in this order).
Definition optimize.cpp:8
bool no_tooltip
Omit the per-node tooltip; it is by far the bulkiest part of the output.
Definition def.h:249
bool dark
Drop the white background and lighten the edges for a dark backdrop.
Definition def.h:248
bool show_hidden
Render otherwise-transparent detached edges (Var→binder back-edges, shared literals/axioms,...
Definition def.h:246
bool lean_labels
Label a node <node_name> <unique_name> instead of the HTML table.
Definition def.h:250
bool default_filter
Show Lam::filter() even if it has its default value.
Definition def.h:245
@ Summary
Flat table aggregated by Phase name.
Definition flags.h:15
@ Tree
Indented tree preserving the order in which Phasees ran.
Definition flags.h:16
@ None
No profiling.
Definition flags.h:14
@ Trace
chrome://tracing compatible output.
Definition flags.h:17
Profile profile
Definition flags.h:28