diff --git a/src/thorin/CMakeLists.txt b/src/thorin/CMakeLists.txt index 16dbcb97f..4055c72b1 100644 --- a/src/thorin/CMakeLists.txt +++ b/src/thorin/CMakeLists.txt @@ -14,6 +14,9 @@ set(THORIN_SOURCES type.h world.cpp world.h + plugin.cpp + plugin.h + plugin_helpers.h analyses/cfg.cpp analyses/cfg.h analyses/domfrontier.cpp diff --git a/src/thorin/plugin.cpp b/src/thorin/plugin.cpp new file mode 100644 index 000000000..fa154f153 --- /dev/null +++ b/src/thorin/plugin.cpp @@ -0,0 +1,78 @@ +#include "thorin/world.h" + +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif + +namespace thorin { +[[nodiscard]] static void* load_plugin_module(const char* plugin_name) { +#ifdef _WIN32 + return LoadLibraryA(plugin_name); +#else + return dlopen(plugin_name, RTLD_LAZY | RTLD_GLOBAL); +#endif +} + +static bool unload_plugin_module(void* plugin_module) { +#ifdef _WIN32 + return FreeLibrary(static_cast(plugin_module)) == TRUE; +#else + return dlclose(plugin_module) == 0; +#endif +} + +template +[[nodiscard]] static T* lookup_plugin_function(void* plugin_module, const char* function_name) { +#ifdef _WIN32 + return reinterpret_cast(GetProcAddress(static_cast(plugin_module), function_name)); +#else + return reinterpret_cast(dlsym(plugin_module, function_name)); +#endif +} + +bool World::load_plugin(const char* plugin_name) { + void* module = load_plugin_module(plugin_name); + + if (!module) { + ELOG("failed to load plugin {}", plugin_name); + return false; + } + + if (auto init = lookup_plugin_function(module, "init")) { + if (!init()) { + ELOG("failed to initialize plugin {}", plugin_name); + unload_plugin_module(module); + return false; + } + } else { + ILOG("plugin {} did not supply an init function", plugin_name); + } + + data_.plugin_modules_.push_back(module); + return true; +} + +unique_plugin_intrinsic World::load_plugin_intrinsic(const char* function_name) const { + for (auto plugin_module : data_.plugin_modules_) { + if (auto create_intrinsic = lookup_plugin_function(plugin_module, function_name)) + return unique_plugin_intrinsic(create_intrinsic()); + } + + return nullptr; +} + +void World::link_plugin_intrinsic(Continuation* cont) { + auto intrinsic = load_plugin_intrinsic(cont->name().c_str()); + + if (!intrinsic) + error(cont->loc(), "failed to link plugin intrinsic {}", cont->name()); + + cont->attributes().intrinsic = Intrinsic::Plugin; + data_.plugin_intrinsics_.push_back({ cont, std::move(intrinsic) }); +} +} diff --git a/src/thorin/plugin.h b/src/thorin/plugin.h new file mode 100644 index 000000000..bc2b5c268 --- /dev/null +++ b/src/thorin/plugin.h @@ -0,0 +1,39 @@ +#ifndef THORIN_PLUGIN_H +#define THORIN_PLUGIN_H + +#include + +namespace thorin { + class Def; + class Continuation; + class App; + class World; + + struct plugin_intrinsic { + virtual void transform(World* world, const Continuation* cont) = 0; + virtual void destroy() = 0; + + protected: + plugin_intrinsic() = default; + plugin_intrinsic(const plugin_intrinsic&) = default; + plugin_intrinsic(plugin_intrinsic&&) = default; + plugin_intrinsic& operator =(const plugin_intrinsic&) = default; + plugin_intrinsic& operator =(plugin_intrinsic&&) = default; + ~plugin_intrinsic() = default; + }; + + struct plugin_deleter { + void operator ()(plugin_intrinsic* ptr) const { + ptr->destroy(); + } + }; + + using unique_plugin_intrinsic = std::unique_ptr; + + extern "C" { + using plugin_init_func_t = bool(); + using plugin_intrinsic_create_func_t = plugin_intrinsic*(); + } +} + +#endif diff --git a/src/thorin/plugin_helpers.h b/src/thorin/plugin_helpers.h new file mode 100644 index 000000000..fa4e8fcd7 --- /dev/null +++ b/src/thorin/plugin_helpers.h @@ -0,0 +1,42 @@ +#ifndef THORIN_PLUGIN_HELPERS_H +#define THORIN_PLUGIN_HELPERS_H + +#include "plugin.h" +#include "continuation.h" +#include "world.h" + +namespace thorin { + template + class plugin_intrinsic_app_wrapper : public virtual plugin_intrinsic { + virtual const Def* transform(World* world, const App* app) = 0; + + void transform(World* world, const Continuation* cont) final override { + for (auto use : cont->copy_uses()) { + if (!use.def()->isa()) + continue; + + auto app = use.def()->as(); + + // transform plugin(mem, args, ret) to ret(mem, func(args)) + + auto output = static_cast(this)->transform(world, app); + + auto app_rebuild = + output ? app->rebuild(*world, world->bottom_type(), {app->arg(app->num_args() - 1), app->arg(0), output}) + : app->rebuild(*world, world->bottom_type(), {app->arg(app->num_args() - 1), app->arg(0)}); + + app->replace_uses(app_rebuild); + } + } + + protected: + plugin_intrinsic_app_wrapper() = default; + plugin_intrinsic_app_wrapper(const plugin_intrinsic_app_wrapper&) = default; + plugin_intrinsic_app_wrapper(plugin_intrinsic_app_wrapper&&) = default; + plugin_intrinsic_app_wrapper& operator =(const plugin_intrinsic_app_wrapper&) = default; + plugin_intrinsic_app_wrapper& operator =(plugin_intrinsic_app_wrapper&&) = default; + ~plugin_intrinsic_app_wrapper() = default; + }; +} + +#endif diff --git a/src/thorin/transform/cleanup_world.cpp b/src/thorin/transform/cleanup_world.cpp index 2748d25d9..46974e67a 100644 --- a/src/thorin/transform/cleanup_world.cpp +++ b/src/thorin/transform/cleanup_world.cpp @@ -18,7 +18,6 @@ class Cleaner { : world_(world) {} - World& world() { return world_; } void cleanup(); void eliminate_tail_rec(); void eta_conversion(); @@ -49,7 +48,7 @@ void Cleaner::eliminate_tail_rec() { } else if (use->isa_nom()) continue; // ignore params - world().ELOG("non-recursive usage of {} index:{} use:{}", scope.entry()->name(), use.index(), use.def()->to_string()); + world_.ELOG("non-recursive usage of {} index:{} use:{}", scope.entry()->name(), use.index(), use.def()->to_string()); only_tail_calls = false; break; } @@ -88,7 +87,7 @@ void Cleaner::eliminate_tail_rec() { } if (new_args.size() != n) { - world().DLOG("tail recursive: {}", entry); + world_.DLOG("tail recursive: {}", entry); auto dropped = drop(scope, args); entry->jump(dropped, new_args); @@ -102,7 +101,7 @@ void Cleaner::eliminate_tail_rec() { void Cleaner::eta_conversion() { for (bool todo = true; todo;) { todo = false; - for (auto def : world().copy_defs()) { + for (auto def : world_.copy_defs()) { auto continuation = def->isa_nom(); if (!continuation || !continuation->has_body()) continue; @@ -111,7 +110,7 @@ void Cleaner::eta_conversion() { auto body = continuation->body(); if (callee == continuation) break; - if (callee->has_body() && !world().is_external(callee) && callee->can_be_inlined()) { + if (callee->has_body() && !world_.is_external(callee) && callee->can_be_inlined()) { auto callee_body = callee->body(); for (size_t i = 0, e = body->num_args(); i != e; ++i) callee->param(i)->replace_uses(body->arg(i)); @@ -131,7 +130,7 @@ void Cleaner::eta_conversion() { // try to subsume continuations which call a parameter // (that is free within that continuation) with that parameter if (auto param = body->callee()->isa()) { - if (param->continuation() == continuation || world().is_external(continuation)) + if (param->continuation() == continuation || world_.is_external(continuation)) continue; if (body->args() == continuation->params_as_defs()) { @@ -181,11 +180,11 @@ void Cleaner::eta_conversion() { void Cleaner::eliminate_params() { // TODO - for (auto ocontinuation : world().copy_continuations()) { + for (auto ocontinuation : world_.copy_continuations()) { std::vector proxy_idx; std::vector param_idx; - if (ocontinuation->has_body() && !world().is_external(ocontinuation)) { + if (ocontinuation->has_body() && !world_.is_external(ocontinuation)) { auto obody = ocontinuation->body(); for (auto use : ocontinuation->uses()) { if (use.index() != 0 || !use->isa_nom()) @@ -201,8 +200,8 @@ void Cleaner::eliminate_params() { } if (!proxy_idx.empty()) { - auto ncontinuation = world().continuation( - world().fn_type(ocontinuation->type()->ops().cut(proxy_idx)), + auto ncontinuation = world_.continuation( + world_.fn_type(ocontinuation->type()->ops().cut(proxy_idx)), ocontinuation->attributes(), ocontinuation->debug_history()); size_t j = 0; for (auto i : param_idx) { @@ -236,14 +235,19 @@ void Cleaner::rebuild() { importer.type_old2new_.rehash(world_.types().capacity()); importer.def_old2new_.rehash(world_.defs().capacity()); - for (auto&& [_, cont] : world().externals()) { + for (auto&& [_, cont] : world_.externals()) { if (cont->is_exported()) importer.import(cont); } + for (auto&& [cont, func] : world_.data_.plugin_intrinsics_) { + if (cont->num_uses() > 0) + importer.import_plugin_intrinsic(cont, std::move(func)); + } + swap(importer.world(), world_); - // verify(world()); + // verify(world_); todo_ |= importer.todo(); } @@ -262,20 +266,20 @@ void Cleaner::verify_closedness() { } }; - for (auto def : world().defs()) + for (auto def : world_.defs()) check(def); } void Cleaner::within(const Def* def) { if (def->isa()) return; // TODO remove once Params are within World's sea of nodes - assert(world().types().contains(def->type())); - assert_unused(world().defs().contains(def)); + assert(world_.types().contains(def->type())); + assert_unused(world_.defs().contains(def)); } void Cleaner::clean_pe_info(std::queue queue, Continuation* cur) { assert(cur->has_body()); auto body = cur->body(); - assert(body->arg(1)->type() == world().ptr_type(world().indefinite_array_type(world().type_pu8()))); + assert(body->arg(1)->type() == world_.ptr_type(world_.indefinite_array_type(world_.type_pu8()))); auto next = body->arg(3); auto msg = body->arg(1)->as()->from()->as()->init()->as(); @@ -296,7 +300,7 @@ void Cleaner::clean_pe_infos() { queue.push(continuation); }; - for (auto&& [_, cont] : world().externals()) + for (auto&& [_, cont] : world_.externals()) if (cont->has_body()) enqueue(cont); while (!queue.empty()) { @@ -326,9 +330,9 @@ void Cleaner::cleanup_fix_point() { eta_conversion(); eliminate_params(); rebuild(); // resolve replaced defs before going to resolve_loads - todo_ |= resolve_loads(world()); + todo_ |= resolve_loads(world_); rebuild(); - if (!world().is_pe_done()) + if (!world_.is_pe_done()) todo_ |= partial_evaluation(world_); else clean_pe_infos(); @@ -339,9 +343,9 @@ void Cleaner::cleanup() { world_.VLOG("start cleanup"); cleanup_fix_point(); - if (!world().is_pe_done()) { - world().mark_pe_done(); - for (auto def : world().defs()) { + if (!world_.is_pe_done()) { + world_.mark_pe_done(); + for (auto def : world_.defs()) { if (auto cont = def->isa_nom()) cont->destroy_filter(); } @@ -353,7 +357,7 @@ void Cleaner::cleanup() { world_.VLOG("end cleanup"); #if THORIN_ENABLE_CHECKS verify_closedness(); - debug_verify(world()); + debug_verify(world_); #endif } diff --git a/src/thorin/transform/importer.cpp b/src/thorin/transform/importer.cpp index af7f5c1c8..b956f58ce 100644 --- a/src/thorin/transform/importer.cpp +++ b/src/thorin/transform/importer.cpp @@ -47,7 +47,7 @@ const Def* Importer::import(const Def* odef) { Array new_conditions(ofilter->num_ops()); for (size_t i = 0, e = ofilter->size(); i != e; ++i) new_conditions[i] = import(ofilter->condition(i)); - auto nfilter = world().filter(new_conditions, ofilter->debug()); + auto nfilter = world_.filter(new_conditions, ofilter->debug()); return nfilter; } @@ -56,13 +56,13 @@ const Def* Importer::import(const Def* odef) { assert(!ocontinuation->dead_); // TODO maybe we want to deal with intrinsics in a more streamlined way if (ocontinuation == ocontinuation->world().branch()) - return def_old2new_[ocontinuation] = world().branch(); + return def_old2new_[ocontinuation] = world_.branch(); if (ocontinuation == ocontinuation->world().end_scope()) - return def_old2new_[ocontinuation] = world().end_scope(); + return def_old2new_[ocontinuation] = world_.end_scope(); auto npi = import(ocontinuation->type())->as(); - ncontinuation = world().continuation(npi, ocontinuation->attributes(), ocontinuation->debug_history()); - assert(&ncontinuation->world() == &world()); - assert(&npi->table() == &world()); + ncontinuation = world_.continuation(npi, ocontinuation->attributes(), ocontinuation->debug_history()); + assert(&ncontinuation->world() == &world_); + assert(&npi->table() == &world_); for (size_t i = 0, e = ocontinuation->num_params(); i != e; ++i) { ncontinuation->param(i)->set_name(ocontinuation->param(i)->debug_history().name); def_old2new_[ocontinuation->param(i)] = ncontinuation->param(i); @@ -74,7 +74,7 @@ const Def* Importer::import(const Def* odef) { def_old2new_[ocontinuation] = ncontinuation; if (ocontinuation->is_external()) - world().make_external(ncontinuation); + world_.make_external(ncontinuation); } size_t size = odef->num_ops(); @@ -82,16 +82,16 @@ const Def* Importer::import(const Def* odef) { for (size_t i = 0; i != size; ++i) { assert(odef->op(i) != odef); nops[i] = import(odef->op(i)); - assert(&nops[i]->world() == &world()); + assert(&nops[i]->world() == &world_); } if (odef->isa_structural()) { - auto ndef = odef->rebuild(world(), ntype, nops); + auto ndef = odef->rebuild(world_, ntype, nops); todo_ |= odef->tag() != ndef->tag(); return def_old2new_[odef] = ndef; } - assert(ncontinuation && &ncontinuation->world() == &world()); + assert(ncontinuation && &ncontinuation->world() == &world_); auto napp = nops[0]->isa(); if (napp) ncontinuation->set_body(napp); @@ -100,4 +100,12 @@ const Def* Importer::import(const Def* odef) { return ncontinuation; } +void Importer::import_plugin_intrinsic(const Continuation* cont, unique_plugin_intrinsic func) { + if (auto ncont = def_old2new_.lookup(cont)) { + assert(&(*ncont)->world() == &world_); + assert((*ncont)->isa()); + world_.data_.plugin_intrinsics_.push_back({ (*ncont)->as(), std::move(func) }); + } +} + } diff --git a/src/thorin/transform/importer.h b/src/thorin/transform/importer.h index c34db9ef3..3861fba0e 100644 --- a/src/thorin/transform/importer.h +++ b/src/thorin/transform/importer.h @@ -22,6 +22,7 @@ class Importer { World& world() { return world_; } const Type* import(const Type*); const Def* import(const Def*); + void import_plugin_intrinsic(const Continuation* cont, unique_plugin_intrinsic impl); bool todo() const { return todo_; } public: diff --git a/src/thorin/transform/plugin_execute.cpp b/src/thorin/transform/plugin_execute.cpp index 054a7c41f..91a536bf4 100644 --- a/src/thorin/transform/plugin_execute.cpp +++ b/src/thorin/transform/plugin_execute.cpp @@ -3,77 +3,15 @@ #include "thorin/transform/partial_evaluation.h" #include "thorin/analyses/scope.h" -#include - namespace thorin { -void plugin_execute(World& world) { +void plugin_execute(World& world, std::vector intrinsics) { world.VLOG("start plugin_execute"); - std::vector plugin_intrinsics; - - while (true) { - plugin_intrinsics.clear(); - - for (auto def : world.defs()) { - auto cont = def->isa_nom(); - if (!cont) continue; - - if (cont->is_intrinsic() && cont->intrinsic() == Intrinsic::Plugin) { - plugin_intrinsics.push_back(cont); - } - } - - if (plugin_intrinsics.empty()) - break; - - sort(plugin_intrinsics.begin(), plugin_intrinsics.end(), [&](const Continuation* a, const Continuation* b) { - const Continuation* depends = b; - while (depends) { - depends = depends->attributes().depends; - if (a == depends) return true; - } - return false; - }); - - for (auto cont : plugin_intrinsics) { - auto plugin_function = world.search_plugin_function(cont->name().c_str()); - if (!plugin_function) { - world.ELOG("Plugin function not found for: {}", cont->name()); - continue; - } - - bool evaluated = false; - for (auto use : cont->copy_uses()) { - if (!use.def()->isa()) { - continue; - } - - auto app = use.def()->as(); - assert(app->callee() == cont); - - if (app->num_uses() == 0) { - continue; - } - - const Def* output = plugin_function(&world, app); - const Def* app_rebuild = nullptr; - if (output) { - app_rebuild = app->rebuild(world, world.bottom_type(), {app->arg(app->num_args() - 1), app->arg(0), output}); - } else { - app_rebuild = app->rebuild(world, world.bottom_type(), {app->arg(app->num_args() - 1), app->arg(0)}); - } - app->replace_uses(app_rebuild); - - //partial_evaluation(world); //TODO: Some form of cleanup would be advisable here. - evaluated = true; - } - - if (evaluated) - break; - } - - world.cleanup(); + // assume that dependents precede dependencies + while (!intrinsics.empty()) { + intrinsics.back().impl->transform(&world, intrinsics.back().cont); + intrinsics.pop_back(); } world.mark_pe_done(false); diff --git a/src/thorin/transform/plugin_execute.h b/src/thorin/transform/plugin_execute.h index 49cd9dec3..ea2c0e1af 100644 --- a/src/thorin/transform/plugin_execute.h +++ b/src/thorin/transform/plugin_execute.h @@ -1,9 +1,11 @@ #ifndef THORIN_TRANSFORM_PLUGIN_EXECUTE_H #define THORIN_TRANSFORM_PLUGIN_EXECUTE_H +#include "thorin/world.h" + namespace thorin { -void plugin_execute(World&); +void plugin_execute(World&, std::vector intrinsics); } diff --git a/src/thorin/world.cpp b/src/thorin/world.cpp index 62af0d519..c8096f9a4 100644 --- a/src/thorin/world.cpp +++ b/src/thorin/world.cpp @@ -10,12 +10,7 @@ #endif #include -#ifdef _MSC_VER -#include -#else -#include -#include -#endif +#include #include "thorin/def.h" #include "thorin/primop.h" @@ -1302,9 +1297,9 @@ void World::opt() { RUN_PASS(flatten_tuples(*this)) RUN_PASS(clone_bodies(*this)) RUN_PASS(split_slots(*this)) - if (plugin_handles.size() > 0) { - RUN_PASS(plugin_execute(*this)); - RUN_PASS(cleanup()); + if (!data_.plugin_intrinsics_.empty()) { + RUN_PASS(plugin_execute(*this, std::move(data_.plugin_intrinsics_))); + // RUN_PASS(cleanup()); } RUN_PASS(closure_conversion(*this)) RUN_PASS(lift_builtins(*this)) @@ -1314,41 +1309,4 @@ void World::opt() { RUN_PASS(cleanup()) RUN_PASS(codegen_prepare(*this)) } - -bool World::register_plugin(const char* plugin_name) { -#ifdef _MSC_VER - return false; -#else // _MSC_VER - void *handle = dlopen(plugin_name, RTLD_LAZY | RTLD_GLOBAL); - if (!handle) { - ELOG("Error loading plugin {}: {}", plugin_name, dlerror()); - ELOG("Is plugin contained in LD_LIBRARY_PATH?"); - return false; - } - dlerror(); - - char *error; - auto initfunc = reinterpret_cast(dlsym(handle, "init")); - if ((error = dlerror()) != NULL) { - ILOG("Plugin {} did not supply an init function", plugin_name); - } else { - initfunc(this); - } - - plugin_handles.push_back(handle); - return true; -#endif // _MSC_VER -} - -World::plugin_func_t* World::search_plugin_function(const char* function_name) const { -#ifdef _MSC_VER -#else // _MSC_VER - for (auto plugin : plugin_handles) { - if (void* plugin_function = dlsym(plugin, function_name)) { - return reinterpret_cast(plugin_function); - } - } -#endif // _MSC_VER - return nullptr; -} } diff --git a/src/thorin/world.h b/src/thorin/world.h index ef27e0c67..0293901cf 100644 --- a/src/thorin/world.h +++ b/src/thorin/world.h @@ -6,12 +6,14 @@ #include #include #include +#include #include "thorin/enums.h" #include "thorin/continuation.h" #include "thorin/primop.h" #include "thorin/util/hash.h" #include "thorin/util/stream.h" +#include "thorin/plugin.h" #include "thorin/config.h" namespace thorin { @@ -321,11 +323,13 @@ class World : public TypeTable, public Streamable { // plugins - using plugin_init_func_t = void(World*); - using plugin_func_t = const Def*(World*, const App*); + struct PluginIntrinsic { + const Continuation* cont; + unique_plugin_intrinsic impl; + }; - bool register_plugin(const char* plugin_name); - plugin_func_t* search_plugin_function(const char* function_name) const; + bool load_plugin(const char* plugin_name); + void link_plugin_intrinsic(Continuation* cont); private: const Param* param(const Type* type, Continuation* continuation, size_t index, Debug dbg); @@ -334,9 +338,6 @@ class World : public TypeTable, public Streamable { template const Def* transcendental(MathOpTag, const Def*, Debug, F&&); template const Def* transcendental(MathOpTag, const Def*, const Def*, Debug, F&&); - std::unique_ptr world_; - std::vector plugin_handles; - /// @name put into see of nodes //@{ template const T* cse(const T* primop) { return cse_base(primop)->template as(); } @@ -371,10 +372,14 @@ class World : public TypeTable, public Streamable { Sea defs_; Continuation* branch_; Continuation* end_scope_; + std::vector plugin_modules_; + std::vector plugin_intrinsics_; } data_; std::shared_ptr stream_; + unique_plugin_intrinsic load_plugin_intrinsic(const char* function_name) const; + friend class Mangler; friend class Cleaner; friend class Continuation;