MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
driver.cpp
Go to the documentation of this file.
1#include "mim/driver.h"
2
3#include <charconv>
4#include <cstring>
5
6#include <algorithm>
7#include <filesystem>
8#include <iostream>
9#include <optional>
10#include <sstream>
11#include <stdexcept>
12
13#include <fe/dl.h>
14#include <fe/sys.h>
15
16#include "mim/config.h"
17#include "mim/plugin.h"
18
19// Any address inside libmim identifies the shared object it was loaded from; see path_to_libmim.
20extern "C" MIM_EXPORT void mim_lib_anchor() {}
21
22namespace mim {
23
24namespace {
25
26/// Install tree @p libmim_path belongs to, i.e. the directory whose `<MIM_LIBDIR>/mim` holds the plugins.
27std::optional<fs::path> prefix_of(const fs::path& libmim_path) {
28 for (auto dir = libmim_path.parent_path(); !dir.empty(); dir = dir.parent_path()) {
29 if (std::error_code ec; fs::is_directory(dir / MIM_LIBDIR / "mim", ec)) return dir;
30 if (dir == dir.root_path()) break;
31 }
32
33 return {};
34}
35
36std::optional<fs::path> path_to_libmim() { return fe::sys::path_to_lib((const void*)&mim_lib_anchor); }
37
38/// Function-local so that Driver::add_static_plugin works during static initialization.
39fe::StrMap<Plugin (*)()>& static_plugins() {
40 static auto map = fe::StrMap<Plugin (*)()>();
41 return map;
42}
43
44/// A prefix may derive what a plain directory already names, and probing it twice only slows lookup down.
45void push(fe::Vector<fs::path>& paths, fs::path path) {
46 if (std::ranges::find(paths, path) == paths.end()) paths.emplace_back(std::move(path));
47}
48
49} // namespace
50
51void Driver::Imports::add(const fe::Src* src, Sym sym, ast::Tok::Tag tag, bool is_path) {
52 // The SrcMap interns paths, so one file is one fe::Src - comparing those settles "same file".
53 // Aliases (`as`) must not add a second entry, so the spelling is not part of the key.
54 for (const auto& entry : entries_)
55 if (entry.tag == tag && entry.src == src) return;
56
57 entries_.emplace_back(Entry{src, sym, tag, is_path});
58}
59
60Driver::Driver(std::string name)
61 : fe::Driver(std::make_unique<Diag>(*this))
62 , version_(MIM_VERSION)
63 , world_(this, sym(name)) {
64#define CODE(t, str) keys_.emplace(sym(str), ast::Tok::Tag::t);
66#undef CODE
67
68#define CODE(str, t) \
69 if (ast::Tok::Tag::t != ast::Tok::Tag::Nil) keys_.emplace(sym(str), ast::Tok::Tag::t);
71#undef CODE
72
73 auto from_env = [](const char* var, auto&& add) {
74 if (auto env = std::getenv(var)) {
75 auto stream = std::stringstream{env};
76 auto path = std::string{};
77 while (std::getline(stream, path, fe::sys::Path_Sep))
78 add(fs::path{path});
79 }
80 };
81
82 from_env("MIM_PLUGIN_PATH", [this](fs::path path) { add_plugin_path(std::move(path)); });
83 from_env("MIM_IMPORT_PATH", [this](fs::path path) { add_import_path(std::move(path)); });
84 from_env("MIM_PREFIX_PATH", [this](fs::path path) { add_prefix_path(std::move(path)); });
85
86 if (auto path = path_to_libmim()) {
87 // A layout that keeps the plugins right next to libmim has no prefix to derive them from.
88 add_plugin_path(path->parent_path() / "mim");
89 if (auto prefix = prefix_of(*path)) add_prefix_path(*std::move(prefix));
90 }
91
93
94 // User paths are added later and must be searched before the environment and derived ones.
95 plugin_dirs_.seal();
96 import_dirs_.seal();
97 prefixes_.seal();
98}
99
100fe::Vector<fs::path> Driver::plugin_paths() const {
101 auto res = fe::Vector<fs::path>();
102 res.emplace_back();
103 for (const auto& dir : plugin_dirs_)
104 push(res, dir);
105 for (const auto& prefix : prefixes_)
106 push(res, prefix / MIM_LIBDIR / "mim");
107 return res;
108}
109
110fe::Vector<fs::path> Driver::import_paths() const {
111 auto res = fe::Vector<fs::path>();
112 res.emplace_back();
113 for (const auto& dir : import_dirs_)
114 push(res, dir);
115 for (const auto& dir : plugin_dirs_)
116 push(res, dir);
117 for (const auto& prefix : prefixes_) {
118 push(res, prefix / MIM_DATADIR / "mim");
119 push(res, prefix / MIM_LIBDIR / "mim");
120 }
121 return res;
122}
123
124fe::Vector<fs::path> Driver::rt_paths() const {
125 auto res = fe::Vector<fs::path>();
126 res.emplace_back("rt");
127 for (const auto& dir : plugin_dirs_)
128 push(res, dir / "rt");
129 for (const auto& prefix : prefixes_)
130 push(res, prefix / MIM_LIBDIR / "mim" / "rt");
131 return res;
132}
133
134std::string Driver::plugin_name(std::string_view name) {
135 constexpr auto prefix = std::string_view("libmim_");
136 auto stem = fs::path(name).stem().string();
137 if (stem.starts_with(prefix)) stem.erase(0, prefix.size());
138 return stem;
139}
140
142 library_ = world.inherit();
143 swap(world, *library_);
144 for (auto mut : library_->externals().muts())
145 world.import(mut)->as_mut()->externalize();
146}
147
148void Driver::add_static_plugin(const char* name, Plugin (*get_plugin)()) { static_plugins()[name] = get_plugin; }
149
150void Driver::load(std::string_view spec, Loc loc) {
151 auto name = plugin_name(spec);
152 log().i("💾 load plugin `{}`", spec);
153
154 if (is_loaded(name)) {
155 log().w("plugin `{}` already loaded", name);
156 return;
157 }
158
159 auto handle = Plugin::Handle{nullptr, fe::dl::close};
160 auto dir = fs::path{};
161 auto path = fs::path{spec};
162 auto sub = path.parent_path();
163 auto get_info = decltype(&mim_get_plugin){};
164
165 // `foo/bar` looks below the `foo` of each search path, so that both halves stay together.
166 auto find = [&, this](const std::string& file, auto&& accept) {
167 for (const auto& search : plugin_paths())
168 if (std::error_code ec; fs::is_regular_file(search / sub / file, ec) && accept(search / sub / file))
169 return search / sub;
170 return fs::path{};
171 };
172
173 if (auto get = fe::lookup(static_plugins(), name)) {
174 get_info = *get;
175 // No shared object pins the directory, so search the `<name>.mim` half like an import.
176 dir = find(std::format("{}.mim", name), [](const fs::path&) { return true; });
177 } else {
178 auto ext = std::format(".{}", fe::dl::Ext);
179 auto open = [&handle](const fs::path& p) {
180 auto str = p.string();
181 return handle.reset(fe::dl::open(str.c_str())), bool(handle);
182 };
183
184 // Only a spec naming the shared object itself is opened as-is; `foo/bar.mim` still wants `foo/libmim_bar`.
185 if (path.is_absolute() && path.extension() == ext && fs::is_regular_file(path) && open(path))
186 dir = path.parent_path();
187 if (!handle) dir = find(std::format("libmim_{}{}", name, ext), open);
188
189 if (!handle) error().e(loc, "cannot open plugin `{}`", spec).bail();
190 get_info = reinterpret_cast<decltype(&mim_get_plugin)>(fe::dl::get(handle.get(), "mim_get_plugin"));
191 }
192
193 auto plugin = get_info();
194 if (version() != plugin.version) {
195 if (!flags().force_load)
196 error()
197 .e(loc, "plugin `{}` has version {} while MimIR has version {}", plugin.name, plugin.version, version())
198 .n("use `--force-load` to load it anyway")
199 .bail();
200 std::println(std::cerr, "warning: plugin {} has version {} while MimIR has version {}", plugin.name,
201 plugin.version, version());
202 }
203 fe::assert_emplace(plugins_, name,
204 Loaded{std::move(handle), std::move(dir), fe::View<PluginSym>(plugin.syms, plugin.num_syms)});
205 // clang-format off
206 if (auto reg = plugin.register_normalizers) reg(normalizers_);
207 if (auto reg = plugin.register_phases) reg(phases_);
208 // clang-format on
209 if (plugin.args) known_args_.emplace_back(name, fe::View<PluginArg>(plugin.args, plugin.num_args));
210 if (plugin.envs) known_envs_.emplace_back(name, fe::View<PluginEnv>(plugin.envs, plugin.num_envs));
211}
212
213void* Driver::get_fun_ptr(std::string_view plugin, const char* name) {
214 auto loaded = fe::lookup(plugins_, plugin);
215 if (!loaded) return nullptr;
216 for (const auto& sym : loaded->syms)
217 if (std::strcmp(sym.name, name) == 0) return sym.ptr;
218 return loaded->handle ? fe::dl::get(loaded->handle.get(), name) : nullptr;
219}
220
221const fe::Vector<std::string>& Driver::args(std::string_view plugin) const {
222 static const fe::Vector<std::string> empty;
223 if (auto args = fe::lookup(plugin_args_, plugin)) return *args;
224 return empty;
225}
226
227std::optional<nat_t> Driver::arg_nat(std::string_view plugin, std::string_view key) {
228 auto val = arg_value(args(plugin), key);
229 if (!val) return {};
230 auto n = nat_t(0);
231 auto end = val->data() + val->size();
232 if (auto [ptr, ec] = std::from_chars(val->data(), end, n); ec != std::errc() || ptr != end) {
233 log().w("ignoring `-X {}:{}={}`: not a number", plugin, key, *val);
234 return {};
235 }
236 return n;
237}
238
240 : driver_(driver) {
241 if (!driver_) return;
242
243 auto& names = driver_->names();
244 if (names.depth++ == 0) {
245 names.clashed = false;
246 names.sym2gid.clear();
247 }
248}
249
251 if (driver_) --driver_->names().depth;
252}
253
254bool PlainNames::clashed() const { return driver_ && driver_->names().clashed; }
255
256bool PlainNames::claim(const Driver& driver, Sym sym, u32 gid) {
257 auto& names = driver.names();
258 if (names.depth == 0) return false;
259 if (auto [i, ins] = names.sym2gid.emplace(sym, gid); !ins && i->second != gid) names.clashed = true;
260 return true;
261}
262
263std::string Diag::render(const std::function<std::string()>& fmt) const {
264 bool clashed = false;
265 auto str = std::string();
266 {
267 auto plain = PlainNames(&driver_);
268 str = CodeDiag::render(fmt);
269 clashed = plain.clashed();
270 }
271 return clashed ? CodeDiag::render(fmt) : str; // the retry must run outside the guard, or it renders plainly again
272}
273
274} // namespace mim
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:597
Renders a diagnostic through PlainNames and - if that turned out ambiguous - once more with Def::uniq...
Definition driver.h:51
std::string render(const std::function< std::string()> &) const override
Definition driver.cpp:263
void add(const fe::Src *src, Sym, ast::Tok::Tag, bool path)
Remembers the directive that pulled in src; a repeated import of the same file adds nothing.
Definition driver.cpp:51
Some "global" variables needed all over the place.
Definition driver.h:64
void add_prefix_path(fs::path path)
Definition driver.h:137
Driver(std::string name={})
Definition driver.cpp:60
void load(std::string_view name, Loc loc={})
Definition driver.cpp:150
static std::string plugin_name(std::string_view name)
Bare plugin name of a possibly path-qualified name: foo/libmim_bar.so → bar.
Definition driver.cpp:134
void add_import_path(fs::path path)
Definition driver.h:136
const Version & version() const
MimIR Version.
Definition driver.h:84
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
bool is_loaded(std::string_view name) const
Definition driver.h:193
Names & names() const
Definition driver.h:108
fe::Vector< fs::path > rt_paths() const
Where a backend looks for its runtime modules.
Definition driver.cpp:124
static void add_static_plugin(const char *name, Plugin(*get_plugin)())
Makes a Plugin linked into this binary available to Driver::load under name; see cmake/Mim....
Definition driver.cpp:148
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
const fe::Vector< std::string > & args(std::string_view plugin) const
Yields an empty fe::Vector if plugin has none.
Definition driver.cpp:221
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
void * get_fun_ptr(std::string_view plugin, const char *name)
Definition driver.cpp:213
fe::Log & log()
Definition driver.h:79
std::optional< nat_t > arg_nat(std::string_view plugin, std::string_view key)
The number passed as -X <plugin>:<key>=<n>, if any; warns about and ignores a non-number.
Definition driver.cpp:227
PlainNames(const Driver *driver)
Activates plain naming on driver until this guard dies; a null driver leaves it off.
Definition driver.cpp:239
bool clashed() const
Definition driver.cpp:254
static bool claim(const Driver &, Sym sym, uint32_t gid)
Registers that gid renders as sym and reports whether the plain sym may be used.
Definition driver.cpp:256
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
std::unique_ptr< World > inherit()
Inherits the State into the new World.
Definition world.h:97
const Def * import(const Def *def)
Imports def of Driver::library into this World.
Definition world.h:314
#define MIM_DATADIR
Definition config.h:14
#define MIM_LIBDIR
Definition config.h:13
#define MIM_EXPORT
Definition config.h:21
#define MIM_INSTALL_PREFIX
Definition config.h:12
void mim_lib_anchor()
Definition driver.cpp:20
Definition ast.h:16
u64 nat_t
Definition types.h:37
mim::Plugin mim_get_plugin()
uint32_t u32
Definition types.h:27
std::optional< std::string_view > arg_value(fe::View< std::string > args, Keys... keys)
Definition plugin.h:64
#define MIM_VERSION
Definition plugin.h:150
Basic info and registration function pointer to be returned from a specific plugin.
Definition plugin.h:155
std::unique_ptr< void, void(*)(void *)> Handle
Definition plugin.h:156
#define CODE(name,...)
Definition tok.h:52
#define MIM_SUBST(m)
Definition tok.h:225
#define MIM_KEY(m)
Definition tok.h:75