diff --git a/CMakeLists.txt b/CMakeLists.txt index 60c708319..f3a4607a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 3.13.4 FATAL_ERROR) +cmake_minimum_required(VERSION 3.20.0 FATAL_ERROR) project(Thorin) set(PACKAGE_VERSION "0.3.9") -set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited config" FORCE) +#set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited config" FORCE) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -11,6 +11,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(THORIN_PROFILE "profile complexity in thorin::HashTable - only works in Debug build" ON) +option (FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." FALSE) +if (${FORCE_COLORED_OUTPUT}) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options (-fdiagnostics-color=always) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options (-fcolor-diagnostics) + endif () +endif () if(CMAKE_BUILD_TYPE STREQUAL "") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Debug or Release" FORCE) @@ -26,6 +34,16 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") find_package(Half REQUIRED) message(STATUS "Building with Half library from ${Half_INCLUDE_DIRS}.") +# find json package for json output support. +if(TARGET nlohmann_json) + set(THORIN_ENABLE_JSON TRUE) +else() + find_package(nlohmann_json 3.2.0 QUIET) + if(nlohmann_json_FOUND) + set(THORIN_ENABLE_JSON TRUE) + endif() +endif() + # check for possible llvm extension find_package(LLVM QUIET CONFIG) if(LLVM_FOUND) @@ -33,9 +51,13 @@ if(LLVM_FOUND) message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") if(LLVM_LINK_LLVM_DYLIB) set(AnyDSL_LLVM_LINK_SHARED "USE_SHARED") + else() + if (BUILD_SHARED_LIBS) + message(SEND_ERROR "Cannot build thorin as a shared library with the current build of LLVM. Build LLVM with LLVM_LINK_LLVM_DYLIB or change BUILD_SHARED_LIBS to off.") + endif() endif() # check for RV - find_package(RV) + find_package(RV QUIET CONFIG) if(RV_FOUND) message(STATUS "Building with RV from LLVM installation.") else() @@ -45,6 +67,26 @@ else() message(STATUS "Building without LLVM and RV. Specify LLVM_DIR to compile with LLVM.") endif() +include(CheckIncludeFile) +check_include_file(sys/resource.h THORIN_ENABLE_RLIMITS) + +if (NOT TARGET shady) + find_package(shady CONFIG) + if (shady_FOUND) + message(STATUS "Found shady at ${shady_DIR}") + set(THORIN_ENABLE_SHADY TRUE) + endif() +else() + export(TARGETS shady api FILE ${CMAKE_BINARY_DIR}/share/anydsl/cmake/shady-exports.cmake) + set(THORIN_ENABLE_SHADY TRUE) +endif() + +find_package(SPIRV-Headers) +if (SPIRV-Headers_FOUND) + message(STATUS "Found SPIRV-Headers at ${SPIRV-Headers_DIR}") + set(THORIN_ENABLE_SPIRV TRUE) +endif() + message(STATUS "Using Debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") message(STATUS "Using Release flags: ${CMAKE_CXX_FLAGS_RELEASE}") if(DEFINED CMAKE_BUILD_TYPE) diff --git a/cmake/modules/FindRV.cmake b/cmake/modules/FindRV.cmake deleted file mode 100644 index 8c852e6b7..000000000 --- a/cmake/modules/FindRV.cmake +++ /dev/null @@ -1,34 +0,0 @@ -# Find the RV library -# -# Once done this will define -# RV_INCLUDE_DIRS - where to find RV library include file -# RV_LIBRARIES - where to find RV library -# RV_FOUND - True if RV library is found - -find_path(RV_INCLUDE_DIR rv/rv.h - PATHS - ${LLVM_INCLUDE_DIRS} - ${LLVM_EXTERNAL_RV_SOURCE_DIR}/include - ${LLVM_BUILD_MAIN_SRC_DIR}/../rv/include - ${LLVM_BUILD_MAIN_SRC_DIR}/tools/rv/include) -if(TARGET RV) - set(RV_LIBRARY RV) -else() - find_library(RV_LIBRARY RV PATHS ${LLVM_LIBRARY_DIRS}) -endif() -if(TARGET gensleef) - set(RV_SLEEF_LIBRARY gensleef) -else() - find_library(RV_SLEEF_LIBRARY gensleef PATHS ${LLVM_LIBRARY_DIRS}) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(RV DEFAULT_MSG RV_INCLUDE_DIR RV_LIBRARY) - -set(RV_INCLUDE_DIRS ${RV_INCLUDE_DIR}) -set(RV_LIBRARIES ${RV_LIBRARY}) -if(RV_SLEEF_LIBRARY) - list(APPEND RV_LIBRARIES ${RV_SLEEF_LIBRARY}) -endif() - -mark_as_advanced(RV_INCLUDE_DIR RV_LIBRARY RV_SLEEF_LIBRARY) diff --git a/cmake/thorin-config.cmake.in b/cmake/thorin-config.cmake.in index c279f3127..641f33e0c 100644 --- a/cmake/thorin-config.cmake.in +++ b/cmake/thorin-config.cmake.in @@ -28,8 +28,11 @@ list(APPEND CMAKE_MODULE_PATH "${Thorin_ROOT_DIR}/cmake/modules") find_path(Half_DIR NAMES half.hpp PATHS ${Half_DIR} $ENV{Half_DIR} "@Half_DIR@" "@Half_INCLUDE_DIR@") find_package(Half REQUIRED) +set(Thorin_HAS_JSON_SUPPORT @THORIN_ENABLE_JSON@) set(Thorin_HAS_LLVM_SUPPORT @LLVM_FOUND@) set(Thorin_HAS_RV_SUPPORT @RV_FOUND@) +set(Thorin_HAS_SHADY_SUPPORT @THORIN_ENABLE_SHADY@) +set(Thorin_HAS_SPIRV_SUPPORT @THORIN_ENABLE_SPIRV@) set(AnyDSL_LLVM_LINK_SHARED @AnyDSL_LLVM_LINK_SHARED@) if(Thorin_HAS_LLVM_SUPPORT) diff --git a/src/thorin/CMakeLists.txt b/src/thorin/CMakeLists.txt index b3335ff5b..7a80892cc 100644 --- a/src/thorin/CMakeLists.txt +++ b/src/thorin/CMakeLists.txt @@ -16,8 +16,6 @@ set(THORIN_SOURCES world.h analyses/cfg.cpp analyses/cfg.h - analyses/domfrontier.cpp - analyses/domfrontier.h analyses/domtree.cpp analyses/domtree.h analyses/free_defs.cpp @@ -37,6 +35,7 @@ set(THORIN_SOURCES be/c/c.h be/config_script/config_script.cpp be/config_script/config_script.h + be/runtime.h be/kernel_config.h tables/allnodes.h tables/arithoptable.h @@ -45,9 +44,6 @@ set(THORIN_SOURCES tables/primtypetable.h tables/mathoptable.h transform/cleanup_world.cpp - transform/cleanup_world.h - transform/clone_bodies.cpp - transform/clone_bodies.h transform/closure_conversion.cpp transform/closure_conversion.h transform/codegen_prepare.h @@ -70,6 +66,8 @@ set(THORIN_SOURCES transform/resolve_loads.h transform/partial_evaluation.cpp transform/partial_evaluation.h + transform/rewrite.cpp + transform/rewrite.h transform/split_slots.cpp transform/split_slots.h transform/hls_dataflow.cpp @@ -93,6 +91,9 @@ set(THORIN_SOURCES util/symbol.h util/types.h util/utility.h + util/graphviz_dump.cpp + util/scoped_dump.h + util/scoped_dump.cpp ) if(LLVM_FOUND) @@ -103,6 +104,10 @@ if(LLVM_FOUND) be/llvm/llvm.h be/llvm/amdgpu.cpp be/llvm/amdgpu.h + be/llvm/amdgpu_hsa.cpp + be/llvm/amdgpu_hsa.h + be/llvm/amdgpu_pal.cpp + be/llvm/amdgpu_pal.h be/llvm/nvvm.cpp be/llvm/nvvm.h be/llvm/parallel.cpp @@ -113,6 +118,28 @@ if(LLVM_FOUND) ) endif() +if (THORIN_ENABLE_SHADY) + list(APPEND THORIN_SOURCES + be/shady/shady.cpp + ) +endif() + +if(THORIN_ENABLE_JSON) + list(APPEND THORIN_SOURCES + be/json/json.cpp + be/json/json.h + ) +endif() + +if(THORIN_ENABLE_SPIRV) + list(APPEND THORIN_SOURCES + be/spirv/spirv.cpp + be/spirv/spirv_types.cpp + be/spirv/spirv_instructions.cpp + be/spirv/spirv.h + ) +endif() + add_library(thorin ${THORIN_SOURCES}) target_include_directories(thorin PUBLIC ${Half_INCLUDE_DIRS} ${Thorin_ROOT_DIR}/src ${CMAKE_BINARY_DIR}/include) @@ -127,3 +154,15 @@ if(LLVM_FOUND) endif() llvm_config(thorin ${AnyDSL_LLVM_LINK_SHARED} ${Thorin_LLVM_COMPONENTS}) endif() + +if (THORIN_ENABLE_SHADY) + if (shady_FOUND) + target_link_libraries(thorin PRIVATE shady::shady) + else() + target_link_libraries(thorin PRIVATE shady) + endif() +endif() + +if(THORIN_ENABLE_JSON) + target_link_libraries(thorin PRIVATE nlohmann_json::nlohmann_json) +endif() diff --git a/src/thorin/analyses/cfg.cpp b/src/thorin/analyses/cfg.cpp index e95b261f3..809944b60 100644 --- a/src/thorin/analyses/cfg.cpp +++ b/src/thorin/analyses/cfg.cpp @@ -6,7 +6,6 @@ #include #include "thorin/world.h" -#include "thorin/analyses/domfrontier.h" #include "thorin/analyses/domtree.h" #include "thorin/analyses/looptree.h" #include "thorin/analyses/scope.h" @@ -30,7 +29,7 @@ Stream& CFNode::stream(Stream& s) const { return s << continuation(); } CFA::CFA(const Scope& scope) : scope_(scope) , entry_(node(scope.entry())) - , exit_ (node(scope.exit() )) + , exit_ (node(scope.entry()->world().end_scope())) { std::queue cfg_queue; ContinuationSet cfg_done; @@ -194,7 +193,6 @@ template const CFNodes& CFG::preds(const CFNode* n) const template const CFNodes& CFG::succs(const CFNode* n) const { assert(n != nullptr); return forward ? n->succs() : n->preds(); } template const DomTreeBase& CFG::domtree() const { return lazy_init(this, domtree_); } template const LoopTree& CFG::looptree() const { return lazy_init(this, looptree_); } -template const DomFrontierBase& CFG::domfrontier() const { return lazy_init(this, domfrontier_); } template class CFG; template class CFG; diff --git a/src/thorin/analyses/cfg.h b/src/thorin/analyses/cfg.h index 1d1786d78..c61207553 100644 --- a/src/thorin/analyses/cfg.h +++ b/src/thorin/analyses/cfg.h @@ -132,7 +132,6 @@ class CFG { const CFNode* operator [] (Continuation* continuation) const { return cfa()[continuation]; } ///< Maps from @p l to @p CFNode. const DomTreeBase& domtree() const; const LoopTree& looptree() const; - const DomFrontierBase& domfrontier() const; static size_t index(const CFNode* n) { return forward ? n->f_index_ : n->b_index_; } @@ -143,7 +142,6 @@ class CFG { Map rpo_; mutable std::unique_ptr> domtree_; mutable std::unique_ptr> looptree_; - mutable std::unique_ptr> domfrontier_; }; //------------------------------------------------------------------------------ diff --git a/src/thorin/analyses/domfrontier.cpp b/src/thorin/analyses/domfrontier.cpp deleted file mode 100644 index 98e4d4f1d..000000000 --- a/src/thorin/analyses/domfrontier.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "thorin/analyses/domfrontier.h" - -#include "thorin/analyses/domtree.h" - -namespace thorin { - -template -void DomFrontierBase::create() { - const auto& domtree = cfg().domtree(); - for (auto n : cfg().reverse_post_order().skip_front()) { - const auto& preds = cfg().preds(n); - if (preds.size() > 1) { - auto idom = domtree.idom(n); - for (auto pred : preds) { - for (auto i = pred; i != idom; i = domtree.idom(i)) - link(i, n); - } - } - } -} - -template class DomFrontierBase; -template class DomFrontierBase; - -} diff --git a/src/thorin/analyses/domfrontier.h b/src/thorin/analyses/domfrontier.h deleted file mode 100644 index 3014e1be8..000000000 --- a/src/thorin/analyses/domfrontier.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef THORIN_ANALYSES_DOMFRONTIER_H -#define THORIN_ANALYSES_DOMFRONTIER_H - -#include "thorin/analyses/cfg.h" - -namespace thorin { - -/** - * A Dominance Frontier Graph. - * The template parameter @p forward determines whether to compute - * regular dominance frontiers or post-dominance frontiers (i.e. control dependence). - * This template parameter is associated with @p CFG's @c forward parameter. - * See Cooper et al, 2001. A Simple, Fast Dominance Algorithm: http://www.cs.rice.edu/~keith/EMBED/dom.pdf - */ -template -class DomFrontierBase { -public: - DomFrontierBase(const DomFrontierBase &) = delete; - DomFrontierBase& operator=(DomFrontierBase) = delete; - - explicit DomFrontierBase(const CFG &cfg) - : cfg_(cfg) - , preds_(cfg) - , succs_(cfg) - { - create(); - } - - const CFG& cfg() const { return cfg_; } - const std::vector& preds(const CFNode* n) const { return preds_[n]; } - const std::vector& succs(const CFNode* n) const { return succs_[n]; } - -private: - void create(); - void link(const CFNode* src, const CFNode* dst) { - succs_[src].push_back(dst); - preds_[dst].push_back(src); - } - - const CFG& cfg_; - typename CFG::template Map> preds_; - typename CFG::template Map> succs_; -}; - -typedef DomFrontierBase DomFrontiers; -typedef DomFrontierBase ControlDeps; - -} - -#endif diff --git a/src/thorin/analyses/free_defs.cpp b/src/thorin/analyses/free_defs.cpp index 31bfd28e0..bef50546e 100644 --- a/src/thorin/analyses/free_defs.cpp +++ b/src/thorin/analyses/free_defs.cpp @@ -23,7 +23,7 @@ DefSet free_defs(const Scope& scope, bool include_closures) { while (!queue.empty()) { auto def = pop(queue); - if (def->isa_structural()) { + if (def->isa_structural() && !def->isa()) { if (!include_closures && def->isa()) { result.emplace(def); queue.push(def->op(1)); diff --git a/src/thorin/analyses/schedule.cpp b/src/thorin/analyses/schedule.cpp index 798de7255..2f7a6bd64 100644 --- a/src/thorin/analyses/schedule.cpp +++ b/src/thorin/analyses/schedule.cpp @@ -1,18 +1,17 @@ #include "thorin/analyses/schedule.h" -#include "thorin/config.h" #include "thorin/continuation.h" #include "thorin/primop.h" #include "thorin/world.h" -#include "thorin/analyses/cfg.h" #include "thorin/analyses/domtree.h" #include "thorin/analyses/looptree.h" #include "thorin/analyses/scope.h" namespace thorin { -Scheduler::Scheduler(const Scope& s) - : scope_(&s) +Scheduler::Scheduler(const Scope& s, ScopesForest& forest) + : forest_(&forest) + , scope_(&s) , cfg_(&scope().f_cfg()) , domtree_(&cfg().domtree()) { @@ -42,22 +41,26 @@ Scheduler::Scheduler(const Scope& s) enqueue(def, i, def->op(i)); } } + + register_defs(s); } -Continuation* Scheduler::early(const Def* def) { - if (auto cont = early_.lookup(def)) return *cont; - if (auto param = def->isa()) return early_[def] = param->continuation(); +void Scheduler::register_defs(const Scope& s) { + for (auto child : s.children_scopes()) { + Scope& cs = forest_->get_scope(child); + register_defs(cs); + } - auto result = scope().entry(); - for (auto op : def->as_structural()->ops()) { - if (!op->isa_nom() && def2uses_.find(op) != def2uses_.end()) { - auto cont = early(op); - if (domtree().depth(cfg(cont)) > domtree().depth(cfg(result))) - result = cont; - } + for (auto def : s.defs()) { + if (!early_.lookup(def)) + early_[def] = s.entry(); } +} - return early_[def] = result; +Continuation* Scheduler::early(const Def* def) { + if (auto cont = early_.lookup(def)) return *cont; + if (auto param = def->isa()) return early_[def] = param->continuation(); + assert(false); } Continuation* Scheduler::late(const Def* def) { @@ -68,6 +71,9 @@ Continuation* Scheduler::late(const Def* def) { result = continuation; } else if (auto param = def->isa()) { result = param->continuation(); + } else if (def->isa_nom()) { + // don't try to late-schedule recursive nodes for now + result = early(def); } else { for (auto use : uses(def)) { auto cont = late(use); diff --git a/src/thorin/analyses/schedule.h b/src/thorin/analyses/schedule.h index b2cfa65aa..223fec3c1 100644 --- a/src/thorin/analyses/schedule.h +++ b/src/thorin/analyses/schedule.h @@ -11,7 +11,7 @@ using DomTree = DomTreeBase; class Scheduler { public: Scheduler() = default; - explicit Scheduler(const Scope&); + explicit Scheduler(const Scope&, ScopesForest&); /// @name getters //@{ @@ -19,9 +19,11 @@ class Scheduler { const F_CFG& cfg() const { return *cfg_; } const CFNode* cfg(Continuation* cont) const { return cfg()[cont]; } const DomTree& domtree() const { return *domtree_; } - const Uses& uses(const Def* def) const { return def2uses_.find(def)->second; } + const Uses& uses(const Def* def) const { assert(def2uses_.contains(def)); return def2uses_.find(def)->second; } //@} + void register_defs(const Scope&); + /// @name compute schedules //@{ Continuation* early(const Def*); @@ -31,6 +33,7 @@ class Scheduler { friend void swap(Scheduler& s1, Scheduler& s2) { using std::swap; + swap(s1.forest_, s2.forest_); swap(s1.scope_, s2.scope_); swap(s1.cfg_, s2.cfg_); swap(s1.domtree_, s2.domtree_); @@ -41,6 +44,7 @@ class Scheduler { } private: + ScopesForest* forest_ = nullptr; const Scope* scope_ = nullptr; const F_CFG* cfg_ = nullptr; const DomTree* domtree_ = nullptr; diff --git a/src/thorin/analyses/scope.cpp b/src/thorin/analyses/scope.cpp index 37d34114c..4688c8a4a 100644 --- a/src/thorin/analyses/scope.cpp +++ b/src/thorin/analyses/scope.cpp @@ -12,133 +12,356 @@ namespace thorin { -Scope::Scope(Continuation* entry) - : world_(entry->world()) - , entry_(entry) - , exit_(world().end_scope()) -{ +Scope::Scope(Continuation* entry) : world_(entry->world()), root(std::make_unique(world())), forest_(*root), entry_(entry) { run(); } +Scope::Scope(Continuation* entry, ScopesForest& forest) + : world_(entry->world()) + , forest_(forest) + , entry_(entry) +{} + Scope::~Scope() {} Scope& Scope::update() { defs_.clear(); - free_ = nullptr; + free_frontier_.clear(); + first_free_param_ = nullptr; free_params_ = nullptr; cfa_ = nullptr; run(); return *this; } -void Scope::run() { +DefSet Scope::potentially_contained() const { + DefSet potential_defs; std::queue queue; auto enqueue = [&] (const Def* def) { - if (defs_.insert(def).second) { + if (potential_defs.insert(def).second) { queue.push(def); - - if (auto continuation = def->isa_nom()) { - // when a continuation is part of this scope, we also enqueue its params, and we assert those to be unique - // TODO most likely redundant once params have the continuation in their ops - for (auto param : continuation->params()) { - auto p = defs_.insert(param); - assert_unused(p.second); - queue.push(param); - } - } } }; - enqueue(entry_); + for (auto param : entry()->params()) + enqueue(param); while (!queue.empty()) { auto def = pop(queue); - if (def != entry_) { - for (auto use : def->uses()) + if (def != entry()) { + for (auto use: def->uses()) enqueue(use); } } - enqueue(exit_); + return potential_defs; +} + +void Scope::run() { + DefSet potential_defs = potentially_contained(); + + unique_queue queue; + + if (entry()->has_body()) + queue.push(entry()->body()); + queue.push(entry()->filter()); + + defs_.insert(entry()); + for (auto p : entry()->params()) + defs_.insert(p); + + while (!queue.empty()) { + auto def = queue.pop(); + + if (potential_defs.contains(def)) { + defs_.insert(def); + for (auto op : def->ops()) + queue.push(op); + } else { + free_frontier_.insert(def); + } + } } -const DefSet& Scope::free() const { - if (!free_) { - free_ = std::make_unique(); +void Scope::verify() { + for (auto def : defs()) { + if (auto cont = def->isa_nom()) { + if (cont == entry()) + continue; + Scope& cont_scope = forest_.get_scope(cont); + assert(cont_scope.has_free_params()); + assert(!cont_scope.contains(entry())); + } + } +} - for (auto def : defs_) { - for (auto op : def->ops()) { - if (!contains(op)) - free_->emplace(op); +static auto first_or_null = [](ParamSet& set) -> const Param* { + for (auto p : set) { + return p; + } + return nullptr; +}; + +// searches for free variable in this scope, starting at the free frontier and transitively searching the scopes of the free continuations we encounter +// stop_after_first is used when we only care about whether this is a top-level scope (ie has no free params) or not, we can stop as soon as one param is found +template +std::tuple Scope::search_free_params() const { + if (free_params_) { + //world().WLOG("free variables analysis: reusing cached results for {}", entry()); + return std::make_tuple(*free_params_, true); + } + + if (stop_after_first && first_free_param_) { + ParamSet one_or_zero_params; + if (*first_free_param_ != nullptr) + one_or_zero_params.insert(first_free_param()); + return std::make_tuple(one_or_zero_params, true); + } + + ParamSet free_params; + /// as much as possible, we'd like to keep the results of those searches, but in the recursive case it's not always possible: + /// if there is a cycle, we stop when we encounter a continuation we're already searching the free variables for + /// this variable keeps track of whether we did that or not, if we didn't and this is a full search, we can safely save the results + bool thorough = true; + bool is_root = forest_.stack_.empty(); + //world().WLOG("free variables analysis: searching transitive ops of: {} (root={}, depth={})", entry(), root, forest_->stack_.size()); + forest_.stack_.push_back(entry()); + + unique_queue queue; + + for (auto def : free_frontier_) + queue.push(def); + + while (!queue.empty()) { + auto free_def = queue.pop(); + assert(!contains(free_def)); + + if (auto param = free_def->isa(); param && !param->continuation()->dead_) { + free_params.insert(param); + if (stop_after_first) + break; + } else if (auto cont = free_def->isa_nom()) { + // the free variables analysis can be recursive, but it's not necessary to inspect our own scope again ... + assert(cont != entry()); + + // if we hit the recursion wall, the results for this free variable search are only valid for the callee + if (std::find(forest_.stack_.begin(), forest_.stack_.end(), cont) != forest_.stack_.end()) { + //world().WLOG("free variables analysis: skipping {} to prevent infinite recursion", cont); + thorough = false; + continue; } + + Scope& scope = forest_.get_scope(cont); + assert(!scope.defs().empty() || !scope.entry()->has_body()); + + // When we have a free continuation in the body of our fn, their free variables are also free in us + auto [callee_free_params, callee_results_thorough] = scope.search_free_params(); + if (!is_root) + thorough &= callee_results_thorough; + + for (auto p: callee_free_params) { + // (those variables have to be free here! otherwise that continuation should be in this scope and not free) + if (contains(p)) { + world().WLOG("Potentially broken scoping: free variable {} showed up in the free variables of {} despite that continuation being part of its scope", p, entry()); + assert(false); + } + free_params.insert(p); + if (stop_after_first) + break; + } + } + else { + for (auto op : free_def->ops()) { + assert(op && "scope analysis doesn't work with unfinished nodes..."); + // the entry might be referenced by the outside world, but that's completely fine + if (op == entry()) + continue; + assert(!contains(op)); + queue.push(op); + } + } + } + + //world().WLOG("free variables analysis: done with : {} (hit_wall={})", entry(), hit_recursion_wall); + + assert(forest_.stack_.back() == entry()); + forest_.stack_.pop_back(); + + // save the results if we can + if (thorough) { + if (stop_after_first) { + first_free_param_ = std::make_optional(first_or_null(free_params)); + return std::make_tuple(free_params, true); + } else { + free_params_ = std::make_unique(std::move(free_params)); + return std::make_tuple(*free_params_, true); } } - return *free_; + for (auto fp : free_params) { + assert(fp->continuation() != entry()); + } + + return std::make_tuple(free_params, thorough); } const ParamSet& Scope::free_params() const { if (!free_params_) { - free_params_ = std::make_unique(); - unique_queue queue; - - auto enqueue = [&](const Def* def) { - if (auto param = def->isa(); param && !param->continuation()->dead_) - free_params_->emplace(param); - else if (def->isa()) - return; - else - queue.push(def); - }; - - for (auto def : free()) - enqueue(def); - while (!queue.empty()) { - for (auto op : queue.pop()->ops()) - enqueue(op); - } + auto [set, valid] = search_free_params(); + assert(valid); + free_params_ = std::make_unique(set); } return *free_params_; } +const Param* Scope::first_free_param() const { + if (!first_free_param_) { + // if we already computed the full free params list, let's reuse that ! + if (free_params_) { + first_free_param_ = std::make_optional(first_or_null(*free_params_)); + } else { + auto [set, valid] = search_free_params(); + assert(valid); + first_free_param_ = std::make_optional(first_or_null(set)); + } + } + + return *first_free_param_; +} + +bool Scope::has_free_params() const { + return first_free_param() != nullptr; +} + const CFA& Scope::cfa() const { return lazy_init(this, cfa_); } const F_CFG& Scope::f_cfg() const { return cfa().f_cfg(); } const B_CFG& Scope::b_cfg() const { return cfa().b_cfg(); } -template -void Scope::for_each(const World& world, std::function f) { - unique_queue continuation_queue; +Continuation* Scope::parent_scope() const { + if (!parent_scope_) { + ContinuationSet candidates_set; + std::queue candidates; - for (auto&& [_, cont] : world.externals()) { - if (cont->has_body()) continuation_queue.push(cont); - } + for (auto param : free_params()) { + if(candidates_set.insert(param->continuation()).second) + candidates.push(param->continuation()); + } - while (!continuation_queue.empty()) { - auto continuation = continuation_queue.pop(); - if (elide_empty && !continuation->has_body()) - continue; - Scope scope(continuation); - f(scope); + if (candidates.empty()) + parent_scope_ = std::make_optional(); + else { + while (true) { + auto candidate = pop(candidates); + // when there is only one candidate left, that's our parent + if (candidates.empty()) { + parent_scope_ = std::make_optional(candidate); + break; + } - unique_queue def_queue; - for (auto def : scope.free()) - def_queue.push(def); + auto other_candidate = pop(candidates); + assert(candidate != other_candidate); + if (forest_.get_scope(candidate).contains(other_candidate)) + candidates.push(other_candidate); + else { + assert(forest_.get_scope(other_candidate).contains(candidate) && "a scope cannot be nested in two unrelated parent scopes"); + candidates.push(candidate); + } + } + } + } - while (!def_queue.empty()) { - auto def = def_queue.pop(); - if (auto continuation = def->isa_nom()) - continuation_queue.push(continuation); + return *parent_scope_; +} + +ContinuationSet Scope::children_scopes() const { + ContinuationSet set; + for (auto def : defs()) { + if (auto cont = def->isa_nom()) { + auto& scope = forest_.get_scope(cont); + if (scope.parent_scope() == entry()) + set.insert(cont); else { - for (auto op : def->ops()) - def_queue.push(op); + //TODO: add check that the parent is eventually us! } } } + return set; +} + +template void ScopesForest::for_each ( std::function); +template void ScopesForest::for_each( std::function); + +Scope& ScopesForest::get_scope(Continuation* entry) { + if (scopes_.contains(entry)) { + auto existing = scopes_.find(entry); + assert((size_t)(existing->second.get()) != 0xbebebebe00000000); + return *existing->second; + } + auto scope = std::make_unique(entry, *this); + Scope* ptr = scope.get(); + ptr->run(); + scopes_[entry] = std::move(scope); +#if THORIN_ENABLE_CHECKS + if (stack_.empty()) + ptr->verify(); +#endif + return *ptr; +} + +ContinuationSet ScopesForest::top_level_scopes() { + ContinuationSet set; + for (auto cont : world_.copy_continuations()) { + auto& scope = get_scope(cont); + assert(stack_.empty()); + if(!scope.has_free_params()) { + set.insert(cont); + } + assert(stack_.empty()); + } + return set; } -template void Scope::for_each (const World&, std::function); -template void Scope::for_each(const World&, std::function); +template +void ScopesForest::for_each(std::function f) { + for (auto cont : top_level_scopes()) { + if (elide_empty && !cont->has_body()) + continue; + auto& scope = get_scope(cont); + f(scope); + } +} + +DEBUG_UTIL void dump_scope_tree(Scope& s, ScopesForest* f = nullptr, int depth = 0) { + for (int i = 0; i < depth; i++) + std::cerr << " "; + std::unique_ptr sf; + if (!f) { + sf = std::make_unique(s.world()); + f = sf.get(); + } + + std::cerr << s.entry()->unique_name() << std::endl; + for (auto c : s.children_scopes()) { + auto& cs = f->get_scope(c); + dump_scope_tree(cs, f, depth + 1); + } +} + +DEBUG_UTIL void dump_scopes(World& w) { + ScopesForest forest(w); + std::vector q; + for (auto cont : w.copy_continuations()) { + auto& s = forest.get_scope(cont); + if (!s.parent_scope()) { + q.push_back(&s); + } + } + + for (auto root : q) { + dump_scope_tree(*root, &forest); + } +} } diff --git a/src/thorin/analyses/scope.h b/src/thorin/analyses/scope.h index fa301a457..c0aa9c7cf 100644 --- a/src/thorin/analyses/scope.h +++ b/src/thorin/analyses/scope.h @@ -16,6 +16,8 @@ typedef CFG B_CFG; class CFA; class CFNode; +class ScopesForest; + /** * A @p Scope represents a region of @p Continuation%s which are live from the view of an @p entry @p Continuation. * Transitively, all user's of the @p entry's parameters are pooled into this @p Scope. @@ -28,6 +30,7 @@ class Scope : public Streamable { Scope& operator=(Scope) = delete; explicit Scope(Continuation* entry); + explicit Scope(Continuation* entry, ScopesForest&); ~Scope(); /// Invoke if you have modified sth in this Scope. @@ -36,19 +39,23 @@ class Scope : public Streamable { //@{ misc getters World& world() const { return world_; } Continuation* entry() const { return entry_; } - Continuation* exit() const { return exit_; } + ScopesForest& forest() const { return forest_; } //@} //@{ get Def%s contained in this Scope const DefSet& defs() const { return defs_; } + const DefSet& free_frontier() const { return free_frontier_; } bool contains(const Def* def) const { return defs_.contains(def); } - /// All @p Def%s referenced but @em not contained in this @p Scope. - const DefSet& free() const; + //@} + /// All @p Param%s that appear free in this @p Scope. const ParamSet& free_params() const; /// Are there any free @p Param%s within this @p Scope. - bool has_free_params() const { return !free_params().empty(); } - //@} + bool has_free_params() const; + const Param* first_free_param() const; + + Continuation* parent_scope() const; + ContinuationSet children_scopes() const; //@{ simple CFA to construct a CFG const CFA& cfa() const; @@ -61,24 +68,50 @@ class Scope : public Streamable { Stream& stream(Stream&) const; ///< Streams thorin to file @p out. //@} + void verify(); +private: + void run(); + DefSet potentially_contained() const; + + template + std::tuple search_free_params() const; + + World& world_; + std::unique_ptr root; + ScopesForest& forest_; + Continuation* entry_ = nullptr; + DefSet defs_; + DefSet free_frontier_; + mutable std::optional first_free_param_; + mutable std::unique_ptr free_params_; + mutable std::unique_ptr cfa_; + mutable std::optional parent_scope_; + + friend ScopesForest; +}; + +class ScopesForest { +public: + ScopesForest(World& world) : world_(world) {} + + Scope& get_scope(Continuation* entry); + + ContinuationSet top_level_scopes(); + /** * Transitively visits all @em reachable Scope%s in @p world that do not have free variables. * We call these Scope%s @em top-level Scope%s. * Select with @p elide_empty whether you want to visit trivial Scope%s of Continuation%s without body. */ template - static void for_each(const World&, std::function); + void for_each(std::function); private: - void run(); - World& world_; - DefSet defs_; - Continuation* entry_ = nullptr; - Continuation* exit_ = nullptr; - mutable std::unique_ptr free_; - mutable std::unique_ptr free_params_; - mutable std::unique_ptr cfa_; + std::vector stack_; + ContinuationMap> scopes_; + + friend Scope; }; } diff --git a/src/thorin/analyses/verify.cpp b/src/thorin/analyses/verify.cpp index 33ba03a19..614bca846 100644 --- a/src/thorin/analyses/verify.cpp +++ b/src/thorin/analyses/verify.cpp @@ -2,36 +2,76 @@ #include "thorin/type.h" #include "thorin/world.h" #include "thorin/analyses/scope.h" +#include "thorin/analyses/free_defs.h" namespace thorin { // TODO this needs serious rewriting -static void verify_calls(World& world) { +static bool verify_calls(World& world, ScopesForest&) { + bool ok = true; for (auto def : world.defs()) { if (auto cont = def->isa()) - cont->verify(); + ok &= cont->verify(); } + return ok; } -static bool verify_top_level(World& world) { +static bool verify_top_level(World& world, ScopesForest& forest) { bool ok = true; - Scope::for_each(world, [&] (const Scope& scope) { - if (scope.has_free_params()) { - for (auto param : scope.free_params()) - world.ELOG("top-level continuation '{}' got free param '{}' belonging to continuation {}", scope.entry(), param, param->continuation()); - world.ELOG("here: {}", scope.entry()); - ok = false; + unique_queue defs; + for (auto& external : world.externals()) + defs.push(external.second); + while (!defs.empty()) { + auto def = defs.pop(); + if (auto cont = def->isa_nom()) { + world.VLOG("verifying external continuation '{}'", cont); + auto& scope = forest.get_scope(cont); + scope.verify(); + if (scope.has_free_params()) { + for (auto param : scope.free_params()) + world.ELOG("top-level continuation '{}' got free param '{}' belonging to continuation {}", scope.entry(), param, param->continuation()); + world.ELOG("here: {}", scope.entry()); + ok = false; + } + } else { + for (auto op : def->ops()) + defs.push(op); } - }); - if (!ok) - world.dump(); + } + for (auto cont : world.copy_continuations()) { + auto& scope = forest.get_scope(cont); + scope.verify(); + } + return ok; +} + +#if 0 +static bool verify_param(World& world) { + bool ok = true; + for (auto def : world.defs()) { + if (auto param = def->isa()) { + auto cont = param->continuation(); + if (cont->dead_) { + world.ELOG("param '{}' originates in dead continuation {}", param, cont); + ok = false; + } + } + } return ok; } +#endif void verify(World& world) { - verify_calls(world); - verify_top_level(world); + ScopesForest forest(world); + bool ok = true; + ok &= verify_calls(world, forest); + ok &= verify_top_level(world, forest); + //TODO: This should not fail! + //ok &= verify_param(world); + if (!ok) + world.dump(); + assert(ok); } } diff --git a/src/thorin/be/c/c.cpp b/src/thorin/be/c/c.cpp index ddc45c2f8..a53a10a14 100644 --- a/src/thorin/be/c/c.cpp +++ b/src/thorin/be/c/c.cpp @@ -55,6 +55,17 @@ inline std::string cl_dialect_guard(CLDialect dialect) { } } +inline const char* lang_to_ext (Lang lang) { + switch (lang) { + case Lang::C99: return ".c"; + case Lang::HLS: return ".hls"; + case Lang::CGRA: return ".cxx"; + case Lang::CUDA: return ".cu"; + case Lang::OpenCL: return ".cl"; + default: THORIN_UNREACHABLE; + } +} + template inline std::string guarded_statement(const std::string guard, Fn fn) { StringStream s; @@ -74,18 +85,19 @@ enum class HlsInterface : uint8_t { class CCodeGen : public thorin::Emitter { public: - CCodeGen(World& world, const Cont2Config& kernel_config, Stream& stream, Stream& graph_stream, Lang lang, bool debug, std::string& flags) - : world_(world) + CCodeGen(Thorin& thorin, const Cont2Config& kernel_config, Stream& stream, Stream& graph_stream, Lang lang, bool debug, std::string& flags) + : thorin_(thorin) + , forest_(world()) , kernel_config_(kernel_config) , lang_(lang) - , fn_mem_(world.fn_type({world.mem_type()})) + , fn_mem_(world().fn_type({world().mem_type()})) , debug_(debug) , flags_(flags) , stream_(stream) , graph_stream_(graph_stream) {} - World& world() const { return world_; } + World& world() const { return thorin_.world(); } void emit_module(); void emit_c_int(); void emit_epilogue(Continuation*); @@ -106,7 +118,8 @@ class CCodeGen : public thorin::Emitter void finalize(Continuation*); private: - std::string convert(const Type*, bool = false); + void convert_primtype(StringStream&s, PrimTypeTag tag, int len, bool templated = false); + std::string convert(const Type*, bool templated = false); std::string addr_space_prefix(AddrSpace); std::string constructor_prefix(const Type*); std::string prefix_type(const Param* param); @@ -129,7 +142,8 @@ class CCodeGen : public thorin::Emitter std::string array_name(const DefiniteArrayType*); std::string tuple_name(const TupleType*); - World& world_; + Thorin& thorin_; + ScopesForest forest_; const Cont2Config& kernel_config_; Lang lang_; const FnType* fn_mem_; @@ -318,6 +332,104 @@ bool CCodeGen::get_interface(HlsInterface &interface, HlsInterface &gmem) { return false; } +inline const char* stddef_primtype_name(PrimTypeTag tag) { + switch (tag) { + case PrimType_ps8: case PrimType_qs8: return "int8_t"; + case PrimType_pu8: case PrimType_qu8: return "uint8_t"; + case PrimType_ps16: case PrimType_qs16: return "int16_t"; + case PrimType_pu16: case PrimType_qu16: return "uint16_t"; + case PrimType_ps32: case PrimType_qs32: return "int32_t"; + case PrimType_pu32: case PrimType_qu32: return "uint32_t"; + case PrimType_ps64: case PrimType_qs64: return "int64_t"; + case PrimType_pu64: case PrimType_qu64: return "uint64_t"; + case PrimType_pf16: case PrimType_qf16: return "half"; + default: THORIN_UNREACHABLE; + } +} + +inline const char* cuda_scalar_primtype(PrimTypeTag tag) { + switch (tag) { + case PrimType_ps8: case PrimType_qs8: return "char"; + case PrimType_pu8: case PrimType_qu8: return "unsigned char"; + case PrimType_ps16: case PrimType_qs16: return "short"; + case PrimType_pu16: case PrimType_qu16: return "unsigned short"; + case PrimType_ps32: case PrimType_qs32: return "int"; + case PrimType_pu32: case PrimType_qu32: return "unsigned int"; + case PrimType_ps64: case PrimType_qs64: return "long long"; + case PrimType_pu64: case PrimType_qu64: return "unsigned long long"; + case PrimType_pf16: case PrimType_qf16: return "f16"; // typedef'd with macro magic + default: THORIN_UNREACHABLE; + } +} + +/// OpenCL uses these for scalar and vectors. +/// CUDA actually uses the same prefixes for its vectors +/// See +/// https://registry.khronos.org/OpenCL/sdk/3.0/docs/man/html/vectorDataTypes.html +/// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#built-in-vector-types +inline const char* opencl_cuda_vectorbase(PrimTypeTag tag) { + switch (tag) { + case PrimType_ps8: case PrimType_qs8: return "char"; + case PrimType_pu8: case PrimType_qu8: return "uchar"; + case PrimType_ps16: case PrimType_qs16: return "short"; + case PrimType_pu16: case PrimType_qu16: return "ushort"; + case PrimType_ps32: case PrimType_qs32: return "int"; + case PrimType_pu32: case PrimType_qu32: return "uint"; + case PrimType_ps64: case PrimType_qs64: return "long"; + case PrimType_pu64: case PrimType_qu64: return "ulong"; + case PrimType_pf16: case PrimType_qf16: return "half"; // NB: cuda has no vectors of half. + default: THORIN_UNREACHABLE; + } +} + +void CCodeGen::convert_primtype(StringStream& s, PrimTypeTag tag, int len, bool templated) { + assert(len > 0); + + // Enable special code paths for f16 and f64 + switch (tag) { + case PrimType_pf16: case PrimType_qf16: use_fp_16_ = true; break; + case PrimType_pf64: case PrimType_qf64: use_fp_64_ = true; break; + default: break; + } + + // 'bool', 'float' and 'double' are identical everywhere + switch (tag) { + case PrimType_bool: s << "bool"; break; + case PrimType_pf32: case PrimType_qf32: s << "float"; break; + case PrimType_pf64: case PrimType_qf64: s << "double"; break; + default: { + if (lang_ == Lang::CUDA && len == 1) + s << cuda_scalar_primtype(tag); + else if (lang_ == Lang::CUDA || lang_ == Lang::OpenCL) + s << opencl_cuda_vectorbase(tag); + else + s << stddef_primtype_name(tag); + break; + } + } + + if (templated) { + StringStream temp; + temp << "<" << s.str(); + swap(s, temp); + s << ">"; + } + + // length suffixes + if (len == 1) + return; + switch (lang_) { + case Lang::CUDA: + case Lang::OpenCL: + s << len; + break; + case Lang::HLS: + case Lang::C99: + s.fmt(" __attribute__ ((ext_vector_size ({})))", len); + break; + } +} + /* * convert */ @@ -328,33 +440,10 @@ std::string CCodeGen::convert(const Type* type, bool templated) { StringStream s; std::string name; - if (type == world().unit() || type->isa() || type->isa()) + if (type == world().unit_type() || type->isa() || type->isa()) s << "void"; else if (auto primtype = type->isa()) { - switch (primtype->primtype_tag()) { - case PrimType_bool: s << "bool"; break; - case PrimType_ps8: case PrimType_qs8: s << "i8"; break; - case PrimType_pu8: case PrimType_qu8: s << "u8"; break; - case PrimType_ps16: case PrimType_qs16: s << "i16"; break; - case PrimType_pu16: case PrimType_qu16: s << "u16"; break; - case PrimType_ps32: case PrimType_qs32: s << "i32"; break; - case PrimType_pu32: case PrimType_qu32: s << "u32"; break; - case PrimType_ps64: case PrimType_qs64: s << "i64"; break; - case PrimType_pu64: case PrimType_qu64: s << "u64"; break; - case PrimType_pf16: case PrimType_qf16: s << "f16"; use_fp_16_ = true; break; - case PrimType_pf32: case PrimType_qf32: s << "f32"; break; - case PrimType_pf64: case PrimType_qf64: s << "f64"; use_fp_64_ = true; break; - default: THORIN_UNREACHABLE; - } - - if (templated) { - StringStream temp; - temp << "<" << s.str(); - swap(s, temp); - s << ">"; - } - if (primtype->is_vector()) - s << primtype->length(); + convert_primtype(s, primtype->primtype_tag(), vector_length(primtype), templated); } else if (auto array = type->isa()) { return types_[type] = convert(array->elem_type()); // IndefiniteArrayType always occurs within a pointer } else if (type->isa()) { @@ -372,24 +461,24 @@ std::string CCodeGen::convert(const Type* type, bool templated) { } else if (auto tuple = type->isa()) { name = tuple_name(tuple); s.fmt("typedef struct {{\t\n"); - s.rangei(tuple->ops(), "\n", [&](size_t i) { s.fmt("{} e{};", convert(tuple->op(i)), i); }); + s.rangei(tuple->ops(), "\n", [&](size_t i) { s.fmt("{} e{};", convert(tuple->types()[i]), i); }); s.fmt("\b\n}} {};\n", name); } else if (auto variant = type->isa()) { types_[variant] = name = variant->name().str(); auto tag_type = - variant->num_ops() < (UINT64_C(1) << 8u) ? world_.type_qu8() : - variant->num_ops() < (UINT64_C(1) << 16u) ? world_.type_qu16() : - variant->num_ops() < (UINT64_C(1) << 32u) ? world_.type_qu32() : - world_.type_qu64(); + variant->num_ops() < (UINT64_C(1) << 8u) ? world().type_qu8() : + variant->num_ops() < (UINT64_C(1) << 16u) ? world().type_qu16() : + variant->num_ops() < (UINT64_C(1) << 32u) ? world().type_qu32() : + world().type_qu64(); s.fmt("typedef struct {{\t\n"); // This is required because we have zero-sized types but C/C++ do not if (variant->has_payload()) { s.fmt("union {{\t\n"); s.rangei(variant->ops(), "\n", [&] (size_t i) { - if (is_type_unit(variant->op(i))) + if (is_type_unit(variant->types()[i])) s << "// "; - s.fmt("{} {};", convert(variant->op(i)), variant->op_name(i)); + s.fmt("{} {};", convert(variant->types()[i]), variant->op_name(i)); }); s.fmt("\b\n}} data;\n"); } @@ -401,14 +490,14 @@ std::string CCodeGen::convert(const Type* type, bool templated) { if ((lang_ == Lang::OpenCL || lang_ == Lang::HLS) && is_channel_type(struct_type)) use_channels_ = true; if (lang_ == Lang::OpenCL && use_channels_) { - s.fmt("typedef {} {}_{};\n", convert(struct_type->op(0)), name, struct_type->gid()); + s.fmt("typedef {} {}_{};\n", convert(struct_type->types()[0]), name, struct_type->gid()); name = (struct_type->name().str() + "_" + std::to_string(type->gid())); } else if (is_channel_type(struct_type) && lang_ == Lang::HLS) { - s.fmt("typedef {} {}_{};\n", convert(struct_type->op(0)), name, struct_type->gid()); + s.fmt("typedef {} {}_{};\n", convert(struct_type->types()[0]), name, struct_type->gid()); name = ("hls::stream<" + name + "_" + std::to_string(type->gid()) + ">"); } else if (is_channel_type(struct_type) && lang_ == Lang::CGRA) { // The following condition makes it impossible to use vectorized channels, ie. struct or array type channels, to be accessed via standard array iterations. They can only with cgra intrinsics or APIs be accessed. - std::string type_str = convert(struct_type->op(0)); + std::string type_str = convert(struct_type->op(0)->as()); if (vector_size_ > 1) if (auto array_type = struct_type->op(0)->isa()) @@ -423,7 +512,7 @@ std::string CCodeGen::convert(const Type* type, bool templated) { s.fmt("//AIE mmul {} obj\n", convert(struct_type) ); } else { s.fmt("typedef struct {{\t\n"); - s.rangei(struct_type->ops(), "\n", [&] (size_t i) { s.fmt("{} {};", convert(struct_type->op(i)), struct_type->op_name(i)); }); + s.rangei(struct_type->ops(), "\n", [&] (size_t i) { s.fmt("{} {};", convert(struct_type->types()[i]), struct_type->op_name(i)); }); s.fmt("\b\n}} {};\n", name); } } else { @@ -753,7 +842,7 @@ void CCodeGen::graph_ctor_gen (const Continuations& graph_conts) { auto bit_width = [&] (const Type* type) { StringStream s; - assert ((type != world().unit() || !(type->isa()) || !(type->isa())) && "Only primary types allowed."); + assert ((type != world().unit_type() || !(type->isa()) || !(type->isa())) && "Only primary types allowed."); size_t actual_num_bits = 0; if (auto primtype = type->isa()) { @@ -821,7 +910,7 @@ void CCodeGen::graph_ctor_gen (const Continuations& graph_conts) { auto op_type = param->type();// TODO: Dummy value for GMem direct access. if (auto ptr_type = param->type()->isa()) { // if not then it is a runtime parameter if(auto struct_type = ptr_type->pointee()->isa()) - op_type = struct_type->op(0); + op_type = struct_type->op(0)->as(); } auto io_index = get_io_mode_index(io_counters, mode); @@ -963,7 +1052,7 @@ void CCodeGen::graph_ctor_gen (const Continuations& graph_conts) { cur_arg_index++; } - auto source_ext = thorin::c::CodeGen(world(), kernel_config_, lang_, debug_, flags_).file_ext(); + auto source_ext = lang_to_ext(lang_); if (cont->has_body() && cont->body()->callee()->isa_nom()) { // TODO: return is_a in if and use it in the body // TODO: soure(kernels) = addr @@ -1016,7 +1105,7 @@ void CCodeGen::emit_module() { Continuation* top_module = nullptr; interface_status = get_interface(interface, gmem_config); - Scope::for_each(world(), [&] (const Scope& scope) { + forest_.for_each([&] (const Scope& scope) { auto entry = scope.entry(); if (entry->is_hls_top() || entry->is_cgra_graph()) { @@ -1064,8 +1153,8 @@ void CCodeGen::emit_module() { } } } - } else - emit_scope(scope); + } else if (scope.entry()->cc() != CC::Thorin && scope.entry()->is_returning()) + emit_scope(scope, forest_); }); if (top_module) { @@ -1075,7 +1164,7 @@ void CCodeGen::emit_module() { } else if (top_module->is_cgra_graph()) top_scope.cgra_graph = true; //hls_top_scope = false; - emit_scope(Scope(top_module)); + emit_scope(Scope(top_module), forest_); } @@ -1113,21 +1202,6 @@ void CCodeGen::emit_module() { stream_ << "#pragma OPENCL EXTENSION cl_khr_fp16 : enable\n"; if (use_fp_64_) stream_ << "#pragma OPENCL EXTENSION cl_khr_fp64 : enable\n"; - - stream_.fmt( "\n" - "typedef char i8;\n" - "typedef uchar u8;\n" - "typedef short i16;\n" - "typedef ushort u16;\n" - "typedef int i32;\n" - "typedef uint u32;\n" - "typedef long i64;\n" - "typedef ulong u64;\n"); - if (use_fp_16_) - stream_.fmt("typedef half f16;\n"); - stream_.fmt( "typedef float f32;\n"); - if (use_fp_64_) - stream_.fmt("typedef double f64;\n"); } stream_.endl(); @@ -1159,49 +1233,23 @@ void CCodeGen::emit_module() { "#include \n" "#include \n" "#include \n" - "using namespace aie::operators;\n"); - } - - if (lang_ == Lang::C99 || lang_ == Lang::HLS || lang_ == Lang::CGRA) { - stream_.fmt( "\n" - "typedef int8_t i8;\n" - "typedef uint8_t u8;\n" - "typedef int16_t i16;\n" - "typedef uint16_t u16;\n" - "typedef int32_t i32;\n" - "typedef uint32_t u32;\n" - "typedef {} i64;\n" - "typedef {} u64;\n" - "typedef float f32;\n" - "typedef double f64;\n" - "\n", (is_cgra_vector_kernel()) ? "acc64" : "int64_t", - (is_cgra_vector_kernel()) ? "acc80" : "uint64_t"); - - if (use_fp_16_ && lang_ == Lang::HLS) - stream_.fmt("typedef half f16;\n"); + "using namespace aie::operators;\n" + "\n" + "typedef {} i64;\n" + "typedef {} u64;\n" + "\n", (is_cgra_vector_kernel()) ? "acc64" : "int64_t", + (is_cgra_vector_kernel()) ? "acc80" : "uint64_t"); } if (lang_ == Lang::CUDA) { - if (use_fp_16_) + if (use_fp_16_) { stream_.fmt("#include \n\n"); - stream_.fmt( "typedef char i8;\n" - "typedef unsigned char u8;\n" - "typedef short i16;\n" - "typedef unsigned short u16;\n" - "typedef int i32;\n" - "typedef unsigned int u32;\n" - "typedef long long i64;\n" - "typedef unsigned long long u64;\n" - "\n"); - if (use_fp_16_) stream_.fmt("#if __CUDACC_VER_MAJOR__ <= 8\n" "typedef half f16;\n" "#else\n" "typedef __half_raw f16;\n" "#endif\n"); - stream_.fmt( "typedef float f32;\n" - "typedef double f64;\n" - "\n"); + } } if (lang_ == Lang::CUDA || lang_ == Lang::HLS) { @@ -1246,15 +1294,15 @@ void CCodeGen::emit_module() { static inline const Type* ret_type(const FnType* fn_type) { auto ret_fn_type = (*std::find_if( - fn_type->ops().begin(), fn_type->ops().end(), [] (const Type* op) { + fn_type->types().begin(), fn_type->types().end(), [] (const Type* op) { return op->order() % 2 == 1; }))->as(); std::vector types; - for (auto op : ret_fn_type->ops()) { + for (auto op : ret_fn_type->types()) { if (op->isa() || is_type_unit(op) || op->order() > 0) continue; types.push_back(op); } - return fn_type->table().tuple_type(types); + return fn_type->world().tuple_type(types); } static inline const Type* pointee_or_elem_type(const PtrType* ptr_type) { @@ -1763,7 +1811,9 @@ void CCodeGen::emit_epilogue(Continuation* cont) { auto&& bb = cont2bb_[cont]; assert(cont->has_body()); auto body = cont->body(); - emit_debug_info(bb.tail, body->arg(0)); + if (body->num_args() > 0) { + emit_debug_info(bb.tail, body->arg(0)); + } if ((lang_ == Lang::OpenCL || (lang_ == Lang::HLS && top_scope.hls)) && (cont->is_exported())) @@ -1794,19 +1844,22 @@ void CCodeGen::emit_epilogue(Continuation* cont) { break; } } else if (body->callee() == world().branch()) { - auto c = emit(body->arg(0)); - auto t = label_name(body->arg(1)); - auto f = label_name(body->arg(2)); + emit_unsafe(body->arg(0)); + auto c = emit(body->arg(1)); + auto t = label_name(body->arg(2)); + auto f = label_name(body->arg(3)); bb.tail.fmt("if ({}) goto {}; else goto {};", c, t, f); } else if (auto callee = body->callee()->as_nom(); callee && callee->intrinsic() == Intrinsic::Match) { - bb.tail.fmt("switch ({}) {{\t\n", emit(body->arg(0))); + emit_unsafe(body->arg(0)); + + bb.tail.fmt("switch ({}) {{\t\n", emit(body->arg(1))); - for (size_t i = 2; i < body->num_args(); i++) { + for (size_t i = 3; i < body->num_args(); i++) { auto arg = body->arg(i)->as(); bb.tail.fmt("case {}: goto {};\n", emit_constant(arg->op(0)), label_name(arg->op(1))); } - bb.tail.fmt("default: goto {};", label_name(body->arg(1))); + bb.tail.fmt("default: goto {};", label_name(body->arg(2))); bb.tail.fmt("\b\n}}"); } else if (body->callee()->isa()) { bb.tail.fmt("return; // bottom: unreachable"); @@ -2195,12 +2248,12 @@ std::string CCodeGen::emit_bottom(const Type* type) { StringStream s; s.fmt("{} ", constructor_prefix(type)); s << "{ "; - s.range(type->ops(), ", ", [&] (const Type* op) { s << emit_bottom(op); }); + s.range(type->ops(), ", ", [&] (const Def* op) { s << emit_bottom(op->as()); }); s << " }"; return s.str(); } else if (auto variant_type = type->isa()) { if (variant_type->has_payload()) { - auto non_unit = *std::find_if(variant_type->ops().begin(), variant_type->ops().end(), + auto non_unit = *std::find_if(variant_type->types().begin(), variant_type->types().end(), [] (const Type* op) { return !is_type_unit(op); }); return constructor_prefix(type) + " { { " + emit_bottom(non_unit) + " }, 0 }"; } @@ -2218,7 +2271,7 @@ void CCodeGen::emit_access(Stream& s, const Type* agg_type, const Def* index, co } else if (agg_type->isa()) { s.fmt("[{}]", emit(index)); } else if (agg_type->isa()) { - s.fmt("{}e{}", prefix, emit_constant(index)); + s.fmt("{}e{}", prefix, primlit_value(index)); } else if (agg_type->isa()) { s.fmt("{}{}", prefix, agg_type->as()->op_name(primlit_value(index))); } else if (agg_type->isa()) { @@ -2255,7 +2308,7 @@ std::string CCodeGen::emit_def(BB* bb, const Def* def) { if (is_unit(def)) return ""; else if (auto bin = def->isa()) { - const char* op; + const char* op = ""; if (auto cmp = bin->isa()) { switch (cmp->cmp_tag()) { case Cmp_eq: op = "=="; break; @@ -2332,6 +2385,8 @@ std::string CCodeGen::emit_def(BB* bb, const Def* def) { s.fmt("(({})->e)", src); } else if (s_ptr && d_ptr && s_ptr->addr_space() == d_ptr->addr_space()) { s.fmt("(({}) {})", d_t, src); + } else if (s_ptr && d_ptr && s_ptr->addr_space() != d_ptr->addr_space() && lang_ == Lang::OpenCL) { + s.fmt("(({}) ((size_t) {}))", d_t, src); } else if (conv->isa()) { auto s_prim = s_type->isa(); auto d_prim = d_type->isa(); @@ -2617,7 +2672,7 @@ std::string CCodeGen::emit_def(BB* bb, const Def* def) { if (auto tup = ass->type()->isa()) { for (size_t i = 1, e = tup->num_ops(); i != e; ++i) { auto name = ass->out(i)->unique_name(); - func_impls_.fmt("{} {};\n", convert(tup->op(i)), name); + func_impls_.fmt("{} {};\n", convert(tup->types()[i]), name); func_defs_.insert(ass->out(i)); outputs.emplace_back(name); defs_[ass->out(i)] = name; @@ -2978,7 +3033,7 @@ Stream& CCodeGen::emit_debug_info(Stream& s, const Def* def) { void CCodeGen::emit_c_int() { // Do not emit C interfaces for definitions that are not used - world().cleanup(); + thorin_.cleanup(); for (auto def : world().defs()) { auto cont = def->isa_nom(); @@ -2990,10 +3045,10 @@ void CCodeGen::emit_c_int() { continue; // Generate C types for structs used by imported or exported functions - for (auto op : cont->type()->ops()) { + for (auto op : cont->type()->types()) { if (auto fn_type = op->isa()) { // Convert the return types as well (those are embedded in return continuations) - for (auto other_op : fn_type->ops()) { + for (auto other_op : fn_type->types()) { if (!other_op->isa()) convert(other_op); } @@ -3028,6 +3083,9 @@ void CCodeGen::emit_c_int() { stream_.fmt("extern \"C\" {{\n"); stream_.fmt("#endif\n\n"); + stream_.fmt("#include \n" // for the 'bool' type + "#include \n\n"); // for the fixed-width integer types + stream_.fmt("typedef int8_t i8;\n" "typedef uint8_t u8;\n" "typedef int16_t i16;\n" @@ -3111,7 +3169,7 @@ std::string CCodeGen::tuple_name(const TupleType* tuple_type) { void CodeGen::emit_stream(std::ostream& stream) { Stream s0(stream); Stream s1 = {}; - CCodeGen(world(), kernel_config_, s0, s1, lang_, debug_, flags_).emit_module(); + CCodeGen(thorin(), kernel_config_, s0, s1, lang_, debug_, flags_).emit_module(); } void CodeGen::emit_stream(std::ostream& stream0, std::ostream& stream1) { @@ -3119,14 +3177,14 @@ void CodeGen::emit_stream(std::ostream& stream0, std::ostream& stream1) { world().WLOG("This backend does not support multiple streams"); Stream s0(stream0); Stream s1(stream1); - CCodeGen CCodeGen_obj(world(), kernel_config_, s0, s1, lang_, debug_, flags_); + CCodeGen CCodeGen_obj(thorin(), kernel_config_, s0, s1, lang_, debug_, flags_); CCodeGen_obj.emit_module(); } -void emit_c_int(World& world, Stream& stream) { +void emit_c_int(Thorin& thorin, Stream& stream) { std::string flags; Stream s {}; - CCodeGen(world, {}, stream, s, Lang::C99, false, flags).emit_c_int(); + CCodeGen(thorin, {}, stream, s, Lang::C99, false, flags).emit_c_int(); } //------------------------------------------------------------------------------ diff --git a/src/thorin/be/c/c.h b/src/thorin/be/c/c.h index 560e7b1f9..72bccc45b 100644 --- a/src/thorin/be/c/c.h +++ b/src/thorin/be/c/c.h @@ -20,11 +20,12 @@ class World; namespace c { enum class Lang : uint8_t { C99, HLS, CGRA, CUDA, OpenCL }; +inline const char* lang_to_ext (Lang lang); class CodeGen : public thorin::CodeGen { public: - CodeGen(World& world, const Cont2Config& kernel_config, Lang lang, bool debug, std::string& flags) - : thorin::CodeGen(world, debug) + CodeGen(Thorin& thorin, const Cont2Config& kernel_config, Lang lang, bool debug, std::string& flags) + : thorin::CodeGen(thorin, debug) , kernel_config_(kernel_config) , lang_(lang) , debug_(debug) @@ -37,14 +38,7 @@ class CodeGen : public thorin::CodeGen { Lang get_lang () const { return lang_; }; const char* file_ext() const override { - switch (lang_) { - case Lang::C99: return ".c"; - case Lang::HLS: return ".hls"; - case Lang::CGRA: return ".cxx"; - case Lang::CUDA: return ".cu"; - case Lang::OpenCL: return ".cl"; - default: THORIN_UNREACHABLE; - } + return lang_to_ext(lang_); } private: @@ -54,7 +48,7 @@ class CodeGen : public thorin::CodeGen { std::string flags_; }; -void emit_c_int(World&, Stream& stream); +void emit_c_int(Thorin&, Stream& stream); } diff --git a/src/thorin/be/codegen.cpp b/src/thorin/be/codegen.cpp index cbcb8cb02..8cf14adf1 100644 --- a/src/thorin/be/codegen.cpp +++ b/src/thorin/be/codegen.cpp @@ -1,91 +1,65 @@ #include "thorin/world.h" #include "thorin/be/codegen.h" -#include "thorin/analyses/scope.h" -#include "thorin/transform/hls_dataflow.h" -#include "thorin/transform/hls_kernel_launch.h" -#include "thorin/transform/cgra_dataflow.h" -#if THORIN_ENABLE_LLVM -#include "thorin/be/llvm/cpu.h" -#include "thorin/be/llvm/nvvm.h" -#include "thorin/be/llvm/amdgpu.h" -#endif #include "thorin/be/c/c.h" #include "thorin/be/config_script/config_script.h" +#include "thorin/be/runtime.h" -namespace thorin { +#if THORIN_ENABLE_LLVM +#include "thorin/be/llvm/nvvm.h" +#include "thorin/be/llvm/amdgpu_hsa.h" +#include "thorin/be/llvm/amdgpu_pal.h" +#endif -static void get_kernel_configs( - Importer& importer, - const std::vector& kernels, - Cont2Config& kernel_configs, - std::function (Continuation*, Continuation*)> use_callback, - const std::function& cgra_callback = {}) -{ - importer.world().opt(); - - auto externals = importer.world().externals(); - if (cgra_callback) - cgra_callback(externals); - - // accessd one time - // add index to extract the port - // make it a function like "kernel find by name" - // for (auto [_, exported] : externals) { - // if (exported->name() == "hls_top") { - // std::cout << "I AM HLS_TOP" <params()) { - // std::cout << "PARAM" <dump(); - // } - // } else if (exported->name() == "cgra_graph"){ - - // std::cout << "I AM CGRA_GRAPH" <params()) { - // std::cout << "PARAM" <dump(); - // } - - // } - // } - for (auto continuation : kernels) { - // recover the imported continuation (lost after the call to opt) - Continuation* imported = nullptr; - for (auto [_, exported] : externals) { - if (!exported->has_body()) continue; - if (exported->name() == continuation->unique_name()) - imported = exported; +#if THORIN_ENABLE_SHADY +#include "thorin/be/shady/shady.h" +#undef empty +#undef nodes +#endif - // if (exported->name() == "hls_top") { - // std::cout << "I AM HLS_TOP" <params()) { - // std::cout << "PARAM" <dump(); - // } - // } else if (exported->name() == "cgra_graph"){ +#if THORIN_ENABLE_SPIRV +#include "thorin/be/spirv/spirv.h" +#endif - // std::cout << "I AM CGRA_GRAPH" <params()) { - // std::cout << "PARAM" <dump(); - // } +#include "thorin/transform/hls_dataflow.h" +#include "thorin/transform/hls_kernel_launch.h" +#include "thorin/transform/cgra_dataflow.h" - // } +namespace thorin { +void Backend::prepare_kernel_configs() { + device_code_.opt(); + auto conts = device_code_.world().copy_continuations(); + for (auto continuation : kernels_) { + // recover the imported continuation (lost after the call to opt) + Continuation* imported = nullptr; + for (auto original_cont : conts) { + if (!original_cont) continue; + if (!original_cont->has_body()) continue; + if (!original_cont->is_exported()) continue; + if (original_cont->name() == continuation->name()) + imported = original_cont; } if (!imported) continue; visit_uses(continuation, [&] (Continuation* use) { assert(use->has_body()); - auto config = use_callback(use, imported); + + auto handler = backends_.intrinsics_.find(use->body()->callee()->as()->intrinsic()); + assert(handler != backends_.intrinsics_.end()); + auto [backend2, get_config] = handler->second; + assert(backend2 == this); + + auto config = get_config(use->body(), imported); if (config) { - auto p = kernel_configs.emplace(imported, std::move(config)); + auto p = kernel_configs_.emplace(imported, std::move(config)); assert_unused(p.second && "single kernel config entry expected"); } return false; }, true); + continuation->world().make_external(continuation); continuation->destroy("codegen"); } } @@ -120,33 +94,9 @@ static uint64_t get_alloc_size(const Def* def) { return size ? static_cast(size->value().get_qu64()) : 0_u64; } -//template -//static bool has_restrict_pointer(const T device, Continuation* use) { -// bool has_restrict = true; -// auto app = use->body(); -// // determine whether or not this kernel uses restrict pointers -// DefSet allocs; -// for (size_t i = LaunchArgs::Num, e = app->num_args(); has_restrict && i != e; ++i) { -// auto arg = app->arg(i); -// if (!arg->type()->isa()) continue; -// auto alloc = get_alloc_call(arg); -// if (!alloc) has_restrict = false; -// auto p = allocs.insert(alloc); -// has_restrict &= p.second; -// } -// return has_restrict; -//} -// -// -// -// -// - -//static bool has_restrict_pointer(int launch_args_num, Continuation* use) { -static bool has_restrict_pointer(int launch_args_num, Continuation* use) { -// determines whether or not a kernel uses restrict pointers +static bool has_restrict_pointer(int launch_args_num, const App* app) { + // determines whether or not a kernel uses restrict pointers auto has_restrict = true; - auto app = use->body(); DefSet allocs; for (size_t i = launch_args_num, e = app->num_args(); has_restrict && i != e; ++i) { auto arg = app->arg(i); @@ -164,9 +114,10 @@ static bool has_restrict_pointer(int launch_args_num, Continuation* use) { // It is true beacause of the design of the data structure // for example the hls_top param with index 2 at position 1 and cgra_graph param with index 3 at position 1 of the array are semantically related. template -//static const auto get_ports(const T param_status, const World::Externals& externals, HlsCgraPorts hls_cgra_ports = HlsCgraPorts()) { static const auto get_ports(const T param_status, const World::Externals& externals, Ports& hls_cgra_ports) { - for (auto [_, exported] : externals) { + for (auto [_, exported_def] : externals) { + auto exported = exported_def->isa(); + if (!exported) continue; //if (exported->name() == "hls_top" || exported->name() == "cgra_graph" ) { if (exported->is_hls_top() || exported->is_cgra_graph() ) { if constexpr (std::is_same_v>) { @@ -201,170 +152,129 @@ static const auto get_ports(const T param_status, const World::Externals& extern } } } - return; + return; } } assert(false && "No top module found!"); } -//static const void get_ports_for(const std::string device_top, Array param_indices, const World::Externals& externals) { -////static const void get_ports_for(const std::string device_top, PortStatus param_indices, const World::Externals& externals) { -// assert((device_top == "hls_top" || device_top == "cgra_graph") && "device top name is not valid!"); -// for (auto [_, exported] : externals) { -// if (exported->name() == device_top) { -// std::cout << "I am " << device_top <param(param_index)->dump(); -// std::cout << exported->param(param_index)->unique_name() << std::endl; -// //TODO: use index to check if a port is W or R. using global2mode or def2mde inside dataflow_HLS -// } -// } -// } -//} -// -// -// -// -//static const void get_ports_for(const std::string device_top, PortStatus port_status, const World::Externals& externals) { -////static const void get_ports_for(const std::string device_top, PortStatus param_indices, const World::Externals& externals) { -// assert((device_top == "hls_top" || device_top == "cgra_graph") && "device top name is not valid!"); -// for (auto [_, exported] : externals) { -// if (exported->name() == device_top) { -// std::cout << "I am " << device_top <param(param_index)->dump(); -// std::cout << exported->param(param_index)->unique_name() << std::endl; -// //TODO: use index to check if a port is W or R. using global2mode or def2mde inside dataflow_HLS -// } -// } -// } -//} - - -////template -//static bool has_restrict_pointer(Device_code device, Continuation* use) { -// bool has_restrict = true; -// auto app = use->body(); -// // determine whether or not this kernel uses restrict pointers -// DefSet allocs; -// //for (size_t i = LaunchArgs::Num, e = app->num_args(); has_restrict && i != e; ++i) { -// for (size_t i = launch_args(device)::Num, e = app->num_args(); has_restrict && i != e; ++i) { -// auto arg = app->arg(i); -// if (!arg->type()->isa()) continue; -// auto alloc = get_alloc_call(arg); -// if (!alloc) has_restrict = false; -// auto p = allocs.insert(alloc); -// has_restrict &= p.second; -// } -// return has_restrict; -//} - -DeviceBackends::DeviceBackends(World& world, int opt, bool debug, std::string& flags) - : cgs {} -{ - for (size_t i = 0; i < cgs.size(); ++i) - importers_.emplace_back(world); +static std::unique_ptr get_gpu_kernel_config(const App* app, Continuation* imported) { + bool has_restrict = has_restrict_pointer(KernelLaunchArgs::Num, app); + + auto it_config = app->arg(KernelLaunchArgs::Config)->isa(); + if (it_config && + it_config->op(0)->isa() && + it_config->op(1)->isa() && + it_config->op(2)->isa()) { + return std::make_unique(std::tuple{ + it_config->op(0)->as()->qu32_value().data(), + it_config->op(1)->as()->qu32_value().data(), + it_config->op(2)->as()->qu32_value().data() + }, has_restrict); + } + return std::make_unique(std::tuple{-1, -1, -1}, has_restrict); +} - // determine different parts of the world which need to be compiled differently - Scope::for_each(world, [&] (const Scope& scope) { - auto continuation = scope.entry(); - Continuation* imported = nullptr; +Backend::Backend(thorin::DeviceBackends& backends, World& src) : backends_(backends), device_code_(src), importer_(std::make_unique(src, device_code_.world())) {} - static const auto backend_intrinsics = std::array { - std::pair { CUDA, Intrinsic::CUDA }, - std::pair { NVVM, Intrinsic::NVVM }, - std::pair { OpenCL, Intrinsic::OpenCL }, - std::pair { AMDGPU, Intrinsic::AMDGPU }, - std::pair { HLS, Intrinsic::HLS }, - std::pair { CGRA, Intrinsic::CGRA } - }; - for (auto [backend, intrinsic] : backend_intrinsics) { - if (is_passed_to_intrinsic(continuation, intrinsic)) { - imported = importers_[backend].import(continuation)->as_nom(); - break; - } - } +struct CudaBackend : public Backend { + explicit CudaBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::CUDA, *this, get_gpu_kernel_config); + } - if (imported == nullptr) - return; + std::unique_ptr create_cg() override { + std::string empty; + return std::make_unique(device_code_, kernel_configs_, c::Lang::CUDA, backends_.debug(), empty); + } +}; - // Necessary so that the names match in the original and imported worlds - imported->set_name(continuation->unique_name()); - for (size_t i = 0, e = continuation->num_params(); i != e; ++i) - imported->param(i)->set_name(continuation->param(i)->name()); - imported->world().make_external(imported); +struct OpenCLBackend : public Backend { + explicit OpenCLBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::OpenCL, *this, get_gpu_kernel_config); + } - kernels.emplace_back(continuation); - }); + std::unique_ptr create_cg() override { + std::string empty; + return std::make_unique(device_code_, kernel_configs_, c::Lang::OpenCL, backends_.debug(), empty); + } +}; - //for (auto backend : std::array { CUDA, NVVM, OpenCL, AMDGPU, CGRA }) { - for (auto backend : std::array { CUDA, NVVM, OpenCL, AMDGPU}) { - if (!importers_[backend].world().empty()) { - //size_t launch_args_num; - // switch (backend) { - // case CUDA: case NVVM: case OpenCL: case AMDGPU: - //launch_args_num = LaunchArgs::Num; break; - // case CGRA: { - //cgra_dataflow(importers_[CGRA]); - // launch_args_num = LaunchArgs::Num; break; - // } - // default: - // THORIN_UNREACHABLE; - // } - - get_kernel_configs(importers_[backend], kernels, kernel_config, [&](Continuation *use, Continuation * /* imported */) { - // bool has_restrict = true; - auto has_restrict = has_restrict_pointer(LaunchArgs::Num, use); - //auto app = use->body(); - // determine whether or not this kernel uses restrict pointers - // DefSet allocs; - // for (size_t i = launch_args_num, e = app->num_args(); has_restrict && i != e; ++i) { - // auto arg = app->arg(i); - // if (!arg->type()->isa()) continue; - // auto alloc = get_alloc_call(arg); - // if (!alloc) has_restrict = false; - // auto p = allocs.insert(alloc); - // has_restrict &= p.second; - // } - // if (backend != CGRA) { - auto it_config = use->body()->arg(LaunchArgs::Config)->as(); - if (it_config->op(0)->isa() && - it_config->op(1)->isa() && - it_config->op(2)->isa()) { - return std::make_unique(std::tuple{ - it_config->op(0)->as()->qu32_value().data(), - it_config->op(1)->as()->qu32_value().data(), - it_config->op(2)->as()->qu32_value().data() - }, has_restrict); - } - // } - return std::make_unique(std::tuple{-1, -1, -1}, has_restrict); - }); - } +#if THORIN_ENABLE_SPIRV +struct OpenCLSPIRVBackend : public Backend { + explicit OpenCLSPIRVBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::OpenCL_SPIRV, *this, get_gpu_kernel_config); } - // if (!importers_[CGRA].world().empty()) { - // cgra_dataflow(importers_[CGRA]); - // } - // get the HLS kernel configurations - Top2Kernel top2kernel; - DeviceDefs device_defs; - Ports hls_cgra_ports; // channel-params between HLS and CGRA - if (!importers_[HLS].world().empty()) { - device_defs = hls_dataflow(importers_[HLS], top2kernel, world, importers_[CGRA]); + std::unique_ptr create_cg() override { + spirv::Target target; + return std::make_unique(device_code_, target, backends_.debug(), &kernel_configs_); + } +}; - get_kernel_configs(importers_[HLS], kernels, kernel_config, [&] (Continuation* use, Continuation* imported) { - auto app = use->body(); +struct LevelZeroSPIRVBackend : public Backend { + explicit LevelZeroSPIRVBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::LevelZero_SPIRV, *this, get_gpu_kernel_config); + } - // auto externals = importers_[HLS].world().externals(); - // std::cout << "LOCAL CODE EXTERNALS" << std::endl; - // for (auto [_, exported] : externals) { - // exported->dump(); - // } + std::unique_ptr create_cg() override { + spirv::Target target; + return std::make_unique(device_code_, target, backends_.debug(), &kernel_configs_); + } +}; +#endif + +#if THORIN_ENABLE_LLVM +struct AMDHSABackend : public Backend { + explicit AMDHSABackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::AMDGPUHSA, *this, get_gpu_kernel_config); + } + + std::unique_ptr create_cg() override { + return std::make_unique(device_code_, kernel_configs_, backends_.opt(), backends_.debug()); + } +}; +struct AMDPALBackend : public Backend { + explicit AMDPALBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::AMDGPUPAL, *this, get_gpu_kernel_config); + } + + std::unique_ptr create_cg() override { + return std::make_unique(device_code_, kernel_configs_, backends_.opt(), backends_.debug()); + } +}; + +struct NVVMBackend : public Backend { + explicit NVVMBackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::NVVM, *this, get_gpu_kernel_config); + } + + std::unique_ptr create_cg() override { + return std::make_unique(device_code_, kernel_configs_, backends_.opt(), backends_.debug()); + } +}; +#endif +#if THORIN_ENABLE_SHADY +struct ShadyBackend : public Backend { + explicit ShadyBackend(DeviceBackends2& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::ShadyCompute, get_gpu_kernel_config); + } + + std::unique_ptr create_cg(const Cont2Config& config) override { + return std::make_unique(device_code_, config, backends_.debug()); + } +}; +#endif + +//TODO: move this somewhere sane. +static Ports hls_cgra_ports; // chanel-params between HLS and CGRA +static DeviceDefs hls_device_defs; +static Top2Kernel hls_top2kernel; + +struct HLSBackend : public Backend { + explicit HLSBackend(DeviceBackends& b, World& src, std::string& hls_flags) : Backend(b, src), hls_flags_(hls_flags) { + b.register_intrinsic(Intrinsic::HLS, *this, [&](const App* app, Continuation* imported) { HLSKernelConfig::Param2Size param_sizes; for (size_t i = hls_free_vars_offset, e = app->num_args(); i != e; ++i) { auto arg = app->arg(i); @@ -372,7 +282,7 @@ DeviceBackends::DeviceBackends(World& world, int opt, bool debug, std::string& f if (!ptr_type) continue; auto size = get_alloc_size(arg); if (size == 0) - world.edef(arg, "array size is not known at compile time"); + b.world().edef(arg, "array size is not known at compile time"); auto elem_type = ptr_type->pointee(); size_t multiplier = 1; if (!elem_type->isa()) { @@ -387,132 +297,88 @@ DeviceBackends::DeviceBackends(World& world, int opt, bool debug, std::string& f } auto prim_type = elem_type->isa(); if (!prim_type) - world.edef(arg, "only pointers to arrays of primitive types are supported"); + b.world().edef(arg, "only pointers to arrays of primitive types are supported"); auto num_elems = size / (multiplier * num_bits(prim_type->primtype_tag()) / 8); // imported has type: fn (mem, fn (mem), ...) param_sizes.emplace(imported->param(i - hls_free_vars_offset + 2), num_elems); } - return std::make_unique(param_sizes); // this config is added into cont2config (kernel_config) map with its continuation - }, [&] (const World::Externals& externals) { - - //auto externals = importers_[HLS].world().externals(); - // for (auto [_, exported] : externals) { - // if (exported->name() == "hls_top") { - // std::cout << "I AM HLS_TOP" <params()) { - // // std::cout << "PARAM" <dump(); - // // } - - // for (auto param_index : std::get<2>(device_defs)) { - // std::cout << "CGRA port param: " << std::endl; - // exported->param(param_index)->dump(); - // } - - // } else if (exported->name() == "cgra_graph"){ - - // std::cout << "I AM CGRA_GRAPH" <params()) { - // std::cout << "PARAM" <dump(); - // } - - // } - // } - - //get_ports_for("hls_top", std::get<2>(device_defs), externals); - get_ports(std::get<2>(device_defs), externals, hls_cgra_ports); + return std::make_unique(param_sizes); + }); + } + + std::unique_ptr create_cg() override { + if (!device_code_.world().empty()) { + get_ports(std::get<2>(hls_device_defs), device_code_.world().externals(), hls_cgra_ports); + + hls_annotate_top(device_code_.world(), hls_top2kernel, kernel_configs_); + hls_kernel_launch(device_code_.world(), std::get<0>(hls_device_defs), kernel_configs_); } - ); - hls_annotate_top(importers_[HLS].world(), top2kernel, kernel_config); // adding hls_top config to cont2config map + return std::make_unique(device_code_, kernel_configs_, c::Lang::HLS, backends_.debug(), hls_flags_); } - hls_kernel_launch(world, std::get<0>(device_defs), kernel_config); - - //TODO: need to write an analysis to check R/W mode on global memory allocaions - if (!importers_[CGRA].world().empty()) { - // at the moment only kernel channel modes are returned - // ports are cgra_graph params that are connected to hls_top - auto [port_indices, cont2param_modes] = cgra_dataflow(importers_[CGRA], world, std::get<1>(device_defs)); - - get_kernel_configs(importers_[CGRA], kernels, kernel_config, [&] (Continuation* use, Continuation* imported) { - CGRAKernelConfig::Param2Mode param2mode; - // The order that channel modes are inserted in param_modes cosecuteviley is aligned with the order that channels appear in imported continuations - // for example, the first mode in param_modes (index = 0) is equal to the first channel in the imported continuation (kernel) - annotate_channel_modes(imported, cont2param_modes, param2mode); - - annotate_interface(imported, use); - - auto app = use->body(); - // for(const auto& [cont, param_modes] : cont2param_modes) { - // //TODO: check for continuation names then insert channel param modes - // //std::cout << "check names" << std::endl; - // //std::cout << cont->name() << "==" << imported->name() << " ?" << std::endl; - // //if (cont->name() == imported->name()) {std::cout << "BINGO" << std::endl;} - // for (auto const& param : imported->params()) { - // if ((param->index() < 2) || is_mem(param) || param->order() != 0 || is_unit(param)) - // continue; - // else if (auto type = param->type(); is_channel_type(type)) {} - - // param->dump(); - // } - // } - - auto has_restrict = has_restrict_pointer(LaunchArgs::Num, use); + std::string& hls_flags_; +}; + +static ContName2ParamModes cgra_cont2param_modes; +static PortIndices cgra_port_indices; + +struct CGRABackend : public Backend { + explicit CGRABackend(DeviceBackends& b, World& src) : Backend(b, src) { + b.register_intrinsic(Intrinsic::CGRA, *this, [&](const App* app, Continuation* imported) { + CGRAKernelConfig::Param2Mode param2mode; + // The order that channel modes are inserted in param_modes cosecuteviley is aligned with the order that channels appear in imported continuations + // for example, the first mode in param_modes (index = 0) is equal to the first channel in the imported continuation (kernel) + + annotate_channel_modes(imported, cgra_cont2param_modes, param2mode); + for (auto use : app->uses()) { + if (use->isa()) + annotate_interface(imported, use->as()); + } + + auto has_restrict = has_restrict_pointer(KernelLaunchArgs::Num, app); // TODO: (-10,-10) auto location , default rtm_ratio to 1 - auto runtime_ratio = app->arg(LaunchArgs::Runtime_ratio); - auto tile_location = app->arg(LaunchArgs::Location)->as(); - auto vector_size = app->arg(LaunchArgs::Vector_size); - if (runtime_ratio->isa() && + auto runtime_ratio = app->arg(KernelLaunchArgs::Runtime_ratio); + auto tile_location = app->arg(KernelLaunchArgs::Location)->as(); + auto vector_size = app->arg(KernelLaunchArgs::Vector_size); + if (runtime_ratio->isa() && tile_location->op(0)->isa() && tile_location->op(1)->isa() && vector_size->isa()) { - auto runtime_ratio_val = runtime_ratio->as()->qf32_value().data(); - auto tile_location_val = std::make_pair(tile_location->op(0)->as()->qu32_value().data(), - tile_location->op(1)->as()->qu32_value().data()); - auto vector_size_val = vector_size->as()->qu32_value().data(); + auto runtime_ratio_val = runtime_ratio->as()->qf32_value().data(); + auto tile_location_val = std::make_pair(tile_location->op(0)->as()->qu32_value().data(), + tile_location->op(1)->as()->qu32_value().data()); + auto vector_size_val = vector_size->as()->qu32_value().data(); - return std::make_unique(runtime_ratio_val, tile_location_val, vector_size_val, param2mode, has_restrict); - } - return std::make_unique(-1, std::make_pair(-1, -1), -1, param2mode, has_restrict); - - // TODO: insert corresponding params from imported using index and add mode - // for (size_t i = cgra_free_vars_offset, e = app->num_args(); i != e; ++i) { - // auto arg = app->arg(i); - // auto ptr_type = arg->type()->isa(); - // //TODO : check types and assign to param2mode - // } - }, [&] (const World::Externals& externals) { + return std::make_unique(runtime_ratio_val, tile_location_val, vector_size_val, param2mode, has_restrict); + } + return std::make_unique(-1, std::make_pair(-1, -1), -1, param2mode, has_restrict); + }); + } + + std::unique_ptr create_cg() override { + std::string empty; + + if (!device_code_.world().empty()) { // TODO: HERE cgra_graph params are correct! Continuation* cgra_graph_cont = nullptr; - for (auto [_, exported] : externals) { - if (auto temp = exported->isa_nom()) { - std::cout << "external codegen" << std::endl; - //temp->dump(); - } - if (exported->isa_nom()->is_cgra_graph()) { - cgra_graph_cont = exported; - for (auto param : exported->params()){ + for (auto [_, exported] : device_code_.world().externals()) { + if (auto cont = exported->isa_nom(); cont && cont->is_cgra_graph()) { + cgra_graph_cont = cont; + for (auto param : cont->params()){ std::cout << "external" << std::endl; //param->dump(); } } } - // } - - //get_ports_for("cgra_graph", port_indices, externals); - //get_ports(std::get<0>(port_indices), externals, hls_cgra_ports); - get_ports(port_indices, externals, hls_cgra_ports); - // just copied from down - // if (!hls_cgra_ports.empty()) { - // the aim is passing cgra_graph cont to annotate_cgra_graph_modes to import the config for non-channel params - //cgra_graph_cont->dump(); - annotate_cgra_graph_modes(cgra_graph_cont, hls_cgra_ports, kernel_config); // adding cgra_graph config to cont2config map - for (const auto& item : kernel_config) { + + get_ports(cgra_port_indices, device_code_.world().externals(), hls_cgra_ports); + annotate_cgra_graph_modes(cgra_graph_cont, hls_cgra_ports, kernel_configs_); // adding cgra_graph config to cont2config map + // + for (const auto& item : kernel_configs_) { auto cont = item.first; if (auto config = item.second->isa(); config) {//std::cout << "FOUND CGRA CONFIG" << std::endl; - //cont->dump(); + //cont->dump(); for (auto param : cont->params()) { if (auto mode = config->param_mode(param); mode != ChannelMode::Undef) { std::cout << "param" << std::endl; @@ -526,78 +392,113 @@ DeviceBackends::DeviceBackends(World& world, int opt, bool debug, std::string& f } } } - // } - //else - // world.WLOG("TODO: CGRA graph is not correclty generated due to direct memory access!"); + //TODO: This should not be emitted here. Eiter find a way to move this code generator outside, or integrate this into the CGRA code generator. + //Maybe a wrapper around these code generators would be in order? We currently expect a single code generator to emit a single file, + //but this is not the case with CGRA. + auto cfg_generator = std::make_unique(device_code_, backends_.debug(), hls_cgra_ports, empty); + + auto name = device_code_.world().name() + cfg_generator->file_ext(); + std::ofstream file(name); + if (!file) + device_code_.world().ELOG("cannot open '{}' for writing", name); + else + cfg_generator->emit_stream(file); } - ); - // just moved up but here is a better place as it is called only once -// if (!hls_cgra_ports.empty()) { -// annotate_cgra_graph_modes(hls_cgra_ports, kernel_config); // adding cgra_graph config to cont2config map -// for (const auto& item : kernel_config) { -// auto cont = item.first; -// if (auto config = item.second->isa(); config) {std::cout << "FOUND CGRA CONFIG" << std::endl; -// cont->dump(); -// for (auto param : cont->params()) { -// if (auto mode = config->param_mode(param); mode != ChannelMode::Undef) { -// std::cout << "param" << std::endl; -// param->dump(); -// std::cout << "mode" << std::endl; -// if (mode == ChannelMode::Read) {std::cout << "Read"<< std::endl; -// } else { -// std::cout << "Write"<< std::endl; -// } -// } -// } -// } -// } -// } -// else -// world.WLOG("TODO: CGRA graph is not correclty generated due to direct memory access!"); - } + return std::make_unique(device_code_, kernel_configs_, c::Lang::CGRA, backends_.debug(), empty); + } +}; +DeviceBackends::DeviceBackends(thorin::World& world, int opt, bool debug, std::string& hls_flags) : world_(world), opt_(opt), debug_(debug) { + register_backend(std::make_unique(*this, world)); + register_backend(std::make_unique(*this, world)); #if THORIN_ENABLE_LLVM - if (!importers_[NVVM ].world().empty()) cgs[NVVM ] = std::make_unique(importers_[NVVM ].world(), kernel_config, debug); - if (!importers_[AMDGPU].world().empty()) cgs[AMDGPU] = std::make_unique(importers_[AMDGPU].world(), kernel_config, opt, debug); -#else - (void)opt; + register_backend(std::make_unique(*this, world)); + register_backend(std::make_unique(*this, world)); + register_backend(std::make_unique(*this, world)); +#endif +#if THORIN_ENABLE_SHADY + register_backend(std::make_unique(*this, world)) #endif +#if THORIN_ENABLE_SPIRV + register_backend(std::make_unique(*this, world)); + register_backend(std::make_unique(*this, world)); +#endif + register_backend(std::make_unique(*this, world, hls_flags)); + register_backend(std::make_unique(*this, world)); - //thorin::config_script::CodeGen cg(world,debug, hls_cgra_ports); - //emit_to_file(cg); - // std::cout << "--------> " < " << elem.first.value().first << "-----" << elem.second.value() << std::endl; - //elem.second.value(); - if (!importers_[CGRA].world().empty()){ - cgs[CGRA] = std::make_unique(importers_[CGRA].world(), debug, hls_cgra_ports, flags); - thorin::config_script::CodeGen cg(world, debug, hls_cgra_ports, flags); - - auto emit_to_file = [&] (thorin::CodeGen& cg) { - auto name = world.name() + cg.file_ext(); - std::ofstream file(name); - if (!file) - world.ELOG("cannot open '{}' for writing", name); - else - cg.emit_stream(file); - }; + search_for_device_code(); +} - emit_to_file(cg); - } +void DeviceBackends::register_backend(std::unique_ptr backend) { + backends_.push_back(std::move(backend)); +} - //if (!importers_[CGRA ].world().empty()) cgs[CGRA ] = std::make_unique(importers_[CGRA ].world(), debug); - for (auto [backend, lang] : std::array { std::pair { CUDA, c::Lang::CUDA }, std::pair { OpenCL, c::Lang::OpenCL }, std::pair { HLS, c::Lang::HLS }, std::pair { CGRA, c::Lang::CGRA } }) - if (!importers_[backend].world().empty()) { cgs[backend] = std::make_unique(importers_[backend].world(), kernel_config, lang, debug, flags); - } +World& DeviceBackends::world() { return world_; } +bool DeviceBackends::debug() { return debug_; } +int DeviceBackends::opt() { return opt_; } +void DeviceBackends::register_intrinsic(thorin::Intrinsic intrinsic, Backend& backend, GetKernelConfigFn f) { + intrinsics_[intrinsic] = std::make_pair(&backend, f); } +void DeviceBackends::search_for_device_code() { + // determine different parts of the world which need to be compiled differently + ScopesForest(world_).for_each([&] (const Scope& scope) { + auto continuation = scope.entry(); + Continuation* imported = nullptr; + + Intrinsic intrinsic = Intrinsic::None; + visit_capturing_intrinsics(continuation, [&] (Continuation* continuation) { + if (continuation->is_offload_intrinsic()) { + intrinsic = continuation->intrinsic(); + return true; + } + return false; + }); + + if (intrinsic == Intrinsic::None) + return; + + auto handler = intrinsics_.find(intrinsic); + assert(handler != intrinsics_.end()); + auto [backend, get_config] = handler->second; + + imported = backend->importer_->import(continuation)->as_nom(); + if (imported == nullptr) + return; + + // Necessary so that the names match in the original and imported worlds + imported->set_name(continuation->unique_name()); + continuation->set_name(continuation->unique_name()); + for (size_t i = 0, e = continuation->num_params(); i != e; ++i) + imported->param(i)->set_name(continuation->param(i)->name()); + imported->world().make_external(imported); + imported->attributes().cc = CC::C; + backend->kernels_.emplace_back(continuation); + }); + + auto [hls_backend, _] = intrinsics_.find(Intrinsic::HLS)->second; + auto [cgra_backend, _] = intrinsics_.find(Intrinsic::CGRA)->second; + + hls_device_defs = hls_dataflow(*hls_backend->importer_, hls_top2kernel, world_, *cgra_backend->importer_); + auto dataflow_result = cgra_dataflow(*cgra_backend->importer_, world_, std::get<1>(hls_device_defs)); + cgra_port_indices = std::get<0>(dataflow_result); + cgra_cont2param_modes = std::get<1>(dataflow_result); + + for (auto& backend : backends_) { + if (backend->thorin().world().empty()) + continue; + + backend->prepare_kernel_configs(); + cgs.emplace_back(backend->create_cg()); + } +} -CodeGen::CodeGen(World& world, bool debug) - : world_(world) +CodeGen::CodeGen(Thorin& thorin, bool debug) + : thorin_(thorin) , debug_(debug) {} diff --git a/src/thorin/be/codegen.h b/src/thorin/be/codegen.h index d143958d1..d4d804e94 100644 --- a/src/thorin/be/codegen.h +++ b/src/thorin/be/codegen.h @@ -8,7 +8,7 @@ namespace thorin { class CodeGen { protected: - CodeGen(World& world, bool debug); + CodeGen(Thorin& thorin, bool debug); public: virtual ~CodeGen() {} @@ -17,67 +17,62 @@ class CodeGen { /// @name getters //@{ - World& world() const { return world_; } + Thorin& thorin() const { return thorin_; } + World& world() const { return thorin().world(); } bool debug() const { return debug_; } //@} private: - World& world_; + Thorin& thorin_; bool debug_; }; -enum Device_code {GPU, FPGA_HLS, FPGA_CL, AIE_CGRA}; -template -struct LaunchArgs {}; -template <> -struct LaunchArgs { - enum { - Mem = 0, - Device, - Space, - Config, - Body, - Return, - Num - }; +struct DeviceBackends; -}; +struct Backend { + Backend(DeviceBackends& backends, World& src); + virtual ~Backend() = default; -template<> -struct LaunchArgs : LaunchArgs {}; - -template<> -struct LaunchArgs { - enum { - Mem = 0, - Device, - Runtime_ratio, - Location, - Vector_size, - Body, - Return, - Num - }; -}; + virtual std::unique_ptr create_cg() = 0; + + Thorin& thorin() { return device_code_; } + Importer& importer() { return *importer_; } + +protected: + DeviceBackends& backends_; + Thorin device_code_; + std::unique_ptr importer_; + + std::vector kernels_; + Cont2Config kernel_configs_; -//template -//LaunchArgs launch_args(Device_code device_code) { -// if (device_code == GPU) -// return LaunchArgs{}; -// else if (device_code == AIE_CGRA) -// return LaunchArgs{}; -//} + void prepare_kernel_configs(); + friend DeviceBackends; +}; struct DeviceBackends { DeviceBackends(World& world, int opt, bool debug, std::string& hls_flags); - Cont2Config kernel_config; - std::vector kernels; + World& world(); + std::vector> cgs; + + int opt(); + bool debug(); + + void register_backend(std::unique_ptr); + using GetKernelConfigFn = std::function(const App*, Continuation*)>; + void register_intrinsic(Intrinsic, Backend&, GetKernelConfigFn); - enum { CUDA, NVVM, OpenCL, AMDGPU, CGRA, HLS, BackendCount }; - std::array, BackendCount> cgs; private: - std::vector importers_; + World& world_; + std::vector> backends_; + std::unordered_map> intrinsics_; + + int opt_; + bool debug_; + + void search_for_device_code(); +friend Backend; }; } diff --git a/src/thorin/be/config_script/config_script.h b/src/thorin/be/config_script/config_script.h index f8af44bfb..ca3d2bfa5 100644 --- a/src/thorin/be/config_script/config_script.h +++ b/src/thorin/be/config_script/config_script.h @@ -19,9 +19,8 @@ namespace config_script { class CodeGen : public thorin::CodeGen { public: - CodeGen(World& world, bool debug, Ports& hls_cgra_ports, std::string& flags) - //CodeGen(World& world, bool debug, Ports& hls_cgra_ports) - : thorin::CodeGen(world, debug) + CodeGen(Thorin& thorin, bool debug, Ports& hls_cgra_ports, std::string& flags) + : thorin::CodeGen(thorin, debug) , hls_cgra_ports_(hls_cgra_ports) , flags_(flags) {} diff --git a/src/thorin/be/emitter.h b/src/thorin/be/emitter.h index 72300ba06..c138ad5e7 100644 --- a/src/thorin/be/emitter.h +++ b/src/thorin/be/emitter.h @@ -1,6 +1,9 @@ #ifndef THORIN_BE_EMITTER_H #define THORIN_BE_EMITTER_H +#include +#include + namespace thorin { template @@ -11,9 +14,41 @@ class Emitter { /// Internal wrapper for @p emit that checks and retrieves/puts the @c Value from @p defs_. Value emit_(const Def* def) { - auto place = def->no_dep() ? entry_ : scheduler_.smart(def); - auto& bb = cont2bb_[place]; - return child().emit_bb(bb, def); + std::stack required_defs; + std::queue todo; + todo.push(def); + + while (!todo.empty()) { + auto def = todo.front(); + todo.pop(); + if (defs_.lookup(def)) continue; + + if (auto memop = def->isa()) { + todo.push(memop->mem()); + required_defs.push(memop->mem()); + } else if (auto extract = def->isa()) { + if (is_mem(extract)) { + todo.push(extract->agg()); + required_defs.push(extract->agg()); + } + } + } + + while (!required_defs.empty()) { + auto r = required_defs.top(); + required_defs.pop(); + emit_unsafe(r); + } + + //auto place = def->no_dep() ? entry_ : scheduler_.smart(def); + auto place = !scheduler_.scope().contains(def) ? entry_ : scheduler_.smart(def); + + if (place) { + auto& bb = cont2bb_[place]; + return child().emit_bb(bb, def); + } else { + return child().emit_constant(def); + } } protected: @@ -39,22 +74,23 @@ class Emitter { return defs_[def] = val; } - void emit_scope(const Scope& scope) { + void emit_scope(const Scope& scope, ScopesForest& forest) { + scope_ = &scope; auto conts = schedule(scope); entry_ = scope.entry(); - assert(entry_->is_returning()); + //assert(entry_->is_returning()); auto fct = child().prepare(scope); for (auto cont : conts) { if (cont->intrinsic() != Intrinsic::EndScope) child().prepare(cont, fct); } - Scheduler new_scheduler(scope); + Scheduler new_scheduler(scope, forest); swap(scheduler_, new_scheduler); for (auto cont : conts) { if (cont->intrinsic() == Intrinsic::EndScope) continue; - assert(cont == entry_ || cont->is_basicblock()); + //assert(cont == entry_ || cont->is_basicblock()); child().emit_epilogue(cont); } @@ -62,13 +98,15 @@ class Emitter { if (cont->intrinsic() != Intrinsic::EndScope) child().finalize(cont); } child().finalize(scope); + scope_ = nullptr; } Scheduler scheduler_; DefMap defs_; - TypeMap types_; + DefMap types_; ContinuationMap cont2bb_; Continuation* entry_ = nullptr; + const Scope* scope_ = nullptr; }; } diff --git a/src/thorin/be/json/json.cpp b/src/thorin/be/json/json.cpp new file mode 100644 index 000000000..4a8d825b8 --- /dev/null +++ b/src/thorin/be/json/json.cpp @@ -0,0 +1,700 @@ +#include "json.h" + +namespace thorin::json { + +class TypeTable { +public: + json nominal_fwd_table = json::array(); + json type_table = json::array(); + + DefMap known_types; + + std::string translate_type (const Def* def) { + const Type * type = def->as(); + auto it = known_types.find(type); + if (it != known_types.end()) { + return it->second; + } + + json result; + if (auto arr = type->isa()) { + auto elem_type = translate_type(arr->elem_type()); + + result["type"] = "def_array"; + result["args"] = { elem_type }; + result["length"] = arr->dim(); + result["name"] = elem_type + "_darr_" + std::to_string(type_table.size()); + } else if (auto arr = type->isa()) { + auto elem_type = translate_type(arr->elem_type()); + + result["type"] = "indef_array"; + result["args"] = { elem_type }; + result["name"] = elem_type + "_iarr_" + std::to_string(type_table.size()); + } else if (type->isa()) { + result["name"] = "bottom_t"; + result["type"] = "bottom"; + } else if (auto fntype = type->isa()) { + json arg_types = json::array(); + for (auto arg : fntype->ops()) { + arg_types.push_back(translate_type(arg)); + } + + result["type"] = "function"; + result["name"] = "_" + std::to_string(type_table.size()); + result["args"] = arg_types; + } else if (auto closuretype = type->isa()) { + json args = json::array(); + for (auto arg : closuretype->ops()) { + args.push_back(translate_type(arg)); + } + + result["type"] = "closure"; + result["name"] = "_" + std::to_string(type_table.size()); + result["args"] = args; + } else if (type->isa()) { + result["name"] = "frame_t"; + result["type"] = "frame"; + } else if (type->isa()) { + result["name"] = "mem_t"; + result["type"] = "mem"; + } else if (auto structtype = type->isa()) { + auto name = "_struct_" + std::to_string(nominal_fwd_table.size()); + known_types[type] = name; + + json arg_names = json::array(); + for (size_t i = 0; i < structtype->num_ops(); ++i) { + arg_names.push_back(structtype->op_name(i).str()); + } + + json forward_decl; + forward_decl["name"] = name; + forward_decl["type"] = "struct"; + forward_decl["struct_name"] = structtype->name().str(); + forward_decl["arg_names"] = arg_names; + nominal_fwd_table.push_back(forward_decl); + + json args = json::array(); + for (size_t i = 0; i < structtype->num_ops(); ++i) { + args.push_back(translate_type(structtype->op(i))); + } + + result["type"] = "struct"; + result["name"] = name; + result["struct_name"] = structtype->name().str(); + result["arg_names"] = arg_names; + result["args"] = args; + } else if (auto varianttype = type->isa()) { + auto name = "_variant_" + std::to_string(nominal_fwd_table.size()); + known_types[type] = name; + + json arg_names = json::array(); + for (size_t i = 0; i < varianttype->num_ops(); ++i) { + arg_names.push_back(varianttype->op_name(i).str()); + } + + json forward_decl; + forward_decl["name"] = name; + forward_decl["type"] = "variant"; + forward_decl["variant_name"] = varianttype->name().str(); + forward_decl["arg_names"] = arg_names; + nominal_fwd_table.push_back(forward_decl); + + json args = json::array(); + for (size_t i = 0; i < varianttype->num_ops(); ++i) { + args.push_back(translate_type(varianttype->op(i))); + } + + result["type"] = "variant"; + result["name"] = name; + result["variant_name"] = varianttype->name().str(); + result["args"] = args; + result["arg_names"] = arg_names; + } else if (auto tupletype = type->isa()) { + json args = json::array(); + for (size_t i = 0; i < tupletype->num_ops(); ++i) { + args.push_back(translate_type(tupletype->op(i))); + } + + result["type"] = "tuple"; + result["name"] = "_" + std::to_string(type_table.size()); + result["args"] = args; + } else if (auto prim = type->isa()) { + result["name"] = "_" + std::to_string(type_table.size()); + result["length"] = prim->length(); + result["type"] = "prim"; + switch (prim->primtype_tag()) { +#define THORIN_ALL_TYPE(T, M) case PrimTypeTag::PrimType_##T: { result["tag"] = #T; break; } +#include + } + } else if (auto ptrtype = type->isa()) { + auto pointee_type = translate_type(ptrtype->pointee()); + + result["type"] = "ptr"; + result["args"] = { pointee_type }; + result["name"] = pointee_type + "_p_" + std::to_string(type_table.size()); + result["length"] = ptrtype->length(); + switch (ptrtype->addr_space()) { + case AddrSpace::Generic: + //result["addrspace"] = "generic"; //Default + break; + case AddrSpace::Global: + result["addrspace"] = "global"; + break; + case AddrSpace::Texture: + result["addrspace"] = "texture"; + break; + case AddrSpace::Shared: + result["addrspace"] = "shared"; + break; + case AddrSpace::Constant: + result["addrspace"] = "constant"; + break; + case AddrSpace::Private: + result["addrspace"] = "private"; + break; + } + } else { + std::cerr << "type cannot be translated\n"; + type->dump(); + THORIN_UNREACHABLE; + } + known_types[type] = result["name"]; + type_table.push_back(result); + return result["name"]; + } +}; + +class DefTable { +public: + DefTable(TypeTable& type_table) : type_table_(type_table) {} + + json decl_table = json::array(); + json def_table = json::array(); + TypeTable& type_table_; + + DefMap known_defs; + + std::string translate_def (const Def * def) { + auto it = known_defs.find(def); + if (it != known_defs.end()) { + return it->second; + } + + if (def->isa()) { + std::stack required_defs; + std::queue todo; + todo.push(def); + + while (!todo.empty()) { + auto def = todo.front(); + todo.pop(); + if (known_defs.lookup(def)) continue; + + if (auto memop = def->isa()) { + todo.push(memop->mem()); + required_defs.push(memop->mem()); + } else if (auto extract = def->isa()) { + if (is_mem(extract)) { + todo.push(extract->agg()); + required_defs.push(extract->agg()); + } + } + } + + while (!required_defs.empty()) { + auto r = pop(required_defs); + translate_def(r); + } + } + + json result; + if (auto cont = def->isa()) { + if (cont->is_intrinsic()) { + if (cont->intrinsic() == Intrinsic::Branch) { + result["name"] = "branch"; + result["type"] = "continuation"; + result["intrinsic"] = "branch"; + } else if (cont->intrinsic() == Intrinsic::Match) { + size_t num_patterns = cont->num_params() - 3; + auto variant_type = type_table_.translate_type(cont->param(1)->type()); + auto name = "_match_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "continuation"; + result["intrinsic"] = "match"; + result["variant_type"] = variant_type; + result["num_patterns"] = num_patterns; + } else { + auto intrinsic_name = cont->name(); + auto intrinsic_type = type_table_.translate_type(cont->type()); + auto name = "_in_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "continuation"; + result["intrinsic"] = intrinsic_name; + result["fn_type"] = intrinsic_type; + } + if (cont->filter() && !cont->filter()->empty()) + result["filter"] = translate_def(cont->filter()); + } else { + auto type = type_table_.translate_type(def->type()); + + //Make the name available in known_defs as early as possible to prevent recursion issues. + auto name = "_cont_" + std::to_string(decl_table.size()); + known_defs[def] = name; + + //TODO: Is this actually required for imported functions? + json arg_names = json::array(); + for (auto arg : cont->params()) { + arg_names.push_back(translate_def(arg)); + } + + json forward_decl; + forward_decl["name"] = name; + forward_decl["type"] = "continuation"; + forward_decl["fn_type"] = type; + forward_decl["arg_names"] = arg_names; + if (cont->is_external()) { + if (cont->cc() == CC::Thorin) + forward_decl["internal"] = cont->name(); + else + forward_decl["external"] = cont->name(); + } + decl_table.push_back(forward_decl); + + if(cont->has_body()) { + auto app = cont->body(); + auto target = translate_def(app->callee()); + json args = json::array(); + for (auto arg : app->args()) { + args.push_back(translate_def(arg)); + } + + result["name"] = name; + result["type"] = "continuation"; + if (cont->filter() && !cont->filter()->empty()) + result["filter"] = translate_def(cont->filter()); + result["app"] = { + {"target", target}, + {"args", args} + }; + } 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; + } + } + } else if (auto lit = def->isa()) { + auto name = "_" + std::to_string(def_table.size()); + auto type = type_table_.translate_type(lit->type()); + + 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; } +#include + default: + assert(false && "not implemented"); + } + } else if (def->isa()) { + auto name = "_" + std::to_string(def_table.size()); + auto type = type_table_.translate_type(def->type()); + + result["name"] = name; + result["type"] = "top"; + result["const_type"] = type; + } else if (def->isa()) { + auto name = "_" + std::to_string(def_table.size()); + auto type = type_table_.translate_type(def->type()); + + result["name"] = name; + result["type"] = "bottom"; + result["const_type"] = type; + } else if (auto param = def->isa()) { + auto name = param->continuation()->unique_name() + "." + std::to_string(param->index()); + known_defs[def] = name; + return name; + } else if (auto load = def->isa()) { + json args = json::array(); + args.push_back(translate_def(load->mem())); + args.push_back(translate_def(load->ptr())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "load"; + result["args"] = args; + } else if (auto store = def->isa()) { + json args = json::array(); + args.push_back(translate_def(store->mem())); + args.push_back(translate_def(store->ptr())); + args.push_back(translate_def(store->val())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "store"; + result["args"] = args; + } else if (auto size_of = def->isa()) { + auto target_type = type_table_.translate_type(size_of->of()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "sizeof"; + result["target_type"] = target_type; + } else if (auto align_of = def->isa()) { + auto target_type = type_table_.translate_type(align_of->of()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "alignof"; + result["target_type"] = target_type; + } else if (auto cast = def->isa()) { + auto source = translate_def(cast->from()); + auto target_type = type_table_.translate_type(cast->type()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "cast"; + result["source"] = source; + result["target_type"] = target_type; + } else if (auto bitcast = def->isa()) { + auto source = translate_def(bitcast->from()); + auto target_type = type_table_.translate_type(bitcast->type()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "bitcast"; + result["source"] = source; + result["target_type"] = target_type; + } else if (auto indef_array = def->isa()) { + auto dim = translate_def(indef_array->op(0)); + auto name = "_" + std::to_string(def_table.size()); + auto element_type = type_table_.translate_type(indef_array->elem_type()); + + result["name"] = name; + result["type"] = "indef_array"; + result["elem_type"] = element_type; + result["dim"] = dim; + } else if (auto def_array = def->isa()) { + json args = json::array(); + for (auto arg : def_array->ops()) { + args.push_back(translate_def(arg)); + } + + auto name = "_" + std::to_string(def_table.size()); + auto element_type = type_table_.translate_type(def_array->elem_type()); + + result["name"] = name; + result["type"] = "def_array"; + result["elem_type"] = element_type; + result["args"] = args; + } else if (auto lea = def->isa()) { + json args = json::array(); + args.push_back(translate_def(lea->ptr())); + args.push_back(translate_def(lea->index())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "lea"; + result["args"] = args; + } else if (auto extract = def->isa()) { + json args = json::array(); + args.push_back(translate_def(extract->agg())); + args.push_back(translate_def(extract->index())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "extract"; + result["args"] = args; + } else if (auto insert = def->isa()) { + json args = json::array(); + args.push_back(translate_def(insert->agg())); + args.push_back(translate_def(insert->index())); + args.push_back(translate_def(insert->value())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "insert"; + result["args"] = args; + } else if (auto closure = def->isa()) { + json args = json::array(); + args.push_back(translate_def(closure->op(0))); + args.push_back(translate_def(closure->op(1))); + auto closure_type = type_table_.translate_type(closure->type()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "closure"; + result["args"] = args; + result["closure_type"] = closure_type; + } else if (auto struct_agg = def->isa()) { + json args = json::array(); + for (auto arg : struct_agg->ops()) { + args.push_back(translate_def(arg)); + } + auto struct_type = type_table_.translate_type(struct_agg->type()); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "struct"; + result["args"] = args; + result["struct_type"] = struct_type; + } else if (auto tuple = def->isa()) { + json args = json::array(); + for (auto arg : tuple->ops()) { + args.push_back(translate_def(arg)); + } + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "tuple"; + result["args"] = args; + } else if (auto vector = def->isa()) { + json args = json::array(); + for (auto arg : vector->ops()) { + args.push_back(translate_def(arg)); + } + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "vector"; + result["args"] = args; + } else if (auto filter = def->isa()) { + json args = json::array(); + for (auto arg : filter->ops()) { + args.push_back(translate_def(arg)); + } + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "filter"; + result["args"] = args; + } else if (auto arithop = def->isa()) { + auto op = arithop->op_name(); + json args = json::array(); + args.push_back(translate_def(arithop->lhs())); + args.push_back(translate_def(arithop->rhs())); + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "arithop"; + result["op"] = op; + result["args"] = args; + } else if (auto mathop = def->isa()) { + auto op = mathop->op_name(); + json args = json::array(); + for (auto arg : mathop->ops()) { + args.push_back(translate_def(arg)); + } + auto name = "_" + std::to_string(def_table.size()); + + result["name"] = name; + result["type"] = "mathop"; + result["op"] = op; + result["args"] = args; + } else if (auto select = def->isa()) { + return bb->op_with_result(spv::Op::OpSelect, convert(def->type()).id, emit_args(select->ops())); + } + + if (!def->has_dep(Dep::Param)) + return emit_constant(def); + + assertf(false, "Incomplete emit(def) definition"); +} + +} diff --git a/src/thorin/be/spirv/spirv.h b/src/thorin/be/spirv/spirv.h new file mode 100644 index 000000000..cae51c7c6 --- /dev/null +++ b/src/thorin/be/spirv/spirv.h @@ -0,0 +1,100 @@ +#ifndef THORIN_SPIRV_H +#define THORIN_SPIRV_H + +#include "thorin/analyses/schedule.h" +#include "thorin/be/codegen.h" +#include "thorin/be/emitter.h" + +namespace thorin::spirv { + +using Id = uint32_t; + +class CodeGen; + +struct FileBuilder; +struct FnBuilder; + +struct Target { + struct { + // Either '4' or '8' + size_t pointer_size = 8; + } mem_layout; + + struct { + bool broken_op_construct_composite = true; + bool static_ac_indices_must_be_i32 = true; + } bugs; + + enum Dialect { + OpenCL, + Vulkan + }; + + Dialect dialect = OpenCL; +}; + +struct ConvertedType { + Id id; + struct Layout { + size_t size, alignment; + }; + std::optional layout; + struct { + std::optional payload_t; + } variant; +}; + +struct BasicBlockBuilder; + +class CodeGen : public thorin::CodeGen, public thorin::Emitter { +public: + CodeGen(Thorin& thorin, Target&, bool debug, const Cont2Config* = nullptr); + + void emit_stream(std::ostream& stream) override; + const char* file_ext() const override { return ".spv"; } + + bool is_valid(Id id) { + return id > 0; + } + + uint32_t convert(AddrSpace); + ConvertedType convert_maybe_void(const Type*); + ConvertedType convert(const Type*); + + Id emit_fun_decl(Continuation*); + + FnBuilder* prepare(const Scope&); + void prepare(Continuation*, FnBuilder*); + void emit_epilogue(Continuation*); + void finalize(const Scope&); + void finalize(Continuation*); + + Id emit_constant(const Def*); + Id emit_bb(BasicBlockBuilder* bb, const Def* def); +protected: + FnBuilder& get_fn_builder(Continuation*); + std::vector emit_intrinsic(const App& app, const Continuation* intrinsic, BasicBlockBuilder* bb); + std::vector emit_args(Defs); + bool should_emit(const Type*); + Id literal(uint32_t); + + Id emit_as_bb(Continuation*); + Id emit_mathop(BasicBlockBuilder* bb, const MathOp& op); + Id emit_composite(BasicBlockBuilder* bb, Id, Defs); + Id emit_composite(BasicBlockBuilder* bb, Id, ArrayRef); + Id emit_ptr_bitcast(BasicBlockBuilder* bb, const PtrType* from, const PtrType* to, Id); + + std::tuple, Id> get_dom_codom(const FnType* fn); + Id get_codom_type(const FnType*); + + Target target_info_; + FileBuilder* builder_; + const Cont2Config* kernel_config_; + DefSet scope_local_defs_; + + friend Target; +}; + +} + +#endif //THORIN_SPIRV_H diff --git a/src/thorin/be/spirv/spirv_builder.hpp b/src/thorin/be/spirv/spirv_builder.hpp new file mode 100644 index 000000000..81dc3fdc5 --- /dev/null +++ b/src/thorin/be/spirv/spirv_builder.hpp @@ -0,0 +1,739 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace thorin::spirv::builder { + +//struct SpvId { uint32_t id; }; +using Id = uint32_t; + +struct SectionBuilder; +struct BasicBlockBuilder; +struct FnBuilder; +struct FileBuilder; + +struct ExtendedInstruction { + const char* set_name; + uint32_t id; +}; + +inline int div_roundup(int a, int b) { + if (a % b == 0) + return a / b; + else + return (a / b) + 1; +} + +static inline std::vector make_literal_string(std::string_view str) { + std::vector v; + int i = 0; + uint32_t cword = 0; + for (char c : str) { + cword = cword | (c & 0xFF) << (i * 8); + i++; + if (i == 4) { + v.push_back(cword); + cword = 0; + i = 0; + } + } + v.push_back(cword); + return v; +} + +struct SectionBuilder { + explicit SectionBuilder(FileBuilder& file_builder) : file_builder_(file_builder) {} + + std::vector data_; + FileBuilder& file_builder_; + +protected: + inline Id fresh_id(); +private: + void output_word(uint32_t word) { + data_.push_back(word); + } +public: + void begin_op(spv::Op op, int size_in_words) { + uint32_t lower = op & 0xFFFFu; + uint32_t upper = (size_in_words << 16) & 0xFFFF0000u; + output_word(lower | upper); + } + + void ref_id(Id id) { + assert(id != 0); + output_word(id); + } + + void literal_string(std::string_view str) { + int i = 0; + uint32_t cword = 0; + for (char c : str) { + cword = cword | (c & 0xFF) << (i * 8); + i++; + if (i == 4) { + output_word(cword); + cword = 0; + i = 0; + } + } + output_word(cword); + } + + void literal_int(uint32_t i) { + output_word(i); + } + + void op(spv::Op op, std::vector operands) { + begin_op(op, operands.size() + 1); + for (auto e : operands) + literal_int(e); + } + + Id op_with_result(spv::Op op, Id type, std::vector operands) { + begin_op(op, operands.size() + 3); + ref_id(type); + auto id = fresh_id(); + ref_id(id); + for (auto e : operands) + literal_int(e); + return id; + } +}; + +struct FileBuilder { + enum UniqueDeclTag { + NONE, + VOID_TYPE, + FN_TYPE, + PTR_TYPE, + DEF_ARR_TYPE, + CONSTANT, + CONSTANT_COMPOSITE, + }; + + /// Prevents duplicate declarations + struct UniqueDeclKey { + UniqueDeclTag tag; + std::vector members; + + bool operator==(const UniqueDeclKey &b) const { + return tag == b.tag && members == b.members; + } + }; + + struct UniqueDeclKeyHasher { + size_t operator() (const UniqueDeclKey& key) const { + size_t acc = 0; + for (auto id : key.members) + acc ^= std::hash{}(id); + return std::hash{}(key.tag) ^ acc; + } + }; + + FileBuilder() : capabilities(*this), extensions(*this), ext_inst_import(*this), entry_points(*this), execution_modes(*this), debug_string_source(*this), debug_names(*this), debug_module_processed(*this), annotations(*this), types_constants(*this), fn_decls(*this), fn_defs(*this) {} + FileBuilder(const FileBuilder&) = delete; + + Id generate_fresh_id() { return { bound++ }; } + + void name(Id id, std::string_view str) { + assert(id < bound); + debug_names.begin_op(spv::Op::OpName, 2 + div_roundup(str.size() + 1, 4)); + debug_names.ref_id(id); + debug_names.literal_string(str); + } + + Id declare_bool_type() { + types_constants.begin_op(spv::Op::OpTypeBool, 2); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + return id; + } + + Id declare_int_type(int width, bool signed_) { + types_constants.begin_op(spv::Op::OpTypeInt, 4); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.literal_int(width); + types_constants.literal_int(signed_ ? 1 : 0); + return id; + } + + Id declare_float_type(int width) { + types_constants.begin_op(spv::Op::OpTypeFloat, 3); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.literal_int(width); + return id; + } + + Id declare_ptr_type(spv::StorageClass storage_class, Id element_type) { + auto key = UniqueDeclKey { PTR_TYPE, { element_type, (uint32_t) storage_class } }; + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + types_constants.begin_op(spv::Op::OpTypePointer, 4); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.literal_int(storage_class); + types_constants.ref_id(element_type); + unique_decls[key] = id; + return id; + } + + Id declare_array_type(Id element_type, Id dim) { + auto key = UniqueDeclKey { DEF_ARR_TYPE, { element_type, dim } }; + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + types_constants.begin_op(spv::Op::OpTypeArray, 4); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.ref_id(element_type); + types_constants.ref_id(dim); + unique_decls[key] = id; + return id; + } + + Id declare_fn_type(std::vector dom, Id codom) { + auto key = UniqueDeclKey { FN_TYPE, {} }; + for (auto d : dom) key.members.push_back(d); + key.members.push_back(codom); + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + + types_constants.begin_op(spv::Op::OpTypeFunction, 3 + dom.size()); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.ref_id(codom); + for (auto arg : dom) + types_constants.ref_id(arg); + unique_decls[key] = id; + return id; + } + + Id declare_struct_type(std::vector elements) { + types_constants.begin_op(spv::Op::OpTypeStruct, 2 + elements.size()); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + for (auto arg : elements) + types_constants.ref_id(arg); + return id; + } + + Id declare_vector_type(Id component_type, uint32_t dim) { + types_constants.begin_op(spv::Op::OpTypeVector, 4); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.ref_id(component_type); + types_constants.literal_int(dim); + return id; + } + + void decorate(Id target, spv::Decoration decoration, std::vector extra = {}) { + annotations.begin_op(spv::Op::OpDecorate, 3 + extra.size()); + annotations.ref_id(target); + annotations.literal_int(decoration); + for (auto e : extra) + annotations.literal_int(e); + } + + void decorate_member(Id target, uint32_t member, spv::Decoration decoration, std::vector extra = {}) { + annotations.begin_op(spv::Op::OpMemberDecorate, 4 + extra.size()); + annotations.ref_id(target); + annotations.literal_int(member); + annotations.literal_int(decoration); + for (auto e : extra) + annotations.literal_int(e); + } + + Id debug_string(std::string string) { + debug_string_source.begin_op(spv::Op::OpString, 2 + div_roundup(string.size() + 1, 4)); + auto id = generate_fresh_id(); + debug_string_source.ref_id(id); + debug_string_source.literal_string(string); + return id; + } + + Id bool_constant(Id type, bool value) { + types_constants.begin_op(value ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse, 3); + auto id = generate_fresh_id(); + types_constants.ref_id(type); + types_constants.ref_id(id); + return id; + } + + Id constant(Id type, std::vector bit_pattern) { + auto key = UniqueDeclKey { CONSTANT, bit_pattern }; + key.members.push_back(type); + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + types_constants.begin_op(spv::Op::OpConstant, 3 + bit_pattern.size()); + auto id = generate_fresh_id(); + types_constants.ref_id(type); + types_constants.ref_id(id); + for (auto arg : bit_pattern) + types_constants.literal_int(arg); + unique_decls[key] = id; + return id; + } + + Id constant_composite(Id type, std::vector ops) { + auto key = UniqueDeclKey { CONSTANT_COMPOSITE, {} }; + key.members.push_back(type); + for (auto op : ops) key.members.push_back(op); + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + types_constants.begin_op(spv::Op::OpConstantComposite, 3 + ops.size()); + auto id = generate_fresh_id(); + types_constants.ref_id(type); + types_constants.ref_id(id); + for (auto op : ops) + types_constants.ref_id(op); + unique_decls[key] = id; + return id; + } + + Id variable(Id type, spv::StorageClass storage_class) { + types_constants.begin_op(spv::Op::OpVariable, 4); + types_constants.ref_id(type); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + types_constants.literal_int(storage_class); + return id; + } + + Id declare_void_type() { + auto key = UniqueDeclKey { VOID_TYPE, { } }; + if (auto iter = unique_decls.find(key); iter != unique_decls.end()) return iter->second; + types_constants.begin_op(spv::Op::OpTypeVoid, 2); + auto id = generate_fresh_id(); + types_constants.ref_id(id); + unique_decls[key] = id; + return id; + } + + Id define_function(FnBuilder& fn_builder, bool define); + + void declare_entry_point(spv::ExecutionModel execution_model, Id entry_point, std::string name, std::vector interface) { + entry_points.begin_op(spv::Op::OpEntryPoint, 3 + div_roundup(name.size() + 1, 4) + interface.size()); + entry_points.literal_int(execution_model); + entry_points.ref_id(entry_point); + entry_points.literal_string(name); + for (auto i : interface) + entry_points.ref_id(i); + } + + void execution_mode(Id entry_point, spv::ExecutionMode execution_mode, std::vector payloads) { + execution_modes.begin_op(spv::Op::OpExecutionMode, 3 + payloads.size()); + execution_modes.ref_id(entry_point); + execution_modes.literal_int(execution_mode); + for (auto d : payloads) + execution_modes.literal_int(d); + } + + void capability(spv::Capability cap) { + auto found = capabilities_set.find(cap); + if (found != capabilities_set.end()) + return; + capabilities.begin_op(spv::Op::OpCapability, 2); + capabilities.data_.push_back(cap); + capabilities_set.insert(cap); + } + + void extension(std::string name) { + auto found = extensions_set.find(name); + if (found != extensions_set.end()) + return; + extensions.begin_op(spv::Op::OpExtension, 1 + div_roundup(name.size() + 1, 4)); + extensions.literal_string(name); + extensions_set.insert(name); + } + + uint32_t version = spv::Version; + + spv::AddressingModel addressing_model = spv::AddressingModel::AddressingModelLogical; + spv::MemoryModel memory_model = spv::MemoryModel::MemoryModelSimple; + +protected: + Id extended_import(std::string name) { + auto found = extended_instruction_sets.find(name); + if (found != extended_instruction_sets.end()) + return found->second; + ext_inst_import.begin_op(spv::Op::OpExtInstImport, 2 + div_roundup(name.size() + 1, 4)); + auto id = generate_fresh_id(); + ext_inst_import.ref_id(id); + ext_inst_import.literal_string(name); + extended_instruction_sets[name] = id; + return id; + } + +private: + std::ostream* output_ = nullptr; + uint32_t bound = 1; + + // Ordered as per https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf#subsection.2.4 + SectionBuilder capabilities; + SectionBuilder extensions; + SectionBuilder ext_inst_import; + SectionBuilder entry_points; + SectionBuilder execution_modes; + SectionBuilder debug_string_source; + SectionBuilder debug_names; + SectionBuilder debug_module_processed; + SectionBuilder annotations; + SectionBuilder types_constants; + SectionBuilder fn_decls; + SectionBuilder fn_defs; + + // SPIR-V disallows duplicate non-aggregate type declarations, we protect against these with this + std::unordered_map unique_decls; + std::unordered_map extended_instruction_sets; + std::unordered_set capabilities_set; + std::unordered_set extensions_set; + + void output_word_le(uint32_t word) { + output_->put((word >> 0) & 0xFFu); + output_->put((word >> 8) & 0xFFu); + output_->put((word >> 16) & 0xFFu); + output_->put((word >> 24) & 0xFFu); + } + + void output_section(SectionBuilder& section) { + for (auto& word : section.data_) { + output_word_le(word); + } + } +public: + void finish(std::ostream& output) { + output_ = &output; + SectionBuilder memory_model_section(*this); + memory_model_section.begin_op(spv::Op::OpMemoryModel, 3); + memory_model_section.data_.push_back(addressing_model); + memory_model_section.data_.push_back(memory_model); + + output_word_le(spv::MagicNumber); + output_word_le(version); // TODO: target a specific spirv version + output_word_le(uint32_t(0)); // TODO get a magic number ? + output_word_le(bound); + output_word_le(uint32_t(0)); // instruction schema padding + + output_section(capabilities); + output_section(extensions); + output_section(ext_inst_import); + output_section(memory_model_section); + output_section(entry_points); + output_section(execution_modes); + output_section(debug_string_source); + output_section(debug_names); + output_section(debug_module_processed); + output_section(annotations); + output_section(types_constants); + output_section(fn_decls); + output_section(fn_defs); + } + + friend BasicBlockBuilder; +}; + +inline Id SectionBuilder::fresh_id() { + return file_builder_.generate_fresh_id(); +} + +struct BasicBlockBuilder : public SectionBuilder { + explicit BasicBlockBuilder(FileBuilder& file_builder) + : SectionBuilder(file_builder), terminator(*this) {} + + struct Phi { + Id type; + Id value; + std::vector> preds; + }; + std::vector phis; + Id label; + + Id undef(Id type) { return op_with_result(spv::Op::OpUndef, type, {}); } + + Id composite(Id aggregate_t, std::vector elements) { return op_with_result(spv::Op::OpCompositeConstruct, aggregate_t, elements); } + + Id extract(Id target_type, Id composite, std::vector indices) { + begin_op(spv::Op::OpCompositeExtract, 4 + indices.size()); + ref_id(target_type); + auto id = fresh_id(); + ref_id(id); + ref_id(composite); + for (auto i : indices) + literal_int(i); + return id; + } + + Id insert(Id target_type, Id object, Id composite, std::vector indices) { + begin_op(spv::Op::OpCompositeInsert, 5 + indices.size()); + ref_id(target_type); + auto id = fresh_id(); + ref_id(id); + ref_id(object); + ref_id(composite); + for (auto i : indices) + literal_int(i); + return id; + } + + Id vector_extract_dynamic(Id target_type, Id vector, Id index) { + begin_op(spv::Op::OpVectorExtractDynamic, 5); + ref_id(target_type); + auto id = fresh_id(); + ref_id(id); + ref_id(vector); + ref_id(index); + return id; + } + + Id vector_insert_dynamic(Id target_type, Id vector, Id component, Id index) { + begin_op(spv::Op::OpVectorInsertDynamic, 6); + ref_id(target_type); + auto id = fresh_id(); + ref_id(id); + ref_id(vector); + ref_id(component); + ref_id(index); + return id; + } + + // Used for almost all conversion operations + Id convert(spv::Op op_, Id target_type, Id value) { + begin_op(op_, 4); + auto id = fresh_id(); + ref_id(target_type); + ref_id(id); + ref_id(value); + return id; + } + + Id access_chain(Id target_type, Id element, std::vector indexes) { + begin_op(spv::Op::OpAccessChain, 4 + indexes.size()); + auto id = fresh_id(); + ref_id(target_type); + ref_id(id); + ref_id(element); + for (auto index : indexes) + ref_id(index); + return id; + } + + Id ptr_access_chain(Id target_type, Id base, Id element, std::vector indexes) { + begin_op(spv::Op::OpPtrAccessChain, 5 + indexes.size()); + auto id = fresh_id(); + ref_id(target_type); + ref_id(id); + ref_id(base); + ref_id(element); + for (auto index : indexes) + ref_id(index); + return id; + } + + Id load(Id target_type, Id pointer, std::vector operands = {}) { + begin_op(spv::Op::OpLoad, 4 + operands.size()); + auto id = fresh_id(); + ref_id(target_type); + ref_id(id); + ref_id(pointer); + for (auto op : operands) + literal_int(op); + return id; + } + + void store(Id value, Id pointer, std::vector operands = {}) { + begin_op(spv::Op::OpStore, 3 + operands.size()); + ref_id(pointer); + ref_id(value); + for (auto op : operands) + literal_int(op); + } + + Id binop(spv::Op op_, Id result_type, Id lhs, Id rhs) { + begin_op(op_, 5); + auto id = fresh_id(); + ref_id(result_type); + ref_id(id); + ref_id(lhs); + ref_id(rhs); + return id; + } + + Id call(Id return_type, Id callee, std::vector arguments) { + begin_op(spv::Op::OpFunctionCall, 4 + arguments.size()); + auto id = fresh_id(); + ref_id(return_type); + ref_id(id); + ref_id(callee); + + for (auto a : arguments) + ref_id(a); + return id; + } + + Id ext_instruction(Id return_type, ExtendedInstruction instr, std::vector arguments); + + struct TerminatorBuilder : public SectionBuilder { + TerminatorBuilder(BasicBlockBuilder& bb) : SectionBuilder(bb.file_builder_), bb(bb) {} + + void branch(Id target) { + begin_op(spv::Op::OpBranch, 2); + ref_id(target); + } + + void branch_conditional(Id condition, Id true_target, Id false_target) { + begin_op(spv::Op::OpBranchConditional, 4); + ref_id(condition); + ref_id(true_target); + ref_id(false_target); + } + + void branch_switch(Id selector, Id default_case, std::vector literals, std::vector cases) { + assert(literals.size() == cases.size()); + begin_op(spv::Op::OpSwitch, 3 + literals.size() * 2); + ref_id(selector); + ref_id(default_case); + for (size_t i = 0; i < literals.size(); i++) { + ref_id(literals[i]); + ref_id(cases[i]); + } + } + + void selection_merge(Id merge_bb, spv::SelectionControlMask selection_control) { + begin_op(spv::Op::OpSelectionMerge, 3); + ref_id(merge_bb); + literal_int(selection_control); + } + + void loop_merge(Id merge_bb, Id continue_bb, spv::LoopControlMask loop_control, std::vector loop_control_ops) { + begin_op(spv::Op::OpLoopMerge, 4 + loop_control_ops.size()); + ref_id(merge_bb); + ref_id(continue_bb); + literal_int(loop_control); + + for (auto e : loop_control_ops) + literal_int(e); + } + + void return_void() { + begin_op(spv::Op::OpReturn, 1); + } + + void return_value(Id value) { + begin_op(spv::Op::OpReturnValue, 2); + ref_id(value); + } + + void unreachable() { + begin_op(spv::Op::OpUnreachable, 1); + } + + private: + BasicBlockBuilder& bb; + }; + + TerminatorBuilder terminator; + +protected: + Id ext_instruction(Id return_type, Id set, uint32_t instruction, std::vector arguments) { + begin_op(spv::Op::OpExtInst, 5 + arguments.size()); + auto id = fresh_id(); + ref_id(return_type); + ref_id(id); + ref_id(set); + literal_int(instruction); + for (auto a : arguments) + ref_id(a); + return id; + } +}; + +struct FnBuilder { + explicit FnBuilder(FileBuilder& file_builder) + : file_builder(file_builder), header(file_builder), variables(file_builder) + { + function_id = file_builder.generate_fresh_id(); + } + + FileBuilder& file_builder; + Id function_id; + + Id fn_type; + Id fn_ret_type; + std::vector bbs_to_emit; + + // Contains OpFunctionParams + SectionBuilder header; + + SectionBuilder variables; + + Id parameter(Id param_type) { + header.begin_op(spv::Op::OpFunctionParameter, 3); + auto id = file_builder.generate_fresh_id(); + header.ref_id(param_type); + header.ref_id(id); + return id; + } + + Id variable(Id type, spv::StorageClass storage_class) { + variables.begin_op(spv::Op::OpVariable, 4); + variables.ref_id(type); + auto id = file_builder.generate_fresh_id(); + variables.ref_id(id); + variables.literal_int(storage_class); + return id; + } +}; + +inline Id FileBuilder::define_function(FnBuilder &fn_builder, bool definition) { + auto& tgt = definition ? fn_defs : fn_decls; + tgt.begin_op(spv::Op::OpFunction, 5); + tgt.ref_id(fn_builder.fn_ret_type); + tgt.ref_id(fn_builder.function_id); + tgt.data_.push_back(spv::FunctionControlMaskNone); + tgt.ref_id(fn_builder.fn_type); + + // Includes stuff like OpFunctionParameters + for (auto w : fn_builder.header.data_) + tgt.data_.push_back(w); + + bool first = true; + for (auto& bb : fn_builder.bbs_to_emit) { + tgt.begin_op(spv::Op::OpLabel, 2); + tgt.ref_id(bb->label); + + if (first) { + for (auto w : fn_builder.variables.data_) + tgt.data_.push_back(w); + first = false; + } + + for (auto& phi : bb->phis) { + tgt.begin_op(spv::Op::OpPhi, 3 + 2 * phi->preds.size()); + tgt.ref_id(phi->type); + tgt.ref_id(phi->value); + assert(!phi->preds.empty()); + for (auto& [pred_value, pred_label] : phi->preds) { + tgt.ref_id(pred_value); + tgt.ref_id(pred_label); + } + } + + for (auto w : bb->data_) + tgt.data_.push_back(w); + + for (auto w : bb->terminator.data_) + tgt.data_.push_back(w); + } + + tgt.begin_op(spv::Op::OpFunctionEnd, 1); + return fn_builder.function_id; +} + +inline Id BasicBlockBuilder::ext_instruction(Id return_type, ExtendedInstruction instr, std::vector arguments) { + return ext_instruction(return_type, file_builder_.extended_import(instr.set_name), instr.id, arguments); +} + +} diff --git a/src/thorin/be/spirv/spirv_instructions.cpp b/src/thorin/be/spirv/spirv_instructions.cpp new file mode 100644 index 000000000..1e011542c --- /dev/null +++ b/src/thorin/be/spirv/spirv_instructions.cpp @@ -0,0 +1,206 @@ +#include "spirv_private.h" + +namespace thorin::spirv { + +struct SpirMathOps { +#define THORIN_MATHOP(mathop_name) builder::ExtendedInstruction mathop_name; +#include "thorin/tables/mathoptable.h" +#undef THORIN_MATHOP +}; + +#include + +SpirMathOps opencl_std = { + .fabs = { "OpenCL.std", OpenCLLIB::Fabs }, + .copysign = { "OpenCL.std", OpenCLLIB::Copysign }, + .round = { "OpenCL.std", OpenCLLIB::Round }, + .floor = { "OpenCL.std", OpenCLLIB::Floor }, + .ceil = { "OpenCL.std", OpenCLLIB::Ceil }, + .fmin = { "OpenCL.std", OpenCLLIB::Fmin }, + .fmax = { "OpenCL.std", OpenCLLIB::Fmax }, + .cos = { "OpenCL.std", OpenCLLIB::Cos }, + .sin = { "OpenCL.std", OpenCLLIB::Sin }, + .tan = { "OpenCL.std", OpenCLLIB::Tan }, + .acos = { "OpenCL.std", OpenCLLIB::Acos }, + .asin = { "OpenCL.std", OpenCLLIB::Asin }, + .atan = { "OpenCL.std", OpenCLLIB::Atan }, + .atan2 = { "OpenCL.std", OpenCLLIB::Atan2 }, + .sqrt = { "OpenCL.std", OpenCLLIB::Sqrt }, + .cbrt = { "OpenCL.std", OpenCLLIB::Cbrt }, + .pow = { "OpenCL.std", OpenCLLIB::Pow }, + .exp = { "OpenCL.std", OpenCLLIB::Exp }, + .exp2 = { "OpenCL.std", OpenCLLIB::Exp2 }, + .log = { "OpenCL.std", OpenCLLIB::Log }, + .log2 = { "OpenCL.std", OpenCLLIB::Log2 }, + .log10 = { "OpenCL.std", OpenCLLIB::Log10 }, +}; + +Id CodeGen::emit_mathop(BasicBlockBuilder* bb, const thorin::MathOp& mathop) { + auto type = mathop.type(); + + SpirMathOps& impl = opencl_std; + std::vector ops; + for (auto& op : mathop.ops()) { + ops.push_back(emit(op)); + } + + if (is_type_f(type)) { + switch (mathop.mathop_tag()) { +#define THORIN_MATHOP(mathop_name) case MathOp_##mathop_name: return bb->ext_instruction(convert(type).id, impl.mathop_name, ops); +#include "thorin/tables/mathoptable.h" + } + } +} + +std::tuple addrspace_atomics_params(World& world, AddrSpace as) { + switch (as) { + case AddrSpace::Global: + return std::make_tuple(spv::ScopeDevice, spv::MemorySemanticsMask::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsMask::MemorySemanticsCrossWorkgroupMemoryMask); + case AddrSpace::Shared: + return std::make_tuple(spv::ScopeWorkgroup, spv::MemorySemanticsMask::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsMask::MemorySemanticsWorkgroupMemoryMask); + default: + world.ELOG("Unsupported address space for atomics: {}", (int) as); + THORIN_UNREACHABLE; + } +} + +std::vector CodeGen::emit_intrinsic(const App& app, const Continuation* intrinsic, BasicBlockBuilder* bb) { + auto get_produced_type = [&]() { + auto ret_type = (*intrinsic->params().back()).type()->as(); + return ret_type->types()[1]; + }; + + SpirMathOps& impl = opencl_std; + auto intrinsic_name = intrinsic->name(); +#define THORIN_MATHOP(mathop_name) \ + if ((#mathop_name) == intrinsic_name) { \ + return { bb->ext_instruction(convert(get_produced_type()).id, impl.mathop_name, emit_args(app.args().skip_back())) }; \ + } +#include "thorin/tables/mathoptable.h" + + if (intrinsic->name() == "spirv.nonsemantic.printf") { + std::vector args; + auto string = app.arg(1); + if (auto arr_type = string->type()->isa(); arr_type->elem_type() == world().type_pu8()) { + auto arr = string->as(); + std::vector the_string; + for (size_t i = 0; i < arr_type->dim(); i++) + the_string.push_back(arr->op(i)->as()->value().get_u8()); + the_string.push_back('\0'); + args.push_back(builder_->debug_string(the_string.data())); + } else world().ELOG("spirv.nonsemantic.printf takes a string literal"); + + for (size_t i = 2; i < app.num_args() - 1; i++) { + args.push_back(emit(app.arg(i))); + } + + builder_->extension("SPV_KHR_non_semantic_info"); + bb->ext_instruction(convert(world().unit_type()).id, { "NonSemantic.DebugPrintf", 1}, args); + return {}; + } else if (intrinsic->name() == "spirv.builtin") { + std::vector productions; + if (auto spv_builtin_lit = app.arg(1)->isa()) { + auto spv_builtin = spv_builtin_lit->value().get_u32(); + auto found = builder_->builtins_.find(spv_builtin); + if (found != builder_->builtins_.end()) { + productions.push_back(found->second); + } else { + auto desired_type = get_produced_type()->as(); + auto id = builder_->variable(convert(desired_type).id, static_cast(convert(desired_type->addr_space()))); + builder_->interface.push_back(id); + builder_->decorate(id, spv::Decoration::DecorationBuiltIn, { spv_builtin }); + builder_->builtins_[spv_builtin] = id; + productions.push_back(id); + } + return productions; + } else + world().ELOG("spirv.builtin requires an integer literal as the argument"); + } else if (intrinsic->name() == "reserve_shared") { + auto produced_t = get_produced_type()->as(); + auto pointee = produced_t->pointee(); + if (auto indef = pointee->isa()) + pointee = indef->elem_type(); + auto size = app.arg(1)->isa(); + if (!size) + world().error(app.loc(), "reserve_shared called with non-constant size"); + else { + auto in_bytes = size->value().get_u64() * convert(pointee).layout->size; + auto type = world().definite_array_type(world().type_pu8(), in_bytes); + Id id = builder_->variable(convert(world().ptr_type(type, 1, AddrSpace::Shared)).id, static_cast(convert(AddrSpace::Shared))); + id = bb->convert(spv::Op::OpBitcast, convert(produced_t).id, id); + return { id }; + } + } else if (intrinsic->name() == "min") { + auto type = get_produced_type(); + if (is_type_f(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::Fmin }, emit_args(app.args().skip_back())) }; + if (is_type_u(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::UMin }, emit_args(app.args().skip_back())) }; + if (is_type_i(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::SMin }, emit_args(app.args().skip_back())) }; + } else if (intrinsic->name() == "max") { + auto type = get_produced_type(); + if (is_type_f(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::Fmax }, emit_args(app.args().skip_back())) }; + if (is_type_u(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::UMax }, emit_args(app.args().skip_back())) }; + if (is_type_i(type)) + return { bb->ext_instruction(convert(get_produced_type()).id, { .set_name = "OpenCL.std", .id = OpenCLLIB::SMax }, emit_args(app.args().skip_back())) }; + } else if (intrinsic->name() == "barrier") { + emit_args(app.args().skip_back()); + bb->op(spv::Op::OpControlBarrier, { literal(spv::Scope::ScopeWorkgroup), literal(spv::Scope::ScopeWorkgroup), literal(spv::MemorySemanticsMask::MemorySemanticsWorkgroupMemoryMask | spv::MemorySemanticsMask::MemorySemanticsSequentiallyConsistentMask) }); + return { }; + } else if (intrinsic->name() == "atomic_add") { + auto args = emit_args(app.args().skip_back()); + auto [ptr, value] = *(std::array*)args.data(); + auto produced = get_produced_type(); + spv::Op op; + if (is_type_f(produced)) { + op = spv::OpAtomicFAddEXT; + auto ct = convert(produced); + switch (ct.layout->size) { + case 2: builder_->capability(spv::Capability::CapabilityAtomicFloat16AddEXT); break; + case 4: builder_->capability(spv::Capability::CapabilityAtomicFloat32AddEXT); break; + case 8: builder_->capability(spv::Capability::CapabilityAtomicFloat64AddEXT); break; + } + builder_->extension("SPV_EXT_shader_atomic_float_add"); + } else if (is_type_i(produced)) + op = spv::OpAtomicIAdd; + else + assert(false && "unknown primitive type for atomic_add"); + auto [scope, semantics] = addrspace_atomics_params(world(), app.arg(1)->type()->as()->addr_space()); + auto result = bb->op_with_result(op, convert(get_produced_type()).id, { ptr, literal(scope), literal(semantics), value }); + return { result }; + } else if (intrinsic->name() == "atomic_min") { + auto args = emit_args(app.args().skip_back()); + auto [ptr, value] = *(std::array*)args.data(); + auto produced = get_produced_type(); + spv::Op op; + if (is_type_f(produced)) { + op = spv::OpAtomicFMinEXT; + auto ct = convert(produced); + switch (ct.layout->size) { + case 2: builder_->capability(spv::Capability::CapabilityAtomicFloat16MinMaxEXT); break; + case 4: builder_->capability(spv::Capability::CapabilityAtomicFloat32MinMaxEXT); break; + case 8: builder_->capability(spv::Capability::CapabilityAtomicFloat64MinMaxEXT); break; + } + builder_->extension("SPV_EXT_shader_atomic_float_min_max"); + } else if (is_type_s(produced)) + op = spv::OpAtomicSMin; + else if (is_type_u(produced)) + op = spv::OpAtomicUMin; + else + assert(false && "unknown primitive type for atomic_add"); + auto [scope, semantics] = addrspace_atomics_params(world(), app.arg(1)->type()->as()->addr_space()); + auto result = bb->op_with_result(op, convert(get_produced_type()).id, { ptr, literal(scope), literal(semantics), value }); + return { result }; + } else if (intrinsic->name() == "rv_all") { + auto args = emit_args(app.args().skip_back()); + auto result = bb->op_with_result(spv::Op::OpGroupAll, convert(get_produced_type()).id, { literal(spv::Scope::ScopeInvocation), emit(app.arg(1)) }); + return { result }; + } + world().ELOG("thorin/spirv: Intrinsic '{}' isn't recognised", intrinsic->name()); + exit(-1); +} + +} diff --git a/src/thorin/be/spirv/spirv_private.h b/src/thorin/be/spirv/spirv_private.h new file mode 100644 index 000000000..ae8f03d51 --- /dev/null +++ b/src/thorin/be/spirv/spirv_private.h @@ -0,0 +1,52 @@ +#ifndef THORIN_SPIRV_PRIVATE_H +#define THORIN_SPIRV_PRIVATE_H + +#include "spirv.h" + +#include "thorin/be/spirv/spirv_builder.hpp" + +namespace thorin::spirv { + +struct BasicBlockBuilder : public builder::BasicBlockBuilder { + explicit BasicBlockBuilder(FnBuilder& fn_builder); + + BasicBlockBuilder(const BasicBlockBuilder&) = delete; + + FnBuilder& fn_builder; + FileBuilder& file_builder; + std::unordered_map phis_map; + + bool semi_inline; +}; + +struct FnBuilder : public builder::FnBuilder { + explicit FnBuilder(FileBuilder& file_builder); + + FnBuilder(const FnBuilder&) = delete; + + FileBuilder& file_builder; + std::vector> bbs; + DefMap params; +}; + +struct FileBuilder : public builder::FileBuilder { + explicit FileBuilder(CodeGen* cg); + FileBuilder(const FileBuilder&) = delete; + + CodeGen* cg; + + FnBuilder* current_fn_ = nullptr; + ContinuationMap> fn_builders_; + std::unordered_map builtins_; + std::vector interface; + + Id u32_t(); + Id u32_constant(uint32_t); + +private: + Id u32_t_ { 0 }; +}; + +} + +#endif // THORIN_SPIRV_PRIVATE_H diff --git a/src/thorin/be/spirv/spirv_types.cpp b/src/thorin/be/spirv/spirv_types.cpp new file mode 100644 index 000000000..c40f94406 --- /dev/null +++ b/src/thorin/be/spirv/spirv_types.cpp @@ -0,0 +1,289 @@ +#include "spirv_private.h" +#include "thorin/util/stream.h" +#include "thorin/util/utility.h" + +namespace thorin::spirv { + +uint32_t CodeGen::convert(AddrSpace as) { + spv::StorageClass storage_class; + switch (as) { + case AddrSpace::Function: storage_class = spv::StorageClassFunction; break; + case AddrSpace::Private: { + storage_class = spv::StorageClassPrivate; + if (target_info_.dialect != Target::Dialect::Vulkan) + builder_->capability(spv::CapabilityVectorComputeINTEL); + break; + } + case AddrSpace::Generic: { + storage_class = spv::StorageClassGeneric; + builder_->capability(spv::Capability::CapabilityGenericPointer); + break; + } + case AddrSpace::Shared: { + storage_class = spv::StorageClassWorkgroup; + break; + } + case AddrSpace::Push: storage_class = spv::StorageClassPushConstant; break; + case AddrSpace::Input: storage_class = spv::StorageClassInput; break; + case AddrSpace::Output: storage_class = spv::StorageClassOutput; break; + case AddrSpace::Global: { + if (target_info_.dialect == Target::Dialect::Vulkan) { + builder_->capability(spv::Capability::CapabilityPhysicalStorageBufferAddresses); + storage_class = spv::StorageClassPhysicalStorageBuffer; + } else + storage_class = spv::StorageClassCrossWorkgroup; + break; + } + default: + assert(false && "This address space is not supported"); + break; + } + return storage_class; +} + +Id CodeGen::get_codom_type(const FnType* fn) { + auto [dom, codom] = get_dom_codom(fn); + assert(codom); + return codom; +} + +std::tuple, Id> CodeGen::get_dom_codom(const FnType* fn) { + Id ret = 0; + std::vector ops; + for (auto op : fn->types()) { + auto fn_type = op->isa(); + if (fn_type && !op->isa()) { + assert(!ret && "only one 'return' supported"); + std::vector ret_types; + for (auto fn_op : fn_type->types()) { + if (!should_emit(fn_op)) + continue; + ret_types.push_back(fn_op); + } + if (ret_types.size() == 1) + ret = convert_maybe_void(ret_types.back()).id; + else + ret = convert_maybe_void(world().tuple_type(ret_types)).id; + } else if (!should_emit(op)) + continue; + else + ops.push_back(convert(op).id); + } + return std::make_tuple(ops, ret); +} + +ConvertedType CodeGen::convert_maybe_void(const thorin::Type* type) { + auto converted = convert(type); + + if ((type->isa() || type->isa()) && converted.layout->size == 0) { + converted.id = builder_->declare_void_type(); + converted.layout = std::nullopt; + return converted; + } + + return converted; +} + +ConvertedType CodeGen::convert(const Type* type) { + // Spir-V requires each primitive type to be "unique", it doesn't allow for example two 32-bit signed integer types. + // Therefore we must enforce that precise/quick types map to the same thing. + switch (type->tag()) { +#define THORIN_Q_TYPE(T, M) \ + case PrimType_##T: \ + type = world().prim_type(PrimType_p##M, type->as()->length()); \ + break; +#include "thorin/tables/primtypetable.h" +#undef THORIN_Q_TYPE + default: break; + } + + // OpenCL has no signed integer types + if (target_info_.dialect == Target::OpenCL) { + switch (type->tag()) { + case Node_PrimType_ps8: + type = world().prim_type(PrimType_pu8, type->as()->length()); \ + break; + case Node_PrimType_ps16: + type = world().prim_type(PrimType_pu16, type->as()->length()); \ + break; + case Node_PrimType_ps32: + type = world().prim_type(PrimType_pu32, type->as()->length()); \ + break; + case Node_PrimType_ps64: + type = world().prim_type(PrimType_pu64, type->as()->length()); \ + break; + default: break; + } + } + + if (auto iter = types_.find(type); iter != types_.end()) + return iter->second; + + // Vector types are stupid and dangerous! + + ConvertedType converted = { 0, std::nullopt }; + + if (auto vec = type->isa(); vec && vec->length() > 1) { + auto component = vec->scalarize(); + auto conv_comp = convert(component); + converted.id = builder_->declare_vector_type(conv_comp.id, (uint32_t) vec->length()); + converted.layout = conv_comp.layout; + converted.layout->size *= vec->length(); + } else switch (type->tag()) { + // Boolean types are typically packed intelligently when declaring in local variables, however with vanilla Vulkan 1.0 they can only be represented via 32-bit integers + // Using extensions, we could use 16 or 8-bit ints instead + // We can also pack them inside structures using bit-twiddling tricks, if the need arises + // Note: this only affects storing booleans inside structures, for regular variables the actual spir-v bool type is used. + case Node_PrimType_bool: + converted.id = builder_->declare_bool_type(); + converted.layout = { 1, 1 }; + break; + case Node_PrimType_ps8: + builder_->capability(spv::Capability::CapabilityInt8); + converted.id = builder_->declare_int_type(8, true); + converted.layout = { 1, 1 }; + break; + case Node_PrimType_pu8: + builder_->capability(spv::Capability::CapabilityInt8); + converted.id = builder_->declare_int_type(8, false); + converted.layout = { 1, 1 }; + break; + case Node_PrimType_ps16: + builder_->capability(spv::Capability::CapabilityInt16); + converted.id = builder_->declare_int_type(16, true); + converted.layout = { 2, 2 }; + break; + case Node_PrimType_pu16: + builder_->capability(spv::Capability::CapabilityInt16); + converted.id = builder_->declare_int_type(16, false); + converted.layout = { 2, 2 }; + break; + case Node_PrimType_ps32: + converted.id = builder_->declare_int_type(32, true ); + converted.layout = { 4, 4 }; + break; + case Node_PrimType_pu32: + converted.id = builder_->declare_int_type(32, false); + converted.layout = { 4, 4 }; + break; + case Node_PrimType_ps64: + builder_->capability(spv::Capability::CapabilityInt64); + converted.id = builder_->declare_int_type(64, true); + converted.layout = { 8, 8 }; + break; + case Node_PrimType_pu64: + builder_->capability(spv::Capability::CapabilityInt64); + converted.id = builder_->declare_int_type(64, false); + converted.layout = { 8, 8 }; + break; + case Node_PrimType_pf16: + builder_->capability(spv::Capability::CapabilityFloat16); + converted.id = builder_->declare_float_type(16); + converted.layout = { 2, 2 }; + break; + case Node_PrimType_pf32: + converted.id = builder_->declare_float_type(32); + converted.layout = { 4, 4 }; + break; + case Node_PrimType_pf64: + builder_->capability(spv::Capability::CapabilityFloat64); + converted.id = builder_->declare_float_type(64); + converted.layout = { 8, 8 }; + break; + case Node_PtrType: { + auto ptr = type->as(); + const Type* pointee = ptr->pointee(); + while (auto arr = pointee->isa()) + pointee = arr->elem_type(); + converted.id = builder_->declare_ptr_type(static_cast(convert(ptr->addr_space())), convert(pointee).id); + converted.layout = { target_info_.mem_layout.pointer_size, target_info_.mem_layout.pointer_size }; + break; + } + case Node_IndefiniteArrayType: { + world().ELOG("Using indefinite types directly is not permitted - they may only be pointed to"); + std::abort(); + } + case Node_DefiniteArrayType: { + auto array = type->as(); + auto element = convert(array->elem_type()); + converted.id = builder_->declare_array_type(element.id, builder_->u32_constant(array->dim())); + converted.layout = { element.layout->size * array->dim(), element.layout->alignment }; + break; + } + + case Node_ClosureType: + case Node_FnType: { + auto [dom, codom] = get_dom_codom(type->as()); + + if (type->tag() == Node_FnType) { + converted.id = builder_->declare_fn_type(dom, codom); + } else { + assert(false && "TODO: handle closure mess"); + THORIN_UNREACHABLE; + } + break; + } + + case Node_StructType: + case Node_TupleType: { + std::vector spv_types; + size_t total_serialized_size = 0; + converted.layout = { 0, 0 }; + for (auto member : type->ops()) { + auto member_type = member->as(); + if (member_type == world().unit_type() || member_type == world().mem_type() || member_type->isa()) continue; + auto converted_member_type = convert(member_type); + assert(converted_member_type.layout); + spv_types.push_back(converted_member_type.id); + converted.layout->alignment = std::max(converted.layout->alignment, converted_member_type.layout->alignment); + converted.layout->size = pad(converted.layout->size + converted_member_type.layout->size, converted.layout->alignment); + } + + converted.id = builder_->declare_struct_type(spv_types); + builder_->name(converted.id, type->to_string()); + break; + } + + case Node_VariantType: { + assert(type->num_ops() > 0 && "empty variants not supported"); + auto tag_type = world().type_pu32(); + + size_t max_serialized_size = 0; + for (auto member : type->as()->types()) { + auto member_type = member->as(); + if (member_type == world().unit_type() || member_type == world().mem_type()) continue; + auto converted_member_type = convert(member_type); + assert(converted_member_type.layout); + if (converted_member_type.layout->size > max_serialized_size) + max_serialized_size = converted_member_type.layout->size; + } + + if (max_serialized_size > 0) { + auto payload_type = world().definite_array_type(world().type_pu8(), max_serialized_size); + auto struct_t = world().struct_type(type->name(), 2); + struct_t->set_op(0, tag_type); + struct_t->set_op(1, payload_type); + converted = convert(struct_t); + converted.variant.payload_t = std::make_optional(payload_type); + } else { + // We keep this useless level of struct so the rest of the code doesn't need a special path to extract the tag + auto struct_t = world().struct_type(type->name(), 1); + struct_t->set_op(0, tag_type); + converted = convert(struct_t); + } + break; + } + + case Node_MemType: { + assert(false && "MemType cannot be converted to SPIR-V"); + } + + default: + THORIN_UNREACHABLE; + } + + types_[type] = converted; + return converted; +} + +} diff --git a/src/thorin/config.h.in b/src/thorin/config.h.in index 5e061fe28..3a6b28644 100644 --- a/src/thorin/config.h.in +++ b/src/thorin/config.h.in @@ -3,7 +3,12 @@ #cmakedefine01 THORIN_ENABLE_CHECKS #cmakedefine01 THORIN_ENABLE_PROFILING +#cmakedefine01 THORIN_ENABLE_CREATION_CONTEXT #cmakedefine01 THORIN_ENABLE_LLVM +#cmakedefine01 THORIN_ENABLE_JSON #cmakedefine01 THORIN_ENABLE_RV +#cmakedefine01 THORIN_ENABLE_SHADY +#cmakedefine01 THORIN_ENABLE_SPIRV +#cmakedefine01 THORIN_ENABLE_RLIMITS #endif diff --git a/src/thorin/continuation.cpp b/src/thorin/continuation.cpp index 455c5e87e..60a0df1b5 100644 --- a/src/thorin/continuation.cpp +++ b/src/thorin/continuation.cpp @@ -2,6 +2,7 @@ #include +#include "thorin/transform/rewrite.h" #include "thorin/type.h" #include "thorin/world.h" #include "thorin/transform/mangle.h" @@ -10,16 +11,34 @@ namespace thorin { //------------------------------------------------------------------------------ -Param::Param(const Type* type, Continuation* continuation, size_t index, Debug dbg) - : Def(Node_Param, type, 1, dbg) +Param::Param(World& world, const Type* type, const Continuation* continuation, size_t index, Debug dbg) + : Def(world, Node_Param, type, { continuation }, dbg) + //: Def(world, Node_Param, type, 1, dbg) , index_(index) { - set_op(0, continuation); + //set_op(0, continuation); +} + +const Def* Param::rebuild(World&, const Type*, Defs defs) const { + assert(defs.size() == 1); + const Def* c = defs[0]; + if (auto r = c->isa()) + c = r->def()->as_nom(); + auto cont = c->as(); + return cont->param(index()); +} + +hash_t Param::vhash() const { + return hash_combine(Def::vhash(), (hash_t) index()); +} + +bool Param::equal(const Def* other) const { + return Def::equal(other) && other->as()->index() == index(); } //------------------------------------------------------------------------------ -App::App(const Defs ops, Debug dbg) : Def(Node_App, ops[0]->world().bottom_type(), ops, dbg) { +App::App(World& world, const Defs ops, Debug dbg) : Def(world, Node_App, world.bottom_type(), ops, dbg) { #if THORIN_ENABLE_CHECKS verify(); if (auto cont = callee()->isa_nom()) @@ -28,7 +47,7 @@ App::App(const Defs ops, Debug dbg) : Def(Node_App, ops[0]->world().bottom_type( #endif } -void App::verify() const { +bool App::verify() const { auto callee_type = callee()->type()->isa(); // works for closures too, no need for a special case assertf(callee_type, "callee type must be a FnType"); assertf(callee_type->num_ops() == num_args(), "app node '{}' has fn type {} with {} parameters, but is supplied {} arguments", this, callee_type, callee_type->num_ops(), num_args()); @@ -37,11 +56,12 @@ void App::verify() const { auto at = arg(i)->type(); assertf(pt == at, "app node argument {} has type {} but the callee was expecting {}", this, at, pt); } + return true; } //------------------------------------------------------------------------------ -Filter::Filter(World& world, const Defs defs, Debug dbg) : Def(Node_Filter, world.bottom_type(), defs, dbg) {} +Filter::Filter(World& world, const Defs defs, Debug dbg) : Def(world, Node_Filter, world.bottom_type(), defs, dbg) {} const Filter* Filter::cut(ArrayRef indices) const { return world().filter(ops().cut(indices), debug()); @@ -49,33 +69,58 @@ const Filter* Filter::cut(ArrayRef indices) const { //------------------------------------------------------------------------------ -Continuation::Continuation(const FnType* fn, const Attributes& attributes, Debug dbg) - : Def(Node_Continuation, fn, 2, dbg) +Continuation::Continuation(World& w, const FnType* pi, const Attributes& attributes, Debug dbg) + : Def(w, Node_Continuation, pi, 2, dbg) , attributes_(attributes) { - params_.reserve(fn->num_ops()); + assert(pi->tag() == Node_FnType && "continuations may not be closures"); + params_.reserve(pi->num_ops()); set_op(0, world().bottom(world().bottom_type())); set_op(1, world().filter({}, dbg)); + + size_t i = 0; + for (auto op : pi->types()) { + auto p = w.param(op, this, i++, dbg); + params_.emplace_back(p); + } } -Continuation* Continuation::stub() const { - Rewriter rewriter; +Continuation* Continuation::stub(Rewriter& rewriter, const Type* nty) const { + assert(!dead_); + auto& nworld = rewriter.dst(); - auto result = world().continuation(type(), attributes(), debug_history()); - for (size_t i = 0, e = num_params(); i != e; ++i) { - result->param(i)->set_name(debug_history().name); - rewriter.old2new[param(i)] = result->param(i); - } + auto npi = nty->isa(); + assert(npi && npi->tag() == Node_FnType); - if (!filter()->is_empty()) { - Array new_conditions(num_params()); - for (size_t i = 0, e = num_params(); i != e; ++i) - new_conditions[i] = rewriter.instantiate(filter()->condition(i)); + Continuation* ncontinuation = nworld.continuation(npi, attributes(), debug()); + assert(&ncontinuation->world() == &nworld); + assert(&npi->world() == &nworld); - result->set_filter(world().filter(new_conditions, filter()->debug())); - } + // TODO: investigate why this hangs + // ncontinuation->set_filter(rewriter.instantiate(filter())->as()); + return ncontinuation; +} - return result; +void Continuation::rebuild_from(Rewriter& rewriter, const Def* old) { + auto ocont = old->as(); + assert(ocont); + + assert(num_params() >= ocont->num_params()); + for (size_t i = 0, e = ocont->num_params(); i != e; ++i) + param(i)->set_name(ocont->param(i)->debug().name); + + set_filter(rewriter.instantiate(ocont->filter())->as()); + + if (ocont->is_external()) + world().make_external(this); + + if (ocont->has_body()) { + auto napp = rewriter.instantiate(ocont->body())->isa(); + // i feel like this ought to be a warning, but maybe a later pass might legitimately do that + assert(napp && "is it legitimate to substitute away an App when rebuilding ?"); + set_body(napp); + } + verify(); } Array Continuation::params_as_defs() const { @@ -94,10 +139,16 @@ const Param* Continuation::mem_param() const { } const Param* Continuation::ret_param() const { + switch (intrinsic()) { + case Intrinsic::Branch: + case Intrinsic::Match: + return nullptr; + default: break; + } const Param* result = nullptr; for (auto param : params()) { if (param->order() >= 1) { - assertf(result == nullptr, "only one ret_param allowed"); + assertf(is_intrinsic() || result == nullptr, "only one ret_param allowed"); result = param; } } @@ -105,30 +156,19 @@ const Param* Continuation::ret_param() const { } void Continuation::destroy(const char* cause) { - world().VLOG("{} has been destroyed by {}", this, cause); + world().ddef(this, "{} has been destroyed by {}", this, cause); destroy_filter(); unset_op(0); set_op(0, world().bottom(world().bottom_type())); dead_ = true; } -const FnType* Continuation::arg_fn_type() const { - assert(has_body()); - Array args(body()->num_args()); - for (size_t i = 0, e = body()->num_args(); i != e; ++i) - args[i] = body()->arg(i)->type(); - - return body()->callee()->type()->isa() - ? world().closure_type(args)->as() - : world().fn_type(args); -} - const Param* Continuation::append_param(const Type* param_type, Debug dbg) { size_t size = type()->num_ops(); Array ops(size + 1); - *std::copy(type()->ops().begin(), type()->ops().end(), ops.begin()) = param_type; + *std::copy(type()->types().begin(), type()->types().end(), ops.begin()) = param_type; clear_type(); - set_type(param_type->table().fn_type(ops)); // update type + set_type(world().fn_type(ops)); // update type auto param = world().param(param_type, this, size, dbg); // append new param params_.push_back(param); @@ -209,12 +249,25 @@ const Filter* Continuation::all_true_filter() const { return world().filter(conditions, debug()); } +/// An all-false filter +const Filter* Continuation::all_false_filter() const { + auto conditions = Array(num_params(), [&](size_t) { return world().literal_bool(false, Debug{}); }); + return world().filter(conditions, debug()); +} + bool Continuation::is_accelerator() const { return Intrinsic::AcceleratorBegin <= intrinsic() && intrinsic() < Intrinsic::AcceleratorEnd; } + +bool Continuation::is_offload_intrinsic() const { return Intrinsic::OffloadBegin <= intrinsic() && intrinsic() < Intrinsic::OffloadEnd; } + void Continuation::set_intrinsic() { if (name() == "cuda") attributes().intrinsic = Intrinsic::CUDA; else if (name() == "nvvm") attributes().intrinsic = Intrinsic::NVVM; else if (name() == "opencl") attributes().intrinsic = Intrinsic::OpenCL; - else if (name() == "amdgpu") attributes().intrinsic = Intrinsic::AMDGPU; + else if (name() == "opencl_spirv") attributes().intrinsic = Intrinsic::OpenCL_SPIRV; + else if (name() == "levelzero") attributes().intrinsic = Intrinsic::LevelZero_SPIRV; + else if (name() == "amdgpu_hsa") attributes().intrinsic = Intrinsic::AMDGPUHSA; + else if (name() == "amdgpu_pal") attributes().intrinsic = Intrinsic::AMDGPUPAL; + else if (name() == "shady_compute") attributes().intrinsic = Intrinsic::ShadyCompute; else if (name() == "cgra") attributes().intrinsic = Intrinsic::CGRA; else if (name() == "hls") attributes().intrinsic = Intrinsic::HLS; else if (name() == "parallel") attributes().intrinsic = Intrinsic::Parallel; @@ -256,33 +309,36 @@ void Continuation::jump(const Def* callee, Defs args, Debug dbg) { verify(); } -void Continuation::branch(const Def* cond, const Def* t, const Def* f, Debug dbg) { - set_body(world().app(world().branch(), {cond, t, f}, dbg)); +void Continuation::branch(const Def* mem, const Def* cond, const Def* t, const Def* f, Debug dbg) { + set_body(world().app(world().branch(), {mem, cond, t, f}, dbg)); verify(); } -void Continuation::match(const Def* val, Continuation* otherwise, Defs patterns, ArrayRef continuations, Debug dbg) { - Array args(patterns.size() + 2); +void Continuation::match(const Def* mem, const Def* val, Continuation* otherwise, Defs patterns, ArrayRef continuations, Debug dbg) { + Array args(patterns.size() + 3); - args[0] = val; - args[1] = otherwise; + args[0] = mem; + args[1] = val; + args[2] = otherwise; assert(patterns.size() == continuations.size()); for (size_t i = 0; i < patterns.size(); i++) - args[i + 2] = world().tuple({patterns[i], continuations[i]}, dbg); + args[i + 3] = world().tuple({patterns[i], continuations[i]}, dbg); set_body(world().app(world().match(val->type(), patterns.size()), args, dbg)); verify(); } -void Continuation::verify() const { +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 { - body()->verify(); + ok &= body()->verify(); assert(!dead_); // destroy() should remove the body assert(intrinsic() == Intrinsic::None); assertf(filter()->is_empty() || num_params() == filter()->size(), "The filter needs to be either empty, or match the param count"); } + return ok; } /// Rewrites the body to only keep the non-specialized arguments diff --git a/src/thorin/continuation.h b/src/thorin/continuation.h index bf4dba4dd..afc525399 100644 --- a/src/thorin/continuation.h +++ b/src/thorin/continuation.h @@ -12,6 +12,7 @@ namespace thorin { class Continuation; +class Rewriter; class Scope; typedef std::vector Continuations; @@ -24,12 +25,15 @@ typedef std::vector Continuations; */ class Param : public Def { private: - Param(const Type* type, Continuation* continuation, size_t index, Debug dbg); + Param(World&, const Type* type, const Continuation* continuation, size_t index, Debug dbg); public: Continuation* continuation() const { return op(0)->as_nom(); } size_t index() const { return index_; } + const Def* rebuild(World&, const Type*, Defs) const override; + bool equal(const Def*) const override; + hash_t vhash() const override; private: const size_t index_; @@ -53,13 +57,18 @@ class Filter : public Def { class App : public Def { private: - App(const Defs ops, Debug dbg); + App(World&, const Defs ops, Debug dbg); public: - const Def* callee() const { return op(0); } - const Def* arg(size_t i) const { return op(1 + i); } - size_t num_args() const { return num_ops() - 1; } - const Defs args() const { return ops().skip_front(); } + enum Ops { + Callee = 0, + FirstArg = 1, + }; + + const Def* callee() const { return op(Ops::Callee); } + const Def* arg(size_t i) const { return op(Ops::FirstArg + i); } + size_t num_args() const { return num_ops() - Ops::FirstArg; } + const Defs args() const { return ops().skip_front(Ops::FirstArg); } const Def* rebuild(World&, const Type*, Defs) const override; Continuations using_continuations() const { @@ -72,7 +81,7 @@ class App : public Def { } void jump(const Def* callee, Defs args, Debug dbg = {}); - void verify() const; + bool verify() const; friend class World; }; @@ -80,8 +89,9 @@ class App : public Def { //------------------------------------------------------------------------------ enum class CC : uint8_t { - C, ///< C calling convention. - Device, ///< Device calling convention. These are special functions only available on a particular device. + Thorin, ///< Standard calling convention for everything that solely lives inside thorin. + C, ///< C calling convention. + Device, ///< Device calling convention. These are special functions only available on a particular device. }; @@ -98,13 +108,19 @@ enum class Interface : uint8_t { enum class Intrinsic : uint8_t { None, AcceleratorBegin, - CUDA = AcceleratorBegin, ///< Internal CUDA-Backend. + OffloadBegin = AcceleratorBegin, + CUDA = OffloadBegin, ///< Internal CUDA-Backend. NVVM, ///< Internal NNVM-Backend. OpenCL, ///< Internal OpenCL-Backend. - AMDGPU, ///< Internal AMDGPU-Backend. + OpenCL_SPIRV, ///< Internal OpenCL-Backend. + LevelZero_SPIRV, ///< Internal SPIRV for Level0-Backend. + AMDGPUHSA, ///< Internal AMDGPU-HSA-Backend. + AMDGPUPAL, ///< Internal AMDGPU-PAL-Backend. + ShadyCompute, ///< Internal Shady Compute Backend. CGRA, ///< Internal CGRA-Backend. HLS, ///< Internal HLS-Backend. Parallel, ///< Internal Parallel-CPU-Backend. + OffloadEnd = Parallel, Fibers, ///< Internal Parallel-CPU-Backend using resumable fibers. Spawn, ///< Internal Parallel-CPU-Backend. Sync, ///< Internal Parallel-CPU-Backend. @@ -120,8 +136,8 @@ enum class Intrinsic : uint8_t { Undef, ///< Intrinsic undef function PipelineContinue, ///< Intrinsic loop-pipelining-HLS-Backend Pipeline, ///< Intrinsic loop-pipelining-HLS-Backend - Branch, ///< branch(cond, T, F). - Match, ///< match(val, otherwise, (case1, cont1), (case2, cont2), ...) + Branch, ///< branch(mem, cond, T, F). + Match, ///< match(mem, val, otherwise, (case1, cont1), (case2, cont2), ...) PeInfo, ///< Partial evaluation debug info. EndScope ///< Dummy function which marks the end of a @p Scope. }; @@ -138,18 +154,17 @@ class Continuation : public Def { Intrinsic intrinsic = Intrinsic::None; Interface interface = Interface::None; size_t buf_size = 0; - CC cc = CC::C; + CC cc = CC::Thorin; Attributes(Intrinsic intrinsic) : intrinsic(intrinsic) {} Attributes(Interface interface) : interface(interface) {} - Attributes(size_t buf_size) : buf_size(buf_size) {} - Attributes(CC cc = CC::C) : cc(cc) {} -}; - + Attributes(size_t buf_size) : buf_size(buf_size) {} + Attributes(CC cc = CC::Thorin) : cc(cc) {} + }; private: - Continuation(const FnType* fn, const Attributes& attributes, Debug dbg); + Continuation(World&, const FnType* pi, const Attributes& attributes, Debug dbg); virtual ~Continuation() { for (auto param : params()) delete param; } //Interface interface_ = Interface::None; //Interface interface_; @@ -157,7 +172,8 @@ class Continuation : public Def { public: const FnType* type() const { return Def::type()->as(); } - Continuation* stub() const; + Continuation* stub(Rewriter&, const Type*) const override; + void rebuild_from(Rewriter&, const Def* old) override; const Param* append_param(const Type* type, Debug dbg = {}); Continuations preds() const; Continuations succs() const; @@ -168,9 +184,6 @@ class Continuation : public Def { const Param* ret_param() const; size_t num_params() const { return params().size(); } - // TODO only used in parallel.cpp to create a dummy value, should be refactored in something cleaner - const FnType* arg_fn_type() const; - Attributes& attributes() { return attributes_; } const Attributes& attributes() const { return attributes_; } Intrinsic intrinsic() const { return attributes().intrinsic; } @@ -203,6 +216,7 @@ class Continuation : public Def { bool is_cgra_graph() const { return name().find("cgra_graph") != std::string::npos; } bool is_mmul() const { return name().find("mmul") != std::string::npos; } bool is_accelerator() const; + bool is_offload_intrinsic() const; bool starts_with (const std::string& prefix) const { return name().size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), name().begin()); @@ -222,9 +236,9 @@ class Continuation : public Def { void destroy(const char*); void jump(const Def* callee, Defs args, Debug dbg = {}); - void branch(const Def* cond, const Def* t, const Def* f, Debug dbg = {}); - void match(const Def* val, Continuation* otherwise, Defs patterns, ArrayRef continuations, Debug dbg = {}); - void verify() const; + void branch(const Def* mem, const Def* cond, const Def* t, const Def* f, Debug dbg = {}); + void match(const Def* mem, const Def* val, Continuation* otherwise, Defs patterns, ArrayRef continuations, Debug dbg = {}); + bool verify() const; const Filter* filter() const { return op(1)->as(); } void set_filter(const Filter* f) { @@ -233,6 +247,7 @@ class Continuation : public Def { } void destroy_filter(); const Filter* all_true_filter() const; + const Filter* all_false_filter() const; /// Counts how many time that continuation is truly used, excluding its own Params and counting reused Apps multiple times /// We need to count re-used apps multiple times because this function is used to make inlining decisions. @@ -249,6 +264,18 @@ class Continuation : public Def { } return true; } + bool never_called() const { + for (auto use : uses()) { + if (auto app = use->isa()) { + if (app->num_uses() != 0) { + return false; + } + } else if (!use->isa()) { + return false; + } + } + return true; + } bool dead_ = false; std::vector params_; diff --git a/src/thorin/debug.h b/src/thorin/debug.h index b4d19d14b..ad23841d7 100644 --- a/src/thorin/debug.h +++ b/src/thorin/debug.h @@ -4,6 +4,7 @@ #include #include +#include "thorin/config.h" #include "thorin/util/stream.h" namespace thorin { @@ -43,18 +44,35 @@ class Debug { Debug() = default; // TODO remove Debug(std::string name, Loc loc = {}, const Def* meta = nullptr) : name(name) +#if THORIN_ENABLE_CREATION_CONTEXT + , creation_context("") +#endif , loc(loc) , meta(meta) {} Debug(const char* name, Loc loc = {}, const Def* meta = nullptr) : Debug(std::string(name), loc, meta) {} +#if THORIN_ENABLE_CREATION_CONTEXT + Debug(std::string name, std::string creation_context, Loc loc = {}, const Def* meta = nullptr) + : name(name) + , creation_context(creation_context) + , loc(loc) + , meta(meta) + {} + Debug(const char* name, const char* creation_context, Loc loc = {}, const Def* meta = nullptr) + : Debug(std::string(name), std::string(creation_context), loc, meta) + {} +#endif Debug(Loc loc) : Debug("", loc) {} //Debug(const Def*); std::string name; +#if THORIN_ENABLE_CREATION_CONTEXT + std::string creation_context; +#endif Loc loc; const Def* meta = nullptr; }; diff --git a/src/thorin/def.cpp b/src/thorin/def.cpp index b644ced9c..988811acf 100644 --- a/src/thorin/def.cpp +++ b/src/thorin/def.cpp @@ -3,6 +3,7 @@ #include #include +#include "thorin/transform/rewrite.h" #include "thorin/continuation.h" #include "thorin/primop.h" #include "thorin/type.h" @@ -14,9 +15,10 @@ namespace thorin { size_t Def::gid_counter_ = 1; -Def::Def(NodeTag tag, const Type* type, Defs ops, Debug dbg) +Def::Def(World& world, NodeTag tag, const Type* type, Defs ops, Debug dbg) : tag_(tag) , ops_(ops.size()) + , world_(world) , type_(type) , debug_(dbg) , gid_(gid_counter_++) @@ -29,9 +31,10 @@ Def::Def(NodeTag tag, const Type* type, Defs ops, Debug dbg) set_op(i, ops[i]); } -Def::Def(NodeTag tag, const Type* type, size_t size, Debug dbg) +Def::Def(World& world, NodeTag tag, const Type* type, size_t size, Debug dbg) : tag_(tag) , ops_(size) + , world_(world) , type_(type) , debug_(dbg) , gid_(gid_counter_++) @@ -41,6 +44,10 @@ Def::Def(NodeTag tag, const Type* type, size_t size, Debug dbg) Dep::Bot ) {} +int Def::order() const { + return type()->order(); +} + Debug Def::debug_history() const { #if THORIN_ENABLE_CHECKS return world().track_history() ? Debug(unique_name(), loc()) : debug(); @@ -54,6 +61,7 @@ void Def::set_name(const std::string& name) const { debug_.name = name; } void Def::set_op(size_t i, const Def* def) { assert(!op(i) && "already set"); assert(def && "setting null pointer"); + assert(&def->world() == &world()); ops_[i] = def; // A Param/Continuation should not have other bits than its own set. // (Right now, Param doesn't have ops, but this will change in the future). @@ -93,7 +101,7 @@ std::string Def::unique_name() const { } bool is_unit(const Def* def) { - return def->type() == def->world().unit(); + return def->type() == def->world().unit_type(); } size_t vector_length(const Def* def) { return def->type()->as()->length(); } @@ -131,9 +139,16 @@ bool is_minus_zero(const Def* def) { return false; } +void Def::rebuild_from(Rewriter& rewriter, const Def* old) { + assert(old->num_ops() == num_ops()); + for (size_t i = 0; i < num_ops(); i++) + set_op(i, rewriter.instantiate(old->op(i))); +} + void Def::replace_uses(const Def* with) const { world().DLOG("replace uses: {} -> {}", this, with); if (this != with) { + assert(&with->world() == &this->world()); for (auto& use : copy_uses()) { auto def = const_cast(use.def()); if (def->isa()) @@ -147,8 +162,6 @@ void Def::replace_uses(const Def* with) const { } } -World& Def::world() const { return *static_cast(&type()->table()); } - uint64_t UseHash::hash(Use use) { assert(use->gid() != uint32_t(-1)); hash_t seed = hash_begin(use.index()); diff --git a/src/thorin/def.h b/src/thorin/def.h index a40a63738..3c88459ca 100644 --- a/src/thorin/def.h +++ b/src/thorin/def.h @@ -5,8 +5,8 @@ #include #include "thorin/enums.h" -#include "thorin/type.h" #include "thorin/debug.h" +#include "thorin/util/array.h" namespace thorin { @@ -14,9 +14,10 @@ namespace thorin { class Continuation; class Def; -class Tracker; +class Rewriter; class Use; class World; +class Type; typedef ArrayRef Defs; @@ -76,6 +77,23 @@ struct UseHash { // using a StackCapacity of 8 covers almost 99% of all real-world use-lists typedef HashSet Uses; +template +struct GIDLt { + bool operator()(T a, T b) const { return a->gid() < b->gid(); } +}; + +template +struct GIDHash { + static hash_t hash(T n) { return thorin::murmur3(n ? n->gid() : 0); } + static bool eq(T a, T b) { return a == b; } + static T sentinel() { return T(1); } +}; + +template +using GIDMap = thorin::HashMap>; +template +using GIDSet = thorin::HashSet>; + template using DefMap = GIDMap; using DefSet = GIDSet; @@ -106,9 +124,9 @@ class Def : public RuntimeCast, public Streamable { protected: /// Constructor for a @em structural Def. - Def(NodeTag tag, const Type* type, Defs args, Debug dbg); + Def(World&, NodeTag tag, const Type* type, Defs args, Debug dbg); /// Constructor for a @em nom Def. - Def(NodeTag tag, const Type* type, size_t size, Debug); + Def(World&, NodeTag tag, const Type* type, size_t size, Debug); virtual ~Def() {} void clear_type() { type_ = nullptr; } @@ -122,7 +140,7 @@ class Def : public RuntimeCast, public Streamable { //@{ NodeTag tag() const { return tag_; } size_t gid() const { return gid_; } - World& world() const; + World& world() const { return world_; }; //@} /// @name ops @@ -139,7 +157,7 @@ class Def : public RuntimeCast, public Streamable { //@{ const Def* out(size_t i) const; bool empty() const { return ops_.empty(); } - void set_op(size_t i, const Def* def); + virtual void set_op(size_t i, const Def* def); void unset_op(size_t i); void unset_ops(); virtual bool has_multiple_outs() const { return false; } @@ -155,7 +173,8 @@ class Def : public RuntimeCast, public Streamable { /// @name type //@{ const Type* type() const { return type_; } - int order() const { return type()->order(); } + + virtual int order() const; //@} /// @name dependence checks @@ -202,7 +221,8 @@ class Def : public RuntimeCast, public Streamable { /// @name rebuild/stub //@{ virtual const Def* rebuild(World&, const Type*, Defs) const { THORIN_UNREACHABLE; } - // TODO stub + virtual Def* stub(Rewriter&, const Type*) const { THORIN_UNREACHABLE; } + virtual void rebuild_from(Rewriter&, const Def* old); //@} void replace_uses(const Def*) const; @@ -230,6 +250,7 @@ class Def : public RuntimeCast, public Streamable { private: const NodeTag tag_; std::vector ops_; + World& world_; const Type* type_; mutable Uses uses_; mutable Debug debug_; @@ -250,7 +271,6 @@ size_t vector_length(const Def*); bool is_unit(const Def*); bool is_primlit(const Def*, int64_t); bool is_minus_zero(const Def*); -inline bool is_mem (const Def* def) { return def->type()->isa(); } inline bool is_zero (const Def* def) { return is_primlit(def, 0); } inline bool is_one (const Def* def) { return is_primlit(def, 1); } inline bool is_allset (const Def* def) { return is_primlit(def, -1); } diff --git a/src/thorin/primop.cpp b/src/thorin/primop.cpp index ddad23575..8b6fa09d7 100644 --- a/src/thorin/primop.cpp +++ b/src/thorin/primop.cpp @@ -1,5 +1,6 @@ #include "thorin/primop.h" #include "thorin/continuation.h" +#include "thorin/transform/rewrite.h" #include "thorin/config.h" #include "thorin/type.h" @@ -15,16 +16,16 @@ namespace thorin { */ PrimLit::PrimLit(World& world, PrimTypeTag tag, Box box, Debug dbg) - : Literal((NodeTag) tag, world.prim_type(tag), dbg) + : Literal(world, (NodeTag) tag, world.prim_type(tag), dbg) , box_(box) {} -Cmp::Cmp(CmpTag tag, const Def* lhs, const Def* rhs, Debug dbg) - : BinOp((NodeTag) tag, lhs->world().type_bool(vector_length(lhs->type())), lhs, rhs, dbg) +Cmp::Cmp(CmpTag tag, World& world, const Def* lhs, const Def* rhs, Debug dbg) + : BinOp(world, (NodeTag) tag, world.type_bool(vector_length(lhs->type())), lhs, rhs, dbg) {} DefiniteArray::DefiniteArray(World& world, const Type* elem, Defs args, Debug dbg) - : Aggregate(Node_DefiniteArray, args, dbg) + : Aggregate(world, Node_DefiniteArray, args, dbg) { set_type(world.definite_array_type(elem, args.size())); #if THORIN_ENABLE_CHECKS @@ -34,13 +35,13 @@ DefiniteArray::DefiniteArray(World& world, const Type* elem, Defs args, Debug db } IndefiniteArray::IndefiniteArray(World& world, const Type* elem, const Def* dim, Debug dbg) - : Aggregate(Node_IndefiniteArray, {dim}, dbg) + : Aggregate(world, Node_IndefiniteArray, {dim}, dbg) { set_type(world.indefinite_array_type(elem)); } Tuple::Tuple(World& world, Defs args, Debug dbg) - : Aggregate(Node_Tuple, args, dbg) + : Aggregate(world, Node_Tuple, args, dbg) { Array elems(num_ops()); for (size_t i = 0, e = num_ops(); i != e; ++i) @@ -50,7 +51,7 @@ Tuple::Tuple(World& world, Defs args, Debug dbg) } Vector::Vector(World& world, Defs args, Debug dbg) - : Aggregate(Node_Vector, args, dbg) + : Aggregate(world, Node_Vector, args, dbg) { if (auto primtype = args.front()->type()->isa()) { assert(primtype->length() == 1); @@ -62,73 +63,69 @@ Vector::Vector(World& world, Defs args, Debug dbg) } } -LEA::LEA(const Def* ptr, const Def* index, Debug dbg) - : Def(Node_LEA, nullptr, {ptr, index}, dbg) +LEA::LEA(World& world, const Def* ptr, const Def* index, Debug dbg) + : Def(world, Node_LEA, nullptr, {ptr, index}, dbg) { - auto& world = index->world(); auto type = ptr_type(); if (auto tuple = ptr_pointee()->isa()) { - set_type(world.ptr_type(get(tuple->ops(), index), type->length(), type->device(), type->addr_space())); + set_type(world.ptr_type(get(tuple->types(), index), type->length(), type->addr_space())); } else if (auto array = ptr_pointee()->isa()) { - set_type(world.ptr_type(array->elem_type(), type->length(), type->device(), type->addr_space())); + set_type(world.ptr_type(array->elem_type(), type->length(), type->addr_space())); } else if (auto struct_type = ptr_pointee()->isa()) { - set_type(world.ptr_type(get(struct_type->ops(), index), type->length(), type->device(), type->addr_space())); + set_type(world.ptr_type(get(struct_type->types(), index), type->length(), type->addr_space())); } else if (auto prim_type = ptr_pointee()->isa()) { assert(prim_type->length() > 1); - set_type(world.ptr_type(world.prim_type(prim_type->primtype_tag()), type->length(), type->device(), type->addr_space())); + set_type(world.ptr_type(world.prim_type(prim_type->primtype_tag()), type->length(), type->addr_space())); } else { THORIN_UNREACHABLE; } } -Known::Known(const Def* def, Debug dbg) - : Def(Node_Known, def->world().type_bool(), {def}, dbg) +Known::Known(World& world, const Def* def, Debug dbg) + : Def(world, Node_Known, world.type_bool(), {def}, dbg) {} -AlignOf::AlignOf(const Def* def, Debug dbg) - : Def(Node_AlignOf, def->world().type_qs64(), {def}, dbg) +AlignOf::AlignOf(World& world, const Def* def, Debug dbg) + : Def(world, Node_AlignOf, world.type_qs64(), {def}, dbg) {} -SizeOf::SizeOf(const Def* def, Debug dbg) - : Def(Node_SizeOf, def->world().type_qs64(), {def}, dbg) +SizeOf::SizeOf(World& world, const Def* def, Debug dbg) + : Def(world, Node_SizeOf, world.type_qs64(), {def}, dbg) {} -Slot::Slot(const Type* type, const Def* frame, Debug dbg) - : Def(Node_Slot, type->table().ptr_type(type), {frame}, dbg) +Slot::Slot(World& world, const Type* type, const Def* frame, Debug dbg) + : Def(world, Node_Slot, world.ptr_type(type), {frame}, dbg) { assert(frame->type()->isa()); } -Global::Global(const Def* init, bool is_mutable, Debug dbg) - : Def(Node_Global, init->type()->table().ptr_type(init->type()), {init}, dbg) +Global::Global(World& world, const Def* init, bool is_mutable, Debug dbg) + : Def(world, Node_Global, world.ptr_type(init->type()), {init}, dbg) , is_mutable_(is_mutable) { assert(!init->has_dep(Dep::Param)); } -Alloc::Alloc(const Type* type, const Def* mem, const Def* extra, Debug dbg) - : MemOp(Node_Alloc, nullptr, {mem, extra}, dbg) +Alloc::Alloc(World& world, const Type* type, const Def* mem, const Def* extra, Debug dbg) + : MemOp(world, Node_Alloc, nullptr, {mem, extra}, dbg) { - World& w = mem->world(); - set_type(w.tuple_type({w.mem_type(), w.ptr_type(type)})); + set_type(world.tuple_type({world.mem_type(), world.ptr_type(type)})); } -Load::Load(const Def* mem, const Def* ptr, Debug dbg) - : Access(Node_Load, nullptr, {mem, ptr}, dbg) +Load::Load(World& world, const Def* mem, const Def* ptr, Debug dbg) + : Access(world, Node_Load, nullptr, {mem, ptr}, dbg) { - World& w = mem->world(); - set_type(w.tuple_type({w.mem_type(), ptr->type()->as()->pointee()})); + set_type(world.tuple_type({world.mem_type(), ptr->type()->as()->pointee()})); } -Enter::Enter(const Def* mem, Debug dbg) - : MemOp(Node_Enter, nullptr, {mem}, dbg) +Enter::Enter(World& world, const Def* mem, Debug dbg) + : MemOp(world, Node_Enter, nullptr, {mem}, dbg) { - World& w = mem->world(); - set_type(w.tuple_type({w.mem_type(), w.frame_type()})); + set_type(world.tuple_type({world.mem_type(), world.frame_type()})); } -Assembly::Assembly(const Type *type, Defs inputs, std::string asm_template, ArrayRef output_constraints, ArrayRef input_constraints, ArrayRef clobbers, Flags flags, Debug dbg) - : MemOp(Node_Assembly, type, inputs, dbg) +Assembly::Assembly(World& world, const Type *type, Defs inputs, std::string asm_template, ArrayRef output_constraints, ArrayRef input_constraints, ArrayRef clobbers, Flags flags, Debug dbg) + : MemOp(world, Node_Assembly, type, inputs, dbg) , asm_template_(asm_template) , output_constraints_(output_constraints) , input_constraints_(input_constraints) @@ -191,7 +188,7 @@ bool Slot::equal(const Def* other) const { return this == other; } * rebuild */ -const Def* App ::rebuild(World& w, const Type* , Defs o) const { return w.app(o[0], o.skip_front(), debug()); } +const Def* App ::rebuild(World& w, const Type* , Defs o) const { return w.app(o[App::Ops::Callee], o.skip_front(App::Ops::FirstArg), debug()); } const Def* ArithOp ::rebuild(World& w, const Type* , Defs o) const { return w.arithop(arithop_tag(), o[0], o[1], debug()); } const Def* Bitcast ::rebuild(World& w, const Type* t, Defs o) const { return w.bitcast(t, o[0], debug()); } const Def* Bottom ::rebuild(World& w, const Type* t, Defs ) const { return w.bottom(t, debug()); } @@ -250,32 +247,13 @@ const Def* IndefiniteArray::rebuild(World& w, const Type* t, Defs o) const { const char* Def::op_name() const { switch (tag()) { +#define THORIN_GLUE(pre, next) #define THORIN_NODE(op, abbr) case Node_##op: return #abbr; -#include "thorin/tables/nodetable.h" - default: THORIN_UNREACHABLE; - } -} - -const char* ArithOp::op_name() const { - switch (tag()) { +#define THORIN_PRIMTYPE(T) case Node_PrimType_##T: return #T; #define THORIN_ARITHOP(op) case ArithOp_##op: return #op; -#include "thorin/tables/arithoptable.h" - default: THORIN_UNREACHABLE; - } -} - -const char* Cmp::op_name() const { - switch (tag()) { #define THORIN_CMP(op) case Cmp_##op: return #op; -#include "thorin/tables/cmptable.h" - default: THORIN_UNREACHABLE; - } -} - -const char* MathOp::op_name() const { - switch (tag()) { #define THORIN_MATHOP(op) case MathOp_##op: return #op; -#include "thorin/tables/mathoptable.h" +#include "thorin/tables/allnodes.h" default: THORIN_UNREACHABLE; } } @@ -288,6 +266,8 @@ const char* Global::op_name() const { return is_mutable() ? "global_mutable" : " * misc */ +bool Global::is_external() const { return world().is_external(this); } + std::string DefiniteArray::as_string() const { std::string res; for (auto op : ops()) { @@ -305,13 +285,13 @@ const Def* Def::out(size_t i) const { const Type* Extract::extracted_type(const Def* agg, const Def* index) { if (auto tuple = agg->type()->isa()) - return get(tuple->ops(), index); + return get(tuple->types(), index); else if (auto array = agg->type()->isa()) return array->elem_type(); else if (auto vector = agg->type()->isa()) return vector->scalarize(); else if (auto struct_type = agg->type()->isa()) - return get(struct_type->ops(), index); + return get(struct_type->types(), index); THORIN_UNREACHABLE; } @@ -333,6 +313,10 @@ const PtrType* Closure::environment_ptr_type(World& world) { return world.ptr_type(world.type_pu8()); } +Continuation* Closure::fn() const { + return op(0)->as_nom(); +} + //------------------------------------------------------------------------------ } diff --git a/src/thorin/primop.h b/src/thorin/primop.h index ad4e2efcb..5b2a7eb33 100644 --- a/src/thorin/primop.h +++ b/src/thorin/primop.h @@ -3,6 +3,7 @@ #include "thorin/config.h" #include "thorin/def.h" +#include "thorin/type.h" #include "thorin/enums.h" #include "thorin/util/hash.h" @@ -10,16 +11,16 @@ namespace thorin { class Literal : public Def { protected: - Literal(NodeTag tag, const Type* type, Debug dbg) - : Def(tag, type, Defs{}, dbg) + Literal(World& world, NodeTag tag, const Type* type, Debug dbg) + : Def(world, tag, type, Defs{}, dbg) {} }; /// This literal represents 'no value'. class Bottom : public Literal { private: - Bottom(const Type* type, Debug dbg) - : Literal(Node_Bottom, type, dbg) + Bottom(World& world, const Type* type, Debug dbg) + : Literal(world, Node_Bottom, type, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -30,8 +31,8 @@ class Bottom : public Literal { /// This literal represents 'any value'. class Top : public Literal { private: - Top(const Type* type, Debug dbg) - : Literal(Node_Top, type, dbg) + Top(World& world, const Type* type, Debug dbg) + : Literal(world, Node_Top, type, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -79,8 +80,8 @@ T get(ArrayRef array, const Def* def) { return array[primlit_value(de /// Akin to cond ? tval : fval. class Select : public Def { private: - Select(const Def* cond, const Def* tval, const Def* fval, Debug dbg) - : Def(Node_Select, tval->type(), {cond, tval, fval}, dbg) + Select(World& world, const Def* cond, const Def* tval, const Def* fval, Debug dbg) + : Def(world, Node_Select, tval->type(), {cond, tval, fval}, dbg) { assert(is_type_bool(cond->type())); assert(tval->type() == fval->type() && "types of both values must be equal"); @@ -100,7 +101,7 @@ class Select : public Def { /// Get the alignment in number of bytes needed for any value (including bottom) of a given @p Type. class AlignOf : public Def { private: - AlignOf(const Def* def, Debug dbg); + AlignOf(World& world, const Def* def, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; @@ -113,7 +114,7 @@ class AlignOf : public Def { /// Get number of bytes needed for any value (including bottom) of a given @p Type. class SizeOf : public Def { private: - SizeOf(const Def* def, Debug dbg); + SizeOf(World& world, const Def* def, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; @@ -126,8 +127,8 @@ class SizeOf : public Def { /// Base class for all side-effect free binary \p Def%s. class BinOp : public Def { protected: - BinOp(NodeTag tag, const Type* type, const Def* lhs, const Def* rhs, Debug dbg) - : Def(tag, type, {lhs, rhs}, dbg) + BinOp(World& world, NodeTag tag, const Type* type, const Def* lhs, const Def* rhs, Debug dbg) + : Def(world, tag, type, {lhs, rhs}, dbg) { assert(lhs->type() == rhs->type() && "types are not equal"); } @@ -140,8 +141,8 @@ class BinOp : public Def { /// One of \p ArithOpTag arithmetic operation. class ArithOp : public BinOp { private: - ArithOp(ArithOpTag tag, const Def* lhs, const Def* rhs, Debug dbg) - : BinOp((NodeTag) tag, lhs->type(), lhs, rhs, dbg) + ArithOp(ArithOpTag tag, World& world, const Def* lhs, const Def* rhs, Debug dbg) + : BinOp(world, (NodeTag) tag, lhs->type(), lhs, rhs, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -149,7 +150,6 @@ class ArithOp : public BinOp { public: const PrimType* type() const { return BinOp::type()->as(); } ArithOpTag arithop_tag() const { return (ArithOpTag) tag(); } - const char* op_name() const override; friend class World; }; @@ -157,14 +157,13 @@ class ArithOp : public BinOp { /// One of \p CmpTag compare. class Cmp : public BinOp { private: - Cmp(CmpTag tag, const Def* lhs, const Def* rhs, Debug dbg); + Cmp(CmpTag tag, World& world, const Def* lhs, const Def* rhs, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; public: const PrimType* type() const { return BinOp::type()->as(); } CmpTag cmp_tag() const { return (CmpTag) tag(); } - const char* op_name() const override; friend class World; }; @@ -172,8 +171,8 @@ class Cmp : public BinOp { /// Common mathematical function such as `sin()` or `cos()`. class MathOp : public Def { private: - MathOp(MathOpTag tag, const Type* type, Defs args, Debug dbg) - : Def((NodeTag)tag, type, args, dbg) + MathOp(World& world, MathOpTag tag, const Type* type, Defs args, Debug dbg) + : Def(world, (NodeTag)tag, type, args, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -181,7 +180,6 @@ class MathOp : public Def { public: const PrimType* type() const { return Def::type()->as(); } MathOpTag mathop_tag() const { return (MathOpTag) tag(); } - const char* op_name() const override; friend class World; }; @@ -189,8 +187,8 @@ class MathOp : public Def { /// Base class for @p Bitcast and @p Cast. class ConvOp : public Def { protected: - ConvOp(NodeTag tag, const Def* from, const Type* to, Debug dbg) - : Def(tag, to, {from}, dbg) + ConvOp(World& world, NodeTag tag, const Def* from, const Type* to, Debug dbg) + : Def(world, tag, to, {from}, dbg) {} public: @@ -200,8 +198,8 @@ class ConvOp : public Def { /// Converts from to type to. class Cast : public ConvOp { private: - Cast(const Type* to, const Def* from, Debug dbg) - : ConvOp(Node_Cast, from, to, dbg) + Cast(World& world, const Type* to, const Def* from, Debug dbg) + : ConvOp(world, Node_Cast, from, to, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -212,8 +210,8 @@ class Cast : public ConvOp { /// Reinterprets the bits of from as type to. class Bitcast : public ConvOp { private: - Bitcast(const Type* to, const Def* from, Debug dbg) - : ConvOp(Node_Bitcast, from, to, dbg) + Bitcast(World& world, const Type* to, const Def* from, Debug dbg) + : ConvOp(world, Node_Bitcast, from, to, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -224,8 +222,8 @@ class Bitcast : public ConvOp { /// Base class for all aggregate data constructers. class Aggregate : public Def { protected: - Aggregate(NodeTag tag, Defs args, Debug dbg) - : Def(tag, nullptr /*set later*/, args, dbg) + Aggregate(World& world, NodeTag tag, Defs args, Debug dbg) + : Def(world, tag, nullptr /*set later*/, args, dbg) {} }; @@ -274,8 +272,8 @@ class Tuple : public Aggregate { /// Data constructor for a @p VariantType. class Variant : public Def { private: - Variant(const VariantType* variant_type, const Def* value, size_t index, Debug dbg) - : Def(Node_Variant, variant_type, {value}, dbg), index_(index) + Variant(World& world, const VariantType* variant_type, const Def* value, size_t index, Debug dbg) + : Def(world, Node_Variant, variant_type, {value}, dbg), index_(index) { assert(variant_type->op(index) == value->type()); } @@ -297,8 +295,8 @@ class Variant : public Def { /// Yields the tag/index for this variant in the supplied integer type class VariantIndex : public Def { private: - VariantIndex(const Type* int_type, const Def* value, Debug dbg) - : Def(Node_VariantIndex, int_type, {value}, dbg) + VariantIndex(World& world, const Type* int_type, const Def* value, Debug dbg) + : Def(world, Node_VariantIndex, int_type, {value}, dbg) { assert(value->type()->isa()); assert(is_type_s(int_type) || is_type_u(int_type)); @@ -311,8 +309,8 @@ class VariantIndex : public Def { class VariantExtract : public Def { private: - VariantExtract(const Type* type, const Def* value, size_t index, Debug dbg) - : Def(Node_VariantExtract, type, {value}, dbg), index_(index) + VariantExtract(World& world, const Type* type, const Def* value, size_t index, Debug dbg) + : Def(world, Node_VariantExtract, type, {value}, dbg), index_(index) { assert(value->type()->as()->op(index) == type); } @@ -333,8 +331,8 @@ class VariantExtract : public Def { /// Data constructor for a @p ClosureType. class Closure : public Aggregate { private: - Closure(const ClosureType* closure_type, const Def* fn, const Def* env, Debug dbg) - : Aggregate(Node_Closure, {fn, env}, dbg) + Closure(World& world, const ClosureType* closure_type, const Def* fn, const Def* env, Debug dbg) + : Aggregate(world, Node_Closure, {fn, env}, dbg) { set_type(closure_type); } @@ -345,14 +343,16 @@ class Closure : public Aggregate { static const Type* environment_type(World&); static const PtrType* environment_ptr_type(World&); + Continuation* fn() const; + friend class World; }; /// Data constructor for a @p StructType. class StructAgg : public Aggregate { private: - StructAgg(const StructType* struct_type, Defs args, Debug dbg) - : Aggregate(Node_StructAgg, args, dbg) + StructAgg(World& world, const StructType* struct_type, Defs args, Debug dbg) + : Aggregate(world, Node_StructAgg, args, dbg) { #if THORIN_ENABLE_CHECKS assert(struct_type->num_ops() == args.size()); @@ -383,8 +383,8 @@ class Vector : public Aggregate { /// Base class for functional @p Insert and @p Extract. class AggOp : public Def { protected: - AggOp(NodeTag tag, const Type* type, Defs args, Debug dbg) - : Def(tag, type, args, dbg) + AggOp(World& world, NodeTag tag, const Type* type, Defs args, Debug dbg) + : Def(world, tag, type, args, dbg) {} public: @@ -397,8 +397,8 @@ class AggOp : public Def { /// Extracts from aggregate agg the element at position index. class Extract : public AggOp { private: - Extract(const Def* agg, const Def* index, Debug dbg) - : AggOp(Node_Extract, extracted_type(agg, index), {agg, index}, dbg) + Extract(World& world, const Def* agg, const Def* index, Debug dbg) + : AggOp(world, Node_Extract, extracted_type(agg, index), {agg, index}, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -417,8 +417,8 @@ class Extract : public AggOp { */ class Insert : public AggOp { private: - Insert(const Def* agg, const Def* index, const Def* value, Debug dbg) - : AggOp(Node_Insert, agg->type(), {agg, index, value}, dbg) + Insert(World& world, const Def* agg, const Def* index, const Def* value, Debug dbg) + : AggOp(world, Node_Insert, agg->type(), {agg, index, value}, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -437,7 +437,7 @@ class Insert : public AggOp { */ class LEA : public Def { private: - LEA(const Def* ptr, const Def* index, Debug dbg); + LEA(World& world, const Def* ptr, const Def* index, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; @@ -454,8 +454,8 @@ class LEA : public Def { /// Casts the underlying @p def to a dynamic value during @p partial_evaluation. class Hlt : public Def { private: - Hlt(const Def* def, Debug dbg) - : Def(Node_Hlt, def->type(), {def}, dbg) + Hlt(World& world, const Def* def, Debug dbg) + : Def(world, Node_Hlt, def->type(), {def}, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -469,7 +469,7 @@ class Hlt : public Def { /// Evaluates to @c true, if @p def is a literal. class Known : public Def { private: - Known(const Def* def, Debug dbg); + Known(World& world, const Def* def, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; @@ -485,8 +485,8 @@ class Known : public Def { */ class Run : public Def { private: - Run(const Def* def, Debug dbg) - : Def(Node_Run, def->type(), {def}, dbg) + Run(World& world, const Def* def, Debug dbg) + : Def(world, Node_Run, def->type(), {def}, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -504,7 +504,7 @@ class Run : public Def { */ class Slot : public Def { private: - Slot(const Type* type, const Def* frame, Debug dbg); + Slot(World& world, const Type* type, const Def* frame, Debug dbg); public: const Def* frame() const { return op(0); } @@ -525,7 +525,7 @@ class Slot : public Def { */ class Global : public Def { private: - Global(const Def* init, bool is_mutable, Debug dbg); + Global(World& world, const Def* init, bool is_mutable, Debug dbg); public: const Def* init() const { return op(0); } @@ -534,10 +534,13 @@ class Global : public Def { const Type* alloced_type() const { return type()->pointee(); } const char* op_name() const override; + bool is_external() const; + void set_init(const Def* new_init) { unset_op(0); set_op(0, new_init); } + const Def* rebuild(World&, const Type*, Defs) const override; + private: hash_t vhash() const override { return murmur3(gid()); } bool equal(const Def* other) const override { return this == other; } - const Def* rebuild(World&, const Type*, Defs) const override; bool is_mutable_; @@ -547,8 +550,8 @@ class Global : public Def { /// Base class for all \p Def%s taking and producing side-effects. class MemOp : public Def { protected: - MemOp(NodeTag tag, const Type* type, Defs args, Debug dbg) - : Def(tag, type, args, dbg) + MemOp(World& world, NodeTag tag, const Type* type, Defs args, Debug dbg) + : Def(world, tag, type, args, dbg) { assert(mem()->type()->isa()); assert(args.size() >= 1); @@ -566,7 +569,7 @@ class MemOp : public Def { /// Allocates memory on the heap. class Alloc : public MemOp { private: - Alloc(const Type* type, const Def* mem, const Def* extra, Debug dbg); + Alloc(World& world, const Type* type, const Def* mem, const Def* extra, Debug dbg); public: const Def* extra() const { return op(1); } @@ -585,8 +588,8 @@ class Alloc : public MemOp { /// Base class for @p Load and @p Store. class Access : public MemOp { protected: - Access(NodeTag tag, const Type* type, Defs args, Debug dbg) - : MemOp(tag, type, args, dbg) + Access(World& world, NodeTag tag, const Type* type, Defs args, Debug dbg) + : MemOp(world, tag, type, args, dbg) { assert(args.size() >= 2); } @@ -598,13 +601,13 @@ class Access : public MemOp { /// Loads with current effect mem from ptr to produce a pair of a new effect and the loaded value. class Load : public Access { private: - Load(const Def* mem, const Def* ptr, Debug dbg); + Load(World& world, const Def* mem, const Def* ptr, Debug dbg); public: bool has_multiple_outs() const override { return true; } const Def* out_val() const { return out(1); } const TupleType* type() const { return MemOp::type()->as(); } - const Type* out_val_type() const { return type()->op(1); } + const Type* out_val_type() const { return type()->op(1)->as(); } private: const Def* rebuild(World&, const Type*, Defs) const override; @@ -615,8 +618,8 @@ class Load : public Access { /// Stores with current effect mem value into ptr while producing a new effect. class Store : public Access { private: - Store(const Def* mem, const Def* ptr, const Def* value, Debug dbg) - : Access(Node_Store, mem->type(), {mem, ptr, value}, dbg) + Store(World& world, const Def* mem, const Def* ptr, const Def* value, Debug dbg) + : Access(world, Node_Store, mem->type(), {mem, ptr, value}, dbg) {} const Def* rebuild(World&, const Type*, Defs) const override; @@ -631,7 +634,7 @@ class Store : public Access { /// Creates a stack \p Frame with current effect mem. class Enter : public MemOp { private: - Enter(const Def* mem, Debug dbg); + Enter(World& world, const Def* mem, Debug dbg); const Def* rebuild(World&, const Type*, Defs) const override; @@ -655,7 +658,7 @@ class Assembly : public MemOp { }; private: - Assembly(const Type *type, Defs inputs, std::string asm_template, ArrayRef output_constraints, + Assembly(World& world, const Type *type, Defs inputs, std::string asm_template, ArrayRef output_constraints, ArrayRef input_constraints, ArrayRef clobbers, Flags flags, Debug dbg); public: diff --git a/src/thorin/rec_stream.cpp b/src/thorin/rec_stream.cpp index 03d6a8c20..590693e31 100644 --- a/src/thorin/rec_stream.cpp +++ b/src/thorin/rec_stream.cpp @@ -48,14 +48,35 @@ void RecStreamer::run() { auto cont = conts.pop(); s.endl().endl(); - if (cont->world().is_external(cont)) - s.fmt("extern "); + if (cont->world().is_external(cont)) { + if (cont->attributes().cc == CC::Thorin) + s.fmt("intern "); + else + s.fmt("extern "); + } + + Scope scope(cont); + if (!scope.has_free_params()) + s.fmt("top_level "); + else { + s.fmt("// free variables: {, }\n", scope.free_params()); + s.fmt("// free frontier: {, }\n", scope.free_frontier()); + } if (cont->has_body()) { std::vector param_names; for (auto param : cont->params()) param_names.push_back(param->unique_name()); - s.fmt("{}: {} = ({, }) => {{\t\n", cont->unique_name(), cont->type(), param_names); - run(cont->body()); // TODO app node + s.fmt("{}: {} = ({, }) @({}) => {{\t\n", cont->unique_name(), cont->type(), param_names, cont->filter()); + run(cont->filter()); + if (defs.contains(cont->body())) { + auto body = cont->body(); + if (auto cont2 = body->isa_nom()) { + s.fmt("{}: {} = {}({, })", cont2->unique_name(), cont2->type(), cont2->body()->callee(), cont2->body()->args()); + } else if (!body->no_dep() && !body->isa()) + body->stream_let(s); + } else { + run(cont->body()); // TODO app node + } s.fmt("\b\n}}"); } else { s.fmt("{}: {} = {{ }}", cont->unique_name(), cont->type()); @@ -68,9 +89,8 @@ void RecStreamer::run() { void Def::dump() const { dump(0); } void Def::dump(size_t max) const { Stream s(std::cout); stream(s, max).endl(); } -void Type::dump() const { Stream s(std::cout); stream(s).endl(); } - Stream& Def::stream(Stream& s) const { + if (isa()) return ((Type*)this)->stream(s); if (isa() || isa() || no_dep()) return stream1(s); return s << unique_name(); } @@ -97,9 +117,14 @@ Stream& Def::stream(Stream& s, size_t max) const { Stream& Def::stream1(Stream& s) const { if (auto param = isa()) { - return s.fmt("{}.{}", param->continuation(), param->unique_name()); + return s.fmt("{}", param->unique_name()); } else if (isa()) { - return s.fmt("cont {}", unique_name()); +#if THORIN_ENABLE_CREATION_CONTEXT + if (debug().creation_context != "") + return s.fmt("cont {} [{}]", unique_name(), debug().creation_context); + else +#endif + return s.fmt("cont {}", unique_name()); } else if (auto app = isa()) { return s.fmt("{}({, })", app->callee(), app->args()); } else if (isa()) { @@ -125,28 +150,91 @@ Stream& Def::stream1(Stream& s) const { s.fmt(": ({, })\n", ass->clobbers()); s.fmt(": ({, })\b", ass->ops()); return s; + } else if (auto global = isa()) { + if (global->is_external()) + return s.fmt("{}", unique_name()); + else + return s.fmt("{}({, })", op_name(), ops()); } - return s.fmt("{}({, }))", op_name(), ops()); + return s.fmt("{}({, })", op_name(), ops()); } Stream& Def::stream_let(Stream& s) const { - return stream1(s.fmt("{}: {} = ", this, type())).endl(); + return stream1(s.fmt("{}: {} = ", this->unique_name(), type())).endl(); } Stream& World::stream(Stream& s) const { RecStreamer rec(s, std::numeric_limits::max()); s << "module '" << name() << "'"; - for (auto&& [_, cont] : externals()) { - rec.conts.push(cont); - rec.run(); + for (auto&& [_, def] : externals()) { + auto cont = def->isa(); + if (cont) { + rec.conts.push(cont); + rec.run(); + } else { + s.fmt("\n{} = {}({, })\n", def->unique_name(), def->op_name(), def->ops()); + } } return s.endl(); } -Stream& Scope::stream(Stream& s) const { +Stream& Scope::stream(Stream&) const { + THORIN_UNREACHABLE; +} + +Stream& Type::stream(Stream& s) const { + if (false) {} + else if (isa()) return s.fmt("*"); + else if (isa()) return s.fmt("!!"); + else if (isa< MemType>()) return s.fmt("mem"); + else if (isa< FrameType>()) return s.fmt("frame"); + else if (auto t = isa()) { + return s.fmt("[{} x {}]", t->dim(), t->elem_type()); + } else if (auto t = isa()) { + return s.fmt("closure [{, }]", t->ops()); + } else if (auto t = isa()) { + return s.fmt("fn[{, }]", t->ops()); + } else if (auto t = isa()) { + return s.fmt("[{}]", t->elem_type()); + } else if (auto t = isa()) { + return s.fmt("struct {}", t->name()); + } else if (auto t = isa()) { + return s.fmt("variant {}", t->name()); + } else if (auto t = isa()) { + return s.fmt("[{, }]", t->ops()); + } else if (auto t = isa()) { + if (t->is_vector()) s.fmt("<{} x", t->length()); + s.fmt("{}*", t->pointee()); + if (t->is_vector()) s.fmt(">"); + + switch (t->addr_space()) { + case AddrSpace::Generic: break; + case AddrSpace::Global: s.fmt("[Global]"); break; + case AddrSpace::Texture: s.fmt("[Tex]"); break; + case AddrSpace::Shared: s.fmt("[Shared]"); break; + case AddrSpace::Constant: s.fmt("[Constant]"); break; + case AddrSpace::Private: s.fmt("[Private]"); break; + case AddrSpace::Function: s.fmt("[Function]"); break; + case AddrSpace::Input: s.fmt("[Input]"); break; + case AddrSpace::Output: s.fmt("[Output]"); break; + default: s.fmt("[{}]", (int) t->addr_space()); break; + } + return s; + } else if (auto t = isa()) { + if (t->is_vector()) s.fmt("<{} x", t->length()); + + switch (t->primtype_tag()) { +#define THORIN_ALL_TYPE(T, M) case Node_PrimType_##T: s.fmt(#T); break; +#include "thorin/tables/primtypetable.h" + default: THORIN_UNREACHABLE; + } + + if (t->is_vector()) s.fmt(">"); + return s; + } THORIN_UNREACHABLE; } diff --git a/src/thorin/tables/nodetable.h b/src/thorin/tables/nodetable.h index a302d270f..8c17d04dc 100644 --- a/src/thorin/tables/nodetable.h +++ b/src/thorin/tables/nodetable.h @@ -47,6 +47,7 @@ THORIN_NODE(Filter, filter) // Type // PrimType + THORIN_NODE(Star, star) THORIN_NODE(App, app) THORIN_NODE(DefiniteArrayType, definite_array_type) THORIN_NODE(FnType, fn) diff --git a/src/thorin/transform/cgra_dataflow.cpp b/src/thorin/transform/cgra_dataflow.cpp index 0d29ce890..08d80b8ca 100644 --- a/src/thorin/transform/cgra_dataflow.cpp +++ b/src/thorin/transform/cgra_dataflow.cpp @@ -37,9 +37,9 @@ PortIndices external_ports_index(const Def2Def global2param, Def2Def param2arg, size_t i = 0; for (auto it = def2dependent_blocks.begin(); it != def2dependent_blocks.end(); ++it) { auto old_common_global = it->first; //def type - if (importer.def_old2new_.contains(old_common_global)) { + if (importer.lookup(old_common_global)) { for (const auto& [global, param] : global2param) { - if (global == importer.def_old2new_[old_common_global]) { + if (global == importer.lookup(old_common_global)) { // this param2arg is after replacing global args with hls_top params that connect to cgra // basically we can name it kernelparam2hls_top_cgra_param auto top_param = param2arg[param]; @@ -166,10 +166,12 @@ void annotate_cgra_graph_modes(Continuation* continuation, const Ports& hls_cgra CgraDeviceDefs cgra_dataflow(Importer& importer, World& old_world, Def2DependentBlocks& def2dependent_blocks) { - auto& world = importer.world(); + auto& world = importer.dst(); for (auto [_, cont] : old_world.externals()) { - Scope scope(cont); + if (!cont->isa()) + continue; + Scope scope(cont->as()); for (auto& block : schedule(scope)) { if (!block->has_body()) continue; @@ -196,91 +198,90 @@ CgraDeviceDefs cgra_dataflow(Importer& importer, World& old_world, Def2Dependent std::vector new_kernels; Def2Def param2arg; // contains map from new kernel channel-parameters to channels (globals) ContName2ParamModes kernel_name2chan_param_modes; // contains map from new kernel to its channel parameter modes - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { Def2Mode def2mode; // channels and their R/W modes extract_kernel_channels(schedule(scope), def2mode); - - auto old_kernel = scope.entry(); //old_kernel->set_interface(); // for each kernel new_param_types contains both the type of kernel parameters and the channels used inside that kernel Array new_param_types(def2mode.size() + old_kernel->num_params()); - std::copy(old_kernel->type()->ops().begin(), - old_kernel->type()->ops().end(), - new_param_types.begin()); - - size_t channel_index = old_kernel->num_params(); - - // The position of the channel parameters in new kernels and their corresponding channel defintion - Array modes(def2mode.size()); - size_t i = 0; - std::vector> channel_param_index2def; - for (auto [channel, mode]: def2mode) { - modes[i++] = mode; - channel_param_index2def.emplace_back(channel_index, channel); - new_param_types[channel_index++] = channel->type(); - } + for (int i = 0; i < old_kernel->type()->num_ops(); i++) { + new_param_types[i] = old_kernel->type()->op(i)->as(); + } - // new kernels signature - // fn(mem, ret_cnt, ... , /channels/ ) - //auto new_kernel = world.continuation(world.fn_type(new_param_types), Interface::Stream, old_kernel->debug()); - auto new_kernel = world.continuation(world.fn_type(new_param_types), old_kernel->debug()); - world.make_external(new_kernel); - //new_kernel->set_interface(); + size_t channel_index = old_kernel->num_params(); + // The position of the channel parameters in new kernels and their corresponding channel defintion + Array modes(def2mode.size()); + size_t i = 0; + std::vector> channel_param_index2def; + for (auto [channel, mode]: def2mode) { + modes[i++] = mode; + channel_param_index2def.emplace_back(channel_index, channel); + new_param_types[channel_index++] = channel->type(); + } - //kernel_new2old.emplace(new_kernel, old_kernel); + // new kernels signature + // fn(mem, ret_cnt, ... , /channels/ ) + //auto new_kernel = world.continuation(world.fn_type(new_param_types), Interface::Stream, old_kernel->debug()); + auto new_kernel = world.continuation(world.fn_type(new_param_types), old_kernel->debug()); + world.make_external(new_kernel); + new_kernel->attributes().cc = CC::C; + new_kernel->set_filter(new_kernel->all_false_filter()); + //new_kernel->set_interface(); - // Kernels without any channels are scheduled in the begening - if (is_single_kernel(new_kernel)) - new_kernels.emplace(new_kernels.begin(),new_kernel); - else - new_kernels.emplace_back(new_kernel); + //kernel_new2old.emplace(new_kernel, old_kernel); - world.make_internal(old_kernel); + // Kernels without any channels are scheduled in the begening + if (is_single_kernel(new_kernel)) + new_kernels.emplace(new_kernels.begin(),new_kernel); + else + new_kernels.emplace_back(new_kernel); - Rewriter rewriter; + world.make_internal(old_kernel); - // rewriting channel parameters - for (auto [channel_param_index, channel] : channel_param_index2def) { - auto channel_param = new_kernel->param(channel_param_index); - rewriter.old2new[channel] = channel_param; - param2arg[channel_param] = channel; // (channel as kernel param, channel as global) - } + Rewriter rewriter(world, world); - // rewriting basicblocks and their parameters - for (auto def : scope.defs()) { - if (auto cont = def->isa_nom()) { - // Copy the basic block by calling stub - // Or reuse the newly created kernel copy if def is the old kernel - auto new_cont = def == old_kernel ? new_kernel : cont->stub(); - //new_cont->set_interface(); - rewriter.old2new[cont] = new_cont; - for (size_t i = 0; i < cont->num_params(); ++i) - rewriter.old2new[cont->param(i)] = new_cont->param(i); - } + // rewriting channel parameters + for (auto [channel_param_index, channel] : channel_param_index2def) { + auto channel_param = new_kernel->param(channel_param_index); + rewriter.insert(channel, channel_param); + param2arg[channel_param] = channel; // (channel as kernel param, channel as global) + } + + // rewriting basicblocks and their parameters + for (auto def : scope.defs()) { + if (auto cont = def->isa_nom()) { + // Copy the basic block by calling stub + // Or reuse the newly created kernel copy if def is the old kernel + auto new_cont = def == old_kernel ? new_kernel : cont->stub(rewriter, cont->type()); + //new_cont->set_interface(); + rewriter.insert(cont, new_cont); + for (size_t i = 0; i < cont->num_params(); ++i) + rewriter.insert(cont->param(i), new_cont->param(i)); } + } - // Rewriting the basic blocks of the kernel using the map - // The rewrite eventually maps the parameters of the old kernel to the first N parameters of the new one - // The channels used inside the kernel are mapped to the parameters N + 1, N + 2, ... - for (auto def : scope.defs()) { - if (auto cont = def->isa_nom()) { // all basic blocks of the scope - if (!cont->has_body()) continue; - auto body = cont->body(); - auto new_callee = rewriter.instantiate(body->callee()); - - Array new_args(body->num_args()); - for ( size_t i = 0; i < body->num_args(); ++i) - new_args[i] = rewriter.instantiate(body->arg(i)); - - auto new_cont = rewriter.old2new[cont]->isa_nom(); - //new_cont->set_interface(); - new_cont->jump(new_callee, new_args, cont->debug()); - //new_cont->set_interface(); - } + // Rewriting the basic blocks of the kernel using the map + // The rewrite eventually maps the parameters of the old kernel to the first N parameters of the new one + // The channels used inside the kernel are mapped to the parameters N + 1, N + 2, ... + for (auto def : scope.defs()) { + if (auto cont = def->isa_nom()) { // all basic blocks of the scope + if (!cont->has_body()) continue; + auto body = cont->body(); + auto new_callee = rewriter.instantiate(body->callee()); + + Array new_args(body->num_args()); + for ( size_t i = 0; i < body->num_args(); ++i) + new_args[i] = rewriter.instantiate(body->arg(i)); + + auto new_cont = rewriter.lookup(cont)->isa_nom(); + //new_cont->set_interface(); + new_cont->jump(new_callee, new_args, cont->debug()); + //new_cont->set_interface(); } + } kernel_name2chan_param_modes.emplace_back(new_kernel->name(), modes); @@ -309,7 +310,7 @@ CgraDeviceDefs cgra_dataflow(Importer& importer, World& old_world, Def2Dependent std::cout << "OLD ASS STREAM2" << std::endl; } if (auto okernel = body->arg(5)->as()->init()) { - auto nkernel = importer.def_old2new_[okernel]; + auto nkernel = importer.lookup(okernel); auto nkernel_cont = nkernel->as_nom(); //nkernel_cont->attributes().interface = callee->interface(); //TODO: IT WORKS here! @@ -540,7 +541,7 @@ CgraDeviceDefs cgra_dataflow(Importer& importer, World& old_world, Def2Dependent } } - world.cleanup(); + //world.cleanup(); // for (auto def : world.defs()) { // if (auto cont = def->isa_nom()) { diff --git a/src/thorin/transform/cleanup_world.cpp b/src/thorin/transform/cleanup_world.cpp index 2748d25d9..65a860e48 100644 --- a/src/thorin/transform/cleanup_world.cpp +++ b/src/thorin/transform/cleanup_world.cpp @@ -2,8 +2,6 @@ #include "thorin/world.h" #include "thorin/analyses/cfg.h" #include "thorin/analyses/scope.h" -#include "thorin/analyses/domtree.h" -#include "thorin/analyses/scope.h" #include "thorin/analyses/verify.h" #include "thorin/transform/importer.h" #include "thorin/transform/mangle.h" @@ -14,14 +12,13 @@ namespace thorin { class Cleaner { public: - Cleaner(World& world) - : world_(world) + Cleaner(Thorin& thorin) + : thorin_(thorin) {} - World& world() { return world_; } + World& world() { return thorin_.world(); } void cleanup(); void eliminate_tail_rec(); - void eta_conversion(); void eliminate_params(); void rebuild(); void verify_closedness(); @@ -31,12 +28,12 @@ class Cleaner { private: void cleanup_fix_point(); void clean_pe_info(std::queue, Continuation*); - World& world_; + Thorin& thorin_; bool todo_ = true; }; void Cleaner::eliminate_tail_rec() { - Scope::for_each(world_, [&](Scope& scope) { + ScopesForest(world()).for_each([&](Scope& scope) { auto entry = scope.entry(); bool only_tail_calls = true; @@ -46,7 +43,7 @@ void Cleaner::eliminate_tail_rec() { if (use.index() == 0 && use->isa()) { recursive = true; continue; - } else if (use->isa_nom()) + } else if (use->isa()) continue; // ignore params world().ELOG("non-recursive usage of {} index:{} use:{}", scope.entry()->name(), use.index(), use.def()->to_string()); @@ -99,86 +96,6 @@ void Cleaner::eliminate_tail_rec() { }); } -void Cleaner::eta_conversion() { - for (bool todo = true; todo;) { - todo = false; - for (auto def : world().copy_defs()) { - auto continuation = def->isa_nom(); - if (!continuation || !continuation->has_body()) continue; - - // eat calls to known continuations that are only used once - while (auto callee = continuation->body()->callee()->isa_nom()) { - auto body = continuation->body(); - if (callee == continuation) break; - - 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)); - - // because App nodes are hash-consed (thus reusable), there is a risk to invalidate their other uses here, if there are indeed any - // can_be_inlined() should account for that by counting reused apps multiple times, but in case it fails we have this pair of asserts as insurance - assert(body->num_uses() == 1); - continuation->jump(callee_body->callee(), callee_body->args(), callee->debug()); // TODO debug - callee->destroy("cleanup: continuation only called once"); - assert(body->num_uses() == 0); - todo_ = todo = true; - } else - break; - } - - auto body = continuation->body(); - // 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)) - continue; - - if (body->args() == continuation->params_as_defs()) { - continuation->replace_uses(body->callee()); - continuation->destroy("cleanup: calls a parameter (no perm)"); - todo_ = todo = true; - continue; - } - - // build the permutation of the arguments - Array perm(body->num_args()); - bool is_permutation = true; - for (size_t i = 0, e = body->num_args(); i != e; ++i) { - auto param_it = std::find(continuation->params().begin(), - continuation->params().end(), - body->arg(i)); - - if (param_it == continuation->params().end()) { - is_permutation = false; - break; - } - - perm[i] = param_it - continuation->params().begin(); - } - - if (!is_permutation) continue; - - // for every use of the continuation at a call site, - // permute the arguments and call the parameter instead - for (auto use : continuation->copy_uses()) { - auto uapp = use->isa(); - if (uapp && use.index() == 0) { - for (auto ucontinuation : uapp->using_continuations()) { - Array new_args(perm.size()); - for (size_t i = 0, e = perm.size(); i != e; ++i) { - new_args[i] = uapp->arg(perm[i]); - } - ucontinuation->jump(param, new_args, ucontinuation->debug()); // TODO debug - todo_ = todo = true; - } - } - } - } - } - } -} - void Cleaner::eliminate_params() { // TODO for (auto ocontinuation : world().copy_continuations()) { @@ -202,7 +119,7 @@ void Cleaner::eliminate_params() { if (!proxy_idx.empty()) { auto ncontinuation = world().continuation( - world().fn_type(ocontinuation->type()->ops().cut(proxy_idx)), + world().fn_type(ocontinuation->type()->types().cut(proxy_idx)), ocontinuation->attributes(), ocontinuation->debug_history()); size_t j = 0; for (auto i : param_idx) { @@ -221,10 +138,9 @@ void Cleaner::eliminate_params() { assert(use.index() == 0); for (auto ucontinuation : uapp->using_continuations()) { ucontinuation->jump(ncontinuation, uapp->args().cut(proxy_idx), ucontinuation->debug()); + todo_ = true; } } - - todo_ = true; } } next_continuation:; @@ -232,16 +148,17 @@ next_continuation:; } void Cleaner::rebuild() { - Importer importer(world_); - importer.type_old2new_.rehash(world_.types().capacity()); - importer.def_old2new_.rehash(world_.defs().capacity()); + auto fresh_world = std::make_unique(world()); + Importer importer(world(), *fresh_world); - for (auto&& [_, cont] : world().externals()) { - if (cont->is_exported()) + for (auto&& [_, def] : world().externals()) { + if (auto cont = def->isa(); cont && cont->is_exported()) importer.import(cont); + if (auto global = def->isa(); global && global->is_external()) + importer.import(global); } - swap(importer.world(), world_); + std::swap(thorin_.world_container(), fresh_world); // verify(world()); @@ -267,8 +184,7 @@ void Cleaner::verify_closedness() { } 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(&def->type()->world() == &world()); assert_unused(world().defs().contains(def)); } @@ -279,7 +195,7 @@ void Cleaner::clean_pe_info(std::queue queue, Continuation* cur) auto next = body->arg(3); auto msg = body->arg(1)->as()->from()->as()->init()->as(); - world_.idef(body->callee(), "pe_info was not constant: {}: {}", msg->as_string(), body->arg(2)); + world().idef(body->callee(), "pe_info was not constant: {}: {}", msg->as_string(), body->arg(2)); cur->jump(next, {body->arg(0)}, cur->debug()); // TODO debug todo_ = true; @@ -288,7 +204,7 @@ void Cleaner::clean_pe_info(std::queue queue, Continuation* cur) } void Cleaner::clean_pe_infos() { - world_.VLOG("cleaning remaining pe_infos"); + world().VLOG("cleaning remaining pe_infos"); std::queue queue; ContinuationSet done; auto enqueue = [&](Continuation* continuation) { @@ -296,8 +212,8 @@ void Cleaner::clean_pe_infos() { queue.push(continuation); }; - for (auto&& [_, cont] : world().externals()) - if (cont->has_body()) enqueue(cont); + for (auto&& [_, def] : world().externals()) + if (auto cont = def->isa(); cont && cont->has_body()) enqueue(cont); while (!queue.empty()) { auto continuation = pop(queue); @@ -319,24 +235,23 @@ void Cleaner::clean_pe_infos() { void Cleaner::cleanup_fix_point() { int i = 0; for (; todo_; ++i) { - world_.VLOG("iteration: {}", i); + world().VLOG("iteration: {}", i); todo_ = false; - if (world_.is_pe_done()) - eliminate_tail_rec(); - eta_conversion(); + rebuild(); + eliminate_tail_rec(); eliminate_params(); rebuild(); // resolve replaced defs before going to resolve_loads todo_ |= resolve_loads(world()); rebuild(); - if (!world().is_pe_done()) - todo_ |= partial_evaluation(world_); - else - clean_pe_infos(); + //if (!world().is_pe_done()) + todo_ |= partial_evaluation(world()); + //else + // clean_pe_infos(); } } void Cleaner::cleanup() { - world_.VLOG("start cleanup"); + world().VLOG("start cleanup"); cleanup_fix_point(); if (!world().is_pe_done()) { @@ -350,13 +265,13 @@ void Cleaner::cleanup() { cleanup_fix_point(); } - world_.VLOG("end cleanup"); + world().VLOG("end cleanup"); #if THORIN_ENABLE_CHECKS verify_closedness(); debug_verify(world()); #endif } -void cleanup_world(World& world) { Cleaner(world).cleanup(); } +void Thorin::cleanup() { Cleaner(*this).cleanup(); } } diff --git a/src/thorin/transform/cleanup_world.h b/src/thorin/transform/cleanup_world.h deleted file mode 100644 index 22c91b0b6..000000000 --- a/src/thorin/transform/cleanup_world.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef THORIN_CLEANUP_WORLD_H -#define THORIN_CLEANUP_WORLD_H - -namespace thorin { - -class World; - -void cleanup_world(World& world); - -} - -#endif diff --git a/src/thorin/transform/clone_bodies.cpp b/src/thorin/transform/clone_bodies.cpp deleted file mode 100644 index 6b48fc456..000000000 --- a/src/thorin/transform/clone_bodies.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "thorin/world.h" -#include "thorin/analyses/scope.h" -#include "thorin/transform/mangle.h" - -namespace thorin { - -/* - * TODO rewrite - */ - -// TODO merge this with lift_builtins -void clone_bodies(World& /*world*/) { -#if 0 - std::vector todo; - - // TODO this looks broken: I guess we should do that in post-order as in lift_builtins - for (auto continuation : world.copy_continuations()) { - if (is_passed_to_accelerator(continuation)) - todo.push_back(continuation); - } - - for (auto continuation : todo) { - Scope scope(continuation); - bool first = true; - for (auto use : continuation->copy_uses()) { - if (first) { - first = false; // re-use the initial continuation as first clone - } else { - auto ncontinuation = clone(scope); - if (auto uapp = use->isa()) { - if (uapp->is_replaced()) continue; // dead app node - auto napp = uapp->with_different_op(use.index(), ncontinuation); - uapp->replace(napp); - } else if (auto primop = use->isa()) { - Array nops(primop->num_ops()); - std::copy(primop->ops().begin(), primop->ops().end(), nops.begin()); - nops[use.index()] = ncontinuation; - primop->replace(primop->rebuild(world, primop->type(), nops)); - } - } - } - } -#endif -} - -} diff --git a/src/thorin/transform/clone_bodies.h b/src/thorin/transform/clone_bodies.h deleted file mode 100644 index 063815c66..000000000 --- a/src/thorin/transform/clone_bodies.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef THORIN_TRANSFORM_CLONE_BODIES_H -#define THORIN_TRANSFORM_CLONE_BODIES_H - -namespace thorin { - -class World; - -void clone_bodies(World&); - -} - -#endif diff --git a/src/thorin/transform/closure_conversion.cpp b/src/thorin/transform/closure_conversion.cpp index bb118c725..2048d77a5 100644 --- a/src/thorin/transform/closure_conversion.cpp +++ b/src/thorin/transform/closure_conversion.cpp @@ -24,29 +24,27 @@ class ClosureConversion { continue; } - auto new_type = world_.fn_type(convert(continuation->type())->ops()); + auto new_type = world_.fn_type(defs2types(convert_type(continuation->type())->ops())); if (new_type != continuation->type()) { + //The function type was changed, so the continuation takes another function as a parameter. auto new_continuation = world_.continuation(new_type->as(), continuation->debug()); if (continuation->is_intrinsic()) new_continuation->set_intrinsic(); + converted.emplace_back(continuation, new_continuation); new_defs_[continuation] = new_continuation; - if (continuation->has_body()) { - auto body = continuation->body(); - for (size_t i = 0, e = continuation->num_params(); i != e; ++i) - new_defs_[continuation->param(i)] = new_continuation->param(i); - // copy existing call from old continuation - new_continuation->jump(body->callee(), body->args(), continuation->debug()); - converted.emplace_back(continuation, new_continuation); - } - } else if (continuation->has_body()) { + for (size_t i = 0, e = continuation->num_params(); i != e; ++i) + new_defs_[continuation->param(i)] = new_continuation->param(i); + } else { + //The type remains unchanged, do not generate a new continuation. + //We still need to add the continuation to converted, to ensure the jump will be converted later on. converted.emplace_back(continuation, continuation); } } // convert the calls to each continuation for (auto pair : converted) - convert_jump(pair.second); + convert_jump(pair.first, pair.second); // remove old continuations for (auto pair : converted) { @@ -57,31 +55,64 @@ class ClosureConversion { } } - void convert_jump(Continuation* continuation) { - assert(continuation->has_body()); - auto body = continuation->body(); - // prevent conversion of calls to vectorize() or cuda(), but allow graph intrinsics + //Convert jump and all arguments. + void convert_jump(Continuation* source, Continuation* target) { + assert(source); + assert(target); + + if (!converted_.emplace(source).second) + return; //Was already converted once before. + + assert(source->has_body()); + auto body = source->body(); + auto callee = body->callee()->isa_nom(); - if (callee == continuation) return; + + if (callee == source) { + target->jump(callee, body->args(), source->debug()); + return; + } + + // prevent conversion of calls to vectorize() or cuda(), but allow graph intrinsics if (!callee || !callee->is_intrinsic()) { Array new_args(body->num_args()); for (size_t i = 0, e = body->num_args(); i != e; ++i) - new_args[i] = convert(body->arg(i)); - continuation->jump(convert(body->callee(), true), new_args, continuation->debug()); + new_args[i] = convert_def(body->arg(i)); + target->jump(convert_def(body->callee(), true), new_args, source->debug()); + } else { + Array new_args(body->num_args()); + for (size_t i = 0, e = body->num_args(); i != e; ++i) { + if (body->arg(i)->type()->isa()) + new_args[i] = body->arg(i); + else if (callee->intrinsic() == Intrinsic::Match && i > 2) + new_args[i] = body->arg(i); + else if (callee->intrinsic() == Intrinsic::HLS && i > 1) + new_args[i] = body->arg(i); + else + new_args[i] = convert_def(body->arg(i)); + } + target->jump(callee, new_args, source->debug()); } } - const Def* convert(const Def* def, bool as_callee = false) { - if (new_defs_.count(def)) def = new_defs_[def]; - if (def->order() <= 1) - return def; + const Def* convert_def(const Def* def, bool as_callee = false) { + if (auto t = def->isa()) + return convert_type(t); + + if (auto * source = def->isa_nom()) { + if (new_defs_.count(def)) def = new_defs_[def]; + auto continuation = def->isa_nom(); + assert(continuation); - if (auto continuation = def->isa_nom()) { - if (!continuation->has_body()) - return continuation; - convert_jump(continuation); if (as_callee) return continuation; + if (!source->has_body()) + return continuation; + if (continuation->order() <= 1) + return continuation; + + convert_jump(source, continuation); //convert_jump must be executed so that continuation has a body. + assert(continuation->has_body()); world_.WLOG("slow: closure generated for '{}'", continuation); @@ -95,7 +126,7 @@ class ClosureConversion { return continuation && (!continuation->has_body() || continuation->is_intrinsic()); }); free_vars.shrink(filtered_out - free_vars.begin()); - auto lifted = lift(scope, free_vars); + auto lifted = lift(scope, scope.entry(), free_vars); // get the environment type const Type* env_type = nullptr; @@ -145,13 +176,17 @@ class ClosureConversion { } wrapper->jump(lifted, wrapper_args); - auto closure_type = convert(continuation->type()); + auto closure_type = convert_type(continuation->type()); return world_.closure(closure_type->as(), wrapper, thin_env ? free_vars[0] : world_.tuple(free_vars), continuation->debug()); } else { + if (new_defs_.count(def)) return new_defs_[def]; + if (def->isa() || def->isa()) + return def; + // TODO need to consider Params? Array ops(def->ops()); - for (auto& op : ops) op = convert(op); - return new_defs_[def] = def->rebuild(world_, convert(def->type()), ops); + for (auto& op : ops) op = convert_def(op); + return new_defs_[def] = def->rebuild(world_, convert_type(def->type()), ops); } THORIN_UNREACHABLE; } @@ -160,10 +195,10 @@ class ClosureConversion { // - fn (A, B, fn(C), fn(D)) => closure(fn (A, B, fn(convert(C)), closure(fn(convert(D))))) // - struct S { fn (X, fn(Y)) } => struct T { closure(fn (X, fn(Y))) } // - ... - const Type* convert(const Type* type) { + const Type* convert_type(const Type* type) { if (new_types_.count(type)) return new_types_[type]; if (type->order() <= 1) return type; - Array ops(type->ops()); + Array ops(type->ops()); const Type* new_type = nullptr; if (type->isa()) { @@ -175,32 +210,33 @@ class ClosureConversion { // accept one parameter of order 1 (the return continuation) for function types bool ret = !type->isa(); for (auto& op : ops) { - op = convert(op); + op = convert_def(op); if (!ret && op->isa() && op->as()->inner_order() == 1) { ret = true; - op = world_.fn_type(op->ops()); + op = world_.fn_type(defs2types(op->ops())); } } if (type->isa()) { - auto struct_type = new_type->as(); + StructType* struct_type = const_cast(new_type->as()); for (size_t i = 0, e = ops.size(); i != e; ++i) - struct_type->set(i, ops[i]); + struct_type->set_op(i, ops[i]); } else { - new_type = type->rebuild(type->table(), ops); + new_type = type->rebuild(world_, type->type(), ops)->as(); } if (new_type->order() <= 1) return new_types_[type] = new_type; else - return new_types_[type] = world_.closure_type(new_type->ops()); + return new_types_[type] = world_.closure_type(defs2types(new_type->ops())); } private: World& world_; Def2Def new_defs_; - Type2Type new_types_; + DefMap new_types_; + ContinuationSet converted_; }; diff --git a/src/thorin/transform/codegen_prepare.cpp b/src/thorin/transform/codegen_prepare.cpp index 95fbbb972..e8218a571 100644 --- a/src/thorin/transform/codegen_prepare.cpp +++ b/src/thorin/transform/codegen_prepare.cpp @@ -1,34 +1,53 @@ #include "thorin/world.h" #include "thorin/analyses/scope.h" +#include "thorin/transform/rewrite.h" namespace thorin { -void codegen_prepare(World& world) { - world.VLOG("start codegen_prepare"); - Scope::for_each(world, [&](Scope& scope) { - world.DLOG("scope: {}", scope.entry()); - bool dirty = false; - auto ret_param = scope.entry()->ret_param(); - assert(ret_param && "scopes should have a return parameter"); - auto ret_cont = world.continuation(ret_param->type()->as(), ret_param->debug()); - ret_cont->jump(ret_param, ret_cont->params_as_defs(), ret_param->debug()); - - for (auto use : ret_param->copy_uses()) { - if (auto uapp = use->isa()) { - if (use.index() != 0) { - auto nops = uapp->copy_ops(); - nops[use.index()] = ret_cont; - auto napp = uapp->rebuild(world, uapp->type(), nops); - uapp->replace_uses(napp); - dirty = true; +struct CodegenPrepare : public Rewriter { + CodegenPrepare(World& src, World& dst) : Rewriter(src, dst) {} + + Continuation* make_wrapper(const Def* old_return_param) { + assert(old_return_param); + auto npi = instantiate(old_return_param->type())->as(); + npi = dst().fn_type(npi->types()); + auto wrapper = dst().continuation(npi, old_return_param->debug()); + return wrapper; + } + + const Def* rewrite(const Def* odef) override { + if (auto app = odef->isa()) { + auto new_ops = Array(app->num_args(), [&](size_t i) -> const Def* { + auto oarg = app->arg(i); + if (auto oparam = oarg->isa()) { + if (oparam == oparam->continuation()->ret_param()) { + auto wrapped = make_wrapper(oarg); + insert(oarg, wrapped); + auto imported_param = instantiate(oparam->continuation())->as_nom()->ret_param(); + wrapped->jump(imported_param, wrapped->params_as_defs(), imported_param->debug()); + return wrapped; + } } - } + return instantiate(app->arg(i)); + }); + return dst().app(instantiate(app->callee()), new_ops); } + return Rewriter::rewrite(odef); + } +}; + +/// this pass makes sure the return param is only called directly, by eta-expanding any uses where it appears in another position +void codegen_prepare(Thorin& thorin) { + thorin.world().VLOG("start codegen_prepare"); + auto& src = thorin.world(); + auto destination = std::make_unique(src); + CodegenPrepare pass(src, *destination.get()); + + for (auto& external : src.externals()) + pass.instantiate(external.second); - if (dirty) - scope.update(); - }); - world.VLOG("end codegen_prepare"); + thorin.world_container().swap(destination); + thorin.world().VLOG("end codegen_prepare"); } } diff --git a/src/thorin/transform/codegen_prepare.h b/src/thorin/transform/codegen_prepare.h index fb4c91bd8..919878260 100644 --- a/src/thorin/transform/codegen_prepare.h +++ b/src/thorin/transform/codegen_prepare.h @@ -5,7 +5,7 @@ namespace thorin { class World; -void codegen_prepare(World&); +void codegen_prepare(Thorin&); } diff --git a/src/thorin/transform/dead_load_opt.cpp b/src/thorin/transform/dead_load_opt.cpp index c88830b79..d3a83e3a8 100644 --- a/src/thorin/transform/dead_load_opt.cpp +++ b/src/thorin/transform/dead_load_opt.cpp @@ -37,7 +37,8 @@ static void dead_load_opt(const Scope& scope) { } void dead_load_opt(World& world) { - Scope::for_each(world, [&] (const Scope& scope) { dead_load_opt(scope); }); + ScopesForest forest(world); + forest.for_each([&] (const Scope& scope) { dead_load_opt(scope); }); } } diff --git a/src/thorin/transform/flatten_tuples.cpp b/src/thorin/transform/flatten_tuples.cpp index 0b4529c25..8187b1001 100644 --- a/src/thorin/transform/flatten_tuples.cpp +++ b/src/thorin/transform/flatten_tuples.cpp @@ -13,10 +13,10 @@ static Continuation* unwrap_def(Def2Def&, Def2Def&, const Def*, const FnType*, s // Computes the type of the wrapped function static const Type* wrapped_type(const FnType* fn_type, size_t max_tuple_size) { std::vector nops; - for (auto op : fn_type->ops()) { + for (auto op : fn_type->types()) { if (auto tuple_type = op->isa()) { if (tuple_type->num_ops() <= max_tuple_size) { - for (auto arg : tuple_type->ops()) + for (auto arg : tuple_type->types()) nops.push_back(arg); } else nops.push_back(op); @@ -26,7 +26,7 @@ static const Type* wrapped_type(const FnType* fn_type, size_t max_tuple_size) { nops.push_back(op); } } - return fn_type->table().fn_type(nops); + return fn_type->world().fn_type(nops); } static Continuation* jump(Continuation* cont, Array& args) { @@ -166,7 +166,7 @@ static Continuation* unwrap_def(Def2Def& wrapped, Def2Def& unwrapped, const Def* return jump(old_cont, call_args); } -static void flatten_tuples(World& world, size_t max_tuple_size) { +static void flatten_tuples(Thorin& thorin, size_t max_tuple_size) { // flatten tuples passed as arguments to functions bool todo = true; Def2Def wrapped, unwrapped; @@ -177,11 +177,11 @@ static void flatten_tuples(World& world, size_t max_tuple_size) { for (auto pair : unwrapped) unwrapped_codom.emplace(pair.second); - for (auto cont : world.copy_continuations()) { + for (auto cont : thorin.world().copy_continuations()) { // do not change the signature of intrinsic/external functions if (!cont->has_body() || cont->is_intrinsic() || - world.is_external(cont) || + thorin.world().is_external(cont) || is_passed_to_accelerator(cont)) continue; @@ -196,7 +196,7 @@ static void flatten_tuples(World& world, size_t max_tuple_size) { todo = true; - world.DLOG("flattened {}", cont); + thorin.world().DLOG("flattened {}", cont); } // remove original versions of wrapped functions @@ -218,12 +218,12 @@ static void flatten_tuples(World& world, size_t max_tuple_size) { for (auto unwrap_pair : unwrapped) inline_calls(unwrap_pair.second->as_nom()); - world.cleanup(); - debug_verify(world); + thorin.cleanup(); + debug_verify(thorin.world()); } -void flatten_tuples(World& world) { - flatten_tuples(world, std::numeric_limits::max()); +void flatten_tuples(Thorin& thorin) { + flatten_tuples(thorin, std::numeric_limits::max()); } } diff --git a/src/thorin/transform/flatten_tuples.h b/src/thorin/transform/flatten_tuples.h index 74b16c792..0e2cbb91d 100644 --- a/src/thorin/transform/flatten_tuples.h +++ b/src/thorin/transform/flatten_tuples.h @@ -2,6 +2,6 @@ namespace thorin { -void flatten_tuples(World& world); +void flatten_tuples(Thorin& thorin); } diff --git a/src/thorin/transform/hls_dataflow.cpp b/src/thorin/transform/hls_dataflow.cpp index 678c9293f..899a37080 100644 --- a/src/thorin/transform/hls_dataflow.cpp +++ b/src/thorin/transform/hls_dataflow.cpp @@ -17,7 +17,7 @@ using Cycle = std::vector>; // makes a data structure that maps global variables to their basic block and their HLS/ CGRA intrinsic void hls_cgra_global_analysis(World& world, std::vector& old_global_maps) { - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { auto kernel = scope.entry(); Def2Block global2block; // global, using basic block, HLS/CGRA for (auto& block : schedule(scope)) { @@ -92,9 +92,9 @@ void hls_cgra_dependency_analysis(Def2DependentBlocks& global2dependent_blocks, void connecting_blocks_old2new(std::vector& target_blocks, const Def2DependentBlocks def2dependent_blocks, Importer& importer, std::function select_block) { for (const auto& [old_common_global, pair] : def2dependent_blocks) { auto old_basicblock = select_block(pair); - if (importer.def_old2new_.contains(old_basicblock)) { - target_blocks.emplace_back(importer.def_old2new_[old_basicblock]); - } + if (importer.lookup(old_basicblock)) { + target_blocks.emplace_back(importer.lookup(old_basicblock)); + } } } @@ -102,8 +102,8 @@ void common_globals_old2new(Array& target_global, const Def2Dependen size_t i = 0; for (auto it = def2dependent_blocks.begin(); it != def2dependent_blocks.end(); ++it) { auto old_common_global = it->first; - if (importer.def_old2new_.contains(old_common_global)) { - target_global[i++] = importer.def_old2new_[old_common_global]; + if (importer.lookup(old_common_global)) { + target_global[i++] = importer.lookup(old_common_global); } } } @@ -112,10 +112,10 @@ void common_globals_old2new(Array& target_global, const Def2Dependen void params2cgra_ports(const Def2Def param2arg, const Def2DependentBlocks def2dependent_blocks, Importer& importer) { for (auto it = def2dependent_blocks.begin(); it != def2dependent_blocks.end(); ++it) { auto old_common_global = it->first; - if (importer.def_old2new_.contains(old_common_global)) { + if (importer.lookup(old_common_global)) { for (const auto& [param, arg] : param2arg) { if (arg->isa()) { - if (arg == importer.def_old2new_[old_common_global]) { + if (arg == importer.lookup(old_common_global)) { std::cout << "I AM HEREEEE!!!" << std::endl; param->dump(); // these params need to be check after their are written with hls_top in next lines of codes @@ -231,7 +231,7 @@ void target_cgra_modes(std::vector& defs2modes, const size_t dependent } else { auto& cgra_world = cont->world(); - Scope::for_each(cgra_world, [&](Scope& scope) { + ScopesForest(cgra_world).for_each([&](Scope& scope) { // Check if the scope contains a Continuation that matches the provided basic block (cont) auto continuation_itr = std::find_if(scope.defs().begin(), scope.defs().end(), [&](const auto& def) { if (def->template isa_nom()) { @@ -452,7 +452,7 @@ bool dependency_resolver(Dependencies& dependencies, const size_t dependent_kern bool has_cgra_callee(World& world) { auto found_cgra = false; - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { for (auto& block : schedule(scope)) { if (!block->has_body()) continue; @@ -532,8 +532,8 @@ void circle_analysis(Dependencies dependencies, World& world, size_t single_kern */ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_world, Importer& importer_cgra) { - auto& world = importer.world(); // world is hls world - auto& cgra_world = importer_cgra.world(); + auto& world = importer.dst(); // world is hls world + auto& cgra_world = importer_cgra.dst(); // TODO: rename to hls_new_kernels // the size of this vector is equal to the size of kernels with deps. @@ -547,7 +547,6 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w // hls_top should be transformed whenever there is a CGRA if (has_cgra_callee(old_world)) std::cout << "FOUND CGRA!" << std::endl; - std::vector old_global_maps; hls_cgra_global_analysis(old_world, old_global_maps); @@ -563,7 +562,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w }); - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { auto old_kernel = scope.entry(); // def is a global in hls world // mode states the global is written/read in a kernel @@ -571,9 +570,10 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w extract_kernel_channels(schedule(scope), hls_def2hls_mode); Array new_param_types(hls_def2hls_mode.size() + old_kernel->num_params()); - std::copy(old_kernel->type()->ops().begin(), - old_kernel->type()->ops().end(), - new_param_types.begin()); + for (auto i = 0; i < old_kernel->type()->num_ops(); i++) { + auto old_type = old_kernel->type()->op(i); + new_param_types[i] = old_type->as(); + } size_t i = old_kernel->num_params(); // This vector records pairs containing: // - The position of the channel parameter for the new kernel @@ -589,6 +589,8 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w // fn(mem, ret_cnt, ... , /channels/ ) auto new_kernel = world.continuation(world.fn_type(new_param_types), old_kernel->debug()); world.make_external(new_kernel); + new_kernel->attributes().cc = CC::C; + new_kernel->set_filter(new_kernel->all_false_filter()); kernel_new2old.emplace(new_kernel, old_kernel); @@ -599,22 +601,27 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w world.make_internal(old_kernel); - Rewriter rewriter; + //TODO: Instead of using this weird contraption, replace the importer in the HLS and CGRA backends respectively. + //These new importers would do the analysis and transformations from cgra_dataflow and hls_dataflow when the kernels are instantiated. + //Some additional analyses could be performed as well, during import possibly. + + Rewriter rewriter(world, world); + // Map the parameters of the old kernel to the first N parameters of the new one // The channels used inside the kernel are mapped to the parameters N + 1, N + 2, ... for (auto pair : index2def) { auto param = new_kernel->param(pair.first); - rewriter.old2new[pair.second] = param; + rewriter.insert(pair.second, param); param2arg[param] = pair.second; // (channel params, globals) } for (auto def : scope.defs()) { if (auto cont = def->isa_nom()) { // Copy the basic block by calling stub // Or reuse the newly created kernel copy if def is the old kernel - auto new_cont = def == old_kernel ? new_kernel : cont->stub(); - rewriter.old2new[cont] = new_cont; + auto new_cont = def == old_kernel ? new_kernel : cont->stub(rewriter, cont->type()); + rewriter.insert(cont, new_cont); for (size_t i = 0; i < cont->num_params(); ++i) - rewriter.old2new[cont->param(i)] = new_cont->param(i); //non-channel params + rewriter.insert(cont->param(i), new_cont->param(i)); //non-channel params } } // Rewriting the basic blocks of the kernel using the map @@ -622,7 +629,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w if (auto cont = def->isa_nom()) { // all basic blocks of the scope if (!cont->has_body()) continue; auto body = cont->body(); - auto new_cont = rewriter.old2new[cont]->isa_nom(); + auto new_cont = rewriter.lookup(cont)->isa_nom(); auto new_callee = rewriter.instantiate(body->callee()); Array new_args(body->num_args()); for ( size_t i = 0; i < body->num_args(); ++i) @@ -630,6 +637,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w new_cont->jump(new_callee, new_args, cont->debug()); } } + if (!is_single_kernel(new_kernel)) kernels_ch_modes.emplace_back(hls_def2hls_mode); }); @@ -718,9 +726,9 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w size_t i = 0; for (auto it = def2dependent_blocks.begin(); it != def2dependent_blocks.end(); ++it) { auto old_common_global = it->first; //def type - if (importer.def_old2new_.contains(old_common_global)) { + if (importer.lookup(old_common_global)) { for (const auto& [global, param] : global2param) { - if (global == importer.def_old2new_[old_common_global]) { + if (global == importer.lookup(old_common_global)) { // this param2arg is after replacing global args with hls_top params that connect to cgra // basically we can name it kernelparam2hls_top_cgra_param auto top_param = param2arg[param]; @@ -759,7 +767,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w std::vector old_kernels_params; for (auto param : hls_top->params()) { if (arg2param.contains(param)) { - auto new_kernel_param = arg2param[param]->as(); + auto new_kernel_param = arg2param[param]->as(); auto old_kernel = kernel_new2old[new_kernel_param->continuation()]; old_kernels_params.emplace_back(old_kernel->as_nom()->param(new_kernel_param->index())); } @@ -771,7 +779,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w for (auto def : old_world.defs()) { if (auto ocontinuation = def->isa_nom()) { auto ncontinuation = elem->as()->continuation(); //TODO: for optimization This line can go out of inner loop - if (ncontinuation == importer.def_old2new_[ocontinuation]) { + if (ncontinuation == importer.lookup(ocontinuation)) { elem = ocontinuation->param(elem->as()->index()); break; } @@ -780,6 +788,8 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w } std::vector target_cgra_kernels_ch_modes; // an elem for each cgra-hls kernel + assert(target_blocks_in_cgra_world.size() == old_globals2old_dependent_blocks.size()); + assert(target_blocks_in_hls_world.size() == old_globals2old_dependent_blocks.size()); target_cgra_modes(target_cgra_kernels_ch_modes, old_globals2old_dependent_blocks.size(), target_blocks_in_cgra_world, target_blocks_in_hls_world); auto enter = world.enter(hls_top->mem_param()); @@ -904,7 +914,7 @@ DeviceDefs hls_dataflow(Importer& importer, Top2Kernel& top2kernel, World& old_w debug_verify(world); - world.cleanup(); + //world.cleanup(); return std::make_tuple(old_kernels_params, old_globals2old_dependent_blocks, index2mode); } diff --git a/src/thorin/transform/hls_kernel_launch.cpp b/src/thorin/transform/hls_kernel_launch.cpp index 928fd5d22..f3ceb4b8d 100644 --- a/src/thorin/transform/hls_kernel_launch.cpp +++ b/src/thorin/transform/hls_kernel_launch.cpp @@ -4,6 +4,7 @@ #include "thorin/continuation.h" #include "thorin/analyses/scope.h" #include "thorin/be/codegen.h" +#include "thorin/be/runtime.h" #include "thorin/analyses/verify.h" namespace thorin { @@ -54,11 +55,11 @@ static Continuation* make_opencl_intrinsic(World& world, const Continuation* con // Basicaly on host side we assume that the top channel type is a struct with a single bool field which will is set to true. if (is_channel_type(param->type())) { auto struct_type = world.struct_type("channel", 1); - struct_type->set(0, world.type_bool()); + struct_type->set_op(0, world.type_bool()); return struct_type->as(); } - return param->type(); + return const_cast(param->type()); }); auto opencl_type = world.fn_type(opencl_param_types); @@ -117,12 +118,12 @@ void hls_kernel_launch(World& world, HlsDeviceParams& device_params, Cont2Config bool last_hls_found = false; Continuation* opencl = nullptr; - const size_t base_opencl_param_num = LaunchArgs::Num; + const size_t base_opencl_param_num = KernelLaunchArgs::Num; Array opencl_args(base_opencl_param_num + top_concrete_params.size()); // TODO: perf opt, we only need to access the main scope // Maybe using world.externals() would be better - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { Schedule scheduled = schedule(scope); for (auto& block : scheduled) { @@ -134,7 +135,7 @@ void hls_kernel_launch(World& world, HlsDeviceParams& device_params, Cont2Config if (auto hls_callee = has_hls_callee(block)) { auto cont_mem_obj = block->mem_param(); auto callee_continuation = hls_callee->isa_nom(); - Continuation* last_hls_cont; + Continuation* last_hls_cont = nullptr; if (!last_hls_found) { // TODO I'm at a loss for what is intended here. This is an assignment - not a check, the net result // is the _only the first_ block with an HLS callee will enter this, which means the first block in the schedule diff --git a/src/thorin/transform/hoist_enters.cpp b/src/thorin/transform/hoist_enters.cpp index c052498e2..3f8997915 100644 --- a/src/thorin/transform/hoist_enters.cpp +++ b/src/thorin/transform/hoist_enters.cpp @@ -24,42 +24,54 @@ static void find_enters(std::deque& enters, Continuation* continua find_enters(enters, mem_param); } -static void hoist_enters(const Scope& scope) { +static bool hoist_enters(const Scope& scope) { World& world = scope.world(); std::deque enters; for (auto n : scope.f_cfg().reverse_post_order()) find_enters(enters, n->continuation()); - if (enters.empty() || enters[0]->mem() != scope.entry()->mem_param()) { world.VLOG("cannot optimize {} - didn't find entry enter", scope.entry()); - return; + return false; } auto entry_enter = enters[0]; auto frame = entry_enter->out_frame(); enters.pop_front(); + bool todo = false; for (auto i = enters.rbegin(), e = enters.rend(); i != e; ++i) { auto old_enter = *i; for (auto use : old_enter->out_frame()->uses()) { auto slot = use->as(); + if (slot->uses().size() > 0) + todo = true; slot->replace_uses(world.slot(slot->alloced_type(), frame, slot->debug())); assert(slot->num_uses() == 0); } } - - for (auto i = enters.rbegin(), e = enters.rend(); i != e; ++i) - (*i)->out_mem()->replace_uses((*i)->mem()); - - if (frame->num_uses() == 0) - entry_enter->out_mem()->replace_uses(entry_enter->mem()); + return todo; } -void hoist_enters(World& world) { - Scope::for_each(world, [] (const Scope& scope) { hoist_enters(scope); }); - world.cleanup(); +// TODO: rewrite this and put it out of its misery +void hoist_enters(Thorin& thorin) { + bool todo = false; + do { + todo = false; + ScopesForest forest(thorin.world()); + for (auto cont : thorin.world().copy_continuations()) { + if (!cont->has_body()) + continue; + auto& scope = forest.get_scope(cont); + if(!scope.has_free_params()) { + if (!todo) todo = hoist_enters(scope); + if (todo) + break; + } + } + } while (todo); + thorin.cleanup(); } } diff --git a/src/thorin/transform/hoist_enters.h b/src/thorin/transform/hoist_enters.h index bff4d9492..5d3155f76 100644 --- a/src/thorin/transform/hoist_enters.h +++ b/src/thorin/transform/hoist_enters.h @@ -5,7 +5,7 @@ namespace thorin { class World; -void hoist_enters(World&); +void hoist_enters(Thorin&); } diff --git a/src/thorin/transform/importer.cpp b/src/thorin/transform/importer.cpp index f125585de..2bebb9d07 100644 --- a/src/thorin/transform/importer.cpp +++ b/src/thorin/transform/importer.cpp @@ -1,100 +1,136 @@ #include "thorin/transform/importer.h" +#include "thorin/transform/mangle.h" +#include "thorin/primop.h" namespace thorin { -const Type* Importer::import(const Type* otype) { - if (auto ntype = type_old2new_.lookup(otype)) { - assert(&(*ntype)->table() == &world_); - return *ntype; - } - size_t size = otype->num_ops(); - - if (auto nominal_type = otype->isa()) { - auto ntype = nominal_type->stub(world_); - type_old2new_[otype] = ntype; - for (size_t i = 0; i != size; ++i) - ntype->set(i, import(otype->op(i))); - return ntype; - } - - Array nops(size); - for (size_t i = 0; i != size; ++i) - nops[i] = import(otype->op(i)); - - auto ntype = otype->rebuild(world_, nops); - type_old2new_[otype] = ntype; - assert(&ntype->table() == &world_); - - return ntype; -} - -const Def* Importer::import(const Def* odef) { - if (auto ndef = def_old2new_.lookup(odef)) { - assert(&(*ndef)->world() == &world_); - return *ndef; - } - - auto ntype = import(odef->type()); - - if (auto oparam = odef->isa()) { - import(oparam->continuation())->as_nom(); - auto nparam = def_old2new_[oparam]; - assert(nparam && &nparam->world() == &world_); - return nparam; - } - - if (auto ofilter = odef->isa()) { - 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()); - return nfilter; - } - - Continuation* ncontinuation = nullptr; - if (auto ocontinuation = odef->isa_nom()) { // create stub in new world - 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(); - if (ocontinuation == 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()); - 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); +const Def* Importer::rewrite(const Def* const odef) { + assert(&odef->world() == &src()); + if (auto memop = odef->isa()) { + // Optimise out dead loads when importing + if (memop->isa() || memop->isa()) { + if (memop->out(1)->num_uses() == 0) { + auto imported_mem = import(memop->mem()); + auto imported_ty = import(memop->out(1)->type())->as(); + todo_ = true; + return(dst().tuple({ imported_mem, dst().bottom(imported_ty) })); + } + } + } else if (auto app = odef->isa()) { + // eat calls to known continuations that are only used once + if (auto callee = app->callee()->isa_nom()) { + if (callee->has_body() && !src().is_external(callee) && callee->can_be_inlined()) { + todo_ = true; + src().VLOG("simplify: inlining continuation {} because it is called exactly once", callee); + for (size_t i = 0; i < callee->num_params(); i++) + insert(callee->param(i), import(app->arg(i))); + + return instantiate(callee->body()); + } + } + } else if (auto closure = odef->isa()) { + bool only_called = true; + for (auto use : closure->uses()) { + if (use.def()->isa() && use.index() == 0) + continue; + only_called = false; + break; + } + if (only_called) { + bool self_param_ok = true; + for (auto use: closure->fn()->params().back()->uses()) { + // the closure argument can be used, but only to extract the environment! + if (auto extract = use.def()->isa(); extract && is_primlit(extract->index(), 1)) + continue; + self_param_ok = false; + break; + } + if (self_param_ok) { + src().VLOG("simplify: eliminating closure {} as it is never passed as an argument, and is not recursive", closure); + Array args(closure->fn()->num_params()); + args.back() = closure; + todo_ = true; + return instantiate(drop(closure->fn(), args)); + } + } + } else if (auto cont = odef->isa_nom()) { + if (cont->has_body()) { + auto body = cont->body(); + // try to subsume continuations which call a def + // (that is free within that continuation) with that def + auto callee = body->callee(); + auto& scope = forest_->get_scope(cont); + if (!scope.contains(callee)) { + if (src().is_external(cont) || callee->type()->tag() != Node_FnType) + goto rebuild; + + if (body->args() == cont->params_as_defs()) { + src().VLOG("simplify: continuation {} calls a free def: {}", cont->unique_name(), body->callee()); + // We completely replace the original continuation + // If we don't do so, then we miss some simplifications + return instantiate(body->callee()); + } else { + // build the permutation of the arguments + Array perm(body->num_args()); + bool is_permutation = true; + for (size_t i = 0, e = body->num_args(); i != e; ++i) { + auto param_it = std::find(cont->params().begin(), + cont->params().end(), + body->arg(i)); + + if (param_it == cont->params().end()) { + is_permutation = false; + break; + } + + perm[i] = param_it - cont->params().begin(); + } + + if (!is_permutation) + goto rebuild; + } + + bool has_calls = false; + // for every use of the continuation at a call site, + // permute the arguments and call the parameter instead + for (auto use : cont->copy_uses()) { + auto uapp = use->isa(); + if (uapp && use.index() == 0) { + todo_ = true; + has_calls = true; + break; + } + } + + if (has_calls) { + auto rebuilt = cont->stub(*this, instantiate(cont->type())->as()); + src().VLOG("simplify: continuation {} calls a free def: {} (with permuted args), introducing a wrapper: {}", cont->unique_name(), body->callee(), rebuilt); + auto wrapped = dst().run(rebuilt); + insert(odef, wrapped); + + rebuilt->set_body(instantiate(body)->as()); + return wrapped; + } + } } - - def_old2new_[ocontinuation] = ncontinuation; - - if (ocontinuation->is_external()) - world().make_external(ncontinuation); - } - - size_t size = odef->num_ops(); - Array nops(size); - for (size_t i = 0; i != size; ++i) { - assert(odef->op(i) != odef); - nops[i] = import(odef->op(i)); - assert(&nops[i]->world() == &world()); } + rebuild: + auto ndef = Rewriter::rewrite(odef); if (odef->isa_structural()) { - auto ndef = odef->rebuild(world(), ntype, nops); + // If some substitution took place + // TODO: this might be dead code at the moment todo_ |= odef->tag() != ndef->tag(); - return def_old2new_[odef] = ndef; } + return ndef; +} - assert(ncontinuation && &ncontinuation->world() == &world()); - auto napp = nops[0]->isa(); - if (napp) - ncontinuation->set_body(napp); - ncontinuation->set_filter(nops[1]->as()); - ncontinuation->verify(); - return ncontinuation; +const Def* Importer::find_origin(const Def* ndef) { + for (auto def : src().defs()) { + if (ndef == lookup(def)) + return def; + } + return nullptr; } } diff --git a/src/thorin/transform/importer.h b/src/thorin/transform/importer.h index c34db9ef3..01cabe42c 100644 --- a/src/thorin/transform/importer.h +++ b/src/thorin/transform/importer.h @@ -3,31 +3,30 @@ #include "thorin/world.h" #include "thorin/config.h" +#include "thorin/transform/rewrite.h" +#include "thorin/analyses/scope.h" namespace thorin { -class Importer { +class Importer : public Rewriter { public: - Importer(World& src) - : world_(src) + explicit Importer(World& src, World& dst) + : Rewriter(src, dst), forest_(std::make_unique(src)) { + assert(&src != &dst); if (src.is_pe_done()) - world_.mark_pe_done(); -#if THORIN_ENABLE_CHECKS - if (src.track_history()) - world_.enable_history(true); -#endif + dst.mark_pe_done(); } - World& world() { return world_; } - const Type* import(const Type*); - const Def* import(const Def*); + const Def* import(const Def* odef) { return instantiate(odef); } + const Def* find_origin(const Def* ndef); bool todo() const { return todo_; } -public: - Type2Type type_old2new_; - Def2Def def_old2new_; - World world_; +protected: + const Def* rewrite(const Def* odef) override; + +private: + std::unique_ptr forest_; bool todo_ = false; }; diff --git a/src/thorin/transform/inliner.cpp b/src/thorin/transform/inliner.cpp index 175f2a518..ffaa8f309 100644 --- a/src/thorin/transform/inliner.cpp +++ b/src/thorin/transform/inliner.cpp @@ -36,11 +36,12 @@ void force_inline(Scope& scope, int threshold) { } } -void inliner(World& world) { +void inliner(Thorin& thorin) { + World& world = thorin.world(); world.VLOG("start inliner"); - static const int factor = 4; - static const int offset = 4; + static const int factor = 8; + static const int offset = 8; ContinuationMap> continuation2scope; @@ -68,7 +69,7 @@ void inliner(World& world) { return nullptr; }; - Scope::for_each(world, [&] (Scope& scope) { + ScopesForest(world).for_each([&] (Scope& scope) { bool dirty = false; for (auto n : scope.f_cfg().post_order()) { auto continuation = n->continuation(); @@ -95,7 +96,7 @@ void inliner(World& world) { world.VLOG("stop inliner"); debug_verify(world); - world.cleanup(); + thorin.cleanup(); } diff --git a/src/thorin/transform/inliner.h b/src/thorin/transform/inliner.h index 89b1dcfd4..dc22de086 100644 --- a/src/thorin/transform/inliner.h +++ b/src/thorin/transform/inliner.h @@ -11,7 +11,7 @@ class World; * If there still remain functions to be inlined, warnings will be emitted */ void force_inline(Scope& scope, int threshold); -void inliner(World& world); +void inliner(Thorin&); } diff --git a/src/thorin/transform/lift_builtins.cpp b/src/thorin/transform/lift_builtins.cpp index 82dd10d43..ea4261578 100644 --- a/src/thorin/transform/lift_builtins.cpp +++ b/src/thorin/transform/lift_builtins.cpp @@ -63,13 +63,15 @@ void lift_pipeline(World& world) { } -void lift_builtins(World& world) { +void lift_builtins(Thorin& thorin) { // This must be run first - lift_pipeline(world); + lift_pipeline(thorin.world()); while (true) { + World& world = thorin.world(); Continuation* cur = nullptr; - Scope::for_each(world, [&] (const Scope& scope) { + ScopesForest forest(world); + forest.for_each([&] (const Scope& scope) { if (cur) return; for (auto n : scope.f_cfg().post_order()) { if (n->continuation()->order() <= 1) @@ -103,7 +105,13 @@ void lift_builtins(World& world) { } } - auto lifted = lift(scope, defs); + Continuation * lifted; + if (scope.entry()->has_body()) + lifted = lift(scope, scope.entry(), defs); + else { + assert(defs.size() == 0 && "Scopes without body cannot have free defs."); + lifted = scope.entry(); + } for (auto use : cur->copy_uses()) { if (auto uapp = use->isa()) { if (auto callee = uapp->callee()->isa_nom()) { @@ -125,7 +133,7 @@ void lift_builtins(World& world) { } } - world.cleanup(); + thorin.cleanup(); } } diff --git a/src/thorin/transform/lift_builtins.h b/src/thorin/transform/lift_builtins.h index 7da4d5e05..e9c413e0b 100644 --- a/src/thorin/transform/lift_builtins.h +++ b/src/thorin/transform/lift_builtins.h @@ -5,7 +5,7 @@ namespace thorin { class World; -void lift_builtins(World&); +void lift_builtins(Thorin&); } diff --git a/src/thorin/transform/mangle.cpp b/src/thorin/transform/mangle.cpp index 0dcc0d909..98b3a559c 100644 --- a/src/thorin/transform/mangle.cpp +++ b/src/thorin/transform/mangle.cpp @@ -7,31 +7,16 @@ namespace thorin { -const Def* Rewriter::instantiate(const Def* odef) { - if (auto ndef = old2new.lookup(odef)) return *ndef; - - if (odef->isa_structural()) { - Array nops(odef->num_ops()); - for (size_t i = 0; i != odef->num_ops(); ++i) - nops[i] = instantiate(odef->op(i)); - - auto nprimop = odef->rebuild(odef->world(), odef->type(), nops); - return old2new[odef] = nprimop; - } - - return old2new[odef] = odef; -} - /// Mangles a continuation's scope /// @p args has the size of the original continuation, a null entry means the parameter remains, non-null substitutes it in scope and removes it from the signature /// @p lift lists defs that should be replaced by a fresh param, to be appended at the end of the signature -Mangler::Mangler(const Scope& scope, Defs args, Defs lift) - : scope_(scope) +Mangler::Mangler(const Scope& scope, Continuation* entry, Defs args, Defs lift) + : Rewriter(scope.world()) + , scope_(scope) , args_(args) , lift_(lift) - , old_entry_(scope.entry()) + , old_entry_(entry) , defs_(scope.defs().capacity()) - , def2def_(scope.defs().capacity()) { assert(old_entry()->has_body()); assert(args.size() == old_entry()->num_params()); @@ -52,6 +37,10 @@ Mangler::Mangler(const Scope& scope, Defs args, Defs lift) for (auto use : pop(queue)->uses()) enqueue(use); } + + is_dropping_ = std::any_of(args.begin(), args.end(), [&](const auto& item) { + return item != nullptr; + }); } Continuation* Mangler::mangle() { @@ -62,120 +51,120 @@ Continuation* Mangler::mangle() { param_types.emplace_back(old_entry()->param(i)->type()); // TODO reduce } - auto fn_type = world().fn_type(param_types); - new_entry_ = world().continuation(fn_type, old_entry()->debug_history()); + auto fn_type = dst().fn_type(param_types); + new_entry_ = dst().continuation(fn_type, old_entry()->debug()); - // map value params - def2def_[old_entry()] = old_entry(); for (size_t i = 0, j = 0, e = old_entry()->num_params(); i != e; ++i) { auto old_param = old_entry()->param(i); if (auto def = args_[i]) - def2def_[old_param] = def; + insert(old_param, def); else { // we recreate params that aren't specialized auto new_param = new_entry()->param(j++); - def2def_[old_param] = new_param; + insert(old_param, new_param); new_param->set_name(old_param->name()); } } for (auto def : lift_) - def2def_[def] = new_entry()->append_param(def->type()); // TODO reduce + insert(def, new_entry()->append_param(def->type())); + + // if we are dropping parameters, we can't necessarily rewrite the entry, see also note about applications in Mangler::rewrite() + if (is_dropping_) + 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); + } - // mangle filter + // cut/widen filter if (!old_entry()->filter()->is_empty()) { Array new_conditions(new_entry()->num_params()); size_t j = 0; for (size_t i = 0, e = old_entry()->num_params(); i != e; ++i) { if (args_[i] == nullptr) - new_conditions[j++] = mangle(old_entry()->filter()->condition(i)); + new_conditions[j++] = instantiate(old_entry()->filter()->condition(i)); } for (size_t e = new_entry()->num_params(); j != e; ++j) - new_conditions[j] = world().literal_bool(false, Debug{}); + new_conditions[j] = dst().literal_bool(false, Debug{}); - new_entry()->set_filter(world().filter(new_conditions, old_entry()->filter()->debug())); + new_entry()->set_filter(dst().filter(new_conditions, old_entry()->filter()->debug())); } - new_entry()->set_body(mangle_body(old_entry()->body())); - + new_entry()->set_body(instantiate(old_entry()->body())->as()); new_entry()->verify(); return new_entry(); } -Continuation* Mangler::mangle_head(Continuation* old_continuation) { - assert(!def2def_.contains(old_continuation)); - assert(old_continuation->has_body()); - Continuation* new_continuation = old_continuation->stub(); - def2def_[old_continuation] = new_continuation; - - for (size_t i = 0, e = old_continuation->num_params(); i != e; ++i) - def2def_[old_continuation->param(i)] = new_continuation->param(i); - - return new_continuation; -} - -const App* Mangler::mangle_body(const App* old_body) { - Array nops(old_body->num_ops()); - for (size_t i = 0, e = nops.size(); i != e; ++i) - nops[i] = mangle(old_body->op(i)); - - Defs nargs(nops.skip_front()); // new args of body - auto ntarget = nops.front(); // new target of body - - // check whether we can optimize tail recursion - if (ntarget == old_entry()) { - std::vector cut; - bool substitute = true; - for (size_t i = 0, e = args_.size(); i != e && substitute; ++i) { - if (auto def = args_[i]) { - substitute &= def == nargs[i]; - cut.push_back(i); +const Def* Mangler::rewrite(const Def* old_def) { + if (!within(old_def)) + return old_def; // we leave free variables alone + if (auto param = old_def->isa()) + assert(within(param->continuation()) && "if the param is not free, the continuation should not be either!"); + if (auto app = old_def->isa()) { + // HACK: only rebuild the branch we actually take + // this is a hack because the uses can be stale (dead stuff can have transitive uses) + if (auto br = app->callee()->isa_nom(); br && br->intrinsic() == Intrinsic::Branch) { + auto condition = instantiate(app->arg(1)); + if (auto lit = condition->isa()) { + auto mem = instantiate(app->arg(0)); + auto target = lit->value().get_bool() ? instantiate(app->arg(2)) : instantiate(app->arg(3)); + return dst().app(target, { mem }); } } - - if (substitute) { - // Q: why not always change to the mangled continuation ? - // A: if you drop a parameter it is replaced by some def (likely a free param), which will be identical for all recursive calls, since they live in the same scope (that's how scopes work) - // so if there originally was a recursive call that specified the to-be-dropped parameter to something else, we need to call the unmangled original to preserve semantics - const auto& args = concat(nargs.cut(cut), new_entry()->params().get_back(lift_.size())); - return world().app(new_entry(), args, old_body->debug()); // TODO debug + if (auto sw = app->callee()->isa_nom(); sw && sw->intrinsic() == Intrinsic::Match) { + auto index = instantiate(app->arg(1)); + if (auto lit = index->isa()) { + for (size_t i = 3; i < app->num_args(); i++) { + auto opattern = src().extract(app->arg(i), 0_s)->as(); + if (instantiate(opattern) == lit) { + auto mem = instantiate(app->arg(0)); + auto target = dst().extract(instantiate(app->arg(i)), 1); + return dst().app(target, { mem }, old_def->debug()); + } + } + } } } + auto ndef = Rewriter::rewrite(old_def); + if (auto app = ndef->isa()) { + // If you drop a parameter it is replaced by some other def, which will be identical for all recursive calls, because it's now specialised + // If there originally was a recursive call that specified the to-be-dropped parameter to something else, we need to call the unmangled original to preserve semantics + if (is_dropping_ && app->callee() == old_entry()) { + auto oargs = app->args(); + auto nargs = Array(oargs.size(), [&](size_t i) { return rewrite(oargs[i]); }); + std::vector cut; + bool substitute = true; + for (size_t i = 0, e = args_.size(); i != e && substitute; ++i) { + if (auto def = args_[i]) { + substitute &= def == nargs[i]; + cut.push_back(i); + } + } - return world().app(ntarget, nargs, old_body->debug()); // TODO debug -} - -const Def* Mangler::mangle(const Def* old_def) { - if (auto new_def = def2def_.lookup(old_def)) - return *new_def; - else if (!within(old_def)) - return old_def; // we leave free variables alone - else if (auto old_continuation = old_def->isa_nom()) { - auto new_continuation = mangle_head(old_continuation); - if (old_continuation->has_body()) - new_continuation->set_body(mangle_body(old_continuation->body())); - return new_continuation; - } else if (auto param = old_def->isa()) { - assert(within(param->continuation())); - mangle(param->continuation()); - assert(def2def_.contains(param)); - return def2def_[param]; - } else { - Array nops(old_def->num_ops()); - for (size_t i = 0, e = old_def->num_ops(); i != e; ++i) - nops[i] = mangle(old_def->op(i)); - - auto type = old_def->type(); // TODO reduce - return def2def_[old_def] = old_def->rebuild(world(), type, nops); + if (substitute) { + const auto& args = concat(nargs.cut(cut), new_entry()->params().get_back(lift_.size())); + return dst().app(new_entry(), args, old_def->debug()); // TODO debug + } + } } + return ndef; } //------------------------------------------------------------------------------ -Continuation* mangle(const Scope& scope, Defs args, Defs lift) { - return Mangler(scope, args, lift).mangle(); +Continuation* mangle(const Scope& scope, Continuation* entry, Defs args, Defs lift) { + return Mangler(scope, entry, args, lift).mangle(); } Continuation* drop(const Def* callee, const Defs specialized_args) { diff --git a/src/thorin/transform/mangle.h b/src/thorin/transform/mangle.h index 52621968d..353c69fee 100644 --- a/src/thorin/transform/mangle.h +++ b/src/thorin/transform/mangle.h @@ -3,55 +3,48 @@ #include "thorin/type.h" #include "thorin/analyses/scope.h" +#include "thorin/transform/rewrite.h" namespace thorin { -struct Rewriter { - const Def* instantiate(const Def* odef); - Def2Def old2new; -}; - -class Mangler { +class Mangler : Rewriter { public: - Mangler(const Scope& scope, Defs args, Defs lift); + Mangler(const Scope& scope, Continuation* entry, Defs args, Defs lift); const Scope& scope() const { return scope_; } - World& world() const { return scope_.world(); } Continuation* mangle(); Continuation* old_entry() const { return old_entry_; } Continuation* new_entry() const { return new_entry_; } private: - const App* mangle_body(const App* obody); - Continuation* mangle_head(Continuation* ocontinuation); - const Def* mangle(const Def* odef); + const Def* rewrite(const Def* odef) override; bool within(const Def* def) { return scope().contains(def) || defs_.contains(def); } + bool is_dropping_; + const Scope& scope_; Defs args_; Defs lift_; - Type2Type type2type_; Continuation* old_entry_; Continuation* new_entry_; DefSet defs_; - Def2Def def2def_; }; -Continuation* mangle(const Scope&, Defs args, Defs lift); +Continuation* mangle(const Scope&, Continuation* entry, Defs args, Defs lift); inline Continuation* drop(const Scope& scope, Defs args) { - return mangle(scope, args, Array()); + return mangle(scope, scope.entry(), args, Array()); } Continuation* drop(const Def* callee, const Defs specialized_args); -inline Continuation* lift(const Scope& scope, Defs defs) { - return mangle(scope, Array(scope.entry()->num_params()), defs); +inline Continuation* lift(const Scope& scope, Continuation* entry, Defs defs) { + return mangle(scope, entry, Array(entry->num_params()), defs); } inline Continuation* clone(const Scope& scope) { - return mangle(scope, Array(scope.entry()->num_params()), Defs()); + return mangle(scope, scope.entry(), Array(scope.entry()->num_params()), Defs()); } } diff --git a/src/thorin/transform/partial_evaluation.cpp b/src/thorin/transform/partial_evaluation.cpp index 28d383a38..f0595640e 100644 --- a/src/thorin/transform/partial_evaluation.cpp +++ b/src/thorin/transform/partial_evaluation.cpp @@ -2,6 +2,7 @@ #include "thorin/world.h" #include "thorin/transform/mangle.h" #include "thorin/util/hash.h" +#include "partial_evaluation.h" namespace thorin { @@ -34,40 +35,32 @@ class PartialEvaluator { bool lower2cff_; HashMap cache_; ContinuationSet done_; - std::queue queue_; - ContinuationMap top_level_; + unique_queue queue_; size_t boundary_; }; +const Def* BetaReducer::rewrite(const Def* odef) { + // leave nominal defs alone + if (odef->isa_nom()) + return odef; + return Rewriter::rewrite(odef); +} + class CondEval { public: - CondEval(Continuation* callee, Defs args, ContinuationMap& top_level) - : callee_(callee) - , top_level_(top_level) + CondEval(Continuation* callee, ScopesForest& forest, Defs args) + : reducer_(callee->world()) + , callee_(callee) + , forest_(forest) { assert(callee->filter()->is_empty() || callee->filter()->size() == args.size()); assert(callee->num_params() == args.size()); for (size_t i = 0, e = args.size(); i != e; ++i) - old2new_[callee->param(i)] = args[i]; + reducer_.provide_arg(callee->param(i), args[i]); } World& world() { return callee_->world(); } - const Def* instantiate(const Def* odef) { - if (auto ndef = old2new_.lookup(odef)) - return *ndef; - - if (odef->isa_structural()) { - Array nops(odef->num_ops()); - for (size_t i = 0; i != odef->num_ops(); ++i) - nops[i] = instantiate(odef->op(i)); - - auto nprimop = odef->rebuild(world(), odef->type(), nops); - return old2new_[odef] = nprimop; - } - - return old2new_[odef] = odef; - } bool eval(size_t i, bool lower2cff) { // the only higher order parameter that is allowed is a single 1st-order fn-parameter of a top-level continuation @@ -75,13 +68,13 @@ class CondEval { auto order = callee_->param(i)->order(); if (lower2cff) if(order >= 2 || (order == 1 - && (!callee_->param(i)->type()->isa() - || (!callee_->is_returning() || (!is_top_level(callee_)))))) { - world().DLOG("bad param({}) {} of continuation {}", i, callee_->param(i), callee_); - return true; - } + && (!callee_->param(i)->type()->isa() + || (!callee_->is_returning() || (!is_top_level(callee_)))))) { + world().DLOG("bad param({}) {} of continuation {}", i, callee_->param(i), callee_); + return true; + } - return (!callee_->is_exported() && callee_->can_be_inlined()) || is_one(instantiate(filter(i))); + return ((!callee_->is_exported() || callee_->attributes().cc == CC::Thorin) && callee_->can_be_inlined()) || is_one(reducer_.instantiate(filter(i))); //return is_one(instantiate(filter(i))); } @@ -90,39 +83,13 @@ class CondEval { } bool is_top_level(Continuation* continuation) { - auto p = top_level_.emplace(continuation, true); - if (!p.second) - return p.first->second; - - Scope scope(continuation); - unique_queue queue; - - for (auto def : scope.free()) - queue.push(def); - - while (!queue.empty()) { - auto def = queue.pop(); - - if (def->isa()) // if FV in this scope is a param, this cont can't be top-level - return top_level_[continuation] = false; - if (auto free_cn = def->isa_nom()) { - // if we have a non-top level continuation in scope as a free variable, - // then it must be bound by some outer continuation, and so we aren't top-level - if (!is_top_level(free_cn)) - return top_level_[continuation] = false; - } else { - for (auto op : def->ops()) - queue.push(op); - } - } - - return top_level_[continuation] = true; + return !forest_.get_scope(continuation).has_free_params(); } private: + BetaReducer reducer_; Continuation* callee_; - Def2Def old2new_; - ContinuationMap& top_level_; + ScopesForest& forest_; }; void PartialEvaluator::eat_pe_info(Continuation* cur) { @@ -146,14 +113,15 @@ void PartialEvaluator::eat_pe_info(Continuation* cur) { bool PartialEvaluator::run() { bool todo = false; - for (auto&& [_, cont] : world().externals()) { + for (auto&& [_, def] : world().externals()) { + auto cont = def->isa(); + if (!cont) continue; if (!cont->has_body()) continue; enqueue(cont); - top_level_[cont] = true; } while (!queue_.empty()) { - auto continuation = pop(queue_); + auto continuation = queue_.pop(); bool force_fold = false; @@ -174,7 +142,9 @@ bool PartialEvaluator::run() { } if (callee->has_body()) { - CondEval cond_eval(callee, body->args(), top_level_); + // TODO cache the forest and only rebuild it when we need to + ScopesForest forest(world()); + CondEval cond_eval(callee, forest, body->args()); std::vector specialize(body->num_args()); @@ -192,12 +162,23 @@ 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); target = drop(callee, specialize); todo = true; } jump_to_dropped_call(continuation, target, specialize); + while (callee && callee->never_called()) { + if (callee->has_body()) { + auto new_callee = const_cast(callee->body()->callee()->isa()); + callee->destroy("partial_evaluation"); + callee = new_callee; + } else { + callee = nullptr; + } + } + if (lower2cff_ && fold) { // re-examine next iteration: // maybe the specialization is not top-level anymore which might need further specialization diff --git a/src/thorin/transform/partial_evaluation.h b/src/thorin/transform/partial_evaluation.h index 4231e9fd6..fd5c2f908 100644 --- a/src/thorin/transform/partial_evaluation.h +++ b/src/thorin/transform/partial_evaluation.h @@ -1,10 +1,24 @@ #ifndef THORIN_TRANSFORM_PARTIAL_EVALUATION_H #define THORIN_TRANSFORM_PARTIAL_EVALUATION_H +#include "thorin/transform/rewrite.h" + namespace thorin { class World; +class BetaReducer : public Rewriter { +public: + BetaReducer(World& w) : Rewriter(w) {} + + void provide_arg(const Param* param, const Def* arg) { + insert(param, arg); + } + +protected: + const Def* rewrite(const Def* odef) override; +}; + bool partial_evaluation(World&, bool lower2cff = false); } diff --git a/src/thorin/transform/resolve_loads.cpp b/src/thorin/transform/resolve_loads.cpp index d0a687ce4..ba916b837 100644 --- a/src/thorin/transform/resolve_loads.cpp +++ b/src/thorin/transform/resolve_loads.cpp @@ -95,7 +95,7 @@ class ResolveLoads { return it->second; if (auto global = alloc->isa()) { // Immutable globals will remain set to their initial value - if (!global->is_mutable()) + if (!global->is_mutable() && (!global->is_external() || !global->init()->isa())) return mapping[alloc] = global->init(); } // Nothing is known about this allocation yet @@ -233,7 +233,7 @@ public: \ while (true) { while (auto bitcast = ptr->isa()) ptr = bitcast->from(); - if (ptr->isa() && !ptr->as()->is_mutable()) + if (ptr->isa() && !ptr->as()->is_mutable() && (!ptr->as()->is_external() || !ptr->as()->init()->isa())) return ptr; // If first == ptr, we are looking at the pointed value. // In that case, we need to make sure the pointer does not escape. diff --git a/src/thorin/transform/rewrite.cpp b/src/thorin/transform/rewrite.cpp new file mode 100644 index 000000000..2c3244c40 --- /dev/null +++ b/src/thorin/transform/rewrite.cpp @@ -0,0 +1,73 @@ +#include "rewrite.h" + +namespace thorin { + +Rewriter::Rewriter(World& src, World& dst) : src_(src), dst_(dst) { + // TODO: rehash is slow-ish, especially in Debug mode + // Many short-lived Rewriters are created that only rebuild a tiny portion of the + // world, such as in CondEval. For these, we end up paying a significant amount + // of time just running this, leading to bad performance. Be smarter or don't do this. + //old2new_.rehash(src.defs().capacity()); +} + +Rewriter::Rewriter(World& src, World& dst, Rewriter& parent) : Rewriter(src, dst) { + old2new_ = parent.old2new_; +} + +const Def* Rewriter::lookup(const thorin::Def* odef) { + if (auto ndef = old2new_.lookup(odef)) return *ndef; + + // TODO maybe we want to deal with intrinsics in a more streamlined way + if (odef == src().branch()) + return dst().branch(); + if (odef == src().end_scope()) + return dst().end_scope(); + return nullptr; +} + +const Def* Rewriter::instantiate(const Def* odef) { + auto found = lookup(odef); + if (found) return found; + + if (&src_ == &dst_ && odef->isa_nom()) + return old2new_[odef] = odef; + + return old2new_[odef] = rewrite(odef); +} + +const Def* Rewriter::insert(const Def* odef, const Def* ndef) { + assert(&odef->world() == &src()); + assert(&ndef->world() == &dst()); + return old2new_[odef] = ndef; +} + +const Def* Rewriter::rewrite(const Def* odef) { + if (odef == odef->world().star()) + return insert(odef, dst().star()); + + auto ntype = instantiate(odef->type())->as(); + + Def* stub = nullptr; + if (odef->isa_nom()) { + stub = odef->stub(*this, ntype); + insert(odef, stub); + } + + if (odef->isa_structural()) { + size_t size = odef->num_ops(); + Array nops(size); + for (size_t i = 0; i != size; ++i) { + assert(odef->op(i) != odef); + nops[i] = instantiate(odef->op(i)); + assert(&nops[i]->world() == &dst()); + } + auto ndef = odef->rebuild(dst(), ntype, nops); + return ndef; + } else { + assert(odef->isa_nom() && stub); + stub->rebuild_from(*this, odef); + return stub; + } +} + +} diff --git a/src/thorin/transform/rewrite.h b/src/thorin/transform/rewrite.h new file mode 100644 index 000000000..5ceba0f1f --- /dev/null +++ b/src/thorin/transform/rewrite.h @@ -0,0 +1,32 @@ +#ifndef THORIN_REWRITE_H +#define THORIN_REWRITE_H + +#include "thorin/world.h" + +namespace thorin { + +class Rewriter { +public: + explicit Rewriter(World& src, World& dst); + explicit Rewriter(World& world) : Rewriter(world, world) {} + + const Def* instantiate(const Def* odef); + const Def* insert(const Def* odef, const Def* ndef); + + World& src() { return src_; } + World& dst() { return dst_; } + +//protected: + explicit Rewriter(World& src, World& dst, Rewriter& parent); + virtual const Def* lookup(const Def* odef); + virtual const Def* rewrite(const Def* odef); + +private: + Def2Def old2new_; + World& src_; + World& dst_; +}; + +} + +#endif diff --git a/src/thorin/transform/split_slots.cpp b/src/thorin/transform/split_slots.cpp index 5289777c9..054587755 100644 --- a/src/thorin/transform/split_slots.cpp +++ b/src/thorin/transform/split_slots.cpp @@ -13,6 +13,7 @@ struct IndexHash { static u32 sentinel() { return 0xFFFFFFFF; } }; +#if 0 static void split(const Slot* slot) { auto array_type = slot->alloced_type()->as(); auto dim = array_type->dim(); @@ -67,8 +68,9 @@ static bool can_split(const Slot* slot) { return true; } +#endif -static bool split_slots(const Scope& scope) { +static bool split_slots(const Scope& /* scope */) { bool todo = false; // TODO #if 0 @@ -86,12 +88,12 @@ static bool split_slots(const Scope& scope) { return todo; } -void split_slots(World& world) { +void split_slots(Thorin& thorin) { bool todo = true; while (todo) { todo = false; - Scope::for_each(world, [&] (const Scope& scope) { todo |= split_slots(scope); }); - world.cleanup(); + ScopesForest(thorin.world()).for_each([&] (const Scope& scope) { todo |= split_slots(scope); }); + thorin.cleanup(); } } diff --git a/src/thorin/transform/split_slots.h b/src/thorin/transform/split_slots.h index dff363e43..767ac1ae8 100644 --- a/src/thorin/transform/split_slots.h +++ b/src/thorin/transform/split_slots.h @@ -8,7 +8,7 @@ class World; /** * Tries to split @p Slot%s that are accessed through constant @p LEA%s. */ -void split_slots(World&); +void split_slots(Thorin&); } diff --git a/src/thorin/type.cpp b/src/thorin/type.cpp index 58815f4c1..837d6ff2d 100644 --- a/src/thorin/type.cpp +++ b/src/thorin/type.cpp @@ -5,21 +5,40 @@ #include #include +#include "thorin/transform/rewrite.h" #include "thorin/continuation.h" #include "thorin/primop.h" #include "thorin/world.h" namespace thorin { -Type::Type(TypeTable& table, int tag, Types ops) - : table_(&table) - , tag_(tag) - , ops_(ops.size()) -{ - for (size_t i = 0, e = num_ops(); i != e; ++i) { - if (auto op = ops[i]) - set(i, op); - } +Type::Type(World& w, NodeTag tag, Defs args, Debug dbg) : Type(w, tag, w.star(), args, dbg) { + // The overridden version of set_op is ignored in the Def ctor, because according to the C++ spec, virtuals are disabled in ctors (!) + // So this is not actually duplicate code - for the nominal types Type::set_op will do what we want. + for (auto& def : args) + order_ = std::max(order_, def->order()); +} +Type::Type(World& w, NodeTag tag, size_t size, Debug dbg) : Type(w, tag, w.star(), size, dbg) {} + +void Type::set_op(size_t i, const Def* def) { + Def::set_op(i, def); + order_ = std::max(order_, def->order()); +} + +Array types2defs(ArrayRef types) { + Array defs(types.size()); + size_t i = 0; + for (auto type : types) + defs[i++] = type->as(); + return defs; +} + +Array defs2types(ArrayRef defs) { + Array types(defs.size()); + size_t i = 0; + for (auto type : defs) + types[i++] = type->as(); + return types; } //------------------------------------------------------------------------------ @@ -28,34 +47,33 @@ Type::Type(TypeTable& table, int tag, Types ops) * rebuild */ -const Type* NominalType::rebuild(TypeTable&, Types) const { +const Type* NominalType::rebuild(World& , const Type* , Defs ) const { THORIN_UNREACHABLE; - return this; } -const Type* BottomType ::rebuild(TypeTable& t, Types ) const { return t.bottom_type(); } -const Type* ClosureType ::rebuild(TypeTable& t, Types o) const { return t.closure_type(o); } -const Type* DefiniteArrayType ::rebuild(TypeTable& t, Types o) const { return t.definite_array_type(o[0], dim()); } -const Type* FnType ::rebuild(TypeTable& t, Types o) const { return t.fn_type(o); } -const Type* FrameType ::rebuild(TypeTable& t, Types ) const { return t.frame_type(); } -const Type* IndefiniteArrayType::rebuild(TypeTable& t, Types o) const { return t.indefinite_array_type(o[0]); } -const Type* MemType ::rebuild(TypeTable& t, Types ) const { return t.mem_type(); } -const Type* PrimType ::rebuild(TypeTable& t, Types ) const { return t.prim_type(primtype_tag(), length()); } -const Type* PtrType ::rebuild(TypeTable& t, Types o) const { return t.ptr_type(o.front(), length(), device(), addr_space()); } -const Type* TupleType ::rebuild(TypeTable& t, Types o) const { return t.tuple_type(o); } +const Type* BottomType ::rebuild(World& w, const Type* , Defs ) const { return w.bottom_type(); } +const Type* ClosureType ::rebuild(World& w, const Type* , Defs o) const { return w.closure_type(defs2types(o)); } +const Type* DefiniteArrayType ::rebuild(World& w, const Type* , Defs o) const { return w.definite_array_type(o[0]->as(), dim()); } +const Type* FnType ::rebuild(World& w, const Type* , Defs o) const { return w.fn_type(defs2types(o)); } +const Type* FrameType ::rebuild(World& w, const Type* , Defs ) const { return w.frame_type(); } +const Type* IndefiniteArrayType::rebuild(World& w, const Type* , Defs o) const { return w.indefinite_array_type(o[0]->as()); } +const Type* MemType ::rebuild(World& w, const Type* , Defs ) const { return w.mem_type(); } +const Type* PrimType ::rebuild(World& w, const Type* , Defs ) const { return w.prim_type(primtype_tag(), length()); } +const Type* PtrType ::rebuild(World& w, const Type* , Defs o) const { return w.ptr_type(o[0]->as(), length(), addr_space()); } +const Type* TupleType ::rebuild(World& w, const Type* , Defs o) const { return w.tuple_type(defs2types(o)); } /* * stub */ -const NominalType* StructType::stub(TypeTable& to) const { - auto type = to.struct_type(name(), num_ops()); +StructType* StructType::stub(Rewriter& rewriter, const Type*) const { + auto type = rewriter.dst().struct_type(name(), num_ops()); std::copy(op_names_.begin(), op_names_.end(), type->op_names().begin()); return type; } -const NominalType* VariantType::stub(TypeTable& to) const { - auto type = to.variant_type(name(), num_ops()); +VariantType* VariantType::stub(Rewriter& rewriter, const Type*) const { + auto type = rewriter.dst().variant_type(name(), num_ops()); std::copy(op_names_.begin(), op_names_.end(), type->op_names().begin()); return type; } @@ -64,8 +82,8 @@ const NominalType* VariantType::stub(TypeTable& to) const { const VectorType* VectorType::scalarize() const { if (auto ptr = isa()) - return table().ptr_type(ptr->pointee()); - return table().prim_type(as()->primtype_tag()); + return world().ptr_type(ptr->pointee()); + return world().prim_type(as()->primtype_tag()); } bool FnType::is_returning() const { @@ -85,7 +103,7 @@ bool FnType::is_returning() const { } bool VariantType::has_payload() const { - return !std::all_of(ops().begin(), ops().end(), is_type_unit); + return !std::all_of(types().begin(), types().end(), is_type_unit); } bool use_lea(const Type* type) { return type->isa() || type->isa(); } @@ -96,17 +114,8 @@ bool use_lea(const Type* type) { return type->isa() || type->isagid())); - return seed; -} - hash_t PtrType::vhash() const { - return hash_combine(VectorType::vhash(), (hash_t)device(), (hash_t)addr_space()); + return hash_combine(VectorType::vhash(), (hash_t)addr_space()); } //------------------------------------------------------------------------------ @@ -115,133 +124,53 @@ hash_t PtrType::vhash() const { * equal */ -bool Type::equal(const Type* other) const { - if (is_nominal()) - return this == other; - if (tag() == other->tag() && num_ops() == other->num_ops()) - return std::equal(ops().begin(), ops().end(), other->ops().begin()); - return false; -} - -bool PtrType::equal(const Type* other) const { +bool PtrType::equal(const Def* other) const { if (!VectorType::equal(other)) return false; auto ptr = other->as(); - return ptr->device() == device() && ptr->addr_space() == addr_space(); -} - -//------------------------------------------------------------------------------ - -/* - * stream - */ - -Stream& Type::stream(Stream& s) const { - if (false) {} - else if (isa()) return s.fmt("!!"); - else if (isa< MemType>()) return s.fmt("mem"); - else if (isa< FrameType>()) return s.fmt("frame"); - else if (auto t = isa()) { - return s.fmt("[{} x {}]", t->dim(), t->elem_type()); - } else if (auto t = isa()) { - return s.fmt("closure [{, }]", t->ops()); - } else if (auto t = isa()) { - return s.fmt("fn[{, }]", t->ops()); - } else if (auto t = isa()) { - return s.fmt("[{}]", t->elem_type()); - } else if (auto t = isa()) { - return s.fmt("struct {}", t->name()); - } else if (auto t = isa()) { - return s.fmt("variant {}", t->name()); - } else if (auto t = isa()) { - return s.fmt("[{, }]", t->ops()); - } else if (auto t = isa()) { - if (t->is_vector()) s.fmt("<{} x", t->length()); - s.fmt("{}*", t->pointee()); - if (t->is_vector()) s.fmt(">"); - if (t->device() != -1) s.fmt("[{}]", t->device()); - - switch (t->addr_space()) { - case AddrSpace::Global: s.fmt("[Global]"); break; - case AddrSpace::Texture: s.fmt("[Tex]"); break; - case AddrSpace::Shared: s.fmt("[Shared]"); break; - case AddrSpace::Constant: s.fmt("[Constant]"); break; - default: /* ignore unknown address space */ break; - } - return s; - } else if (auto t = isa()) { - if (t->is_vector()) s.fmt("<{} x", t->length()); - - switch (t->primtype_tag()) { -#define THORIN_ALL_TYPE(T, M) case Node_PrimType_##T: s.fmt(#T); break; -#include "thorin/tables/primtypetable.h" - default: THORIN_UNREACHABLE; - } - - if (t->is_vector()) s.fmt(">"); - return s; - } - THORIN_UNREACHABLE; + return ptr->addr_space() == addr_space(); } -//------------------------------------------------------------------------------ - -TypeTable::TypeTable() - : unit_ (insert(*this, Types())) - , fn0_ (insert(*this, Types())) - , bottom_ty_(insert(*this)) - , mem_ (insert(*this)) - , frame_ (insert(*this)) +TypeTable::TypeTable(World& world) + : world_(world) + , star_ (world.put((world))) + , unit_ (world.put(world, Defs(), Debug())) + , fn0_ (world.put(world, Defs(), Node_FnType, Debug())) + , bottom_ty_(world.put(world, Debug())) + , mem_ (world.put(world, Debug())) + , frame_ (world.put(world, Debug())) { #define THORIN_ALL_TYPE(T, M) \ - primtypes_[PrimType_##T - Begin_PrimType] = insert(*this, PrimType_##T, 1); + primtypes_[PrimType_##T - Begin_PrimType] = world.make(world, PrimType_##T, 1, Debug()); #include "thorin/tables/primtypetable.h" } -const Type* TypeTable::tuple_type(Types ops) { - return ops.size() == 1 ? ops.front() : insert(*this, ops); +const Type* World::tuple_type(Types ops) { + return ops.size() == 1 ? ops.front()->as() : make(*this, types2defs(ops), Debug()); } -const StructType* TypeTable::struct_type(Symbol name, size_t size) { - auto type = new StructType(*this, name, size, types_.size()); - const auto& p = types_.insert(type); - assert_unused(p.second && "hash/equal broken"); - return type; +StructType* World::struct_type(Symbol name, size_t size) { + return put(*this, name, size, Debug()); } -const VariantType* TypeTable::variant_type(Symbol name, size_t size) { - auto type = new VariantType(*this, name, size, types_.size()); - const auto& p = types_.insert(type); - assert_unused(p.second && "hash/equal broken"); - return type; +VariantType* World::variant_type(Symbol name, size_t size) { + return put(*this, name, size, Debug()); } -const PrimType* TypeTable::prim_type(PrimTypeTag tag, size_t length) { +const PrimType* World::prim_type(PrimTypeTag tag, size_t length) { size_t i = tag - Begin_PrimType; assert(i < (size_t) Num_PrimTypes); - return length == 1 ? primtypes_[i] : insert(*this, tag, length); + return length == 1 ? types_.primtypes_[i] : make(*this, tag, length, Debug()); } -const PtrType* TypeTable::ptr_type(const Type* pointee, size_t length, int32_t device, AddrSpace addr_space) { - return insert(*this, pointee, length, device, addr_space); +const PtrType* World::ptr_type(const Type* pointee, size_t length, AddrSpace addr_space) { + return make(*this, pointee, length, addr_space, Debug()); } -const FnType* TypeTable::fn_type(Types args) { return insert(*this, args); } -const ClosureType* TypeTable::closure_type(Types args) { return insert(*this, args); } -const DefiniteArrayType* TypeTable::definite_array_type(const Type* elem, u64 dim) { return insert(*this, elem, dim); } -const IndefiniteArrayType* TypeTable::indefinite_array_type(const Type* elem) { return insert(*this, elem); } - -template -const T* TypeTable::insert(Args&&... args) { - T t(std::forward(args)...); - auto it = types_.find(&t); - if (it != types_.end()) - return (*it)->template as(); - auto new_t = new T(std::move(t)); - new_t->gid_ = types_.size(); - types_.emplace(new_t); - return new_t; -} +const FnType* World::fn_type(Types args) { return make(*this, types2defs(args), Node_FnType, Debug()); } +const ClosureType* World::closure_type(Types args) { return make(*this, types2defs(args), Debug()); } +const DefiniteArrayType* World::definite_array_type(const Type* elem, u64 dim) { return make(*this, elem, dim, Debug()); } +const IndefiniteArrayType* World::indefinite_array_type(const Type* elem) { return make(*this, elem, Debug()); } //------------------------------------------------------------------------------ diff --git a/src/thorin/type.h b/src/thorin/type.h index 2ce899290..8d69b4aa2 100644 --- a/src/thorin/type.h +++ b/src/thorin/type.h @@ -1,6 +1,7 @@ #ifndef THORIN_TYPE_H #define THORIN_TYPE_H +#include "thorin/def.h" #include "thorin/enums.h" #include "thorin/util/hash.h" #include "thorin/util/cast.h" @@ -15,174 +16,170 @@ class Type; using Types = ArrayRef; /// Base class for all \p Type%s. -class Type : public RuntimeCast, public Streamable { +class Type : public Def { protected: - Type(TypeTable& table, int tag, Types ops); - - void set(size_t i, const Type* type) { - ops_[i] = type; - order_ = std::max(order_, type->order()); - } + /// Constructor for a @em structural Type. + Type(World& w, NodeTag tag, const Type* type, Defs args, Debug dbg) : Def(w, tag, type, args, dbg) {} + Type(World& w, NodeTag tag, Defs args, Debug dbg); + /// Constructor for a @em nom Type. + Type(World& w, NodeTag tag, const Type* type, size_t size, Debug dbg) : Def(w, tag, type, size, dbg) {} + Type(World& w, NodeTag tag, size_t size, Debug dbg); public: - int tag() const { return tag_; } - TypeTable& table() const { return *table_; } - - Types ops() const { return ops_; } - const Type* op(size_t i) const { return ops()[i]; } - size_t num_ops() const { return ops_.size(); } - bool empty() const { return ops_.empty(); } - - bool is_nominal() const { return nominal_; } ///< A nominal @p Type is always different from each other @p Type. - int order() const { return order_; } - size_t gid() const { return gid_; } - hash_t hash() const { return hash_ == 0 ? hash_ = vhash() : hash_; } - virtual bool equal(const Type*) const; - virtual const Type* rebuild(TypeTable&, Types) const = 0; + int order() const override { return order_; } + void set_op(size_t i, const Def *def) override; Stream& stream(Stream&) const; - void dump() const; + std::vector filter_type_ops() const { + std::vector type_ops; + for (auto& op : ops()) { + if (auto t = op->isa()) + type_ops.push_back(t); + } + return type_ops; + } protected: - virtual hash_t vhash() const; - - mutable hash_t hash_ = 0; - mutable bool nominal_ = false; int order_ = 0; - size_t gid_; + friend class World; +}; -private: - mutable TypeTable* table_; +class Star : public Type { +protected: + explicit Star(World& w) : Type(w, Node_Star, nullptr, 0, {}) { + set_type(this); + } + + friend class World; +}; - int tag_; - thorin::Array ops_; +Array types2defs(ArrayRef types); +Array defs2types(ArrayRef types); - friend TypeTable; +template +class TypeOpsMixin { +public: + Types types() const { + Defs defs = static_cast(this)->ops(); + const Def* const* ptr = defs.begin(); + auto ptr2 = reinterpret_cast(ptr); + auto types = Types(ptr2, defs.size()); + return types; + } }; /// Type of a tuple (structurally typed). -class TupleType : public Type { +class TupleType : public Type, public TypeOpsMixin { private: - TupleType(TypeTable& table, Types ops) - : Type(table, Node_TupleType, ops) + TupleType(World& world, Defs ops, Debug dbg) + : Type(world, Node_TupleType, ops, dbg) {} public: - const Type* rebuild(TypeTable&, Types) const override; - - friend class TypeTable; + const Type* rebuild(World&, const Type*, Defs) const override; + friend class World; }; /// Base class for nominal types (types that have /// a name that uniquely identifies them). class NominalType : public Type { protected: - NominalType(TypeTable& table, int tag, Symbol name, size_t size, size_t gid) - : Type(table, tag, thorin::Array(size)) + NominalType(World& world, NodeTag tag, Symbol name, size_t size, Debug dbg) + : Type(world, tag, size, dbg) , name_(name) , op_names_(size) - { - nominal_ = true; - gid_ = gid; - } + {} Symbol name_; Array op_names_; private: - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; public: Symbol name() const { return name_; } + using Type::op_name; //Would be hidden otherwise. Symbol op_name(size_t i) const { return op_names_[i]; } - void set(size_t i, const Type* type) const { - return const_cast(this)->Type::set(i, type); - } void set_op_name(size_t i, Symbol name) const { const_cast(this)->op_names_[i] = name; } Array& op_names() const { return const_cast(this)->op_names_; } - - /// Recreates a fresh new nominal type of the - /// same kind with the same number of operands, - /// initially all unset. - virtual const NominalType* stub(TypeTable&) const = 0; }; -class StructType : public NominalType { +class StructType : public NominalType, public TypeOpsMixin { private: - StructType(TypeTable& table, Symbol name, size_t size, size_t gid) - : NominalType(table, Node_StructType, name, size, gid) + StructType(World& world, Symbol name, size_t size, Debug dbg) + : NominalType(world, Node_StructType, name, size, dbg) {} public: - const NominalType* stub(TypeTable&) const override; + virtual StructType* stub(Rewriter&, const Type*) const override; - friend class TypeTable; + friend class World; }; -class VariantType : public NominalType { +class VariantType : public NominalType, public TypeOpsMixin { private: - VariantType(TypeTable& table, Symbol name, size_t size, size_t gid) - : NominalType(table, Node_VariantType, name, size, gid) + VariantType(World& world, Symbol name, size_t size, Debug dbg) + : NominalType(world, Node_VariantType, name, size, dbg) {} public: - const NominalType* stub(TypeTable&) const override; + virtual VariantType* stub(Rewriter&, const Type*) const override; bool has_payload() const; - friend class TypeTable; + friend class World; }; /// The type of the memory monad. class MemType : public Type { private: - MemType(TypeTable& table) - : Type(table, Node_MemType, {}) + MemType(World& world, Debug dbg) + : Type(world, Node_MemType, Defs(), dbg) {} - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; /// The type of App nodes. class BottomType : public Type { private: - BottomType(TypeTable& table) - : Type(table, Node_BotType, {}) + BottomType(World& world, Debug dbg) + : Type(world, Node_BotType, Defs(), dbg) {} - const Type* rebuild(TypeTable& to, Types ops) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; /// The type of a stack frame. class FrameType : public Type { private: - FrameType(TypeTable& table) - : Type(table, Node_FrameType, {}) + FrameType(World& world, Debug dbg) + : Type(world, Node_FrameType, Defs(), dbg) {} - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; /// Base class for all SIMD types. class VectorType : public Type { protected: - VectorType(TypeTable& table, int tag, Types ops, size_t length) - : Type(table, tag, ops) + VectorType(World& world, NodeTag tag, Defs ops, size_t length, Debug dbg) + : Type(world, tag, ops, dbg) , length_(length) {} hash_t vhash() const override { return hash_combine(Type::vhash(), length()); } - bool equal(const Type* other) const override { - return Type::equal(other) && this->length() == other->as()->length(); + bool equal(const Def* other) const override { + return Def::equal(other) && this->length() == other->as()->length(); } public: @@ -202,15 +199,15 @@ inline size_t vector_length(const Type* type) { return type->as()->l /// Primitive type. class PrimType : public VectorType { private: - PrimType(TypeTable& table, PrimTypeTag tag, size_t length) - : VectorType(table, (int) tag, {}, length) + PrimType(World& world, PrimTypeTag tag, size_t length, Debug dbg) + : VectorType(world, (NodeTag) tag, Defs(), length, dbg) {} public: PrimTypeTag primtype_tag() const { return (PrimTypeTag) tag(); } - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; inline bool is_primtype (const Type* t) { return thorin::is_primtype(t->tag()); } @@ -235,33 +232,34 @@ enum class AddrSpace : uint32_t { Texture = 2, Shared = 3, Constant = 4, + Private = 5, // Corresponds to the 'private' storage class in compute kernels/shaders, as in thread-private + Function = 6, // Corresponds to the 'function' storage class in SPIR-V + Push = 7, // Corresponds to the 'push constant' storage class in SPIR-V + Input = 8, + Output = 9, }; /// Pointer type. -class PtrType : public VectorType { +class PtrType : public VectorType, public TypeOpsMixin { private: - PtrType(TypeTable& table, const Type* pointee, size_t length, int32_t device, AddrSpace addr_space) - : VectorType(table, Node_PtrType, {pointee}, length) + PtrType(World& world, const Type* pointee, size_t length, AddrSpace addr_space, Debug dbg) + : VectorType(world, Node_PtrType, {pointee}, length, dbg) , addr_space_(addr_space) - , device_(device) {} public: - const Type* pointee() const { return op(0); } + const Type* pointee() const { return op(0)->as(); } AddrSpace addr_space() const { return addr_space_; } - int32_t device() const { return device_; } - bool is_host_device() const { return device_ == -1; } hash_t vhash() const override; - bool equal(const Type* other) const override; + bool equal(const Def* other) const override; private: - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; AddrSpace addr_space_; - int32_t device_; - friend class TypeTable; + friend class World; }; /// Returns true if the given type is small enough to fit in a closure environment @@ -269,10 +267,10 @@ inline bool is_thin(const Type* type) { return type->isa() || type->isa() || is_type_unit(type); } -class FnType : public Type { +class FnType : public Type, public TypeOpsMixin { protected: - FnType(TypeTable& table, Types ops, int tag = Node_FnType) - : Type(table, tag, ops) + FnType(World& world, Defs ops, NodeTag tag, Debug dbg) + : Type(world, tag, ops, dbg) { ++order_; } @@ -282,15 +280,15 @@ class FnType : public Type { bool is_returning() const; private: - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; class ClosureType : public FnType { private: - ClosureType(TypeTable& table, Types ops) - : FnType(table, ops, Node_ClosureType) + ClosureType(World& world, Defs ops, Debug dbg) + : FnType(world, ops, Node_ClosureType, dbg) { inner_order_ = order_; order_ = 0; @@ -298,155 +296,84 @@ class ClosureType : public FnType { public: int inner_order() const { return inner_order_; } - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; private: int inner_order_; - friend class TypeTable; + friend class World; }; //------------------------------------------------------------------------------ -class ArrayType : public Type { +class ArrayType : public Type, public TypeOpsMixin { protected: - ArrayType(TypeTable& table, int tag, const Type* elem_type) - : Type(table, tag, {elem_type}) + ArrayType(World& world, NodeTag tag, const Type* elem_type, Debug dbg) + : Type(world, tag, {elem_type}, dbg) {} public: - const Type* elem_type() const { return op(0); } + const Type* elem_type() const { return op(0)->as(); } }; class IndefiniteArrayType : public ArrayType { public: - IndefiniteArrayType(TypeTable& table, const Type* elem_type) - : ArrayType(table, Node_IndefiniteArrayType, elem_type) + IndefiniteArrayType(World& world, const Type* elem_type, Debug dbg) + : ArrayType(world, Node_IndefiniteArrayType, elem_type, dbg) {} private: - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; - friend class TypeTable; + friend class World; }; class DefiniteArrayType : public ArrayType { public: - DefiniteArrayType(TypeTable& table, const Type* elem_type, u64 dim) - : ArrayType(table, Node_DefiniteArrayType, elem_type) + DefiniteArrayType(World& world, const Type* elem_type, u64 dim, Debug dbg) + : ArrayType(world, Node_DefiniteArrayType, elem_type, dbg) , dim_(dim) {} u64 dim() const { return dim_; } hash_t vhash() const override { return hash_combine(Type::vhash(), dim()); } - bool equal(const Type* other) const override { - return Type::equal(other) && this->dim() == other->as()->dim(); + bool equal(const Def* other) const override { + return Def::equal(other) && this->dim() == other->as()->dim(); } private: - const Type* rebuild(TypeTable&, Types) const override; + const Type* rebuild(World&, const Type*, Defs) const override; u64 dim_; - friend class TypeTable; + friend class World; }; bool use_lea(const Type*); //------------------------------------------------------------------------------ -/// Container for all types. Types are hashed and can be compared using pointer equality. class TypeTable { -private: - struct TypeHash { - static hash_t hash(const Type* t) { return t->hash(); } - static bool eq(const Type* t1, const Type* t2) { return t2->equal(t1); } - static const Type* sentinel() { return (const Type*)(1); } - }; - - typedef thorin::HashSet TypeSet; - public: - TypeTable(); - - const Type* tuple_type(Types ops); - const TupleType* unit() { return unit_; } ///< Returns unit, i.e., an empty @p TupleType. - const VariantType* variant_type(Symbol name, size_t size); - const StructType* struct_type(Symbol name, size_t size); - -#define THORIN_ALL_TYPE(T, M) \ - const PrimType* type_##T(size_t length = 1) { return prim_type(PrimType_##T, length); } -#include "thorin/tables/primtypetable.h" - const PrimType* prim_type(PrimTypeTag tag, size_t length = 1); - const BottomType* bottom_type() const { return bottom_ty_; } - const MemType* mem_type() const { return mem_; } - const FrameType* frame_type() const { return frame_; } - const PtrType* ptr_type(const Type* pointee, size_t length = 1, int32_t device = -1, AddrSpace addr_space = AddrSpace::Generic); - const FnType* fn_type() { return fn0_; } ///< Returns an empty @p FnType. - const FnType* fn_type(Types args); - const ClosureType* closure_type(Types args); - const DefiniteArrayType* definite_array_type(const Type* elem, u64 dim); - const IndefiniteArrayType* indefinite_array_type(const Type* elem); - - const TypeSet& types() const { return types_; } - - friend void swap(TypeTable& t1, TypeTable& t2) { - using std::swap; - swap(t1.types_, t2.types_); - swap(t1.unit_, t2.unit_); - swap(t1.fn0_, t2.fn0_); - swap(t1.bottom_ty_, t2.bottom_ty_); - swap(t1.mem_, t2.mem_); - swap(t1.frame_, t2.frame_); - std::swap_ranges(t1.primtypes_, t1.primtypes_ + Num_PrimTypes, t2.primtypes_); - - t1.fix(); - t2.fix(); - } - -private: - void fix() { - for (auto type : types_) - type->table_ = this; - } - - template - const T* insert(Args&&... args); + explicit TypeTable(World& world); private: - TypeSet types_; + World& world_; + const Type* star_; const TupleType* unit_; ///< tuple(). const FnType* fn0_; const BottomType* bottom_ty_; const MemType* mem_; const FrameType* frame_; const PrimType* primtypes_[Num_PrimTypes]; -}; - -//------------------------------------------------------------------------------ - -template -struct GIDLt { - bool operator()(T a, T b) const { return a->gid() < b->gid(); } -}; -template -struct GIDHash { - static hash_t hash(T n) { return thorin::murmur3(n->gid()); } - static bool eq(T a, T b) { return a == b; } - static T sentinel() { return T(1); } + friend class World; }; -template -using GIDMap = thorin::HashMap>; -template -using GIDSet = thorin::HashSet>; +//------------------------------------------------------------------------------ -template -using TypeMap = GIDMap; -using Type2Type = TypeMap; -using TypeSet = GIDSet; +inline bool is_mem (const Def* def) { return def->type()->isa(); } //------------------------------------------------------------------------------ diff --git a/src/thorin/util/graphviz_dump.cpp b/src/thorin/util/graphviz_dump.cpp new file mode 100644 index 000000000..d85ea4bb4 --- /dev/null +++ b/src/thorin/util/graphviz_dump.cpp @@ -0,0 +1,315 @@ +#ifndef DOT_DUMP_H +#define DOT_DUMP_H + +#include "thorin/world.h" +#include "thorin/analyses/scope.h" + +namespace thorin { + +/// Outputs the raw thorin IR as a graph without performing any scope or scheduling analysis +struct DotPrinter { + DotPrinter(World& world, const char* filename = "world.dot") : world_(world), forest_(world) { + file = std::ofstream(filename); + begin(); + } + + ~DotPrinter() { + end(); + } +private: + int indent = 0; + std::string endl() { + std::string s = "\n"; + for (int i = 0; i < indent; i++) + s += " "; + return s; + } + std::string up() { + indent++; + return ""; + } + std::string down() { + indent--; + return ""; + } + + void begin() { + file << "digraph " << "world" << " {" << up(); + file << endl() << "bgcolor=transparent;"; + } + + void end() { + file << arrows.str(); + file << down() << endl() << "}" << endl(); + } + + std::string def_id(const Def* def) { + return std::string(tag2str(def->tag())) + "_" + std::to_string(def->gid()); + } + + std::string emit_def(const Def* def); + std::string dump_literal(const Literal*); + std::string dump_continuation(Continuation* cont); + + std::stringstream arrows; + + void arrow(const std::string& src, const std::string& dst, const std::string& extra) { + if (src.empty() || dst.empty()) + return; + arrows << endl() << src << " -> " << dst << " " << extra << ";"; + } +public: + std::string dump_def(const Def* def); + + void run() { + delay_printing_ops = false; + while (!todo.empty()) { + auto def = todo.pop(); + dump_def(def); + } + } + + void print_scope(Scope& scope) { + file << "subgraph cluster_" << u_++ << " {" << up() << endl(); + + auto cont = scope.entry(); + emit_def(cont); + + for (auto child : scope.children_scopes()) + print_scope(scope.forest().get_scope(child)); + + for (auto def : scope.defs()) { + if (!done.contains(def) && !def->isa()) { + emit_def(def); + } + } + + file << down() << endl() << "}" << endl(); + }; + + bool print_lower_order_args = true; + bool print_instanced_filters = false; + bool print_literals = true; + bool delay_printing_ops = true; + Scope* single_scope = nullptr; + +private: + thorin::World& world_; + ScopesForest forest_; + + int u_ = 0; + + unique_queue todo; + DefMap done; + std::ofstream file; +}; + +std::string DotPrinter::dump_def(const Def* def) { + if (done.contains(def)) + return done[def]; + + if (delay_printing_ops) { + todo.push(def); + return def_id(def); + } + + return emit_def(def); +} + +std::string DotPrinter::emit_def(const Def* def) { + assert(!done.contains(def)); + if (auto cont = def->isa_nom()) + return dump_continuation(cont); + else if (def->isa()) + return dump_literal(def->as()); + else { + // default (primops) + std::string color = ""; + std::string fillcolor = "darkseagreen1"; + std::string style = "filled"; + std::string shape = "oval"; + std::string label = std::string(def->op_name()) + " :: " + def->name(); + + std::unique_ptr>> filtered_ops; + + if (single_scope && single_scope->free_frontier().contains(def)) + color = "blue"; + + if (def->isa()) { + fillcolor = "grey"; + shape = "oval"; + label = def->unique_name(); + } else if (auto app = def->isa()) { + fillcolor = "darkgreen"; + + filtered_ops = std::make_unique>>(); + + if (auto callee_cont = app->callee()->isa_nom()) { + switch (callee_cont->intrinsic()) { + case Intrinsic::Branch: { + fillcolor = "lightblue"; + label = "branch"; + if (print_lower_order_args) { + (*filtered_ops).emplace_back("mem", app->arg(0)); + (*filtered_ops).emplace_back("condition", app->arg(1)); + } + (*filtered_ops).emplace_back("true", app->arg(2)); + (*filtered_ops).emplace_back("false", app->arg(3)); + + goto print_node; + } + case Intrinsic::Match: { + fillcolor = "lightblue"; + label = "match"; + + if (print_lower_order_args) { + (*filtered_ops).emplace_back("mem", app->arg(0)); + (*filtered_ops).emplace_back("inspectee", app->arg(1)); + } + + (*filtered_ops).emplace_back("default_case", app->arg(2)); + for (size_t i = 3; i < app->num_args(); i+=2) { + if (print_lower_order_args) + (*filtered_ops).emplace_back("case", app->arg(i)); + (*filtered_ops).emplace_back("case", app->arg(i + 1)); + } + + goto print_node; + } + default: break; + } + } + + (*filtered_ops).emplace_back("callee", app->callee()); + for (size_t i = 0; i < app->num_args(); i++) { + if (print_lower_order_args || app->arg(i)->type()->order() >= 1) + (*filtered_ops).emplace_back("arg"+std::to_string(i), app->arg(i)); + } + } else if (auto variant_ctor = def->isa()) { + label = "variant(" + std::to_string(variant_ctor->index()) + ")"; + } else if (auto variant_extract = def->isa()) { + label = "variant_extract(" + std::to_string(variant_extract->index()) + ")"; + } + + print_node: + + file << endl() << def_id(def) << " [" << up(); + + file << endl() << "label = \""; + file << label; + file << "\";"; + + file << endl() << "shape = " << shape << ";"; + file << endl() << "style = " << style << ";"; + file << endl() << "fillcolor = " << fillcolor << ";"; + if (color != "") + file << endl() << "color = " << color << ";"; + + file << down() << endl() << "]"; + done.emplace(def, def_id(def)); + + if (!filtered_ops) { + for (size_t i = 0; i < def->num_ops(); i++) { + const auto& op = def->op(i); + arrow(def_id(def), dump_def(op), "[arrowhead=vee,label=\"o" + std::to_string(i) + "\",fontsize=8,fontcolor=grey]"); + } + } else { + for (auto [edge_label, op] : *filtered_ops) { + arrow(def_id(def), dump_def(op), "[arrowhead=vee,label=\"" + edge_label + "\",fontsize=8,fontcolor=grey]"); + } + } + + return def_id(def); + } +} + +std::string DotPrinter::dump_literal(const Literal* def) { + if (!print_literals) + return ""; + assert(def->num_ops() == 0); + file << endl() << def_id(def) << " [" << up(); + + file << endl() << "label = \""; + file << def->to_string(); + file << "\";"; + + file << endl() << "style = dotted;"; + + file << down() << endl() << "]"; + + done.emplace(def, def_id(def)); + return def_id(def); +} + +std::string DotPrinter::dump_continuation(Continuation* cont) { + done.emplace(cont, def_id(cont)); + auto intrinsic = cont->intrinsic(); + file << endl() << def_id(cont) << " [" << up(); + + file << endl() << "label = \""; + if (cont->is_external()) + file << "[extern]\\n"; + auto name = cont->name(); + if (!cont->is_external()) + name = cont->unique_name(); + + file << name << "("; + for (size_t i = 0; i < cont->num_params(); i++) { + file << cont->param(i)->type()->to_string() << (i + 1 == cont->num_params() ? "" : ", "); + } + file << ")"; + + file << "\";"; + + file << endl() << "shape = rectangle;"; + if (intrinsic != Intrinsic::None) { + file << endl() << "color = lightblue;"; + file << endl() << "style = filled;"; + } + if (cont->is_external()) { + file << endl() << "color = pink;"; + file << endl() << "style = filled;"; + } + + file << down() << endl() << "]"; + + if (cont->has_body()) + arrow(def_id(cont), dump_def(cont->body()), "[arrowhead=normal]"); + + return def_id(cont); +} + +DEBUG_UTIL void dump_dot_world(World& world) { + DotPrinter printer(world); + for (auto& external: world.externals()) { + printer.dump_def(external.second); + } + printer.run(); +} + +DEBUG_UTIL void dump_dot_def(const Def* def) { + DotPrinter printer(def->world()); + printer.dump_def(def); + printer.run(); +} + +DEBUG_UTIL void dump_dot_scopes(World& world) { + DotPrinter printer(world); + ScopesForest forest(world); + for (auto& top_level : forest.top_level_scopes()) { + auto& scope = forest.get_scope(top_level); + printer.print_scope(scope); + } + printer.run(); +} + +DEBUG_UTIL void dump_dot_scope(Scope& scope) { + DotPrinter printer(scope.world()); + printer.single_scope = &scope; + printer.print_scope(scope); + printer.run(); +} + +} + +#endif //DOT_DUMP_H diff --git a/src/thorin/util/scoped_dump.cpp b/src/thorin/util/scoped_dump.cpp new file mode 100644 index 000000000..ec97dbbc4 --- /dev/null +++ b/src/thorin/util/scoped_dump.cpp @@ -0,0 +1,192 @@ +#include "scoped_dump.h" + +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_intrinsic()) + s.fmt("intrinsic "); + + switch (cont->cc()) { + case CC::Thorin: break; + case CC::C: s.fmt("cc(C) "); break; + case CC::Device: s.fmt("cc(Device) "); break; + default: s.fmt("cc(?)"); break; + } + + s.fmt(Green); + s.fmt("cont "); + s.fmt(Reset); + + if (!cont->filter()->is_empty()) { + s.fmt(Cyan); + s.fmt("@("); + stream_def(s, cont->filter()); + s.fmt(")"); + s.fmt(Reset); + } + + s.fmt(Red); + s.fmt("{}", cont->unique_name()); + s.fmt(Reset); + s.fmt("("); + const FnType* t = cont->type(); + for (size_t i = 0; i < cont->num_params(); i++) { + s.fmt(Yellow); + s.fmt("{}: ", cont->param(i)->unique_name()); + s.fmt(Blue); + s.fmt("{}", t->types()[i]); + s.fmt(Reset); + if (i + 1 < cont->num_params()) + s.fmt(", "); + } + s.fmt(")"); + if (!cont->has_body()) { + s.fmt(";"); + return; + } + + s.fmt(" = {{\t\n"); + + for (auto p : cont->params()) + done_.insert(p); + + Scope& sc = forest_.get_scope(cont); + scopes_to_defs_[cont] = std::make_unique>(); + auto children = sc.children_scopes(); + size_t i = 0; + for (auto child : children) { + stream_cont(s, child); + //if (i + 1 < children.size()) + s.fmt("\n\n"); + i++; + } + + prepare_def(cont, cont->body()); + + auto defs = *scopes_to_defs_[cont]; + stream_defs(s, defs); + s.fmt("{}", cont->body()->unique_name()); + s.fmt("\b\n}}"); +} + +void ScopedWorld::prepare_def(Continuation* in, const thorin::Def* def) const { + if (done_.contains(def)) + return; + done_.insert(def); + if (def->isa_nom()) + return; + + while (in) { + Scope* scope = &forest_.get_scope(in); + if (scope->contains(def)) + break; + in = scope->parent_scope(); + } + + for (auto op : def->ops()) + prepare_def(in, op); + + if (!in) + top_lvl_.push_back(def); + else + scopes_to_defs_[in]->push_back(def); +} + +void ScopedWorld::stream_op(thorin::Stream& s, const thorin::Def* op) const { + //if (is_mem(op)) + // s.fmt(Gray); + if (op->isa()) + s.fmt(Cyan); + if (op->isa()) + s.fmt(Yellow); + if (op->isa()) + s.fmt(Red); + s.fmt("{}", op); + s.fmt(Reset); +} + +void ScopedWorld::stream_ops(thorin::Stream& s, Defs ops) const { + s.fmt("("); + size_t j = 0; + for (auto op : ops) { + stream_op(s, op); + if (j + 1 < ops.size()) + s.fmt(", "); + j++; + } + s.fmt(")"); +} + +bool ScopedWorld::print_inline(const thorin::Def* def) const { + if (def->isa()) + return true; + return false; +} + +void ScopedWorld::stream_def(thorin::Stream& s, const thorin::Def* def) const { + if (auto app = def->isa()) { + stream_op(s, app->callee()); + stream_ops(s, app->args()); + return; + } + if (def->isa()) { + def->stream1(s); + return; + } + if (def->isa_nom()) { + s.fmt("{}", def->unique_name()); + return; + } + + s.fmt(Green); + s.fmt("{}", def->op_name()); + s.fmt(Reset); + stream_ops(s, def->ops()); +} + +void ScopedWorld::stream_defs(thorin::Stream& s, std::vector& defs) const { + for (auto def : defs) { + if (print_inline(def)) + continue; + s.fmt("{}: ", def->unique_name()); + s.fmt(Blue); + s.fmt("{}", def->type()); + s.fmt(Reset); + s.fmt(" = "); + stream_def(s, def); + s.fmt("\n"); + } +} + +Stream& ScopedWorld::stream(thorin::Stream& s) const { + auto tl = forest_.top_level_scopes(); + size_t i = 0; + for (auto root : tl) { + stream_cont(s, root); + //if (i + 1 < tl.size()) + s.fmt("\n\n"); + i++; + } + + stream_defs(s, top_lvl_); + + return s; +} + +void World::dump_scoped(bool use_color) const { + ScopedWorld s(*const_cast(this), ScopedWorld::Config { use_color }); + s.dump(); +} + +void World::dump_scoped_to_disk() const { + ScopedWorld s(*const_cast(this), ScopedWorld::Config { false }); + auto name = this->name() + ".dump"; + std::ofstream file(name); + Stream st(file); + s.stream(st); +} + +} diff --git a/src/thorin/util/scoped_dump.h b/src/thorin/util/scoped_dump.h new file mode 100644 index 000000000..09aff4df5 --- /dev/null +++ b/src/thorin/util/scoped_dump.h @@ -0,0 +1,53 @@ +#include "thorin/world.h" +#include "thorin/analyses/scope.h" +#include "thorin/util/stream.h" + +namespace thorin { + +#define COLORS(C) \ +C(Black, "\u001b[30m") \ +C(Red, "\u001b[31m") \ +C(Green, "\u001b[32m") \ +C(Yellow, "\u001b[33m") \ +C(Blue, "\u001b[34m") \ +C(Magenta, "\u001b[35m") \ +C(Cyan, "\u001b[36m") \ +C(White, "\u001b[37m") \ +C(Reset, "\u001b[0m") \ + +struct ScopedWorld : public Streamable { + struct Config { + bool use_color; + }; + + ScopedWorld(World& w, Config cfg = { true }) : world_(w), forest_(w), config_(cfg) { +#define T(n, c) n = cfg.use_color ? c : ""; + COLORS(T) +#undef T + } + + World& world_; + mutable ScopesForest forest_; + + mutable DefSet done_; + mutable ContinuationMap>> scopes_to_defs_; + mutable std::vector top_lvl_; + Config config_; + +#define T(n, c) const char* n; + COLORS(T) +#undef T + + Stream& stream(Stream&) const; +private: + + bool print_inline(const Def* def) const; + void stream_cont(thorin::Stream& s, Continuation* cont) const; + void prepare_def(Continuation* in, const Def* def) const; + void stream_op(thorin::Stream&, const Def* op) const; + void stream_ops(thorin::Stream& s, Defs defs) const; + void stream_def(thorin::Stream& s, const Def* def) const; + void stream_defs(thorin::Stream& s, std::vector& defs) const; +}; + +} diff --git a/src/thorin/world.cpp b/src/thorin/world.cpp index 47f34b6aa..5c3339685 100644 --- a/src/thorin/world.cpp +++ b/src/thorin/world.cpp @@ -11,14 +11,20 @@ #include +#if THORIN_ENABLE_CREATION_CONTEXT +#include +#endif + +#if THORIN_ENABLE_RLIMITS +#include +#endif + #include "thorin/def.h" #include "thorin/primop.h" #include "thorin/continuation.h" #include "thorin/type.h" #include "thorin/analyses/scope.h" #include "thorin/analyses/verify.h" -#include "thorin/transform/cleanup_world.h" -#include "thorin/transform/clone_bodies.h" #include "thorin/transform/closure_conversion.h" #include "thorin/transform/codegen_prepare.h" #include "thorin/transform/dead_load_opt.h" @@ -42,27 +48,23 @@ namespace thorin { * constructor and destructor */ -World::World(const std::string& name) { +World::World(const std::string& name) : types_(TypeTable(*this)) { data_.name_ = name; - data_.branch_ = continuation(fn_type({type_bool(), fn_type(), fn_type()}), Intrinsic::Branch, {"br"}); + data_.branch_ = continuation(fn_type({mem_type(), type_bool(), fn_type({mem_type()}), fn_type({mem_type()})}), Intrinsic::Branch, {"br"}); data_.end_scope_ = continuation(fn_type(), Intrinsic::EndScope, {"end_scope"}); } -World::~World() { - for (auto def : data_.defs_) delete def; -} - const Def* World::variant_index(const Def* value, Debug dbg) { if (auto variant = value->isa()) return literal_qu64(variant->index(), dbg); - return cse(new VariantIndex(type_qu64(), value, dbg)); + return cse(new VariantIndex(*this, type_qu64(), value, dbg)); } const Def* World::variant_extract(const Def* value, size_t index, Debug dbg) { - auto type = value->type()->as()->op(index); + auto type = value->type()->as()->op(index)->as(); if (auto variant = value->isa()) return variant->index() == index ? variant->value() : bottom(type); - return cse(new VariantExtract(type, value, index, dbg)); + return cse(new VariantExtract(*this, type, value, index, dbg)); } /* @@ -139,7 +141,9 @@ const Def* World::arithop(ArithOpTag tag, const Def* a, const Def* b, Debug dbg) } case ArithOp_mul: switch (type) { -#define THORIN_ALL_TYPE(T, M) case PrimType_##T: return literal(type, Box(T(l.get_##T() * r.get_##T())), dbg); +#define THORIN_P_TYPE(T, M) case PrimType_##T: return literal(type, Box(T(l.get_##T() * r.get_##T())), dbg); +#define THORIN_Q_TYPE(T, M) case PrimType_##T: return literal(type, Box(T(l.get_##T() * r.get_##T())), dbg); +#define THORIN_BOOL_TYPE(T, M) case PrimType_##T: return literal(type, Box(T(l.get_##T() && r.get_##T())), dbg); #include "thorin/tables/primtypetable.h" default: THORIN_UNREACHABLE; } @@ -391,7 +395,7 @@ const Def* World::arithop(ArithOpTag tag, const Def* a, const Def* b, Debug dbg) return arithop(tag, a_lhs_lv, arithop(tag, a_same->rhs(), b, dbg), dbg); } - return cse(new ArithOp(tag, a, b, dbg)); + return cse(new ArithOp(tag, *this, a, b, dbg)); } const Def* World::arithop_not(const Def* def, Debug dbg) { return arithop_xor(allset(def->type(), dbg, vector_length(def)), def, dbg); } @@ -483,7 +487,7 @@ const Def* World::cmp(CmpTag tag, const Def* a, const Def* b, Debug dbg) { } } - return cse(new Cmp(tag, a, b, dbg)); + return cse(new Cmp(tag, *this, a, b, dbg)); } /* @@ -500,7 +504,7 @@ const Def* World::convert(const Type* dst_type, const Def* src, Debug dbg) { Array new_tuple(dst_tuple_type->num_ops()); for (size_t i = 0, e = new_tuple.size(); i != e; ++i) - new_tuple[i] = convert(dst_type->op(i), extract(src, i, dbg), dbg); + new_tuple[i] = convert(dst_tuple_type->types()[i], extract(src, i, dbg), dbg); return tuple(new_tuple, dbg); } @@ -616,7 +620,7 @@ const Def* World::cast(const Type* to, const Def* from, Debug dbg) { } } - return cse(new Cast(to, from, dbg)); + return cse(new Cast(*this, to, from, dbg)); } const Def* World::bitcast(const Type* to, const Def* from, Debug dbg) { @@ -655,7 +659,7 @@ const Def* World::bitcast(const Type* to, const Def* from, Debug dbg) { return vector(ops, dbg); } - return cse(new Bitcast(to, from, dbg)); + return cse(new Bitcast(*this, to, from, dbg)); } /* @@ -706,13 +710,18 @@ const Def* World::extract(const Def* agg, const Def* index, Debug dbg) { if (auto insert = agg->isa()) { if (index == insert->index()) return insert->value(); - else if (index->template isa()) { - if (insert->index()->template isa()) - return extract(insert->agg(), index, dbg); + else if (auto index_lit = index->isa()) { + if (auto insert_index_lit = insert->index()->isa()) { + if (index_lit->value() == insert_index_lit->value()) { + return insert->value(); + } else { + return extract(insert->agg(), index, dbg); + } + } } } - return cse(new Extract(agg, index, dbg)); + return cse(new Extract(*this, agg, index, dbg)); } const Def* World::insert(const Def* agg, const Def* index, const Def* value, Debug dbg) { @@ -730,13 +739,13 @@ const Def* World::insert(const Def* agg, const Def* index, const Def* value, Deb } else if (auto tuple_type = agg->type()->isa()) { Array args(tuple_type->num_ops()); size_t i = 0; - for (auto type : tuple_type->ops()) + for (auto type : tuple_type->types()) args[i++] = agg->isa() ? bottom(type, dbg) : top(type, dbg); agg = tuple(args, dbg); } else if (auto struct_type = agg->type()->isa()) { Array args(struct_type->num_ops()); size_t i = 0; - for (auto type : struct_type->ops()) + for (auto type : struct_type->types()) args[i++] = agg->isa() ? bottom(type, dbg) : top(type, dbg); agg = struct_agg(struct_type, args, dbg); } @@ -760,14 +769,14 @@ const Def* World::insert(const Def* agg, const Def* index, const Def* value, Deb } } - return cse(new Insert(agg, index, value, dbg)); + return cse(new Insert(*this, agg, index, value, dbg)); } const Def* World::lea(const Def* ptr, const Def* index, Debug dbg) { if (fold_1_tuple(ptr->type()->as()->pointee(), index)) return ptr; - return cse(new LEA(ptr, index, dbg)); + return cse(new LEA(*this, ptr, index, dbg)); } const Def* World::select(const Def* cond, const Def* a, const Def* b, Debug dbg) { @@ -785,21 +794,21 @@ const Def* World::select(const Def* cond, const Def* a, const Def* b, Debug dbg) if (a == b) return a; - return cse(new Select(cond, a, b, dbg)); + return cse(new Select(*this, cond, a, b, dbg)); } const Def* World::align_of(const Type* type, Debug dbg) { if (auto ptype = type->isa()) return literal(qs64(num_bits(ptype->primtype_tag()) / 8), dbg); - return cse(new AlignOf(bottom(type, dbg), dbg)); + return cse(new AlignOf(*this, bottom(type, dbg), dbg)); } const Def* World::size_of(const Type* type, Debug dbg) { if (auto ptype = type->isa()) return literal(qs64(num_bits(ptype->primtype_tag()) / 8), dbg); - return cse(new SizeOf(bottom(type, dbg), dbg)); + return cse(new SizeOf(*this, bottom(type, dbg), dbg)); } /* @@ -824,7 +833,7 @@ const Def* World::transcendental(MathOpTag tag, const Def* arg, Debug dbg, F&& f THORIN_UNREACHABLE; } } - return cse(new MathOp(tag, arg->type(), { arg }, dbg)); + return cse(new MathOp(*this, tag, arg->type(), { arg }, dbg)); } template @@ -846,7 +855,7 @@ const Def* World::transcendental(MathOpTag tag, const Def* left, const Def* righ THORIN_UNREACHABLE; } } - return cse(new MathOp(tag, left->type(), { left, right }, dbg)); + return cse(new MathOp(*this, tag, left->type(), { left, right }, dbg)); } template @@ -1019,7 +1028,7 @@ const Def* World::load(const Def* mem, const Def* ptr, Debug dbg) { return tuple({mem, tuple({}, dbg)}); } } - return cse(new Load(mem, ptr, dbg)); + return cse(new Load(*this, mem, ptr, dbg)); } bool is_agg_const(const Def* def) { @@ -1029,7 +1038,7 @@ bool is_agg_const(const Def* def) { const Def* World::store(const Def* mem, const Def* ptr, const Def* value, Debug dbg) { if (value->isa()) return mem; - return cse(new Store(mem, ptr, value, dbg)); + return cse(new Store(*this, mem, ptr, value, dbg)); } const Def* World::enter(const Def* mem, Debug dbg) { @@ -1037,15 +1046,15 @@ const Def* World::enter(const Def* mem, Debug dbg) { // in order to simplify as we go and prevent code size from exploding if (auto e = Enter::is_out_mem(mem)) return e; - return cse(new Enter(mem, dbg)); + return cse(new Enter(*this, mem, dbg)); } const Def* World::alloc(const Type* type, const Def* mem, const Def* extra, Debug dbg) { - return cse(new Alloc(type, mem, extra, dbg)); + return cse(new Alloc(*this, type, mem, extra, dbg)); } const Def* World::global(const Def* init, bool is_mutable, Debug dbg) { - return cse(new Global(init, is_mutable, dbg)); + return cse(new Global(*this, init, is_mutable, dbg)); } const Def* World::global_immutable_string(const std::string& str, Debug dbg) { @@ -1060,7 +1069,7 @@ const Def* World::global_immutable_string(const std::string& str, Debug dbg) { } const Assembly* World::assembly(const Type* type, Defs inputs, std::string asm_template, ArrayRef output_constraints, ArrayRef input_constraints, ArrayRef clobbers, Assembly::Flags flags, Debug dbg) { - return cse(new Assembly(type, inputs, asm_template, output_constraints, input_constraints, clobbers, flags, dbg))->as();; + return cse(new Assembly(*this, type, inputs, asm_template, output_constraints, input_constraints, clobbers, flags, dbg))->as();; } const Assembly* World::assembly(Types types, const Def* mem, Defs inputs, std::string asm_template, ArrayRef output_constraints, ArrayRef input_constraints, ArrayRef clobbers, Assembly::Flags flags, Debug dbg) { @@ -1081,7 +1090,7 @@ const Assembly* World::assembly(Types types, const Def* mem, Defs inputs, std::s const Def* World::hlt(const Def* def, Debug dbg) { if (is_pe_done()) return def; - return cse(new Hlt(def, dbg)); + return cse(new Hlt(*this, def, dbg)); } const Def* World::known(const Def* def, Debug dbg) { @@ -1089,12 +1098,12 @@ const Def* World::known(const Def* def, Debug dbg) { return literal_bool(false, dbg); if (!def->has_dep(Dep::Param)) return literal_bool(true, dbg); - return cse(new Known(def, dbg)); + return cse(new Known(*this, def, dbg)); } const Def* World::run(const Def* def, Debug dbg) { if (is_pe_done()) return def; - return cse(new Run(def, dbg)); + return cse(new Run(*this, def, dbg)); } /* @@ -1102,28 +1111,37 @@ const Def* World::run(const Def* def, Debug dbg) { */ Continuation* World::continuation(const FnType* fn, Continuation::Attributes attributes, Debug dbg) { - auto cont = put(fn, attributes, dbg); +#if THORIN_ENABLE_CREATION_CONTEXT + void *array[10]; + size_t size = backtrace(array, 10); + assert(size >= 2); + char ** symbols = backtrace_symbols(array, 10); - size_t i = 0; - for (auto op : fn->ops()) { - auto p = param(op, cont, i++, dbg); - cont->params_.emplace_back(p); - } + dbg.creation_context = symbols[1]; +#endif + + auto cont = put(*this, fn, attributes, dbg); + +#if THORIN_ENABLE_CREATION_CONTEXT + free(symbols); +#endif return cont; } Continuation* World::match(const Type* type, size_t num_patterns) { - Array arg_types(num_patterns + 2); - arg_types[0] = type; - arg_types[1] = fn_type(); - for (size_t i = 0; i < num_patterns; i++) - arg_types[i + 2] = tuple_type({type, fn_type()}); + Array arg_types(num_patterns + 3); + arg_types[0] = mem_type(); + arg_types[1] = type; + arg_types[2] = fn_type({mem_type()}); + for (size_t i = 0; i < num_patterns; i++) { + arg_types[i + 3] = tuple_type({type, fn_type({mem_type()})}); + } return continuation(fn_type(arg_types), Intrinsic::Match, {"match"}); } -const Param* World::param(const Type* type, Continuation* continuation, size_t index, Debug dbg) { - auto param = new Param(type, continuation, index, dbg); +const Param* World::param(const Type* type, const Continuation* continuation, size_t index, Debug dbg) { + auto param = cse(new Param(*this, type, continuation, index, dbg)); #if THORIN_ENABLE_CHECKS if (state_.breakpoints.contains(param->gid())) THORIN_BREAK; #endif @@ -1138,27 +1156,28 @@ const Filter* World::filter(const Defs defs, Debug dbg) { const App* World::app(const Def* callee, const Defs args, Debug dbg) { if (auto continuation = callee->isa()) { switch (continuation->intrinsic()) { + // See also mangle::instantiate when modifying this. case Intrinsic::Branch: { - assert(args.size() == 3); - auto cond = args[0], t = args[1], f = args[2]; + assert(args.size() == 4); + auto mem = args[0], cond = args[1], t = args[2], f = args[3]; if (auto lit = cond->isa()) - return app(lit->value().get_bool() ? t : f, {}, dbg); + return app(lit->value().get_bool() ? t : f, { mem }, dbg); if (t == f) - return app(t, {}, dbg); + return app(t, { mem }, dbg); if (is_not(cond)) { auto inverted = cond->as()->rhs(); - return app(branch(), {inverted, f, t}, dbg); + return app(branch(), {mem, inverted, f, t}, dbg); } break; } case Intrinsic::Match: - if (args.size() == 2) return app(args[1], {}, dbg); - if (auto lit = args[0]->isa()) { - for (size_t i = 2; i < args.size(); i++) { + if (args.size() == 3) return app(args[2], { args[0] }, dbg); + if (auto lit = args[1]->isa()) { + for (size_t i = 3; i < args.size(); i++) { if (extract(args[i], 0_s)->as() == lit) - return app(extract(args[i], 1), {}, dbg); + return app(extract(args[i], 1), { args[0] }, dbg); } - return app(args[1], {}, dbg); + return app(args[2], { args[0] }, dbg); } break; default: @@ -1171,7 +1190,7 @@ const App* World::app(const Def* callee, const Defs args, Debug dbg) { for (size_t i = 0; i < args.size(); i++) ops[i + 1] = args[i]; - return cse(new App(ops, dbg)); + return cse(new App(*this, ops, dbg)); } /* @@ -1276,28 +1295,50 @@ const Def* World::cse_base(const Def* def) { * optimizations */ -void World::cleanup() { cleanup_world(*this); } +Thorin::Thorin(const std::string& name) + : world_(std::make_unique(name)) +{} + +Thorin::Thorin(thorin::World& src) : world_(std::make_unique(src)) {} -void World::opt() { +void Thorin::opt() { + bool debug_passes = getenv("THORIN_DEBUG_PASSES"); #define RUN_PASS(pass) \ { \ - VLOG("running pass {}", #pass); \ - pass; \ - debug_verify(*this); \ + world().VLOG("running pass {}", #pass); \ + pass; \ + debug_verify(world()); \ + if (debug_passes) world().dump_scoped(); \ } RUN_PASS(cleanup()) - RUN_PASS(while (partial_evaluation(*this, true))); // lower2cff + RUN_PASS(while (partial_evaluation(world(), true))); // lower2cff RUN_PASS(flatten_tuples(*this)) - RUN_PASS(clone_bodies(*this)) RUN_PASS(split_slots(*this)) - RUN_PASS(closure_conversion(*this)) + RUN_PASS(closure_conversion(world())) RUN_PASS(lift_builtins(*this)) RUN_PASS(inliner(*this)) RUN_PASS(hoist_enters(*this)) - RUN_PASS(dead_load_opt(*this)) + RUN_PASS(dead_load_opt(world())) RUN_PASS(cleanup()) RUN_PASS(codegen_prepare(*this)) } +bool Thorin::ensure_stack_size(size_t new_size) { +#if THORIN_ENABLE_RLIMITS + struct rlimit rl; + int result = getrlimit(RLIMIT_STACK, &rl); + if(result != 0) return false; + + rl.rlim_cur = new_size; + result = setrlimit(RLIMIT_STACK, &rl); + if(result != 0) return false; + + return true; +#else + return false; +#endif +} + + } diff --git a/src/thorin/world.h b/src/thorin/world.h index 96e30af2b..4b77d8072 100644 --- a/src/thorin/world.h +++ b/src/thorin/world.h @@ -43,7 +43,7 @@ enum class LogLevel { Debug, Verbose, Info, Warn, Error }; * All worlds are completely independent from each other. * This is particular useful for multi-threading. */ -class World : public TypeTable, public Streamable { +class World : public Streamable { public: struct SeaHash { static hash_t hash(const Def* def) { return def->hash(); } @@ -65,7 +65,7 @@ class World : public TypeTable, public Streamable { using Sea = HashSet;///< This @p HashSet contains Thorin's "sea of nodes". using Breakpoints = HashSet; - using Externals = HashMap; + using Externals = HashMap; World(World&&) = delete; World& operator=(const World&) = delete; @@ -78,7 +78,6 @@ class World : public TypeTable, public Streamable { stream_ = other.stream_; state_ = other.state_; } - ~World(); /// @name manage global identifier - a unique number for each Def //@{ @@ -90,12 +89,42 @@ class World : public TypeTable, public Streamable { //@{ bool empty() { return data_.externals_.empty(); } const Externals& externals() const { return data_.externals_; } - void make_external(Continuation* cont) { data_.externals_.emplace(cont->unique_name(), cont); } - void make_internal(Continuation* cont) { data_.externals_.erase(cont->unique_name()); } - bool is_external(const Continuation* cont) { return data_.externals_.contains(cont->unique_name()); } - Continuation* lookup(const std::string& name) { return data_.externals_.lookup(name).value_or(nullptr); } + void make_external(Def* cont) { assert(&cont->world() == this); data_.externals_.emplace(cont->unique_name(), cont); } + void make_internal(Def* cont) { assert(&cont->world() == this); data_.externals_.erase(cont->unique_name()); } + bool is_external(const Def* cont) { return data_.externals_.contains(cont->unique_name()); } + Def* lookup(const std::string& name) { return data_.externals_.lookup(name).value_or(nullptr); } + DEBUG_UTIL Continuation* find_cont(const char* name) { + for (auto cont : copy_continuations()) { + if (cont->unique_name() == name) + return cont; + } + return nullptr; + } //@} + // types + + const Type* star() { return types_.star_; } + + const Type* tuple_type(Types ops); + const TupleType* unit_type() { return tuple_type({})->as(); } ///< Returns unit, i.e., an empty @p TupleType. + VariantType* variant_type(Symbol name, size_t size); + StructType* struct_type(Symbol name, size_t size); + +#define THORIN_ALL_TYPE(T, M) \ + const PrimType* type_##T(size_t length = 1) { return prim_type(PrimType_##T, length); } +#include "thorin/tables/primtypetable.h" + const PrimType* prim_type(PrimTypeTag tag, size_t length = 1); + const BottomType* bottom_type() { return make(*this, Debug()); } + const MemType* mem_type() { return make(*this, Debug()); } + const FrameType* frame_type() { return make(*this, Debug()); } + const PtrType* ptr_type(const Type* pointee, size_t length = 1, AddrSpace addr_space = AddrSpace::Generic); + const FnType* fn_type() { return fn_type({}); } ///< Returns an empty @p FnType. + const FnType* fn_type(Types args); + const ClosureType* closure_type(Types args); + const DefiniteArrayType* definite_array_type(const Type* elem, u64 dim); + const IndefiniteArrayType* indefinite_array_type(const Type* elem); + // literals #define THORIN_ALL_TYPE(T, M) \ @@ -110,8 +139,8 @@ class World : public TypeTable, public Streamable { const Def* one(const Type* type, Debug dbg = {}, size_t length = 1) { return one(type->as()->primtype_tag(), dbg, length); } const Def* allset(PrimTypeTag tag, Debug dbg = {}, size_t length = 1); const Def* allset(const Type* type, Debug dbg = {}, size_t length = 1) { return allset(type->as()->primtype_tag(), dbg, length); } - const Def* top(const Type* type, Debug dbg = {}, size_t length = 1) { return splat(cse(new Top(type, dbg)), length); } - const Def* bottom(const Type* type, Debug dbg = {}, size_t length = 1) { return splat(cse(new Bottom(type, dbg)), length); } + const Def* top(const Type* type, Debug dbg = {}, size_t length = 1) { return splat(cse(new Top(*this, type, dbg)), length); } + const Def* bottom(const Type* type, Debug dbg = {}, size_t length = 1) { return splat(cse(new Bottom(*this, type, dbg)), length); } const Def* bottom(PrimTypeTag tag, Debug dbg = {}, size_t length = 1) { return bottom(prim_type(tag), dbg, length); } // arithops @@ -156,15 +185,15 @@ class World : public TypeTable, public Streamable { return cse(new IndefiniteArray(*this, elem, dim, dbg)); } const Def* struct_agg(const StructType* struct_type, Defs args, Debug dbg = {}) { - return try_fold_aggregate(cse(new StructAgg(struct_type, args, dbg))); + return try_fold_aggregate(cse(new StructAgg(*this, struct_type, args, dbg))); } const Def* tuple(Defs args, Debug dbg = {}) { return args.size() == 1 ? args.front() : try_fold_aggregate(cse(new Tuple(*this, args, dbg))); } - const Def* variant(const VariantType* variant_type, const Def* value, size_t index, Debug dbg = {}) { return cse(new Variant(variant_type, value, index, dbg)); } + const Def* variant(const VariantType* variant_type, const Def* value, size_t index, Debug dbg = {}) { return cse(new Variant(*this, variant_type, value, index, dbg)); } const Def* variant_index (const Def* value, Debug dbg = {}); const Def* variant_extract(const Def* value, size_t index, Debug dbg = {}); - const Def* closure(const ClosureType* closure_type, const Def* fn, const Def* env, Debug dbg = {}) { return cse(new Closure(closure_type, fn, env, dbg)); } + const Def* closure(const ClosureType* closure_type, const Def* fn, const Def* env, Debug dbg = {}) { return cse(new Closure(*this, closure_type, fn, env, dbg)); } const Def* vector(Defs args, Debug dbg = {}) { if (args.size() == 1) return args[0]; return try_fold_aggregate(cse(new Vector(*this, args, dbg))); @@ -216,7 +245,7 @@ class World : public TypeTable, public Streamable { const Def* load(const Def* mem, const Def* ptr, Debug dbg = {}); const Def* store(const Def* mem, const Def* ptr, const Def* val, Debug dbg = {}); const Def* enter(const Def* mem, Debug dbg = {}); - const Def* slot(const Type* type, const Def* frame, Debug dbg = {}) { return cse(new Slot(type, frame, dbg)); } + 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* global(const Def* init, bool is_mutable = true, Debug dbg = {}); @@ -243,12 +272,9 @@ class World : public TypeTable, public Streamable { Continuation* branch() const { return data_.branch_; } Continuation* match(const Type* type, size_t num_patterns); Continuation* end_scope() const { return data_.end_scope_; } + const App* app(const Def* callee, const Defs args, Debug dbg = {}); const Filter* filter(const Defs, Debug dbg = {}); - /// Performs dead code, unreachable code and unused type elimination. - void cleanup(); - void opt(); - // getters const std::string& name() const { return data_.name_; } @@ -275,6 +301,8 @@ class World : public TypeTable, public Streamable { /// @name logging //@{ + void dump_scoped(bool=true) const; + void dump_scoped_to_disk() const; Stream& stream(Stream&) const; Stream& stream() { return *stream_; } /// Writes to a file named @c name(). @@ -301,6 +329,7 @@ class World : public TypeTable, public Streamable { // Ignore log void ignore() {} + template void ddef(const Def* def, const char* fmt, Args&&... args) { log(LogLevel::Debug, def->loc(), fmt, std::forward(args)...); } template void idef(const Def* def, const char* fmt, Args&&... args) { log(LogLevel::Info, def->loc(), fmt, std::forward(args)...); } template void wdef(const Def* def, const char* fmt, Args&&... args) { log(LogLevel::Warn, def->loc(), fmt, std::forward(args)...); } template void edef(const Def* def, const char* fmt, Args&&... args) { error(def->loc(), fmt, std::forward(args)...); } @@ -310,17 +339,8 @@ class World : public TypeTable, public Streamable { static std::string colorize(const std::string& str, int color); //@} - friend void swap(World& w1, World& w2) { - using std::swap; - swap(static_cast(w1), static_cast(w2)); - swap(w1.state_, w2.state_); - swap(w1.data_, w2.data_); - swap(w1.stream_, w2.stream_); - } - private: - const Param* param(const Type* type, Continuation* continuation, size_t index, Debug dbg); - const App* app(const Def* callee, const Defs args, Debug dbg = {}); + 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&&); template const Def* transcendental(MathOpTag, const Def*, const Def*, Debug, F&&); @@ -329,6 +349,11 @@ class World : public TypeTable, public Streamable { //@{ template const T* cse(const T* primop) { return cse_base(primop)->template as(); } const Def* cse_base(const Def*); + template + const T* make(Args&&... args) { + auto def = new T(args...); + return cse(def); + } template T* put(Args&&... args) { @@ -361,14 +386,38 @@ class World : public TypeTable, public Streamable { Continuation* end_scope_; } data_; + TypeTable types_; + std::shared_ptr stream_; friend class Mangler; friend class Cleaner; friend class Continuation; + friend class Param; friend class Filter; friend class App; friend class Importer; + friend class Thorin; + friend class TypeTable; +}; + +class Thorin { +public: + /// Initial world constructor + explicit Thorin(const std::string& name); + explicit Thorin(World& src); + + World& world() { return *world_; }; + std::unique_ptr& world_container() { return world_; } + + /// Performs dead code, unreachable code and unused type elimination. + void cleanup(); + void opt(); + + bool ensure_stack_size(size_t new_size); + +private: + std::unique_ptr world_; }; }