diff --git a/src/thorin/CMakeLists.txt b/src/thorin/CMakeLists.txt index 15817401d..6abf54430 100644 --- a/src/thorin/CMakeLists.txt +++ b/src/thorin/CMakeLists.txt @@ -47,6 +47,10 @@ set(THORIN_SOURCES transform/codegen_prepare.cpp transform/dead_load_opt.cpp transform/dead_load_opt.h + transform/hls_channels.cpp + transform/hls_channels.h + transform/hls_kernel_launch.h + transform/hls_kernel_launch.cpp transform/hoist_enters.cpp transform/hoist_enters.h transform/flatten_tuples.cpp @@ -59,18 +63,16 @@ set(THORIN_SOURCES transform/lift_builtins.h transform/mangle.cpp transform/mangle.h - transform/resolve_loads.cpp - transform/resolve_loads.h transform/partial_evaluation.cpp transform/partial_evaluation.h transform/rewrite.cpp transform/rewrite.h + transform/plugin_execute.cpp + transform/plugin_execute.h + transform/resolve_loads.cpp + transform/resolve_loads.h transform/split_slots.cpp transform/split_slots.h - transform/hls_channels.cpp - transform/hls_channels.h - transform/hls_kernel_launch.h - transform/hls_kernel_launch.cpp util/array.h util/cast.h util/hash.h @@ -150,3 +152,5 @@ endif() if(THORIN_ENABLE_JSON) target_link_libraries(thorin PRIVATE nlohmann_json::nlohmann_json) endif() + +target_link_libraries(thorin PRIVATE ${CMAKE_DL_LIBS}) diff --git a/src/thorin/be/emitter.h b/src/thorin/be/emitter.h index c138ad5e7..6fa7d8407 100644 --- a/src/thorin/be/emitter.h +++ b/src/thorin/be/emitter.h @@ -41,7 +41,7 @@ class Emitter { } //auto place = def->no_dep() ? entry_ : scheduler_.smart(def); - auto place = !scheduler_.scope().contains(def) ? entry_ : scheduler_.smart(def); + auto place = !(&scheduler_.scope()) ? nullptr : (!scheduler_.scope().contains(def) ? entry_ : scheduler_.smart(def)); if (place) { auto& bb = cont2bb_[place]; diff --git a/src/thorin/be/json/json.cpp b/src/thorin/be/json/json.cpp index 8188caa3f..c8275da3c 100644 --- a/src/thorin/be/json/json.cpp +++ b/src/thorin/be/json/json.cpp @@ -227,6 +227,45 @@ class DefTable { result["intrinsic"] = "match"; result["variant_type"] = variant_type; result["num_patterns"] = num_patterns; + } else if (cont->intrinsic() == Intrinsic::Plugin) { + auto intrinsic_name = cont->name(); + auto intrinsic_type = type_table_.translate_type(cont->type()); + auto name = "_plugin_" + std::to_string(decl_table.size()); + known_defs[def] = name; + + json forward_decl; + forward_decl["name"] = name; + forward_decl["type"] = "continuation"; + forward_decl["intrinsic"] = intrinsic_name; + forward_decl["fn_type"] = intrinsic_type; + forward_decl["plugin"] = true; + + bool emit_node = false; + if (cont->attributes().depends) { + result["depends"] = translate_def(cont->attributes().depends); + + emit_node = true; + } + if (cont->filter() && !cont->filter()->empty()) { + //The filter will most certainly rely on these parameters. + json arg_names = json::array(); + for (auto arg : cont->params()) { + arg_names.push_back(translate_def(arg)); + } + forward_decl["arg_names"] = arg_names; + + emit_node = true; + } + + decl_table.push_back(forward_decl); + + if (emit_node) { + result["name"] = name; + result["type"] = "continuation"; + result["plugin"] = true; + } else { + return name; + } } else { auto intrinsic_name = cont->name(); auto intrinsic_type = type_table_.translate_type(cont->type()); @@ -283,9 +322,12 @@ class DefTable { }; } else { //Early return. We do not have a body, so there is no point in writing something to the def table. - if (cont->filter() && !cont->filter()->empty()) - assert(false && "These filters cannot be generated RN"); - return name; + if (cont->filter() && !cont->filter()->empty()) { + result["name"] = name; + result["type"] = "continuation"; + result["filter"] = translate_def(cont->filter()); + } else + return name; } } } else if (auto lit = def->isa()) { @@ -295,11 +337,19 @@ class DefTable { result["name"] = name; result["type"] = "const"; result["const_type"] = type; - //result["value"] = lit->value().get_s32(); //TODO: this looks wrong. What I get should depend on the lit type. + switch (lit->primtype_tag()) { #define THORIN_I_TYPE(T, M) case PrimType_##T: { result["value"] = lit->value().get_##M(); break; } #define THORIN_BOOL_TYPE(T, M) case PrimType_##T: { result["value"] = lit->value().get_##M(); break; } -#define THORIN_F_TYPE(T, M) case PrimType_##T: { result["value"] = (double)lit->value().get_##M(); break; } +#define THORIN_F_TYPE(T, M) case PrimType_##T: { \ + double value = (double)lit->value().get_##M(); \ + result["value"] = value; \ + if (value == INFINITY) { result["special"] = "inf"; } \ + if (value == - INFINITY) { result["special"] = "-inf"; } \ + if (value == NAN) { result["special"] = "nan"; } \ + if (value == - NAN) { result["special"] = "- nan"; } \ + break; \ +} #include default: assert(false && "not implemented"); diff --git a/src/thorin/be/llvm/amdgpu.h b/src/thorin/be/llvm/amdgpu.h index 277b8d595..2ac56a175 100644 --- a/src/thorin/be/llvm/amdgpu.h +++ b/src/thorin/be/llvm/amdgpu.h @@ -22,6 +22,7 @@ class AMDGPUCodeGen : public CodeGen { llvm::Value* emit_mathop(llvm::IRBuilder<>&, const MathOp*) override; llvm::Value* emit_reserve(llvm::IRBuilder<>&, const Continuation*) override; std::string get_alloc_name() const override { return "malloc"; } + std::string get_release_name() const override { return "free"; } const Cont2Config& kernel_config_; }; diff --git a/src/thorin/be/llvm/cpu.h b/src/thorin/be/llvm/cpu.h index 78db51cd9..73908f54e 100644 --- a/src/thorin/be/llvm/cpu.h +++ b/src/thorin/be/llvm/cpu.h @@ -13,6 +13,7 @@ class CPUCodeGen : public CodeGen { protected: std::string get_alloc_name() const override { return "anydsl_alloc"; } + std::string get_release_name() const override { return "anydsl_release"; } }; } diff --git a/src/thorin/be/llvm/llvm.cpp b/src/thorin/be/llvm/llvm.cpp index ecea16edd..8e790eb53 100644 --- a/src/thorin/be/llvm/llvm.cpp +++ b/src/thorin/be/llvm/llvm.cpp @@ -1013,6 +1013,9 @@ llvm::Value* CodeGen::emit_builder(llvm::IRBuilder<>& irbuilder, const Def* def) } else if (auto alloc = def->isa()) { emit_unsafe(alloc->mem()); return emit_alloc(irbuilder, alloc->alloced_type(), alloc->extra()); + } else if (auto release = def->isa()) { + emit_unsafe(release->mem()); + return emit_release(irbuilder, release->alloc()); } else if (auto slot = def->isa()) { return emit_alloca(irbuilder, convert(slot->type()->as()->pointee()), slot->unique_name()); } else if (auto vector = def->isa()) { @@ -1058,6 +1061,15 @@ llvm::Value* CodeGen::emit_alloc(llvm::IRBuilder<>& irbuilder, const Type* type, return irbuilder.CreatePointerCast(void_ptr, llvm::PointerType::get(context(), 0)); } +llvm::Value* CodeGen::emit_release(llvm::IRBuilder<>& irbuilder, const Def* alloc) { + auto llvm_release = runtime_->get(*this, get_release_name().c_str()); + llvm::Value* llvm_alloc = emit(alloc); + llvm::Value* cast_alloc = irbuilder.CreatePointerCast(llvm_alloc, irbuilder.getInt8PtrTy()); + llvm::Value* release_args[] = { irbuilder.getInt32(0), cast_alloc }; + irbuilder.CreateCall(llvm_release, release_args); + return nullptr; +} + llvm::AllocaInst* CodeGen::emit_alloca(llvm::IRBuilder<>& irbuilder, llvm::Type* type, const std::string& name) { // Emit the alloca in the entry block auto entry = &irbuilder.GetInsertBlock()->getParent()->getEntryBlock(); diff --git a/src/thorin/be/llvm/llvm.h b/src/thorin/be/llvm/llvm.h index 7d5414331..66f679f3a 100644 --- a/src/thorin/be/llvm/llvm.h +++ b/src/thorin/be/llvm/llvm.h @@ -74,6 +74,7 @@ class CodeGen : public thorin::CodeGen, public thorin::Emitter&, llvm::Type*, const std::string&); llvm::Value* emit_alloc (llvm::IRBuilder<>&, const Type*, const Def*); + llvm::Value* emit_release (llvm::IRBuilder<>&, const Def*); virtual void emit_fun_decl_hook(Continuation*, llvm::Function*) {} virtual llvm::Value* map_param(llvm::Function*, llvm::Argument* a, const Param*) { return a; } @@ -87,6 +88,7 @@ class CodeGen : public thorin::CodeGen, public thorin::Emitter&, const Continuation*, bool=false); virtual std::string get_alloc_name() const = 0; + virtual std::string get_release_name() const = 0; llvm::BasicBlock* cont2bb(Continuation* cont) { return cont2bb_[cont].first; } virtual llvm::Value* emit_global(const Global*); diff --git a/src/thorin/be/llvm/nvvm.cpp b/src/thorin/be/llvm/nvvm.cpp index 88c488da8..9e630cdb4 100644 --- a/src/thorin/be/llvm/nvvm.cpp +++ b/src/thorin/be/llvm/nvvm.cpp @@ -19,8 +19,8 @@ namespace thorin::llvm { -NVVMCodeGen::NVVMCodeGen(Thorin& thorin, const Cont2Config& kernel_config, int opt, bool debug) - : CodeGen(thorin, llvm::CallingConv::C, llvm::CallingConv::PTX_Device, llvm::CallingConv::PTX_Kernel, 0, debug) +NVVMCodeGen::NVVMCodeGen(Thorin& thorin, const Cont2Config& kernel_config, int /* opt */, bool /* debug */) + : CodeGen(thorin, llvm::CallingConv::C, llvm::CallingConv::PTX_Device, llvm::CallingConv::PTX_Kernel, 0, false) , kernel_config_(kernel_config) { auto triple = llvm::Triple(llvm::sys::getDefaultTargetTriple()); diff --git a/src/thorin/be/llvm/nvvm.h b/src/thorin/be/llvm/nvvm.h index 3c5b8596d..f00d20d08 100644 --- a/src/thorin/be/llvm/nvvm.h +++ b/src/thorin/be/llvm/nvvm.h @@ -33,6 +33,7 @@ class NVVMCodeGen : public CodeGen { llvm::Value* emit_global(const Global*) override; std::string get_alloc_name() const override { return "malloc"; } + std::string get_release_name() const override { return "free"; } private: llvm::Function* get_texture_handle_fun(llvm::IRBuilder<>&); diff --git a/src/thorin/continuation.cpp b/src/thorin/continuation.cpp index 9001206b4..80c982a8b 100644 --- a/src/thorin/continuation.cpp +++ b/src/thorin/continuation.cpp @@ -59,6 +59,18 @@ bool App::verify() const { return true; } +void App::jump(const Def* callee, Defs args, Debug dbg) { + unset_ops(); + resize(args.size() + 1); + + set_op(0, callee); + for (int i = 0, e = args.size(); i < e; i++) { + set_op(i + 1, args[i]); + } + + verify(); +} + //------------------------------------------------------------------------------ Filter::Filter(World& world, const Defs defs, Debug dbg) : Def(world, Node_Filter, world.bottom_type(), defs, dbg) {} @@ -300,9 +312,7 @@ void Continuation::match(const Def* mem, const Def* val, Continuation* otherwise bool Continuation::verify() const { bool ok = true; - if (!has_body()) - assertf(filter()->is_empty(), "continuations with no body should have an empty (no) filter"); - else { + if (has_body()) { ok &= body()->verify(); assert(!dead_); // destroy() should remove the body assert(intrinsic() == Intrinsic::None); diff --git a/src/thorin/continuation.h b/src/thorin/continuation.h index 73e47e36a..995fb8a36 100644 --- a/src/thorin/continuation.h +++ b/src/thorin/continuation.h @@ -118,6 +118,7 @@ enum class Intrinsic : uint8_t { Branch, ///< branch(mem, cond, T, F). Match, ///< match(mem, val, otherwise, (case1, cont1), (case2, cont2), ...) PeInfo, ///< Partial evaluation debug info. + Plugin, ///< Some plugin derived intrinsic. Indentified by its name. EndScope ///< Dummy function which marks the end of a @p Scope. }; @@ -131,12 +132,13 @@ class Continuation : public Def { struct Attributes { Intrinsic intrinsic = Intrinsic::None; CC cc = CC::Thorin; + const Continuation* depends = nullptr; Attributes(Intrinsic intrinsic) : intrinsic(intrinsic) {} Attributes(CC cc = CC::Thorin) : cc(cc) {} }; -private: +protected: Continuation(World&, const FnType* pi, const Attributes& attributes, Debug dbg); virtual ~Continuation() { for (auto param : params()) delete param; } diff --git a/src/thorin/primop.cpp b/src/thorin/primop.cpp index a1222f4fe..000e17fc3 100644 --- a/src/thorin/primop.cpp +++ b/src/thorin/primop.cpp @@ -112,6 +112,12 @@ Alloc::Alloc(World& world, const Type* type, const Def* mem, const Def* extra, D set_type(world.tuple_type({world.mem_type(), world.ptr_type(type)})); } +Release::Release(World& world, const Def* mem, const Def* alloc, Debug dbg) + : MemOp(world, Node_Release, nullptr, {mem, alloc}, dbg) +{ + set_type(world.mem_type()); +} + Load::Load(World& world, const Def* mem, const Def* ptr, Debug dbg) : Access(world, Node_Load, nullptr, {mem, ptr}, dbg) { @@ -223,6 +229,10 @@ const Def* Alloc::rebuild(World& w, const Type* t, Defs o) const { return w.alloc(t->as()->op(1)->as()->pointee(), o[0], o[1], debug()); } +const Def* Release::rebuild(World& w, const Type* t, Defs o) const { + return w.release(o[0], o[1], debug()); +} + const Def* Assembly::rebuild(World& w, const Type* t, Defs o) const { return w.assembly(t, o, asm_template(), output_constraints(), input_constraints(), clobbers(), flags(), debug()); } diff --git a/src/thorin/primop.h b/src/thorin/primop.h index 5b2a7eb33..487865cb4 100644 --- a/src/thorin/primop.h +++ b/src/thorin/primop.h @@ -585,6 +585,20 @@ class Alloc : public MemOp { friend class World; }; +class Release : public MemOp { +private: + Release(World& world, const Def* mem, const Def* alloc, Debug dbg); + +public: + const Def* alloc() const { return op(1); } + +private: + const Def* rebuild(World&, const Type*, Defs) const override; + + friend class World; +}; + + /// Base class for @p Load and @p Store. class Access : public MemOp { protected: diff --git a/src/thorin/rec_stream.cpp b/src/thorin/rec_stream.cpp index 1ddf66f4a..f5c9dcd2c 100644 --- a/src/thorin/rec_stream.cpp +++ b/src/thorin/rec_stream.cpp @@ -63,6 +63,12 @@ void RecStreamer::run() { s.fmt("// free frontier: {, }\n", scope.free_frontier()); } + if (cont->is_intrinsic() && cont->intrinsic() == Intrinsic::Plugin) { + s.fmt("plugin "); + if (cont->attributes().depends) + s.fmt("[depends {}] ", cont->attributes().depends->unique_name()); + } + if (cont->has_body()) { std::vector param_names; for (auto param : cont->params()) param_names.push_back(param->unique_name()); diff --git a/src/thorin/tables/nodetable.h b/src/thorin/tables/nodetable.h index 8c17d04dc..55e0bcbd0 100644 --- a/src/thorin/tables/nodetable.h +++ b/src/thorin/tables/nodetable.h @@ -12,6 +12,7 @@ THORIN_NODE(BlobPtr, mem_blob) // MemOp THORIN_NODE(Alloc, alloc) + THORIN_NODE(Release, release) // Access THORIN_NODE(Load, load) THORIN_NODE(Store, store) diff --git a/src/thorin/transform/cleanup_world.cpp b/src/thorin/transform/cleanup_world.cpp index 65a860e48..a2ce30e0d 100644 --- a/src/thorin/transform/cleanup_world.cpp +++ b/src/thorin/transform/cleanup_world.cpp @@ -30,6 +30,7 @@ class Cleaner { void clean_pe_info(std::queue, Continuation*); Thorin& thorin_; bool todo_ = true; +friend class Thorin; }; void Cleaner::eliminate_tail_rec() { @@ -244,7 +245,7 @@ void Cleaner::cleanup_fix_point() { todo_ |= resolve_loads(world()); rebuild(); //if (!world().is_pe_done()) - todo_ |= partial_evaluation(world()); + todo_ |= partial_evaluation(thorin_); //else // clean_pe_infos(); } @@ -258,7 +259,8 @@ void Cleaner::cleanup() { world().mark_pe_done(); for (auto def : world().defs()) { if (auto cont = def->isa_nom()) - cont->destroy_filter(); + if (cont->cc() != CC::Thorin) + cont->destroy_filter(); } todo_ = true; @@ -273,5 +275,6 @@ void Cleaner::cleanup() { } void Thorin::cleanup() { Cleaner(*this).cleanup(); } +void Thorin::cleanup_fix_point() { Cleaner(*this).cleanup_fix_point(); } } diff --git a/src/thorin/transform/closure_conversion.cpp b/src/thorin/transform/closure_conversion.cpp index 2048d77a5..d92490ab9 100644 --- a/src/thorin/transform/closure_conversion.cpp +++ b/src/thorin/transform/closure_conversion.cpp @@ -74,7 +74,7 @@ class ClosureConversion { } // prevent conversion of calls to vectorize() or cuda(), but allow graph intrinsics - if (!callee || !callee->is_intrinsic()) { + if (!callee || !callee->is_intrinsic() || callee->intrinsic() == Intrinsic::Plugin) { Array new_args(body->num_args()); for (size_t i = 0, e = body->num_args(); i != e; ++i) new_args[i] = convert_def(body->arg(i)); diff --git a/src/thorin/transform/codegen_prepare.cpp b/src/thorin/transform/codegen_prepare.cpp index e8218a571..81942de72 100644 --- a/src/thorin/transform/codegen_prepare.cpp +++ b/src/thorin/transform/codegen_prepare.cpp @@ -43,8 +43,11 @@ void codegen_prepare(Thorin& thorin) { auto destination = std::make_unique(src); CodegenPrepare pass(src, *destination.get()); - for (auto& external : src.externals()) + for (auto& external : src.externals()) { + if (auto cont = external.second->isa(); cont && cont->cc() == CC::Thorin) + continue; pass.instantiate(external.second); + } thorin.world_container().swap(destination); thorin.world().VLOG("end codegen_prepare"); diff --git a/src/thorin/transform/mangle.cpp b/src/thorin/transform/mangle.cpp index 98b3a559c..99f844fcb 100644 --- a/src/thorin/transform/mangle.cpp +++ b/src/thorin/transform/mangle.cpp @@ -74,15 +74,18 @@ Continuation* Mangler::mangle() { insert(old_entry(), old_entry()); else { // if we're only adding parameters, we can replace the entry by a small wrapper calling into the lifted entry - auto recursion_wrapper = dst().continuation(old_entry()->type()); - insert(old_entry(), recursion_wrapper); - std::vector args; - for (auto p : recursion_wrapper->params_as_defs()) - args.push_back(p); - size_t i = 0; - for ([[maybe_unused]] auto def : lift_) - args.push_back(new_entry()->param(recursion_wrapper->num_params() + i++)); - recursion_wrapper->jump(new_entry(), args); + // only do this if the entry is not also lifted, otherwise this would overwrite the newly generated parameter. + if (!lookup(old_entry())) { + auto recursion_wrapper = dst().continuation(old_entry()->type()); + insert(old_entry(), recursion_wrapper); + std::vector args; + for (auto p : recursion_wrapper->params_as_defs()) + args.push_back(p); + size_t i = 0; + for ([[maybe_unused]] auto def : lift_) + args.push_back(new_entry()->param(recursion_wrapper->num_params() + i++)); + recursion_wrapper->jump(new_entry(), args); + } } // cut/widen filter diff --git a/src/thorin/transform/partial_evaluation.cpp b/src/thorin/transform/partial_evaluation.cpp index f0595640e..c8e3bace6 100644 --- a/src/thorin/transform/partial_evaluation.cpp +++ b/src/thorin/transform/partial_evaluation.cpp @@ -16,13 +16,13 @@ struct HashApp { class PartialEvaluator { public: - PartialEvaluator(World& world, bool lower2cff) - : world_(world) + PartialEvaluator(Thorin& thorin, bool lower2cff) + : thorin_(thorin) , lower2cff_(lower2cff) , boundary_(Def::gid_counter()) {} - World& world() { return world_; } + World& world() { return thorin_.world(); } bool run(); void enqueue(Continuation* continuation) { if (continuation->gid() < 2 * boundary_ && done_.emplace(continuation).second) @@ -31,7 +31,7 @@ class PartialEvaluator { void eat_pe_info(Continuation*); private: - World& world_; + Thorin& thorin_; bool lower2cff_; HashMap cache_; ContinuationSet done_; @@ -130,7 +130,7 @@ bool PartialEvaluator::run() { const App* body = continuation->body(); const Def* callee_def = continuation->body()->callee(); - if (auto run = callee_def->isa()) { + while (auto run = callee_def->isa()) { force_fold = true; callee_def = run->def(); } @@ -141,6 +141,96 @@ bool PartialEvaluator::run() { continue; } + if (callee->intrinsic() == Intrinsic::Plugin) { + if (callee->attributes().depends) { + size_t num_dependend_uses = callee->attributes().depends->num_uses() - callee->attributes().depends->num_params(); + + //std::cerr << "Analyzing " << callee->unique_name() << " with dependency " << callee->attributes().depends->unique_name() << "\n"; + //std::cerr << " => has " << num_dependend_uses << " real dependencies\n"; + if (num_dependend_uses > 0) { + //Push the next continue so that other plugins get executed. + for (auto arg : body->args()) { + if (auto cont = arg->isa()) { + queue_.push(const_cast(cont)); + } + } + continue; + } + } + + ScopesForest forest(world()); + CondEval cond_eval(callee, forest, body->args()); + + //TODO: build specialize here to allow for parameter hiding. + bool fold = false; + for (size_t i = 0, e = body->num_args(); i != e; ++i) { + if (cond_eval.eval(i, lower2cff_)) { + fold = true; + break; + } + } + + if (not body->arg(body->num_args() - 1)->isa()) + fold = false; //Cannot execute plugin if the target is not a continuation. + + if (fold) { + std::vector specialize(body->arg(body->num_args() - 1)->as()->num_params()); + specialize[0] = body->arg(0); + + const auto& p = cache_.emplace(body, nullptr); + const Continuation* target = p.first->second; + // create new specialization if not found in cache + try { + if (p.second) { + world().idef(continuation, "Plugin execute: {}", callee); + + auto plugin_function = thorin_.search_plugin_function(callee->name().c_str()); + if (!plugin_function) { + world().ELOG("Plugin function not found for: {}", callee->name()); + continue; + } + + const Def* output = plugin_function(&world(), body); + if (output->isa()) { //The plugin cannot produce an output, but we should run another iteration. + world().ddef(continuation, "Plugin did not produce a usable output: {}", callee); + for (auto arg : body->args()) { + if (auto cont = arg->isa()) { + queue_.push(const_cast(cont)); + } + } + todo = true; + continue; + } + + if (output) + specialize[1] = output; + + target = body->arg(body->num_args() - 1)->as(); + todo = true; + } + + continuation->jump(target, specialize); + } catch (const std::runtime_error& e) { //The plugin is unhappy about the general state. We should not use it to determine the fixed-point state. + std::cerr << "Error in plugin function: " << e.what() << "\n"; + for (auto arg : body->args()) { + if (auto cont = arg->isa()) { + queue_.push(const_cast(cont)); + } + } + continue; + } + + if (lower2cff_ && fold) { + // re-examine next iteration: + // maybe the specialization is not top-level anymore which might need further specialization + queue_.push(continuation); + continue; + } + } + + continue; + } + if (callee->has_body()) { // TODO cache the forest and only rebuild it when we need to ScopesForest forest(world()); @@ -162,14 +252,14 @@ bool PartialEvaluator::run() { Continuation*& target = p.first->second; // create new specialization if not found in cache if (p.second) { - world_.ddef(continuation, "Specializing call to {}", callee); + world().ddef(continuation, "Specializing call to {}", callee); target = drop(callee, specialize); todo = true; } jump_to_dropped_call(continuation, target, specialize); - while (callee && callee->never_called()) { + while (callee && callee->never_called() && !callee->is_external()) { if (callee->has_body()) { auto new_callee = const_cast(callee->body()->callee()->isa()); callee->destroy("partial_evaluation"); @@ -198,11 +288,11 @@ bool PartialEvaluator::run() { //------------------------------------------------------------------------------ -bool partial_evaluation(World& world, bool lower2cff) { +bool partial_evaluation(Thorin& thorin, bool lower2cff) { auto name = lower2cff ? "lower2cff" : "partial_evaluation"; - world.VLOG("start {}", name); - auto res = PartialEvaluator(world, lower2cff).run(); - world.VLOG("end {}", name); + thorin.world().VLOG("start {}", name); + auto res = PartialEvaluator(thorin, lower2cff).run(); + thorin.world().VLOG("end {}", name); return res; } diff --git a/src/thorin/transform/partial_evaluation.h b/src/thorin/transform/partial_evaluation.h index fd5c2f908..ffa8ec5cd 100644 --- a/src/thorin/transform/partial_evaluation.h +++ b/src/thorin/transform/partial_evaluation.h @@ -19,7 +19,7 @@ class BetaReducer : public Rewriter { const Def* rewrite(const Def* odef) override; }; -bool partial_evaluation(World&, bool lower2cff = false); +bool partial_evaluation(Thorin&, bool lower2cff = false); } diff --git a/src/thorin/transform/plugin_execute.cpp b/src/thorin/transform/plugin_execute.cpp new file mode 100644 index 000000000..fe1b0f533 --- /dev/null +++ b/src/thorin/transform/plugin_execute.cpp @@ -0,0 +1,120 @@ +#include "thorin/world.h" +#include "thorin/transform/plugin_execute.h" +#include "thorin/transform/partial_evaluation.h" +#include "thorin/analyses/scope.h" + +#include + +namespace thorin { + +class PluginExecute { +public: + PluginExecute(Thorin& thorin) + : thorin(thorin) + {} + +private: + Thorin& thorin; + + World& world() { return thorin.world(); } + +public: + void run() { + 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) { + //Plugins with more dependencies go to the end. + //If a plugin depends on another, then the depth is clearly higher. + + int depth_a = 0; //TODO: cache those numbers. + const Continuation* depends_a = a; + while (depends_a->attributes().depends) { + depends_a = depends_a->attributes().depends; + depth_a++; + } + + int depth_b = 0; + const Continuation* depends_b = b; + while (depends_b->attributes().depends) { + depends_b = depends_b->attributes().depends; + depth_b++; + } + + return depth_a < depth_b; + }); + + world().VLOG("Plugin execution order:"); + for (auto cont : plugin_intrinsics) { + world().VLOG("{}", cont->unique_name()); + } + + bool evaluated = false; + + for (auto cont : plugin_intrinsics) { + auto plugin_function = thorin.search_plugin_function(cont->name().c_str()); + if (!plugin_function) { + world().ELOG("Plugin function not found for: {}", cont->name()); + continue; + } + + 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; + } + + try { + 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; + } catch (const std::runtime_error& e) { + std::cerr << "Error in plugin function: " << e.what() << "\n"; + } + } + + if (evaluated) + break; + } + if (!evaluated) break; + thorin.cleanup(); + } + + world().mark_pe_done(false); + thorin.cleanup(); + } +}; + +void plugin_execute(Thorin& thorin) { + PluginExecute(thorin).run(); +} + +} diff --git a/src/thorin/transform/plugin_execute.h b/src/thorin/transform/plugin_execute.h new file mode 100644 index 000000000..9f37fc224 --- /dev/null +++ b/src/thorin/transform/plugin_execute.h @@ -0,0 +1,12 @@ +#ifndef THORIN_TRANSFORM_PLUGIN_EXECUTE_H +#define THORIN_TRANSFORM_PLUGIN_EXECUTE_H + +namespace thorin { + +class Thorin; + +void plugin_execute(Thorin&); + +} + +#endif diff --git a/src/thorin/transform/rewrite.cpp b/src/thorin/transform/rewrite.cpp index ac352568f..f40ae173a 100644 --- a/src/thorin/transform/rewrite.cpp +++ b/src/thorin/transform/rewrite.cpp @@ -48,6 +48,10 @@ const Def* Rewriter::rewrite(const Def* odef) { if (odef->isa_nom()) { stub = odef->stub(*this, ntype); insert(odef, stub); + + if (auto ocont = odef->isa_nom()) + if (ocont->attributes().depends) + stub->as_nom()->attributes().depends = instantiate(ocont->attributes().depends)->as(); } if (odef->isa_structural()) { @@ -59,6 +63,11 @@ const Def* Rewriter::rewrite(const Def* odef) { assert(&nops[i]->world() == &dst()); } auto ndef = odef->rebuild(dst(), ntype, nops); + + if (auto global = odef->isa(); global && global->is_external()) { + dst().make_external(const_cast(ndef)); + } + return ndef; } else { assert(odef->isa_nom() && stub); @@ -67,4 +76,4 @@ const Def* Rewriter::rewrite(const Def* odef) { } } -} \ No newline at end of file +} diff --git a/src/thorin/util/scoped_dump.cpp b/src/thorin/util/scoped_dump.cpp index b035ac3ce..f14769c5d 100644 --- a/src/thorin/util/scoped_dump.cpp +++ b/src/thorin/util/scoped_dump.cpp @@ -4,14 +4,22 @@ namespace thorin { void ScopedWorld::stream_cont(thorin::Stream& s, Continuation* cont) const { s.fmt(Magenta); - if (cont->is_external()) - s.fmt("extern "); + if (cont->is_external()) { + if (cont->cc() == CC::Thorin) + s.fmt("intern "); + else + s.fmt("extern "); + } if (cont->is_intrinsic()) s.fmt("intrinsic "); s.fmt(Red); s.fmt("{}", cont->unique_name()); s.fmt(Reset); + s.fmt(Green); + s.fmt("@"); + stream_def(s, cont->filter()); + s.fmt(Reset); s.fmt("("); const FnType* t = cont->type(); for (size_t i = 0; i < cont->num_params(); i++) { @@ -46,6 +54,7 @@ void ScopedWorld::stream_cont(thorin::Stream& s, Continuation* cont) const { } prepare_def(cont, cont->body()); + prepare_def(cont, cont->filter()); auto defs = *scopes_to_defs_[cont]; stream_defs(s, defs); diff --git a/src/thorin/util/scoped_dump.h b/src/thorin/util/scoped_dump.h index 388c4f60d..fbbdabe5e 100644 --- a/src/thorin/util/scoped_dump.h +++ b/src/thorin/util/scoped_dump.h @@ -20,7 +20,7 @@ struct ScopedWorld : public Streamable { bool use_color; }; - ScopedWorld(World& w, Config cfg = { true }) : world_(w), forest_(w), config_(cfg) { + ScopedWorld(World& w, Config cfg = { getenv("THORIN_NO_COLOR") ? false : true }) : world_(w), forest_(w), config_(cfg) { #define T(n, c) n = cfg.use_color ? c : ""; COLORS(T) #undef T diff --git a/src/thorin/world.cpp b/src/thorin/world.cpp index 96b3322f5..76df6d340 100644 --- a/src/thorin/world.cpp +++ b/src/thorin/world.cpp @@ -10,6 +10,11 @@ #endif #include +#ifdef _MSC_VER +#include +#else +#include +#endif #if THORIN_ENABLE_CREATION_CONTEXT #include @@ -25,6 +30,7 @@ #include "thorin/type.h" #include "thorin/analyses/scope.h" #include "thorin/analyses/verify.h" +#include "thorin/transform/plugin_execute.h" #include "thorin/transform/closure_conversion.h" #include "thorin/transform/codegen_prepare.h" #include "thorin/transform/dead_load_opt.h" @@ -1053,6 +1059,10 @@ const Def* World::alloc(const Type* type, const Def* mem, const Def* extra, Debu return cse(new Alloc(*this, type, mem, extra, dbg)); } +const Def* World::release(const Def* mem, const Def* alloc, Debug dbg) { + return cse(new Release(*this, mem, alloc, dbg)); +} + const Def* World::global(const Def* init, bool is_mutable, Debug dbg) { return cse(new Global(*this, init, is_mutable, dbg)); } @@ -1310,9 +1320,13 @@ void Thorin::opt() { } RUN_PASS(cleanup()) - RUN_PASS(while (partial_evaluation(world(), true))); // lower2cff + RUN_PASS(while (partial_evaluation(*this, true))); // lower2cff RUN_PASS(flatten_tuples(*this)) RUN_PASS(split_slots(*this)) + //if (plugin_handles.size() > 0) { + // RUN_PASS(plugin_execute(*this)); + // RUN_PASS(cleanup()); + //} RUN_PASS(closure_conversion(world())) RUN_PASS(lift_builtins(*this)) RUN_PASS(inliner(*this)) @@ -1338,5 +1352,40 @@ bool Thorin::ensure_stack_size(size_t new_size) { #endif } +bool Thorin::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) { + world().ELOG("Error loading plugin {}: {}", plugin_name, dlerror()); + world().ELOG("Is plugin contained in LD_LIBRARY_PATH?"); + return false; + } + dlerror(); + + char *error; + auto initfunc = reinterpret_cast(dlsym(handle, "init")); + if ((error = dlerror()) != NULL) { + world().ILOG("Plugin {} did not provide an init function", plugin_name); + } else { + initfunc(&world()); + } + + plugin_handles.push_back(handle); + return true; +#endif // _MSC_VER +} +Thorin::plugin_func_t* Thorin::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 6e62023d3..cd13b6a0a 100644 --- a/src/thorin/world.h +++ b/src/thorin/world.h @@ -248,6 +248,7 @@ class World : public Streamable { const Def* slot(const Type* type, const Def* frame, Debug dbg = {}) { return cse(new Slot(*this, type, frame, dbg)); } const Def* alloc(const Type* type, const Def* mem, const Def* extra, Debug dbg = {}); const Def* alloc(const Type* type, const Def* mem, Debug dbg = {}) { return alloc(type, mem, literal_qu64(0, dbg), dbg); } + const Def* release(const Def* mem, const Def* alloc, Debug dbg = {}); const Def* global(const Def* init, bool is_mutable = true, Debug dbg = {}); const Def* global_immutable_string(const std::string& str, Debug dbg = {}); const Def* lea(const Def* ptr, const Def* index, Debug dbg); @@ -339,7 +340,8 @@ class World : public Streamable { static std::string colorize(const std::string& str, int color); //@} -private: +//TODO: Some example plugins need access to cse and data_.defs_ to put new defs in, there has to be a better way than eposing this direcly though. +//private: const Param* param(const Type* type, const Continuation*, size_t index, Debug dbg); const Def* try_fold_aggregate(const Aggregate*); template const Def* transcendental(MathOpTag, const Def*, Debug, F&&); @@ -411,12 +413,21 @@ class Thorin { /// Performs dead code, unreachable code and unused type elimination. void cleanup(); + void cleanup_fix_point(); void opt(); bool ensure_stack_size(size_t new_size); + // plugins + + using plugin_init_func_t = void(World*); + using plugin_func_t = const Def*(World*, const App*); + + bool register_plugin(const char* plugin_name); + plugin_func_t* search_plugin_function(const char* function_name) const; private: std::unique_ptr world_; + std::vector plugin_handles; }; }