├── .gitignore ├── .travis.yml.off ├── CMakeLists.txt ├── LICENSE.TXT ├── README.md ├── cmake ├── modules │ └── FindHalf.cmake └── thorin-config.cmake.in ├── doxyfile └── src ├── CMakeLists.txt └── thorin ├── CMakeLists.txt ├── analyses ├── cfg.cpp ├── cfg.h ├── domtree.cpp ├── domtree.h ├── free_defs.cpp ├── free_defs.h ├── looptree.cpp ├── looptree.h ├── schedule.cpp ├── schedule.h ├── scope.cpp ├── scope.h ├── verify.cpp └── verify.h ├── be ├── c │ ├── c.cpp │ └── c.h ├── codegen.cpp ├── codegen.h ├── emitter.h ├── json │ ├── json.cpp │ └── json.h ├── kernel_config.h ├── llvm │ ├── amdgpu.cpp │ ├── amdgpu.h │ ├── amdgpu_hsa.cpp │ ├── amdgpu_hsa.h │ ├── amdgpu_pal.cpp │ ├── amdgpu_pal.h │ ├── cpu.cpp │ ├── cpu.h │ ├── llvm.cpp │ ├── llvm.h │ ├── nvvm.cpp │ ├── nvvm.h │ ├── parallel.cpp │ ├── runtime.cpp │ ├── runtime.h │ ├── runtime.inc │ └── vectorize.cpp ├── runtime.h ├── shady │ ├── shady.cpp │ └── shady.h └── spirv │ ├── spirv.cpp │ ├── spirv.h │ ├── spirv_builder.hpp │ ├── spirv_instructions.cpp │ ├── spirv_private.h │ └── spirv_types.cpp ├── config.h.in ├── continuation.cpp ├── continuation.h ├── debug.cpp ├── debug.h ├── def.cpp ├── def.h ├── enums.cpp ├── enums.h ├── primop.cpp ├── primop.h ├── rec_stream.cpp ├── tables ├── allnodes.h ├── arithoptable.h ├── cmptable.h ├── mathoptable.h ├── nodetable.h └── primtypetable.h ├── transform ├── cleanup_world.cpp ├── closure_conversion.cpp ├── closure_conversion.h ├── codegen_prepare.cpp ├── codegen_prepare.h ├── dead_load_opt.cpp ├── dead_load_opt.h ├── flatten_tuples.cpp ├── flatten_tuples.h ├── hls_channels.cpp ├── hls_channels.h ├── hls_kernel_launch.cpp ├── hls_kernel_launch.h ├── hoist_enters.cpp ├── hoist_enters.h ├── importer.cpp ├── importer.h ├── inliner.cpp ├── inliner.h ├── lift_builtins.cpp ├── lift_builtins.h ├── mangle.cpp ├── mangle.h ├── partial_evaluation.cpp ├── partial_evaluation.h ├── resolve_loads.cpp ├── resolve_loads.h ├── rewrite.cpp ├── rewrite.h ├── split_slots.cpp └── split_slots.h ├── type.cpp ├── type.h ├── util ├── array.h ├── cast.h ├── graphviz_dump.cpp ├── hash.cpp ├── hash.h ├── indexmap.h ├── indexset.h ├── iterator.h ├── scoped_dump.cpp ├── scoped_dump.h ├── stream.cpp ├── stream.h ├── symbol.cpp ├── symbol.h ├── types.h └── utility.h ├── world.cpp └── world.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.bc 2 | *.cl 3 | *.cu 4 | *.ll 5 | *.nvvm 6 | *.amdgpu 7 | *.s 8 | *.out 9 | CMakeCache.txt 10 | CMakeFiles/ 11 | build* 12 | test/impala 13 | tags 14 | .vimrc 15 | -------------------------------------------------------------------------------- /.travis.yml.off: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: required 4 | dist: trusty 5 | 6 | cache: 7 | - ccache 8 | 9 | install: 10 | - mkdir -p ~/cmake 11 | - mkdir -p ~/work 12 | 13 | - cd ~/work 14 | - git clone --branch=$TRAVIS_BRANCH https://github.com/AnyDSL/anydsl.git 15 | 16 | - cd anydsl 17 | - bash scripts/install_cmake.sh ~/cmake/ 18 | - export PATH=~/cmake/bin:~/work/anydsl/llvm_install/bin:~/work/anydsl/impala/build/bin:$PATH 19 | 20 | - cp config.sh.template config.sh 21 | - sed -i 's/BRANCH:=master/BRANCH:=$TRAVIS_BRANCH/g' config.sh 22 | - sed -i 's/HTTPS:=false/HTTPS:=true/g' config.sh 23 | - sed -i 's/FETCH_LLVM:=false/FETCH_LLVM:=true/g' config.sh 24 | - cat config.sh 25 | 26 | - CXX=/home/travis/work/anydsl/llvm_install/bin/clang++ ./setup.sh 27 | 28 | script: 29 | - cd impala/test/ 30 | - python run_tests.py . 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20.0 FATAL_ERROR) 2 | 3 | project(Thorin) 4 | 5 | set(PACKAGE_VERSION "0.3.9") 6 | #set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited config" FORCE) 7 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1) 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | 11 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) 12 | option(THORIN_PROFILE "profile complexity in thorin::HashTable - only works in Debug build" ON) 13 | 14 | if(CMAKE_BUILD_TYPE STREQUAL "") 15 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Debug or Release" FORCE) 16 | endif() 17 | 18 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 19 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 20 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 21 | 22 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") 23 | 24 | # check for Half library 25 | find_package(Half REQUIRED) 26 | message(STATUS "Building with Half library from ${Half_INCLUDE_DIRS}.") 27 | 28 | # find json package for json output support. 29 | if(TARGET nlohmann_json) 30 | set(THORIN_ENABLE_JSON TRUE) 31 | else() 32 | find_package(nlohmann_json 3.2.0 QUIET) 33 | if(nlohmann_json_FOUND) 34 | set(THORIN_ENABLE_JSON TRUE) 35 | endif() 36 | endif() 37 | 38 | # check for possible llvm extension 39 | find_package(LLVM QUIET CONFIG) 40 | if(LLVM_FOUND) 41 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 42 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 43 | if(LLVM_LINK_LLVM_DYLIB) 44 | set(AnyDSL_LLVM_LINK_SHARED "USE_SHARED") 45 | else() 46 | if (BUILD_SHARED_LIBS) 47 | 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.") 48 | endif() 49 | endif() 50 | # check for RV 51 | find_package(RV QUIET CONFIG) 52 | if(RV_FOUND) 53 | message(STATUS "Building with RV from LLVM installation.") 54 | else() 55 | message(STATUS "Building without RV. Install RV as part of LLVM.") 56 | endif() 57 | else() 58 | message(STATUS "Building without LLVM and RV. Specify LLVM_DIR to compile with LLVM.") 59 | endif() 60 | 61 | include(CheckIncludeFile) 62 | check_include_file(sys/resource.h THORIN_ENABLE_RLIMITS) 63 | 64 | if (NOT TARGET shady) 65 | find_package(shady CONFIG) 66 | if (shady_FOUND) 67 | message(STATUS "Found shady at ${shady_DIR}") 68 | set(THORIN_ENABLE_SHADY TRUE) 69 | endif() 70 | else() 71 | export(TARGETS shady api FILE ${CMAKE_BINARY_DIR}/share/anydsl/cmake/shady-exports.cmake) 72 | set(THORIN_ENABLE_SHADY TRUE) 73 | endif() 74 | 75 | find_package(SPIRV-Headers) 76 | if (SPIRV-Headers_FOUND) 77 | message(STATUS "Found SPIRV-Headers at ${SPIRV-Headers_DIR}") 78 | set(THORIN_ENABLE_SPIRV TRUE) 79 | endif() 80 | 81 | message(STATUS "Using Debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") 82 | message(STATUS "Using Release flags: ${CMAKE_CXX_FLAGS_RELEASE}") 83 | if(DEFINED CMAKE_BUILD_TYPE) 84 | message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 85 | endif() 86 | 87 | set(Thorin_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 88 | 89 | # configure thorin preprocessor definitions 90 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 91 | set(THORIN_ENABLE_CHECKS TRUE) 92 | endif() 93 | if(THORIN_PROFILE) 94 | set(THORIN_ENABLE_PROFILING TRUE) 95 | endif() 96 | if(LLVM_FOUND) 97 | set(THORIN_ENABLE_LLVM TRUE) 98 | endif() 99 | if(RV_FOUND) 100 | set(THORIN_ENABLE_RV TRUE) 101 | endif() 102 | configure_file(src/thorin/config.h.in ${CMAKE_BINARY_DIR}/include/thorin/config.h @ONLY) 103 | 104 | add_subdirectory(src) 105 | 106 | export(TARGETS thorin FILE ${CMAKE_BINARY_DIR}/share/anydsl/cmake/thorin-exports.cmake) 107 | configure_file(cmake/thorin-config.cmake.in ${CMAKE_BINARY_DIR}/share/anydsl/cmake/thorin-config.cmake @ONLY) 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thorin 2 | The Higher-Order Intermediate Representation 3 | 4 | ## Build Instructions 5 | 6 | See [Build Instructions](https://anydsl.github.io/Build-Instructions). 7 | 8 | ## Documentation 9 | 10 | See our [AnyDSL/Thorin Website](https://anydsl.github.io/Thorin). 11 | -------------------------------------------------------------------------------- /cmake/modules/FindHalf.cmake: -------------------------------------------------------------------------------- 1 | # Find the Half library 2 | # 3 | # To set manually the paths, define these environment variables: 4 | # Half_DIR - Include path 5 | # 6 | # Once done this will define 7 | # Half_INCLUDE_DIRS - where to find Half library include file 8 | # Half_FOUND - True if Half library is found 9 | 10 | find_path(Half_DIR half.hpp PATHS ${Half_DIR} $ENV{Half_DIR} PATH_SUFFIXES include DOC "C++ library for half precision floating point arithmetics.") 11 | find_path(Half_INCLUDE_DIR half.hpp PATHS ${Half_DIR} PATH_SUFFIXES include) 12 | 13 | include(FindPackageHandleStandardArgs) 14 | find_package_handle_standard_args(Half DEFAULT_MSG Half_INCLUDE_DIR) 15 | 16 | set(Half_INCLUDE_DIRS ${Half_INCLUDE_DIR}) 17 | 18 | mark_as_advanced(Half_INCLUDE_DIR) 19 | -------------------------------------------------------------------------------- /cmake/thorin-config.cmake.in: -------------------------------------------------------------------------------- 1 | # Provides Thorin library and include path. 2 | # Once done this will define 3 | # 4 | # Thorin_INCLUDE_DIRS (including dependencies to LLVM/RV/Half) 5 | # Thorin_LIBRARIES (including dependencies to LLVM/RV) 6 | # Thorin_FOUND 7 | # Thorin_HAS_LLVM_SUPPORT | Thorin_HAS_RV_SUPPORT 8 | # AnyDSL_LLVM_LINK_SHARED 9 | 10 | cmake_minimum_required(VERSION 3.13.4) 11 | 12 | find_path(Thorin_DIR 13 | NAMES thorin-config.cmake 14 | PATHS 15 | ${Thorin_DIR} ${AnyDSL_thorin_DIR} $ENV{Thorin_DIR} 16 | ${CMAKE_CURRENT_LIST_DIR} 17 | "@CMAKE_CURRENT_BINARY_DIR@" 18 | "@CMAKE_INSTALL_PREFIX@" 19 | PATH_SUFFIXES 20 | share/anydsl/cmake 21 | ) 22 | 23 | find_path(Thorin_ROOT_DIR 24 | NAMES cmake/thorin-config.cmake.in src/thorin/world.h 25 | PATHS ${Thorin_DIR} $ENV{Thorin_DIR} ${Thorin_ROOT_DIR} "@CMAKE_CURRENT_SOURCE_DIR@" "@Thorin_ROOT_DIR@" "@CMAKE_SOURCE_DIR@") 26 | list(APPEND CMAKE_MODULE_PATH "${Thorin_ROOT_DIR}/cmake/modules") 27 | 28 | find_path(Half_DIR NAMES half.hpp PATHS ${Half_DIR} $ENV{Half_DIR} "@Half_DIR@" "@Half_INCLUDE_DIR@") 29 | find_package(Half REQUIRED) 30 | 31 | set(Thorin_HAS_JSON_SUPPORT @THORIN_ENABLE_JSON@) 32 | set(Thorin_HAS_LLVM_SUPPORT @LLVM_FOUND@) 33 | set(Thorin_HAS_RV_SUPPORT @RV_FOUND@) 34 | set(Thorin_HAS_SHADY_SUPPORT @THORIN_ENABLE_SHADY@) 35 | set(Thorin_HAS_SPIRV_SUPPORT @THORIN_ENABLE_SPIRV@) 36 | set(AnyDSL_LLVM_LINK_SHARED @AnyDSL_LLVM_LINK_SHARED@) 37 | 38 | if(Thorin_HAS_LLVM_SUPPORT) 39 | find_package(LLVM QUIET CONFIG PATHS ${LLVM_DIR} $ENV{LLVM_DIR} "@LLVM_DIR@" NO_DEFAULT_PATH) 40 | 41 | if(Thorin_HAS_RV_SUPPORT) 42 | find_package(RV QUIET) 43 | endif() 44 | endif() 45 | 46 | find_path(Thorin_INCLUDE_DIR NAMES thorin/world.h PATHS ${Thorin_ROOT_DIR}/src) 47 | find_path(Thorin_CONFIG_DIR NAMES thorin/config.h 48 | PATHS 49 | ${Thorin_DIR} ${AnyDSL_thorin_DIR} $ENV{Thorin_DIR} 50 | ${CMAKE_CURRENT_LIST_DIR} 51 | "@CMAKE_CURRENT_BINARY_DIR@" 52 | "@CMAKE_INSTALL_PREFIX@" 53 | PATH_SUFFIXES 54 | include 55 | ../../../include 56 | ) 57 | 58 | if(NOT TARGET thorin) 59 | include(${Thorin_DIR}/thorin-exports.cmake) 60 | endif() 61 | set(Thorin_LIBRARY thorin) 62 | 63 | set(Thorin_DEPS_INCLUDE_DIRS ${Half_INCLUDE_DIRS} ${Thorin_CONFIG_DIR}) 64 | if(LLVM_FOUND) 65 | list(APPEND Thorin_DEPS_INCLUDE_DIRS ${LLVM_INCLUDE_DIRS}) 66 | endif(LLVM_FOUND) 67 | if(RV_FOUND) 68 | list(APPEND Thorin_DEPS_INCLUDE_DIRS ${RV_INCLUDE_DIRS}) 69 | endif(RV_FOUND) 70 | 71 | set(Thorin_LIBRARIES ${Thorin_LIBRARY}) 72 | set(Thorin_INCLUDE_DIRS ${Thorin_INCLUDE_DIR} ${Thorin_DEPS_INCLUDE_DIRS}) 73 | 74 | include(FindPackageHandleStandardArgs) 75 | find_package_handle_standard_args(Thorin DEFAULT_MSG Thorin_DIR Thorin_INCLUDE_DIR) 76 | 77 | mark_as_advanced(Thorin_LIBRARY Thorin_INCLUDE_DIR Thorin_CONFIG_DIR Thorin_ROOT_DIR) 78 | -------------------------------------------------------------------------------- /doxyfile: -------------------------------------------------------------------------------- 1 | @INCLUDE = ../layout/doxyfile.skeleton 2 | 3 | PROJECT_NAME = Thorin 4 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # build thorin lib 2 | add_subdirectory(thorin) 3 | 4 | if(MSVC) 5 | target_compile_definitions(thorin PRIVATE -D_SCL_SECURE_NO_WARNINGS) 6 | target_compile_definitions(thorin PRIVATE -D_CRT_SECURE_NO_WARNINGS) 7 | target_compile_options(thorin PRIVATE "/wd4800" "/wd4520") 8 | target_compile_options(thorin PRIVATE "/experimental:external" "/external:anglebrackets" "/external:W0") 9 | else() 10 | target_compile_options(thorin PRIVATE "-Wall" "-Wextra") 11 | endif() 12 | 13 | # check for colored terminal 14 | set(COLOR_TTY_AVAILABLE TRUE) 15 | if(WIN32) 16 | # Windows console does not support ANSI escape codes 17 | set(COLOR_TTY_AVAILABLE FALSE) 18 | endif() 19 | set(COLORIZE_OUTPUT ${COLOR_TTY_AVAILABLE} CACHE BOOL "Set to true to enable colored error messages") 20 | if(COLORIZE_OUTPUT) 21 | target_compile_definitions(thorin PRIVATE -DCOLORIZE_LOG) 22 | endif() 23 | -------------------------------------------------------------------------------- /src/thorin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(THORIN_SOURCES 2 | continuation.cpp 3 | continuation.h 4 | debug.cpp 5 | debug.h 6 | def.cpp 7 | def.h 8 | enums.cpp 9 | enums.h 10 | primop.cpp 11 | primop.h 12 | rec_stream.cpp 13 | type.cpp 14 | type.h 15 | world.cpp 16 | world.h 17 | analyses/cfg.cpp 18 | analyses/cfg.h 19 | analyses/domtree.cpp 20 | analyses/domtree.h 21 | analyses/free_defs.cpp 22 | analyses/free_defs.h 23 | analyses/looptree.cpp 24 | analyses/looptree.h 25 | analyses/schedule.cpp 26 | analyses/schedule.h 27 | analyses/scope.cpp 28 | analyses/scope.h 29 | analyses/verify.cpp 30 | analyses/verify.h 31 | be/codegen.cpp 32 | be/codegen.h 33 | be/emitter.h 34 | be/c/c.cpp 35 | be/c/c.h 36 | be/runtime.h 37 | be/kernel_config.h 38 | tables/allnodes.h 39 | tables/arithoptable.h 40 | tables/cmptable.h 41 | tables/nodetable.h 42 | tables/primtypetable.h 43 | tables/mathoptable.h 44 | transform/cleanup_world.cpp 45 | transform/closure_conversion.cpp 46 | transform/closure_conversion.h 47 | transform/codegen_prepare.h 48 | transform/codegen_prepare.cpp 49 | transform/dead_load_opt.cpp 50 | transform/dead_load_opt.h 51 | transform/hoist_enters.cpp 52 | transform/hoist_enters.h 53 | transform/flatten_tuples.cpp 54 | transform/flatten_tuples.h 55 | transform/importer.cpp 56 | transform/importer.h 57 | transform/inliner.cpp 58 | transform/inliner.h 59 | transform/lift_builtins.cpp 60 | transform/lift_builtins.h 61 | transform/mangle.cpp 62 | transform/mangle.h 63 | transform/resolve_loads.cpp 64 | transform/resolve_loads.h 65 | transform/partial_evaluation.cpp 66 | transform/partial_evaluation.h 67 | transform/rewrite.cpp 68 | transform/rewrite.h 69 | transform/split_slots.cpp 70 | transform/split_slots.h 71 | transform/hls_channels.cpp 72 | transform/hls_channels.h 73 | transform/hls_kernel_launch.h 74 | transform/hls_kernel_launch.cpp 75 | util/array.h 76 | util/cast.h 77 | util/hash.h 78 | util/hash.cpp 79 | util/indexmap.h 80 | util/indexset.h 81 | util/iterator.h 82 | util/stream.cpp 83 | util/stream.h 84 | util/symbol.cpp 85 | util/symbol.h 86 | util/types.h 87 | util/utility.h 88 | util/graphviz_dump.cpp 89 | util/scoped_dump.h 90 | util/scoped_dump.cpp 91 | ) 92 | 93 | if(LLVM_FOUND) 94 | list(APPEND THORIN_SOURCES 95 | be/llvm/cpu.cpp 96 | be/llvm/cpu.h 97 | be/llvm/llvm.cpp 98 | be/llvm/llvm.h 99 | be/llvm/amdgpu.cpp 100 | be/llvm/amdgpu.h 101 | be/llvm/amdgpu_hsa.cpp 102 | be/llvm/amdgpu_hsa.h 103 | be/llvm/amdgpu_pal.cpp 104 | be/llvm/amdgpu_pal.h 105 | be/llvm/nvvm.cpp 106 | be/llvm/nvvm.h 107 | be/llvm/parallel.cpp 108 | be/llvm/runtime.inc 109 | be/llvm/runtime.cpp 110 | be/llvm/runtime.h 111 | be/llvm/vectorize.cpp 112 | ) 113 | endif() 114 | 115 | if (THORIN_ENABLE_SHADY) 116 | list(APPEND THORIN_SOURCES 117 | be/shady/shady.cpp 118 | ) 119 | endif() 120 | 121 | if(THORIN_ENABLE_JSON) 122 | list(APPEND THORIN_SOURCES 123 | be/json/json.cpp 124 | be/json/json.h 125 | ) 126 | endif() 127 | 128 | if(THORIN_ENABLE_SPIRV) 129 | list(APPEND THORIN_SOURCES 130 | be/spirv/spirv.cpp 131 | be/spirv/spirv_types.cpp 132 | be/spirv/spirv_instructions.cpp 133 | be/spirv/spirv.h 134 | ) 135 | endif() 136 | 137 | add_library(thorin ${THORIN_SOURCES}) 138 | target_include_directories(thorin PUBLIC ${Half_INCLUDE_DIRS} ${Thorin_ROOT_DIR}/src ${CMAKE_BINARY_DIR}/include) 139 | 140 | if(LLVM_FOUND) 141 | set(Thorin_LLVM_COMPONENTS core support ipo target ${LLVM_TARGETS_TO_BUILD}) 142 | target_include_directories(thorin SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS}) 143 | target_compile_definitions(thorin PRIVATE ${LLVM_DEFINITIONS}) 144 | if(RV_FOUND) 145 | target_include_directories(thorin PRIVATE ${RV_INCLUDE_DIRS}) 146 | target_link_libraries(thorin PRIVATE ${RV_LIBRARIES}) 147 | list(APPEND Thorin_LLVM_COMPONENTS analysis passes transformutils) 148 | endif() 149 | llvm_config(thorin ${AnyDSL_LLVM_LINK_SHARED} ${Thorin_LLVM_COMPONENTS}) 150 | endif() 151 | 152 | if (THORIN_ENABLE_SHADY) 153 | if (shady_FOUND) 154 | target_link_libraries(thorin PRIVATE shady::shady) 155 | else() 156 | target_link_libraries(thorin PRIVATE shady) 157 | endif() 158 | endif() 159 | 160 | if(THORIN_ENABLE_JSON) 161 | target_link_libraries(thorin PRIVATE nlohmann_json::nlohmann_json) 162 | endif() 163 | -------------------------------------------------------------------------------- /src/thorin/analyses/cfg.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/analyses/cfg.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "thorin/world.h" 9 | #include "thorin/analyses/domtree.h" 10 | #include "thorin/analyses/looptree.h" 11 | #include "thorin/analyses/scope.h" 12 | #include "thorin/util/utility.h" 13 | 14 | namespace thorin { 15 | 16 | //------------------------------------------------------------------------------ 17 | 18 | uint64_t CFNode::gid_counter_ = 0; 19 | 20 | void CFNode::link(const CFNode* other) const { 21 | this ->succs_.emplace(other); 22 | other->preds_.emplace(this); 23 | } 24 | 25 | Stream& CFNode::stream(Stream& s) const { return s << continuation(); } 26 | 27 | //------------------------------------------------------------------------------ 28 | 29 | CFA::CFA(const Scope& scope) 30 | : scope_(scope) 31 | , entry_(node(scope.entry())) 32 | , exit_ (node(scope.entry()->world().end_scope())) 33 | { 34 | std::queue cfg_queue; 35 | ContinuationSet cfg_done; 36 | 37 | auto cfg_enqueue = [&] (Continuation* continuation) { 38 | if (cfg_done.emplace(continuation).second) 39 | cfg_queue.push(continuation); 40 | }; 41 | 42 | cfg_queue.push(scope.entry()); 43 | 44 | while (!cfg_queue.empty()) { 45 | auto src = pop(cfg_queue); 46 | std::queue queue; 47 | DefSet done; 48 | 49 | auto enqueue = [&] (const Def* def) { 50 | if (def->order() > 0 && scope.contains(def) && done.emplace(def).second) { 51 | if (auto dst = def->isa_nom()) { 52 | cfg_enqueue(dst); 53 | node(src)->link(node(dst)); 54 | } else 55 | queue.push(def); 56 | } 57 | }; 58 | 59 | if (src->has_body()) 60 | queue.push(src->body()); 61 | 62 | while (!queue.empty()) { 63 | auto def = pop(queue); 64 | if (def->isa()) 65 | continue; 66 | for (auto op : def->ops()) 67 | enqueue(op); 68 | } 69 | } 70 | 71 | link_to_exit(); 72 | verify(); 73 | } 74 | 75 | const CFNode* CFA::node(Continuation* continuation) { 76 | auto& n = nodes_[continuation]; 77 | if (n == nullptr) 78 | n = new CFNode(continuation); 79 | return n; 80 | } 81 | 82 | CFA::~CFA() { 83 | for (const auto& p : nodes_) 84 | delete p.second; 85 | } 86 | 87 | const F_CFG& CFA::f_cfg() const { return lazy_init(this, f_cfg_); } 88 | const B_CFG& CFA::b_cfg() const { return lazy_init(this, b_cfg_); } 89 | 90 | void CFA::link_to_exit() { 91 | typedef thorin::GIDSet CFNodeSet; 92 | 93 | CFNodeSet reachable; 94 | std::queue queue; 95 | 96 | // first, link all nodes without succs to exit 97 | for (auto p : nodes()) { 98 | auto n = p.second; 99 | if (n != exit() && n->succs().empty()) 100 | n->link(exit()); 101 | } 102 | 103 | auto backwards_reachable = [&] (const CFNode* n) { 104 | auto enqueue = [&] (const CFNode* n) { 105 | if (reachable.emplace(n).second) 106 | queue.push(n); 107 | }; 108 | 109 | enqueue(n); 110 | 111 | while (!queue.empty()) { 112 | for (auto pred : pop(queue)->preds()) 113 | enqueue(pred); 114 | } 115 | }; 116 | 117 | std::stack stack; 118 | CFNodeSet on_stack; 119 | 120 | auto push = [&] (const CFNode* n) { 121 | if (on_stack.emplace(n).second) { 122 | stack.push(n); 123 | return true; 124 | } 125 | 126 | return false; 127 | }; 128 | 129 | backwards_reachable(exit()); 130 | push(entry()); 131 | 132 | while (!stack.empty()) { 133 | auto n = stack.top(); 134 | 135 | bool todo = false; 136 | for (auto succ : n->succs()) 137 | todo |= push(succ); 138 | 139 | if (!todo) { 140 | if (!reachable.contains(n)) { 141 | n->link(exit()); 142 | backwards_reachable(n); 143 | } 144 | 145 | stack.pop(); 146 | } 147 | } 148 | } 149 | 150 | void CFA::verify() { 151 | bool error = false; 152 | for (const auto& p : nodes()) { 153 | auto in = p.second; 154 | if (in != entry() && in->preds_.size() == 0) { 155 | scope().world().VLOG("missing predecessors: {}", in->continuation()); 156 | error = true; 157 | } 158 | } 159 | 160 | if (error) { 161 | // TODO 162 | assert(false && "CFG not sound"); 163 | } 164 | } 165 | 166 | //------------------------------------------------------------------------------ 167 | 168 | template 169 | CFG::CFG(const CFA& cfa) 170 | : cfa_(cfa) 171 | , rpo_(*this) 172 | { 173 | auto index = post_order_visit(entry(), size()); 174 | assert_unused(index == 0); 175 | } 176 | 177 | template 178 | size_t CFG::post_order_visit(const CFNode* n, size_t i) { 179 | auto& n_index = forward ? n->f_index_ : n->b_index_; 180 | n_index = size_t(-2); 181 | 182 | for (auto succ : succs(n)) { 183 | if (index(succ) == size_t(-1)) 184 | i = post_order_visit(succ, i); 185 | } 186 | 187 | n_index = i-1; 188 | rpo_[n] = n; 189 | return n_index; 190 | } 191 | 192 | template const CFNodes& CFG::preds(const CFNode* n) const { assert(n != nullptr); return forward ? n->preds() : n->succs(); } 193 | template const CFNodes& CFG::succs(const CFNode* n) const { assert(n != nullptr); return forward ? n->succs() : n->preds(); } 194 | template const DomTreeBase& CFG::domtree() const { return lazy_init(this, domtree_); } 195 | template const LoopTree& CFG::looptree() const { return lazy_init(this, looptree_); } 196 | 197 | template class CFG; 198 | template class CFG; 199 | 200 | //------------------------------------------------------------------------------ 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/thorin/analyses/cfg.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_CFG_H 2 | #define THORIN_ANALYSES_CFG_H 3 | 4 | #include 5 | 6 | #include "thorin/analyses/scope.h" 7 | #include "thorin/util/array.h" 8 | #include "thorin/util/indexmap.h" 9 | #include "thorin/util/indexset.h" 10 | #include "thorin/util/stream.h" 11 | 12 | namespace thorin { 13 | 14 | //------------------------------------------------------------------------------ 15 | 16 | template class LoopTree; 17 | template class DomTreeBase; 18 | template class DomFrontierBase; 19 | 20 | typedef GIDSet CFNodes; 21 | 22 | /** 23 | * A Control-Flow Node. 24 | * Managed by @p CFA. 25 | */ 26 | class CFNode : public RuntimeCast, public Streamable { 27 | public: 28 | CFNode(Continuation* continuation) 29 | : continuation_(continuation) 30 | , gid_(gid_counter_++) 31 | {} 32 | 33 | uint64_t gid() const { return gid_; } 34 | Continuation* continuation() const { return continuation_; } 35 | Stream& stream(Stream&) const; 36 | 37 | private: 38 | const CFNodes& preds() const { return preds_; } 39 | const CFNodes& succs() const { return succs_; } 40 | void link(const CFNode* other) const; 41 | 42 | mutable size_t f_index_ = -1; ///< RPO index in a forward @p CFG. 43 | mutable size_t b_index_ = -1; ///< RPO index in a backwards @p CFG. 44 | 45 | Continuation* continuation_; 46 | size_t gid_; 47 | static uint64_t gid_counter_; 48 | mutable CFNodes preds_; 49 | mutable CFNodes succs_; 50 | 51 | friend class CFA; 52 | template friend class CFG; 53 | }; 54 | 55 | //------------------------------------------------------------------------------ 56 | 57 | /// Control Flow Analysis. 58 | class CFA { 59 | public: 60 | CFA(const CFA&) = delete; 61 | CFA& operator= (CFA) = delete; 62 | 63 | explicit CFA(const Scope& scope); 64 | ~CFA(); 65 | 66 | const Scope& scope() const { return scope_; } 67 | size_t size() const { return nodes().size(); } 68 | const ContinuationMap& nodes() const { return nodes_; } 69 | const F_CFG& f_cfg() const; 70 | const B_CFG& b_cfg() const; 71 | const CFNode* operator[](Continuation* cont) const { return nodes_.lookup(cont).value_or(nullptr); } 72 | 73 | private: 74 | void link_to_exit(); 75 | void verify(); 76 | const CFNodes& preds(Continuation* continuation) const { auto cn = nodes_.find(continuation)->second; assert(cn); return cn->preds(); } 77 | const CFNodes& succs(Continuation* continuation) const { auto cn = nodes_.find(continuation)->second; assert(cn); return cn->succs(); } 78 | const CFNode* entry() const { return entry_; } 79 | const CFNode* exit() const { return exit_; } 80 | const CFNode* node(Continuation*); 81 | 82 | const Scope& scope_; 83 | ContinuationMap nodes_; 84 | const CFNode* entry_; 85 | const CFNode* exit_; 86 | mutable std::unique_ptr f_cfg_; 87 | mutable std::unique_ptr b_cfg_; 88 | 89 | template friend class CFG; 90 | }; 91 | 92 | //------------------------------------------------------------------------------ 93 | 94 | /** 95 | * A Control-Flow Graph. 96 | * A small wrapper for the information obtained by a @p CFA. 97 | * The template parameter @p forward determines the direction of the edges. 98 | * @c true means a conventional @p CFG. 99 | * @c false means that all edges in this @p CFG are reverted. 100 | * Thus, a dominance analysis, for example, becomes a post-dominance analysis. 101 | * @see DomTreeBase 102 | */ 103 | template 104 | class CFG { 105 | public: 106 | template 107 | using Map = IndexMap, const CFNode*, Value>; 108 | using Set = IndexSet, const CFNode*>; 109 | 110 | CFG(const CFG&) = delete; 111 | CFG& operator= (CFG) = delete; 112 | 113 | explicit CFG(const CFA&); 114 | 115 | const CFA& cfa() const { return cfa_; } 116 | size_t size() const { return cfa().size(); } 117 | const CFNodes& preds(const CFNode* n) const; 118 | const CFNodes& succs(const CFNode* n) const; 119 | const CFNodes& preds(Continuation* continuation) const { return preds(cfa()[continuation]); } 120 | const CFNodes& succs(Continuation* continuation) const { return succs(cfa()[continuation]); } 121 | size_t num_preds(const CFNode* n) const { return preds(n).size(); } 122 | size_t num_succs(const CFNode* n) const { return succs(n).size(); } 123 | size_t num_preds(Continuation* continuation) const { return num_preds(cfa()[continuation]); } 124 | size_t num_succs(Continuation* continuation) const { return num_succs(cfa()[continuation]); } 125 | const CFNode* entry() const { return forward ? cfa().entry() : cfa().exit(); } 126 | const CFNode* exit() const { return forward ? cfa().exit() : cfa().entry(); } 127 | 128 | ArrayRef reverse_post_order() const { return rpo_.array(); } 129 | Range::const_reverse_iterator> post_order() const { return reverse_range(rpo_.array()); } 130 | const CFNode* reverse_post_order(size_t i) const { return rpo_.array()[i]; } ///< Maps from reverse post-order index to @p CFNode. 131 | const CFNode* post_order(size_t i) const { return rpo_.array()[size()-1-i]; } ///< Maps from post-order index to @p CFNode. 132 | const CFNode* operator [] (Continuation* continuation) const { return cfa()[continuation]; } ///< Maps from @p l to @p CFNode. 133 | const DomTreeBase& domtree() const; 134 | const LoopTree& looptree() const; 135 | 136 | static size_t index(const CFNode* n) { return forward ? n->f_index_ : n->b_index_; } 137 | 138 | private: 139 | size_t post_order_visit(const CFNode* n, size_t i); 140 | 141 | const CFA& cfa_; 142 | Map rpo_; 143 | mutable std::unique_ptr> domtree_; 144 | mutable std::unique_ptr> looptree_; 145 | }; 146 | 147 | //------------------------------------------------------------------------------ 148 | 149 | } 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /src/thorin/analyses/domtree.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/analyses/domtree.h" 2 | 3 | namespace thorin { 4 | 5 | template 6 | void DomTreeBase::create() { 7 | // Cooper et al, 2001. A Simple, Fast Dominance Algorithm. http://www.cs.rice.edu/~keith/EMBED/dom.pdf 8 | 9 | // all idoms different from entry are set to their first found dominating pred 10 | for (auto n : cfg().reverse_post_order().skip_front()) { 11 | for (auto pred : cfg().preds(n)) { 12 | if (cfg().index(pred) < cfg().index(n)) { 13 | idoms_[n] = pred; 14 | goto outer_loop; 15 | } 16 | } 17 | THORIN_UNREACHABLE; 18 | outer_loop:; 19 | } 20 | 21 | for (bool todo = true; todo;) { 22 | todo = false; 23 | 24 | for (auto n : cfg().reverse_post_order().skip_front()) { 25 | const CFNode* new_idom = nullptr; 26 | for (auto pred : cfg().preds(n)) 27 | new_idom = new_idom ? least_common_ancestor(new_idom, pred) : pred; 28 | 29 | assert(new_idom); 30 | if (idom(n) != new_idom) { 31 | idoms_[n] = new_idom; 32 | todo = true; 33 | } 34 | } 35 | } 36 | 37 | for (auto n : cfg().reverse_post_order().skip_front()) 38 | children_[idom(n)].push_back(n); 39 | } 40 | 41 | template 42 | void DomTreeBase::depth(const CFNode* n, int i) { 43 | depth_[n] = i; 44 | for (auto child : children(n)) 45 | depth(child, i+1); 46 | } 47 | 48 | template 49 | const CFNode* DomTreeBase::least_common_ancestor(const CFNode* i, const CFNode* j) const { 50 | assert(i && j); 51 | while (index(i) != index(j)) { 52 | while (index(i) < index(j)) j = idom(j); 53 | while (index(j) < index(i)) i = idom(i); 54 | } 55 | return i; 56 | } 57 | 58 | template class DomTreeBase; 59 | template class DomTreeBase; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/thorin/analyses/domtree.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_DOMTREE_H 2 | #define THORIN_ANALYSES_DOMTREE_H 3 | 4 | #include "thorin/analyses/cfg.h" 5 | 6 | namespace thorin { 7 | 8 | /** 9 | * A Dominance Tree. 10 | * The template parameter @p forward determines 11 | * whether a regular dominance tree (@c true) or a post-dominance tree (@c false) should be constructed. 12 | * This template parameter is associated with @p CFG's @c forward parameter. 13 | */ 14 | template 15 | class DomTreeBase { 16 | public: 17 | DomTreeBase(const DomTreeBase&) = delete; 18 | DomTreeBase& operator=(DomTreeBase) = delete; 19 | 20 | explicit DomTreeBase(const CFG& cfg) 21 | : cfg_(cfg) 22 | , children_(cfg) 23 | , idoms_(cfg) 24 | , depth_(cfg) 25 | { 26 | create(); 27 | depth(root(), 0); 28 | } 29 | 30 | const CFG& cfg() const { return cfg_; } 31 | size_t index(const CFNode* n) const { return cfg().index(n); } 32 | const std::vector& children(const CFNode* n) const { return children_[n]; } 33 | const CFNode* root() const { return *idoms_.begin(); } 34 | const CFNode* idom(const CFNode* n) const { return idoms_[n]; } 35 | int depth(const CFNode* n) const { return depth_[n]; } 36 | const CFNode* least_common_ancestor(const CFNode* i, const CFNode* j) const; 37 | 38 | private: 39 | void create(); 40 | void depth(const CFNode* n, int i); 41 | 42 | const CFG& cfg_; 43 | typename CFG::template Map> children_; 44 | typename CFG::template Map idoms_; 45 | typename CFG::template Map depth_; 46 | }; 47 | 48 | typedef DomTreeBase DomTree; 49 | typedef DomTreeBase PostDomTree; 50 | 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/thorin/analyses/free_defs.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/analyses/scope.h" 3 | 4 | namespace thorin { 5 | 6 | // TODO get rid of this mess 7 | DefSet free_defs(const Scope& scope, bool include_closures) { 8 | DefSet result, done(scope.defs().capacity()); 9 | std::queue queue; 10 | 11 | auto enqueue_ops = [&] (const Def* def) { 12 | for (auto op : def->ops()) { 13 | auto p = done.emplace(op); 14 | if (p.second) 15 | queue.push(op); 16 | } 17 | }; 18 | 19 | for (auto def : scope.defs()) { 20 | if (auto nom = def->isa_nom()) 21 | enqueue_ops(nom); 22 | } 23 | 24 | while (!queue.empty()) { 25 | auto def = pop(queue); 26 | if (def->isa_structural() && !def->isa()) { 27 | if (!include_closures && def->isa()) { 28 | result.emplace(def); 29 | queue.push(def->op(1)); 30 | goto queue_next; 31 | } 32 | for (auto op : def->ops()) { 33 | if ((op->isa() || op->type()->isa()) && !scope.contains(op)) { 34 | result.emplace(def); 35 | goto queue_next; 36 | } 37 | } 38 | 39 | // HACK for bitcasting address spaces 40 | if (auto bitcast = def->isa()) { 41 | if (auto dst_ptr = bitcast->type()->isa()) { 42 | if (auto src_ptr = bitcast->from()->type()->isa()) { 43 | if ( dst_ptr->pointee()->isa() 44 | && dst_ptr->addr_space() != src_ptr->addr_space() 45 | && !scope.contains(bitcast->from())) { 46 | result.emplace(bitcast); 47 | goto queue_next; 48 | } 49 | } 50 | } 51 | } 52 | 53 | enqueue_ops(def); 54 | } else if (!scope.contains(def)) 55 | result.emplace(def); 56 | queue_next:; 57 | } 58 | 59 | return result; 60 | } 61 | 62 | DefSet free_defs(Continuation* entry) { 63 | Scope scope(entry); 64 | return free_defs(scope, true); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/thorin/analyses/free_defs.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_FREE_DEFS_H 2 | #define THORIN_ANALYSES_FREE_DEFS_H 3 | 4 | #include "thorin/continuation.h" 5 | 6 | namespace thorin { 7 | 8 | class Scope; 9 | 10 | DefSet free_defs(const Scope&, bool include_closures = true); 11 | DefSet free_defs(Continuation* entry); 12 | 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/thorin/analyses/looptree.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/analyses/looptree.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "thorin/continuation.h" 8 | #include "thorin/analyses/cfg.h" 9 | 10 | /* 11 | * The implementation is based on Steensgard's algorithm to find loops in irreducible CFGs. 12 | * 13 | * In short, Steensgard's algorithm recursively applies Tarjan's SCC algorithm to find nested SCCs. 14 | * In the next recursion, backedges from the prior run are ignored. 15 | * Please, check out 16 | * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm 17 | * for more details on Tarjan's SCC algorithm 18 | */ 19 | 20 | namespace thorin { 21 | 22 | enum { 23 | InSCC = 1, // is in current walk_scc run? 24 | OnStack = 2, // is in current SCC stack? 25 | IsHead = 4, // all heads are marked, so subsequent runs can ignore backedges when searching for SCCs 26 | }; 27 | 28 | template 29 | class LoopTreeBuilder { 30 | public: 31 | typedef typename LoopTree::Base Base; 32 | typedef typename LoopTree::Leaf Leaf; 33 | typedef typename LoopTree::Head Head; 34 | 35 | LoopTreeBuilder(LoopTree& looptree) 36 | : looptree_(looptree) 37 | , numbers_(cfg()) 38 | , states_(cfg()) 39 | , set_(cfg()) 40 | , index_(0) 41 | { 42 | stack_.reserve(looptree.cfg().size()); 43 | build(); 44 | } 45 | 46 | private: 47 | struct Number { 48 | Number() 49 | : dfs(-1) 50 | , low(-1) 51 | {} 52 | Number(size_t i) 53 | : dfs(i) 54 | , low(i) 55 | {} 56 | 57 | size_t dfs; // depth-first-search number 58 | size_t low; // low link (see Tarjan's SCC algo) 59 | }; 60 | 61 | void build(); 62 | const CFG& cfg() const { return looptree_.cfg(); } 63 | Number& number(const CFNode* n) { return numbers_[n]; } 64 | size_t& lowlink(const CFNode* n) { return number(n).low; } 65 | size_t& dfs(const CFNode* n) { return number(n).dfs; } 66 | bool on_stack(const CFNode* n) { assert(set_.contains(n)); return (states_[n] & OnStack) != 0; } 67 | bool in_scc(const CFNode* n) { return states_[n] & InSCC; } 68 | bool is_head(const CFNode* n) { return states_[n] & IsHead; } 69 | 70 | bool is_leaf(const CFNode* n, size_t num) { 71 | if (num == 1) { 72 | for (const auto& succ : cfg().succs(n)) { 73 | if (!is_head(succ) && n == succ) 74 | return false; 75 | } 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | void push(const CFNode* n) { 82 | assert(set_.contains(n) && (states_[n] & OnStack) == 0); 83 | stack_.emplace_back(n); 84 | states_[n] |= OnStack; 85 | } 86 | 87 | int visit(const CFNode* n, int counter) { 88 | visit_first(set_, n); 89 | numbers_[n] = Number(counter++); 90 | push(n); 91 | return counter; 92 | } 93 | 94 | void recurse(Head* parent, ArrayRef heads, int depth); 95 | int walk_scc(const CFNode* cur, Head* parent, int depth, int scc_counter); 96 | 97 | private: 98 | LoopTree& looptree_; 99 | typename CFG::template Map numbers_; 100 | typename CFG::template Map states_; 101 | typename CFG::Set set_; 102 | size_t index_; 103 | std::vector stack_; 104 | }; 105 | 106 | template 107 | void LoopTreeBuilder::build() { 108 | for (const auto& n : cfg().reverse_post_order()) // clear all flags 109 | states_[n] = 0; 110 | 111 | auto head = new Head(nullptr, 0, std::vector(0)); 112 | looptree_.root_.reset(head); 113 | recurse(head, {cfg().entry()}, 1); 114 | } 115 | 116 | template 117 | void LoopTreeBuilder::recurse(Head* parent, ArrayRef heads, int depth) { 118 | size_t cur_new_child = 0; 119 | for (const auto& head : heads) { 120 | set_.clear(); 121 | walk_scc(head, parent, depth, 0); 122 | 123 | // now mark all newly found heads globally as head 124 | for (size_t e = parent->num_children(); cur_new_child != e; ++cur_new_child) { 125 | for (const auto& head : parent->child(cur_new_child)->cf_nodes()) 126 | states_[head] |= IsHead; 127 | } 128 | } 129 | 130 | for (const auto& node : parent->children()) { 131 | if (auto new_parent = node->template isa()) 132 | recurse(new_parent, new_parent->cf_nodes(), depth + 1); 133 | } 134 | } 135 | 136 | template 137 | int LoopTreeBuilder::walk_scc(const CFNode* cur, Head* parent, int depth, int scc_counter) { 138 | scc_counter = visit(cur, scc_counter); 139 | 140 | for (const auto& succ : cfg().succs(cur)) { 141 | if (is_head(succ)) 142 | continue; // this is a backedge 143 | if (!set_.contains(succ)) { 144 | scc_counter = walk_scc(succ, parent, depth, scc_counter); 145 | lowlink(cur) = std::min(lowlink(cur), lowlink(succ)); 146 | } else if (on_stack(succ)) 147 | lowlink(cur) = std::min(lowlink(cur), lowlink(succ)); 148 | } 149 | 150 | // root of SCC 151 | if (lowlink(cur) == dfs(cur)) { 152 | std::vector heads; 153 | 154 | // mark all cf_nodes in current SCC (all cf_nodes from back to cur on the stack) as 'InSCC' 155 | size_t num = 0, e = stack_.size(), b = e - 1; 156 | do { 157 | states_[stack_[b]] |= InSCC; 158 | ++num; 159 | } while (stack_[b--] != cur); 160 | 161 | // for all cf_nodes in current SCC 162 | for (size_t i = ++b; i != e; ++i) { 163 | const CFNode* n = stack_[i]; 164 | 165 | if (cfg().entry() == n) 166 | heads.emplace_back(n); // entries are axiomatically heads 167 | else { 168 | for (const auto& pred : cfg().preds(n)) { 169 | // all backedges are also inducing heads 170 | // but do not yet mark them globally as head -- we are still running through the SCC 171 | if (!in_scc(pred)) { 172 | heads.emplace_back(n); 173 | break; 174 | } 175 | } 176 | } 177 | } 178 | 179 | if (is_leaf(cur, num)) { 180 | assert(heads.size() == 1); 181 | looptree_.leaves_[heads.front()] = new Leaf(index_++, parent, depth, heads); 182 | } else 183 | new Head(parent, depth, heads); 184 | 185 | // reset InSCC and OnStack flags 186 | for (size_t i = b; i != e; ++i) 187 | states_[stack_[i]] &= ~(OnStack | InSCC); 188 | 189 | // pop whole SCC 190 | stack_.resize(b); 191 | } 192 | 193 | return scc_counter; 194 | } 195 | 196 | //------------------------------------------------------------------------------ 197 | 198 | template 199 | LoopTree::Base::Base(Node node, Head* parent, int depth, const std::vector& cf_nodes) 200 | : node_(node) 201 | , parent_(parent) 202 | , cf_nodes_(cf_nodes) 203 | , depth_(depth) 204 | { 205 | if (parent_) 206 | parent_->children_.emplace_back(this); 207 | } 208 | 209 | template 210 | Stream& LoopTree::Leaf::stream(Stream& s) const { return s.fmt("<{} | dfs: {}", cf_node(), index()); } 211 | 212 | template 213 | Stream& LoopTree::Head::stream(Stream& s) const { return s.fmt("[{, }]", this->cf_nodes()); } 214 | 215 | //------------------------------------------------------------------------------ 216 | 217 | template 218 | LoopTree::LoopTree(const CFG& cfg) 219 | : cfg_(cfg) 220 | , leaves_(cfg) 221 | { 222 | LoopTreeBuilder(*this); 223 | } 224 | 225 | template class LoopTree; 226 | template class LoopTree; 227 | 228 | //------------------------------------------------------------------------------ 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/thorin/analyses/looptree.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_LOOPTREE_H 2 | #define THORIN_ANALYSES_LOOPTREE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "thorin/analyses/cfg.h" 8 | #include "thorin/util/array.h" 9 | #include "thorin/util/cast.h" 10 | 11 | namespace thorin { 12 | 13 | template class LoopTreeBuilder; 14 | 15 | /** 16 | * Calculates a loop nesting forest rooted at @p root_. 17 | * The implementation uses Steensgard's algorithm. 18 | * Check out G. Ramalingam, "On Loops, Dominators, and Dominance Frontiers", 1999, for more information. 19 | */ 20 | template 21 | class LoopTree { 22 | public: 23 | class Head; 24 | 25 | /** 26 | * Represents a node of a loop nesting forest. 27 | * Please refer to G. Ramalingam, "On Loops, Dominators, and Dominance Frontiers", 1999 28 | * for an introduction to loop nesting forests. 29 | * A @p Base consists of a set of header @p CFNode%s. 30 | * The header CFNode%s are the set of CFNode%s not dominated by any other @p CFNode within the loop. 31 | * The root node is a @p Head without any CFNode%s but further children and @p depth_ -1. 32 | * Thus, the forest is pooled into a tree. 33 | */ 34 | class Base : public RuntimeCast, public Streamable::Base> { 35 | public: 36 | enum class Node { Head, Leaf }; 37 | 38 | Base(Node node, Head* parent, int depth, const std::vector&); 39 | virtual ~Base() {} 40 | 41 | Node node() const { return node_; } 42 | int depth() const { return depth_; } 43 | const Head* parent() const { return parent_; } 44 | ArrayRef cf_nodes() const { return cf_nodes_; } 45 | size_t num_cf_nodes() const { return cf_nodes().size(); } 46 | virtual Stream& stream(Stream&) const = 0; 47 | 48 | protected: 49 | Node node_; 50 | Head* parent_; 51 | std::vector cf_nodes_; 52 | int depth_; 53 | }; 54 | 55 | /// A Head owns further nodes as children. 56 | class Head : public Base { 57 | private: 58 | Head(Head* parent, int depth, const std::vector& cf_nodes) 59 | : Base(Node, parent, depth, cf_nodes) 60 | {} 61 | 62 | public: 63 | ArrayRef> children() const { return children_; } 64 | const Base* child(size_t i) const { return children_[i].get(); } 65 | size_t num_children() const { return children().size(); } 66 | bool is_root() const { return Base::parent_ == 0; } 67 | Stream& stream(Stream&) const override; 68 | 69 | static constexpr auto Node = Base::Node::Head; 70 | 71 | private: 72 | std::vector> children_; 73 | 74 | friend class Base; 75 | friend class LoopTreeBuilder; 76 | }; 77 | 78 | /// A Leaf only holds a single @p CFNode and does not have any children. 79 | class Leaf : public Base { 80 | private: 81 | Leaf(size_t index, Head* parent, int depth, const std::vector& cf_nodes) 82 | : Base(Node, parent, depth, cf_nodes) 83 | , index_(index) 84 | { 85 | assert(Leaf::num_cf_nodes() == 1); 86 | } 87 | 88 | public: 89 | const CFNode* cf_node() const { return Leaf::cf_nodes().front(); } 90 | /// Index of a DFS of the @p LoopTree's @p Leaf%s. 91 | size_t index() const { return index_; } 92 | Stream& stream(Stream&) const override; 93 | 94 | static constexpr auto Node = Base::Node::Leaf; 95 | 96 | private: 97 | size_t index_; 98 | 99 | friend class LoopTreeBuilder; 100 | }; 101 | 102 | LoopTree(const LoopTree&) = delete; 103 | LoopTree& operator=(LoopTree) = delete; 104 | 105 | explicit LoopTree(const CFG& cfg); 106 | const CFG& cfg() const { return cfg_; } 107 | const Head* root() const { return root_.get(); } 108 | const Leaf* operator[](const CFNode* n) const { return find(leaves_, n); } 109 | 110 | private: 111 | static void get_nodes(std::vector& nodes, const Base* node) { 112 | nodes.push_back(node); 113 | if (auto head = node->template isa()) { 114 | for (const auto& child : head->children()) 115 | get_nodes(nodes, child.get()); 116 | } 117 | } 118 | 119 | const CFG& cfg_; 120 | typename CFG::template Map leaves_; 121 | std::unique_ptr root_; 122 | 123 | friend class LoopTreeBuilder; 124 | }; 125 | 126 | } 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /src/thorin/analyses/schedule.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/analyses/schedule.h" 2 | 3 | #include "thorin/continuation.h" 4 | #include "thorin/primop.h" 5 | #include "thorin/world.h" 6 | #include "thorin/analyses/domtree.h" 7 | #include "thorin/analyses/looptree.h" 8 | #include "thorin/analyses/scope.h" 9 | 10 | namespace thorin { 11 | 12 | Scheduler::Scheduler(const Scope& s, ScopesForest& forest) 13 | : forest_(&forest) 14 | , scope_(&s) 15 | , cfg_(&scope().f_cfg()) 16 | , domtree_(&cfg().domtree()) 17 | { 18 | std::queue queue; 19 | DefSet done; 20 | 21 | auto enqueue = [&](const Def* def, size_t i, const Def* op) { 22 | if (scope().contains(op)) { 23 | auto [_, ins] = def2uses_[op].emplace(i, def); 24 | assert_unused(ins); 25 | if (auto [_, ins] = done.emplace(op); ins) queue.push(op); 26 | } 27 | }; 28 | 29 | for (auto n : cfg().reverse_post_order()) { 30 | queue.push(n->continuation()); 31 | auto p = done.emplace(n->continuation()); 32 | assert_unused(p.second); 33 | } 34 | 35 | while (!queue.empty()) { 36 | auto def = pop(queue); 37 | for (size_t i = 0, e = def->num_ops(); i != e; ++i) { 38 | // all reachable continuations have already been registered above 39 | // NOTE we might still see references to unreachable continuations in the schedule 40 | if (!def->op(i)->isa()) 41 | enqueue(def, i, def->op(i)); 42 | } 43 | } 44 | 45 | register_defs(s); 46 | } 47 | 48 | void Scheduler::register_defs(const Scope& s) { 49 | for (auto child : s.children_scopes()) { 50 | Scope& cs = forest_->get_scope(child); 51 | register_defs(cs); 52 | } 53 | 54 | for (auto def : s.defs()) { 55 | if (!early_.lookup(def)) 56 | early_[def] = s.entry(); 57 | } 58 | } 59 | 60 | Continuation* Scheduler::early(const Def* def) { 61 | if (auto cont = early_.lookup(def)) return *cont; 62 | if (auto param = def->isa()) return early_[def] = param->continuation(); 63 | assert(false); 64 | } 65 | 66 | Continuation* Scheduler::late(const Def* def) { 67 | if (auto cont = late_.lookup(def)) return *cont; 68 | 69 | Continuation* result = nullptr; 70 | if (auto continuation = def->isa_nom()) { 71 | result = continuation; 72 | } else if (auto param = def->isa()) { 73 | result = param->continuation(); 74 | } else if (def->isa_nom()) { 75 | // don't try to late-schedule recursive nodes for now 76 | result = early(def); 77 | } else { 78 | for (auto use : uses(def)) { 79 | auto cont = late(use); 80 | result = result ? domtree().least_common_ancestor(cfg(result), cfg(cont))->continuation() : cont; 81 | } 82 | } 83 | 84 | return late_[def] = result; 85 | } 86 | 87 | Continuation* Scheduler::smart(const Def* def) { 88 | if (auto cont = smart_.lookup(def)) return *cont; 89 | 90 | auto e = cfg(early(def)); 91 | auto l = cfg(late (def)); 92 | auto s = l; 93 | 94 | int depth = cfg().looptree()[l]->depth(); 95 | for (auto i = l; i != e;) { 96 | auto idom = domtree().idom(i); 97 | assert(i != idom); 98 | i = idom; 99 | 100 | if (i == nullptr) { 101 | scope_->world().WLOG("this should never occur - don't know where to put {}", def); 102 | s = l; 103 | break; 104 | } 105 | 106 | if (int cur_depth = cfg().looptree()[i]->depth(); cur_depth < depth) { 107 | s = i; 108 | depth = cur_depth; 109 | } 110 | } 111 | 112 | return smart_[def] = s->continuation(); 113 | } 114 | 115 | Schedule schedule(const Scope& scope) { 116 | // until we have sth better simply use the RPO of the CFG 117 | Schedule result; 118 | for (auto n : scope.f_cfg().reverse_post_order()) 119 | result.emplace_back(n->continuation()); 120 | 121 | return result; 122 | } 123 | 124 | #if 0 125 | void Schedule::verify() { 126 | #if THORIN_ENABLE_CHECKS 127 | bool ok = true; 128 | auto& domtree = cfg().domtree(); 129 | Schedule::Map block2mem(*this); 130 | 131 | for (auto& block : *this) { 132 | const Def* mem = block.continuation()->mem_param(); 133 | auto idom = block.continuation() != scope().entry() ? domtree.idom(block.node()) : block.node(); 134 | mem = mem ? mem : block2mem[(*this)[idom]]; 135 | for (auto primop : block) { 136 | if (auto memop = primop->isa()) { 137 | if (memop->mem() != mem) { 138 | world().WLOG("incorrect schedule: {} @ '{}'; current mem is {} @ '{}') - scope entry: {}", memop, memop->location(), mem, mem->location(), scope_.entry()); 139 | ok = false; 140 | } 141 | mem = memop->out_mem(); 142 | } 143 | } 144 | block2mem[block] = mem; 145 | } 146 | 147 | assert(ok && "incorrectly wired or scheduled memory operations"); 148 | #endif 149 | } 150 | 151 | std::ostream& Schedule::stream(std::ostream& os) const { 152 | for (auto& block : *this) { 153 | auto continuation = block.continuation(); 154 | if (continuation->intrinsic() != Intrinsic::EndScope) { 155 | bool indent = continuation != scope().entry(); 156 | if (indent) 157 | os << up; 158 | os << endl; 159 | continuation->stream_head(os) << up_endl; 160 | for (auto primop : block) 161 | primop->stream_assignment(os); 162 | 163 | continuation->stream_jump(os) << down_endl; 164 | if (indent) 165 | os << down; 166 | } 167 | } 168 | return os << endl; 169 | } 170 | 171 | void Schedule::write_thorin(const char* filename) const { std::ofstream file(filename); stream(file); } 172 | 173 | void Schedule::thorin() const { 174 | auto filename = world().name() + "_" + scope().entry()->unique_name() + ".thorin"; 175 | write_thorin(filename.c_str()); 176 | } 177 | 178 | //------------------------------------------------------------------------------ 179 | 180 | void verify_mem(World& world) { 181 | Scope::for_each(world, [&](const Scope& scope) { schedule(scope); }); 182 | } 183 | #endif 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/thorin/analyses/schedule.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_SCHEDULE_H 2 | #define THORIN_ANALYSES_SCHEDULE_H 3 | 4 | #include "thorin/analyses/cfg.h" 5 | 6 | namespace thorin { 7 | 8 | template class DomTreeBase; 9 | using DomTree = DomTreeBase; 10 | 11 | class Scheduler { 12 | public: 13 | Scheduler() = default; 14 | explicit Scheduler(const Scope&, ScopesForest&); 15 | 16 | /// @name getters 17 | //@{ 18 | const Scope& scope() const { return *scope_; } 19 | const F_CFG& cfg() const { return *cfg_; } 20 | const CFNode* cfg(Continuation* cont) const { return cfg()[cont]; } 21 | const DomTree& domtree() const { return *domtree_; } 22 | const Uses& uses(const Def* def) const { assert(def2uses_.contains(def)); return def2uses_.find(def)->second; } 23 | //@} 24 | 25 | void register_defs(const Scope&); 26 | 27 | /// @name compute schedules 28 | //@{ 29 | Continuation* early(const Def*); 30 | Continuation* late (const Def*); 31 | Continuation* smart(const Def*); 32 | //@} 33 | 34 | friend void swap(Scheduler& s1, Scheduler& s2) { 35 | using std::swap; 36 | swap(s1.forest_, s2.forest_); 37 | swap(s1.scope_, s2.scope_); 38 | swap(s1.cfg_, s2.cfg_); 39 | swap(s1.domtree_, s2.domtree_); 40 | swap(s1.early_, s2.early_); 41 | swap(s1.late_, s2.late_); 42 | swap(s1.smart_, s2.smart_); 43 | swap(s1.def2uses_, s2.def2uses_); 44 | } 45 | 46 | private: 47 | ScopesForest* forest_ = nullptr; 48 | const Scope* scope_ = nullptr; 49 | const F_CFG* cfg_ = nullptr; 50 | const DomTree* domtree_ = nullptr; 51 | DefMap early_; 52 | DefMap late_; 53 | DefMap smart_; 54 | DefMap def2uses_; 55 | }; 56 | 57 | using Schedule = std::vector; 58 | Schedule schedule(const Scope&); 59 | 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/thorin/analyses/scope.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_SCOPE_H 2 | #define THORIN_ANALYSES_SCOPE_H 3 | 4 | #include 5 | 6 | #include "thorin/continuation.h" 7 | #include "thorin/util/array.h" 8 | #include "thorin/util/stream.h" 9 | 10 | namespace thorin { 11 | 12 | template class CFG; 13 | typedef CFG F_CFG; 14 | typedef CFG B_CFG; 15 | 16 | class CFA; 17 | class CFNode; 18 | 19 | class ScopesForest; 20 | 21 | /** 22 | * A @p Scope represents a region of @p Continuation%s which are live from the view of an @p entry @p Continuation. 23 | * Transitively, all user's of the @p entry's parameters are pooled into this @p Scope. 24 | * @p entry() will be first, @p exit() will be last. 25 | * @warning All other @p Continuation%s are in no particular order. 26 | */ 27 | class Scope : public Streamable { 28 | public: 29 | Scope(const Scope&) = delete; 30 | Scope& operator=(Scope) = delete; 31 | 32 | explicit Scope(Continuation* entry); 33 | explicit Scope(Continuation* entry, ScopesForest&); 34 | ~Scope(); 35 | 36 | /// Invoke if you have modified sth in this Scope. 37 | Scope& update(); 38 | 39 | //@{ misc getters 40 | World& world() const { return world_; } 41 | Continuation* entry() const { return entry_; } 42 | ScopesForest& forest() const { return forest_; } 43 | //@} 44 | 45 | //@{ get Def%s contained in this Scope 46 | const DefSet& defs() const { return defs_; } 47 | const DefSet& free_frontier() const { return free_frontier_; } 48 | bool contains(const Def* def) const { return defs_.contains(def); } 49 | //@} 50 | 51 | /// All @p Param%s that appear free in this @p Scope. 52 | const ParamSet& free_params() const; 53 | /// Are there any free @p Param%s within this @p Scope. 54 | bool has_free_params() const; 55 | const Param* first_free_param() const; 56 | 57 | Continuation* parent_scope() const; 58 | ContinuationSet children_scopes() const; 59 | 60 | //@{ simple CFA to construct a CFG 61 | const CFA& cfa() const; 62 | const F_CFG& f_cfg() const; 63 | const B_CFG& b_cfg() const; 64 | //@} 65 | 66 | /// @name logging 67 | //@{ 68 | Stream& stream(Stream&) const; ///< Streams thorin to file @p out. 69 | //@} 70 | 71 | void verify(); 72 | private: 73 | void run(); 74 | DefSet potentially_contained() const; 75 | 76 | template 77 | std::tuple search_free_params() const; 78 | 79 | World& world_; 80 | std::unique_ptr root; 81 | ScopesForest& forest_; 82 | Continuation* entry_ = nullptr; 83 | DefSet defs_; 84 | DefSet free_frontier_; 85 | mutable std::optional first_free_param_; 86 | mutable std::unique_ptr free_params_; 87 | mutable std::unique_ptr cfa_; 88 | mutable std::optional parent_scope_; 89 | 90 | friend ScopesForest; 91 | }; 92 | 93 | class ScopesForest { 94 | public: 95 | ScopesForest(World& world) : world_(world) {} 96 | 97 | Scope& get_scope(Continuation* entry); 98 | 99 | ContinuationSet top_level_scopes(); 100 | 101 | /** 102 | * Transitively visits all @em reachable Scope%s in @p world that do not have free variables. 103 | * We call these Scope%s @em top-level Scope%s. 104 | * Select with @p elide_empty whether you want to visit trivial Scope%s of Continuation%s without body. 105 | */ 106 | template 107 | void for_each(std::function); 108 | 109 | private: 110 | World& world_; 111 | std::vector stack_; 112 | ContinuationMap> scopes_; 113 | 114 | friend Scope; 115 | }; 116 | 117 | } 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /src/thorin/analyses/verify.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/type.h" 3 | #include "thorin/world.h" 4 | #include "thorin/analyses/scope.h" 5 | #include "thorin/analyses/free_defs.h" 6 | 7 | namespace thorin { 8 | 9 | // TODO this needs serious rewriting 10 | 11 | static bool verify_calls(World& world, ScopesForest&) { 12 | bool ok = true; 13 | for (auto def : world.defs()) { 14 | if (auto cont = def->isa()) 15 | ok &= cont->verify(); 16 | } 17 | return ok; 18 | } 19 | 20 | static bool verify_top_level(World& world, ScopesForest& forest) { 21 | bool ok = true; 22 | unique_queue defs; 23 | for (auto& external : world.externals()) 24 | defs.push(external.second); 25 | while (!defs.empty()) { 26 | auto def = defs.pop(); 27 | if (auto cont = def->isa_nom()) { 28 | world.VLOG("verifying external continuation '{}'", cont); 29 | auto& scope = forest.get_scope(cont); 30 | scope.verify(); 31 | if (scope.has_free_params()) { 32 | for (auto param : scope.free_params()) 33 | world.ELOG("top-level continuation '{}' got free param '{}' belonging to continuation {}", scope.entry(), param, param->continuation()); 34 | world.ELOG("here: {}", scope.entry()); 35 | ok = false; 36 | } 37 | } else { 38 | for (auto op : def->ops()) 39 | defs.push(op); 40 | } 41 | } 42 | for (auto cont : world.copy_continuations()) { 43 | auto& scope = forest.get_scope(cont); 44 | scope.verify(); 45 | } 46 | return ok; 47 | } 48 | 49 | #if 0 50 | static bool verify_param(World& world) { 51 | bool ok = true; 52 | for (auto def : world.defs()) { 53 | if (auto param = def->isa()) { 54 | auto cont = param->continuation(); 55 | if (cont->dead_) { 56 | world.ELOG("param '{}' originates in dead continuation {}", param, cont); 57 | ok = false; 58 | } 59 | } 60 | } 61 | return ok; 62 | } 63 | #endif 64 | 65 | void verify(World& world) { 66 | ScopesForest forest(world); 67 | bool ok = true; 68 | ok &= verify_calls(world, forest); 69 | ok &= verify_top_level(world, forest); 70 | //TODO: This should not fail! 71 | //ok &= verify_param(world); 72 | if (!ok) 73 | world.dump(); 74 | assert(ok); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/thorin/analyses/verify.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ANALYSES_VERIFY_H 2 | #define THORIN_ANALYSES_VERIFY_H 3 | 4 | #include "thorin/config.h" 5 | 6 | namespace thorin { 7 | 8 | class Continuation; 9 | class World; 10 | 11 | void verify(World& world); 12 | 13 | #if THORIN_ENABLE_CHECKS 14 | inline void debug_verify(World& world) { verify(world); } 15 | #else 16 | inline void debug_verify(World&) {} 17 | #endif 18 | 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/thorin/be/c/c.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_C_H 2 | #define THORIN_BE_C_H 3 | 4 | #include 5 | #include 6 | 7 | #include "thorin/be/codegen.h" 8 | 9 | namespace thorin { 10 | 11 | class World; 12 | 13 | namespace c { 14 | 15 | enum class Lang : uint8_t { C99, HLS, CUDA, OpenCL }; 16 | 17 | class CodeGen : public thorin::CodeGen { 18 | public: 19 | CodeGen(Thorin& thorin, const Cont2Config& kernel_config, Lang lang, bool debug, std::string& flags) 20 | : thorin::CodeGen(thorin, debug) 21 | , kernel_config_(kernel_config) 22 | , lang_(lang) 23 | , debug_(debug) 24 | , flags_(flags) 25 | {} 26 | 27 | void emit_stream(std::ostream& stream) override; 28 | 29 | const char* file_ext() const override { 30 | switch (lang_) { 31 | case Lang::C99: return ".c"; 32 | case Lang::HLS: return ".hls"; 33 | case Lang::CUDA: return ".cu"; 34 | case Lang::OpenCL: return ".cl"; 35 | default: THORIN_UNREACHABLE; 36 | } 37 | } 38 | 39 | private: 40 | const Cont2Config& kernel_config_; 41 | Lang lang_; 42 | bool debug_; 43 | std::string flags_; 44 | }; 45 | 46 | void emit_c_int(Thorin&, Stream& stream); 47 | 48 | } 49 | 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/thorin/be/codegen.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_CODEGEN_H 2 | #define THORIN_CODEGEN_H 3 | 4 | #include "thorin/transform/importer.h" 5 | #include "thorin/be/kernel_config.h" 6 | 7 | namespace thorin { 8 | 9 | class CodeGen { 10 | protected: 11 | CodeGen(Thorin& thorin, bool debug); 12 | public: 13 | virtual ~CodeGen() {} 14 | 15 | virtual void emit_stream(std::ostream& stream) = 0; 16 | virtual const char* file_ext() const = 0; 17 | 18 | /// @name getters 19 | //@{ 20 | Thorin& thorin() const { return thorin_; } 21 | World& world() const { return thorin().world(); } 22 | bool debug() const { return debug_; } 23 | //@} 24 | 25 | private: 26 | Thorin& thorin_; 27 | bool debug_; 28 | }; 29 | 30 | struct DeviceBackends; 31 | 32 | struct Backend { 33 | Backend(DeviceBackends& backends, World& src); 34 | virtual ~Backend() = default; 35 | 36 | virtual std::unique_ptr create_cg() = 0; 37 | 38 | Thorin& thorin() { return device_code_; } 39 | Importer& importer() { return *importer_; } 40 | 41 | protected: 42 | DeviceBackends& backends_; 43 | Thorin device_code_; 44 | std::unique_ptr importer_; 45 | 46 | std::vector kernels_; 47 | Cont2Config kernel_configs_; 48 | 49 | void prepare_kernel_configs(); 50 | friend DeviceBackends; 51 | }; 52 | 53 | struct DeviceBackends { 54 | DeviceBackends(World& world, int opt, bool debug, std::string& hls_flags); 55 | 56 | World& world(); 57 | std::vector> cgs; 58 | 59 | int opt(); 60 | bool debug(); 61 | 62 | void register_backend(std::unique_ptr); 63 | using GetKernelConfigFn = std::function(const App*, Continuation*)>; 64 | void register_intrinsic(Intrinsic, Backend&, GetKernelConfigFn); 65 | 66 | private: 67 | World& world_; 68 | std::vector> backends_; 69 | std::unordered_map> intrinsics_; 70 | 71 | int opt_; 72 | bool debug_; 73 | 74 | void search_for_device_code(); 75 | friend Backend; 76 | }; 77 | 78 | } 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /src/thorin/be/emitter.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_EMITTER_H 2 | #define THORIN_BE_EMITTER_H 3 | 4 | #include 5 | #include 6 | 7 | namespace thorin { 8 | 9 | template 10 | class Emitter { 11 | private: 12 | constexpr const Child& child() const { return *static_cast(this); }; 13 | constexpr Child& child() { return *static_cast(this); }; 14 | 15 | /// Internal wrapper for @p emit that checks and retrieves/puts the @c Value from @p defs_. 16 | Value emit_(const Def* def) { 17 | std::stack required_defs; 18 | std::queue todo; 19 | todo.push(def); 20 | 21 | while (!todo.empty()) { 22 | auto def = todo.front(); 23 | todo.pop(); 24 | if (defs_.lookup(def)) continue; 25 | 26 | if (auto memop = def->isa()) { 27 | todo.push(memop->mem()); 28 | required_defs.push(memop->mem()); 29 | } else if (auto extract = def->isa()) { 30 | if (is_mem(extract)) { 31 | todo.push(extract->agg()); 32 | required_defs.push(extract->agg()); 33 | } 34 | } 35 | } 36 | 37 | while (!required_defs.empty()) { 38 | auto r = required_defs.top(); 39 | required_defs.pop(); 40 | emit_unsafe(r); 41 | } 42 | 43 | //auto place = def->no_dep() ? entry_ : scheduler_.smart(def); 44 | auto place = !scheduler_.scope().contains(def) ? entry_ : scheduler_.smart(def); 45 | 46 | if (place) { 47 | auto& bb = cont2bb_[place]; 48 | return child().emit_bb(bb, def); 49 | } else { 50 | return child().emit_constant(def); 51 | } 52 | } 53 | 54 | protected: 55 | //@{ 56 | /// @name default implementations 57 | void finalize(const Scope&) {} 58 | void finalize(Continuation*) {} 59 | //@} 60 | 61 | /// Recursively emits code. @c mem -typed @p Def%s return sth that is @c !child().is_valid(value) - this variant asserts in this case. 62 | Value emit(const Def* def) { 63 | auto res = emit_unsafe(def); 64 | assert(child().is_valid(res)); 65 | return res; 66 | } 67 | 68 | /// As above but returning @c !child().is_valid(value) is permitted. 69 | Value emit_unsafe(const Def* def) { 70 | if (auto val = defs_.lookup(def)) return *val; 71 | if (auto cont = def->isa_nom()) return defs_[cont] = child().emit_fun_decl(cont); 72 | 73 | auto val = emit_(def); 74 | return defs_[def] = val; 75 | } 76 | 77 | void emit_scope(const Scope& scope, ScopesForest& forest) { 78 | scope_ = &scope; 79 | auto conts = schedule(scope); 80 | entry_ = scope.entry(); 81 | //assert(entry_->is_returning()); 82 | 83 | auto fct = child().prepare(scope); 84 | for (auto cont : conts) { 85 | if (cont->intrinsic() != Intrinsic::EndScope) child().prepare(cont, fct); 86 | } 87 | 88 | Scheduler new_scheduler(scope, forest); 89 | swap(scheduler_, new_scheduler); 90 | 91 | for (auto cont : conts) { 92 | if (cont->intrinsic() == Intrinsic::EndScope) continue; 93 | //assert(cont == entry_ || cont->is_basicblock()); 94 | child().emit_epilogue(cont); 95 | } 96 | 97 | for (auto cont : conts) { 98 | if (cont->intrinsic() != Intrinsic::EndScope) child().finalize(cont); 99 | } 100 | child().finalize(scope); 101 | scope_ = nullptr; 102 | } 103 | 104 | Scheduler scheduler_; 105 | DefMap defs_; 106 | DefMap types_; 107 | ContinuationMap cont2bb_; 108 | Continuation* entry_ = nullptr; 109 | const Scope* scope_ = nullptr; 110 | }; 111 | 112 | } 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/thorin/be/json/json.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_JSON_H 2 | #define THORIN_BE_JSON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "thorin/be/codegen.h" 9 | 10 | namespace thorin { 11 | 12 | class Thorin; 13 | 14 | namespace json { 15 | 16 | using json = nlohmann::json; 17 | 18 | class CodeGen : public thorin::CodeGen { 19 | public: 20 | CodeGen(Thorin& thorin, bool debug, std::string& target_triple, std::string& target_cpu, std::string& target_attr) 21 | : thorin::CodeGen(thorin, debug) 22 | , target_triple(target_triple) 23 | , target_cpu(target_cpu) 24 | , target_attr(target_attr) 25 | {} 26 | 27 | void emit_json(json& j); 28 | void emit_stream(std::ostream& stream) override; 29 | 30 | const char* file_ext() const override { 31 | return ".thorin.json"; 32 | } 33 | private: 34 | std::string& target_triple; 35 | std::string& target_cpu; 36 | std::string& target_attr; 37 | }; 38 | 39 | } 40 | 41 | } 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/thorin/be/kernel_config.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_KERNEL_CONFIG_H 2 | #define THORIN_BE_KERNEL_CONFIG_H 3 | 4 | #include "thorin/util/cast.h" 5 | #include "thorin/util/hash.h" 6 | 7 | namespace thorin { 8 | 9 | class KernelConfig : public RuntimeCast { 10 | public: 11 | virtual ~KernelConfig() {} 12 | }; 13 | 14 | typedef ContinuationMap> Cont2Config; 15 | 16 | class GPUKernelConfig : public KernelConfig { 17 | public: 18 | GPUKernelConfig(std::tuple block, bool has_restrict = false) 19 | : block_(block), has_restrict_(has_restrict) 20 | {} 21 | 22 | std::tuple block_size() const { return block_; } 23 | 24 | bool has_restrict() const { return has_restrict_; } 25 | 26 | private: 27 | std::tuple block_; 28 | bool has_restrict_; 29 | }; 30 | 31 | class HLSKernelConfig : public KernelConfig { 32 | public: 33 | typedef GIDMap Param2Size; 34 | HLSKernelConfig(const Param2Size& param_sizes) 35 | : param_sizes_(param_sizes) 36 | {} 37 | 38 | uint32_t param_size(const Param* param) const { 39 | auto it = param_sizes_.find(param); 40 | if (it != param_sizes_.end()) 41 | return it->second; 42 | return 0; 43 | } 44 | 45 | private: 46 | Param2Size param_sizes_; 47 | }; 48 | 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/be/llvm/amdgpu.h" 2 | 3 | #include // TODO don't use std::unordered_* 4 | 5 | #include "thorin/primop.h" 6 | #include "thorin/world.h" 7 | 8 | namespace thorin::llvm { 9 | 10 | AMDGPUCodeGen::AMDGPUCodeGen(Thorin& thorin, llvm::CallingConv::ID function_calling_convention, llvm::CallingConv::ID device_calling_convention, llvm::CallingConv::ID kernel_calling_convention, const Cont2Config& kernel_config, int opt, bool debug) 11 | : CodeGen(thorin, function_calling_convention, device_calling_convention, kernel_calling_convention, opt, debug) 12 | , kernel_config_(kernel_config) {} 13 | 14 | //------------------------------------------------------------------------------ 15 | // Kernel code 16 | //------------------------------------------------------------------------------ 17 | 18 | void AMDGPUCodeGen::emit_fun_decl_hook(Continuation* continuation, llvm::Function* f) { 19 | auto config = kernel_config_.find(continuation); 20 | if (config != kernel_config_.end()) { 21 | auto block = config->second->as()->block_size(); 22 | if (std::get<0>(block) > 0 && std::get<1>(block) > 0 && std::get<2>(block) > 0) { 23 | Array annotation_values_wgsize(3); 24 | auto int32_type = llvm::IntegerType::get(context(), 32); 25 | annotation_values_wgsize[0] = llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(int32_type, std::get<0>(block))); 26 | annotation_values_wgsize[1] = llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(int32_type, std::get<1>(block))); 27 | annotation_values_wgsize[2] = llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(int32_type, std::get<2>(block))); 28 | f->setMetadata(llvm::StringRef("reqd_work_group_size"), llvm::MDNode::get(context(), llvm_ref(annotation_values_wgsize))); 29 | } 30 | } 31 | } 32 | 33 | llvm::Value* AMDGPUCodeGen::emit_global(const Global* global) { 34 | if (global->is_mutable()) 35 | world().wdef(global, "AMDGPU: Global variable '{}' will not be synced with host", global); 36 | return CodeGen::emit_global(global); 37 | } 38 | 39 | llvm::Value* AMDGPUCodeGen::emit_mathop(llvm::IRBuilder<>& irbuilder, const MathOp* mathop) { 40 | auto make_key = [] (MathOpTag tag, unsigned bitwidth) { return (static_cast(tag) << 16) | bitwidth; }; 41 | static const std::unordered_map ocml_functions = { 42 | #define MATH_FUNCTION(name) \ 43 | { make_key(MathOp_##name, 32), "__ocml_" #name "_f32" }, \ 44 | { make_key(MathOp_##name, 64), "__ocml_" #name "_f64" }, 45 | MATH_FUNCTION(fabs) 46 | MATH_FUNCTION(copysign) 47 | MATH_FUNCTION(round) 48 | MATH_FUNCTION(floor) 49 | MATH_FUNCTION(ceil) 50 | MATH_FUNCTION(fmin) 51 | MATH_FUNCTION(fmax) 52 | MATH_FUNCTION(cos) 53 | MATH_FUNCTION(sin) 54 | MATH_FUNCTION(tan) 55 | MATH_FUNCTION(acos) 56 | MATH_FUNCTION(asin) 57 | MATH_FUNCTION(atan) 58 | MATH_FUNCTION(atan2) 59 | MATH_FUNCTION(sqrt) 60 | MATH_FUNCTION(cbrt) 61 | MATH_FUNCTION(pow) 62 | MATH_FUNCTION(exp) 63 | MATH_FUNCTION(exp2) 64 | MATH_FUNCTION(log) 65 | MATH_FUNCTION(log2) 66 | MATH_FUNCTION(log10) 67 | #undef MATH_FUNCTION 68 | }; 69 | auto key = make_key(mathop->mathop_tag(), num_bits(mathop->type()->primtype_tag())); 70 | auto call = call_math_function(irbuilder, mathop, ocml_functions.at(key)); 71 | llvm::cast(call)->setCallingConv(function_calling_convention_); 72 | return call; 73 | } 74 | 75 | llvm::Value* AMDGPUCodeGen::emit_reserve(llvm::IRBuilder<>& irbuilder, const Continuation* continuation) { 76 | return emit_reserve_shared(irbuilder, continuation, true); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_AMDGPU_H 2 | #define THORIN_BE_LLVM_AMDGPU_H 3 | 4 | #include "thorin/be/llvm/llvm.h" 5 | 6 | namespace thorin { 7 | 8 | namespace llvm { 9 | 10 | namespace llvm = ::llvm; 11 | 12 | class AMDGPUCodeGen : public CodeGen { 13 | public: 14 | AMDGPUCodeGen(Thorin&, llvm::CallingConv::ID, llvm::CallingConv::ID, llvm::CallingConv::ID, const Cont2Config&, int opt, bool debug); 15 | 16 | const char* file_ext() const override { return ".amdgpu"; } 17 | 18 | protected: 19 | void emit_fun_decl_hook(Continuation*, llvm::Function*) override; 20 | llvm::Function* emit_fun_decl(Continuation*) override = 0; 21 | llvm::Value* emit_global(const Global*) override; 22 | llvm::Value* emit_mathop(llvm::IRBuilder<>&, const MathOp*) override; 23 | llvm::Value* emit_reserve(llvm::IRBuilder<>&, const Continuation*) override; 24 | std::string get_alloc_name() const override { return "malloc"; } 25 | 26 | const Cont2Config& kernel_config_; 27 | }; 28 | 29 | } 30 | 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu_hsa.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/be/llvm/amdgpu_hsa.h" 2 | 3 | namespace thorin::llvm { 4 | 5 | AMDGPUHSACodeGen::AMDGPUHSACodeGen(Thorin& thorin, const Cont2Config& kernel_config, int opt, bool debug) 6 | : AMDGPUCodeGen(thorin, llvm::CallingConv::C, llvm::CallingConv::C, llvm::CallingConv::AMDGPU_KERNEL, kernel_config, opt, debug) 7 | { 8 | module().setDataLayout("e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7"); 9 | module().setTargetTriple("amdgcn-amd-amdhsa"); 10 | } 11 | 12 | //------------------------------------------------------------------------------ 13 | // Kernel code 14 | //------------------------------------------------------------------------------ 15 | 16 | llvm::Function* AMDGPUHSACodeGen::emit_fun_decl(Continuation* continuation) { 17 | if (continuation->name() == "llvm.amdgcn.implicitarg.ptr") 18 | if (auto f = defs_.lookup(entry_); f && llvm::isa(*f)) 19 | llvm::cast(*f)->addFnAttr("amdgpu-implicitarg-ptr"); 20 | if (continuation->name() == "__ockl_printf_begin") 21 | if (auto f = defs_.lookup(entry_); f && llvm::isa(*f)) 22 | llvm::cast(*f)->addFnAttr("amdgpu-implicitarg-num-bytes", "32"); 23 | return CodeGen::emit_fun_decl(continuation); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu_hsa.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_AMDGPU_HSA_H 2 | #define THORIN_BE_LLVM_AMDGPU_HSA_H 3 | 4 | #include "thorin/be/llvm/amdgpu.h" 5 | 6 | namespace thorin { 7 | 8 | namespace llvm { 9 | 10 | namespace llvm = ::llvm; 11 | 12 | class AMDGPUHSACodeGen : public AMDGPUCodeGen { 13 | public: 14 | AMDGPUHSACodeGen(Thorin& thorin, const Cont2Config&, int opt, bool debug); 15 | 16 | protected: 17 | llvm::Function* emit_fun_decl(Continuation*) override; 18 | }; 19 | 20 | } 21 | 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu_pal.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/be/llvm/amdgpu_pal.h" 2 | 3 | namespace thorin::llvm { 4 | 5 | AMDGPUPALCodeGen::AMDGPUPALCodeGen(Thorin& thorin, const Cont2Config& kernel_config, int opt, bool debug) 6 | : AMDGPUCodeGen(thorin, llvm::CallingConv::AMDGPU_Gfx, llvm::CallingConv::C, llvm::CallingConv::AMDGPU_CS, kernel_config, opt, debug) 7 | { 8 | module().setDataLayout("e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7"); 9 | module().setTargetTriple("amdgcn-amd-amdpal"); 10 | } 11 | 12 | //------------------------------------------------------------------------------ 13 | // Kernel code 14 | //------------------------------------------------------------------------------ 15 | 16 | 17 | llvm::Function* AMDGPUPALCodeGen::emit_fun_decl(Continuation* continuation) { 18 | return CodeGen::emit_fun_decl(continuation); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/amdgpu_pal.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_AMDGPU_PAL_H 2 | #define THORIN_BE_LLVM_AMDGPU_PAL_H 3 | 4 | #include "thorin/be/llvm/amdgpu.h" 5 | 6 | namespace thorin { 7 | 8 | namespace llvm { 9 | 10 | namespace llvm = ::llvm; 11 | 12 | class AMDGPUPALCodeGen : public AMDGPUCodeGen { 13 | public: 14 | AMDGPUPALCodeGen(Thorin& thorin, const Cont2Config&, int opt, bool debug); 15 | 16 | protected: 17 | llvm::Function* emit_fun_decl(Continuation*) override; 18 | }; 19 | 20 | } 21 | 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/cpu.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/be/llvm/cpu.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace thorin::llvm { 10 | 11 | CPUCodeGen::CPUCodeGen(Thorin& thorin, int opt, bool debug, std::string& target_triple, std::string& target_cpu, std::string& target_attr) 12 | : CodeGen(thorin, llvm::CallingConv::C, llvm::CallingConv::C, llvm::CallingConv::C, opt, debug) 13 | { 14 | llvm::InitializeNativeTarget(); 15 | auto triple_str = llvm::sys::getDefaultTargetTriple(); 16 | auto cpu_str = llvm::sys::getHostCPUName(); 17 | std::string features_str; 18 | auto features = llvm::sys::getHostCPUFeatures(); 19 | for (auto& feature : features) 20 | features_str += (feature.getValue() ? "+" : "-") + feature.getKey().str() + ","; 21 | 22 | if (!target_triple.empty() && !target_cpu.empty()) { 23 | llvm::InitializeAllTargets(); 24 | llvm::InitializeAllTargetMCs(); 25 | triple_str = target_triple; 26 | cpu_str = target_cpu; 27 | features_str = target_attr; 28 | } 29 | 30 | std::string error; 31 | auto target = llvm::TargetRegistry::lookupTarget(triple_str, error); 32 | assert(target && "can't create target for target architecture"); 33 | llvm::TargetOptions options; 34 | machine_.reset(target->createTargetMachine(triple_str, cpu_str, features_str, options, std::nullopt)); 35 | module().setDataLayout(machine_->createDataLayout()); 36 | module().setTargetTriple(triple_str); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_CPU_H 2 | #define THORIN_BE_LLVM_CPU_H 3 | 4 | #include "thorin/be/llvm/llvm.h" 5 | 6 | namespace thorin::llvm { 7 | 8 | namespace llvm = ::llvm; 9 | 10 | class CPUCodeGen : public CodeGen { 11 | public: 12 | CPUCodeGen(Thorin&, int opt, bool debug, std::string& target_triple, std::string& target_cpu, std::string& target_attr); 13 | 14 | protected: 15 | std::string get_alloc_name() const override { return "anydsl_alloc"; } 16 | }; 17 | 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/llvm.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_LLVM_H 2 | #define THORIN_BE_LLVM_LLVM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "thorin/config.h" 10 | #include "thorin/continuation.h" 11 | #include "thorin/analyses/schedule.h" 12 | #include "thorin/be/codegen.h" 13 | #include "thorin/be/emitter.h" 14 | #include "thorin/be/llvm/runtime.h" 15 | #include "thorin/be/kernel_config.h" 16 | #include "thorin/transform/importer.h" 17 | 18 | namespace thorin { 19 | 20 | class World; 21 | 22 | namespace llvm { 23 | 24 | namespace llvm = ::llvm; 25 | 26 | using BB = std::pair>>; 27 | 28 | class CodeGen : public thorin::CodeGen, public thorin::Emitter { 29 | protected: 30 | CodeGen( 31 | Thorin&, 32 | llvm::CallingConv::ID function_calling_convention, 33 | llvm::CallingConv::ID device_calling_convention, 34 | llvm::CallingConv::ID kernel_calling_convention, 35 | int opt, bool debug); 36 | public: 37 | virtual ~CodeGen() {} 38 | 39 | /// @name getters 40 | //@{ 41 | llvm::LLVMContext& context() { return *context_; } 42 | llvm::Module& module() { return *module_; } 43 | const llvm::Module& module() const { return *module_; } 44 | llvm::TargetMachine& machine() { return *machine_; } 45 | int opt() const { return opt_; } 46 | //@} 47 | 48 | const char* file_ext() const override { return ".ll"; } 49 | void emit_stream(std::ostream& stream) override; 50 | // Note: This moves the context and module of the class, 51 | // rendering the current CodeGen object invalid. 52 | std::pair< 53 | std::unique_ptr, 54 | std::unique_ptr> emit_module(); 55 | llvm::Function* prepare(const Scope&); 56 | virtual void prepare(Continuation*, llvm::Function*); 57 | llvm::Value* emit_constant(const Def* def); 58 | llvm::Value* emit_bb(BB&, const Def* def); 59 | llvm::Value* emit_builder(llvm::IRBuilder<>&, const Def* def); 60 | virtual llvm::Function* emit_fun_decl(Continuation*); 61 | bool is_valid(llvm::Value* value) { return value != nullptr; } 62 | void finalize(const Scope&); 63 | void finalize(Continuation*) {} 64 | void emit_epilogue(Continuation*); 65 | 66 | protected: 67 | /// @name convert 68 | //@{ 69 | llvm::Type* convert(const Type*); 70 | virtual unsigned convert_addr_space(const AddrSpace); 71 | virtual llvm::FunctionType* convert_fn_type(Continuation*); 72 | virtual llvm::FunctionType* convert_closure_type(const Type*); 73 | //@} 74 | 75 | llvm::AllocaInst* emit_alloca(llvm::IRBuilder<>&, llvm::Type*, const std::string&); 76 | llvm::Value* emit_alloc (llvm::IRBuilder<>&, const Type*, const Def*); 77 | virtual void emit_fun_decl_hook(Continuation*, llvm::Function*) {} 78 | virtual llvm::Value* map_param(llvm::Function*, llvm::Argument* a, const Param*) { return a; } 79 | 80 | virtual llvm::Value* emit_mathop (llvm::IRBuilder<>&, const MathOp*); 81 | virtual llvm::Value* emit_load (llvm::IRBuilder<>&, const Load*); 82 | virtual llvm::Value* emit_store (llvm::IRBuilder<>&, const Store*); 83 | virtual llvm::Value* emit_lea (llvm::IRBuilder<>&, const LEA*); 84 | virtual llvm::Value* emit_assembly(llvm::IRBuilder<>&, const Assembly* assembly); 85 | 86 | virtual llvm::Value* emit_reserve(llvm::IRBuilder<>&, const Continuation*); 87 | llvm::Value* emit_reserve_shared(llvm::IRBuilder<>&, const Continuation*, bool=false); 88 | 89 | virtual std::string get_alloc_name() const = 0; 90 | llvm::BasicBlock* cont2bb(Continuation* cont) { return cont2bb_[cont].first; } 91 | 92 | virtual llvm::Value* emit_global(const Global*); 93 | llvm::GlobalVariable* emit_global_variable(llvm::Type*, const std::string&, unsigned, bool=false); 94 | 95 | void optimize(); 96 | void verify() const; 97 | void create_loop(llvm::IRBuilder<>&, llvm::Value*, llvm::Value*, llvm::Value*, llvm::Function*, std::function); 98 | llvm::Value* create_tmp_alloca(llvm::IRBuilder<>&, llvm::Type*, std::function); 99 | llvm::Value* call_math_function(llvm::IRBuilder<>&, const MathOp*, const std::string&); 100 | 101 | std::vector split_values(llvm::IRBuilder<>&, Types domain, llvm::Value* value); 102 | /// Emits a 'call' using already emitted arguments. Returns the call instruction if appropriate 103 | llvm::CallInst* emit_call(llvm::IRBuilder<>&, const Def* callee, std::vector& args); 104 | 105 | private: 106 | Continuation* emit_peinfo(llvm::IRBuilder<>&, Continuation*); 107 | std::vector emit_intrinsic(llvm::IRBuilder<>&, Continuation*); 108 | void emit_hls(llvm::IRBuilder<>&, Continuation*); 109 | void emit_parallel(llvm::IRBuilder<>&, Continuation*); 110 | void emit_fibers(llvm::IRBuilder<>&, Continuation*); 111 | llvm::Value* emit_spawn(llvm::IRBuilder<>&, Continuation*); 112 | void emit_sync(llvm::IRBuilder<>&, Continuation*); 113 | void emit_vectorize_continuation(llvm::IRBuilder<>&, Continuation*); 114 | llvm::Value* emit_atomic(llvm::IRBuilder<>&, Continuation*); 115 | std::vector emit_cmpxchg(llvm::IRBuilder<>&, Continuation*, bool); 116 | void emit_fence(llvm::IRBuilder<>&, Continuation*); 117 | llvm::Value* emit_atomic_load(llvm::IRBuilder<>&, Continuation*); 118 | void emit_atomic_store(llvm::IRBuilder<>&, Continuation*); 119 | llvm::Value* emit_bitcast(llvm::IRBuilder<>&, const Def*, const Type*); 120 | void emit_vectorize(u32, llvm::Function*, llvm::CallInst*); 121 | void emit_phi_arg(llvm::IRBuilder<>&, const Param*, llvm::Value*); 122 | 123 | // Note: The module and context have to be stored as pointers, so 124 | // that ownership of the module can be moved to the JIT (LLVM currently 125 | // has no std::move or std::swap implementation for modules and contexts). 126 | std::unique_ptr context_; 127 | std::unique_ptr module_; 128 | 129 | int opt_; 130 | 131 | protected: 132 | std::unique_ptr machine_; 133 | llvm::DIBuilder dibuilder_; 134 | llvm::DICompileUnit* dicompile_unit_; 135 | llvm::CallingConv::ID function_calling_convention_; 136 | llvm::CallingConv::ID device_calling_convention_; 137 | llvm::CallingConv::ID kernel_calling_convention_; 138 | llvm::DIScope* discope_ = nullptr; 139 | std::unique_ptr runtime_; 140 | #if THORIN_ENABLE_RV 141 | std::vector> vec_todo_; 142 | #endif 143 | 144 | friend class Runtime; 145 | }; 146 | 147 | //------------------------------------------------------------------------------ 148 | 149 | template 150 | llvm::ArrayRef llvm_ref(const Array& array) { return llvm::ArrayRef(array.begin(), array.end()); } 151 | 152 | } // namespace llvm_be 153 | 154 | } // namespace thorin 155 | 156 | #endif 157 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/nvvm.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_NVVM_H 2 | #define THORIN_BE_LLVM_NVVM_H 3 | 4 | #include "thorin/be/llvm/llvm.h" 5 | 6 | namespace thorin { 7 | 8 | class Load; 9 | 10 | namespace llvm { 11 | 12 | namespace llvm = ::llvm; 13 | 14 | class NVVMCodeGen : public CodeGen { 15 | public: 16 | NVVMCodeGen(Thorin&, const Cont2Config&, int opt, bool debug); // NVVM-specific optimizations are run in the runtime 17 | 18 | const char* file_ext() const override { return ".nvvm"; } 19 | 20 | protected: 21 | void emit_fun_decl_hook(Continuation*, llvm::Function*) override; 22 | llvm::FunctionType* convert_fn_type(Continuation*) override; 23 | llvm::Value* map_param(llvm::Function*, llvm::Argument*, const Param*) override; 24 | void prepare(Continuation*, llvm::Function*) override; 25 | 26 | llvm::Value* emit_load(llvm::IRBuilder<>&, const Load*) override; 27 | llvm::Value* emit_store(llvm::IRBuilder<>&, const Store*) override; 28 | llvm::Value* emit_lea(llvm::IRBuilder<>&, const LEA*) override; 29 | llvm::Value* emit_mathop(llvm::IRBuilder<>&, const MathOp*) override; 30 | 31 | llvm::Value* emit_reserve(llvm::IRBuilder<>&, const Continuation*) override; 32 | 33 | llvm::Value* emit_global(const Global*) override; 34 | 35 | std::string get_alloc_name() const override { return "malloc"; } 36 | 37 | private: 38 | llvm::Function* get_texture_handle_fun(llvm::IRBuilder<>&); 39 | llvm::GlobalVariable* resolve_global_variable(const Param*); 40 | 41 | const Cont2Config& kernel_config_; 42 | ParamMap metadata_; 43 | }; 44 | 45 | } 46 | 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_BE_LLVM_RUNTIME_H 2 | #define THORIN_BE_LLVM_RUNTIME_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "thorin/world.h" 10 | #include "thorin/be/runtime.h" 11 | 12 | namespace thorin::llvm { 13 | 14 | namespace llvm = ::llvm; 15 | 16 | class CodeGen; 17 | 18 | class Runtime { 19 | public: 20 | Runtime(llvm::LLVMContext&, llvm::Module&); 21 | 22 | /// Emits a call to anydsl_launch_kernel. 23 | llvm::Value* launch_kernel( 24 | CodeGen&, llvm::IRBuilder<>&, llvm::Value* device, 25 | llvm::Value* file, llvm::Value* kernel, 26 | llvm::Value* grid, llvm::Value* block, 27 | llvm::Value* args, llvm::Value* sizes, llvm::Value* aligns, llvm::Value* allocs, llvm::Value* types, 28 | llvm::Value* num_args); 29 | 30 | /// Emits a call to anydsl_parallel_for. 31 | llvm::Value* parallel_for( 32 | CodeGen&, llvm::IRBuilder<>&, 33 | llvm::Value* num_threads, llvm::Value* lower, llvm::Value* upper, 34 | llvm::Value* closure_ptr, llvm::Value* fun_ptr); 35 | 36 | /// Emits a call to anydsl_fibers_spawn. 37 | llvm::Value* spawn_fibers( 38 | CodeGen&, llvm::IRBuilder<>&, 39 | llvm::Value* num_threads, llvm::Value* num_blocks, llvm::Value* num_warps, 40 | llvm::Value* closure_ptr, llvm::Value* fun_ptr); 41 | 42 | /// Emits a call to anydsl_spawn_thread. 43 | llvm::Value* spawn_thread(CodeGen&, llvm::IRBuilder<>&, llvm::Value* closure_ptr, llvm::Value* fun_ptr); 44 | /// Emits a call to anydsl_sync_thread. 45 | llvm::Value* sync_thread(CodeGen&, llvm::IRBuilder<>&, llvm::Value* id); 46 | 47 | void emit_host_code( 48 | CodeGen& code_gen, llvm::IRBuilder<>& builder, 49 | Platform platform, const std::string& ext, Continuation* continuation); 50 | 51 | llvm::Function* get(CodeGen& code_gen, const char* name); 52 | 53 | protected: 54 | llvm::Module& target_; 55 | const llvm::DataLayout& layout_; 56 | 57 | std::unique_ptr runtime_; 58 | }; 59 | 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/thorin/be/llvm/runtime.inc: -------------------------------------------------------------------------------- 1 | namespace thorin { 2 | enum class KernelArgType : uint8_t { Val = 0, Ptr, Struct }; 3 | 4 | static const char* runtime_definitions = R"( 5 | ; Module anydsl runtime decls 6 | declare noalias ptr @anydsl_alloc(i32, i64); 7 | declare noalias ptr @anydsl_alloc_unified(i32, i64); 8 | declare void @anydsl_release(i32, ptr); 9 | declare void @anydsl_launch_kernel(i32, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, i32); 10 | declare void @anydsl_parallel_for(i32, i32, i32, ptr, ptr); 11 | declare void @anydsl_fibers_spawn(i32, i32, i32, ptr, ptr); 12 | declare i32 @anydsl_spawn_thread(ptr, ptr); 13 | declare void @anydsl_sync_thread(i32); 14 | declare i32 @anydsl_create_graph(); 15 | declare i32 @anydsl_create_task(i32, { ptr, i64 }); 16 | declare void @anydsl_create_edge(i32, i32); 17 | declare void @anydsl_execute_graph(i32, i32); 18 | )"; 19 | } 20 | -------------------------------------------------------------------------------- /src/thorin/be/runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_RUNTIME_H 2 | #define THORIN_RUNTIME_H 3 | 4 | /// Backend-agnostic information to interface with the runtime component 5 | namespace thorin { 6 | 7 | enum Platform { 8 | CPU_PLATFORM, 9 | CUDA_PLATFORM, 10 | OPENCL_PLATFORM, 11 | HSA_PLATFORM, 12 | PAL_PLATFORM, 13 | LEVEL_ZERO_PLATFORM, 14 | SHADY_PLATFORM, 15 | }; 16 | 17 | enum KernelLaunchArgs { 18 | Mem = 0, 19 | Device, 20 | Space, 21 | Config, 22 | Body, 23 | Return, 24 | Num 25 | }; 26 | 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/thorin/be/shady/shady.h: -------------------------------------------------------------------------------- 1 | #include "thorin/be/codegen.h" 2 | #include "thorin/analyses/schedule.h" 3 | #include "thorin/be/emitter.h" 4 | 5 | namespace shady { 6 | extern "C" { 7 | #include 8 | } 9 | } 10 | 11 | namespace thorin::shady_be { 12 | 13 | // using BB = std::pair; 14 | 15 | struct BB { 16 | /// For an entry BB, this will also be the head of the entire function 17 | shady::Node* head; 18 | shady::BodyBuilder* builder; 19 | const shady::Node* terminator; 20 | }; 21 | 22 | class CodeGen : public thorin::CodeGen, public thorin::Emitter { 23 | public: 24 | CodeGen(Thorin&, Cont2Config&, bool debug); 25 | 26 | void emit_stream(std::ostream& stream) override; 27 | const char* file_ext() const override { return ".shady"; } 28 | 29 | shady::Node* prepare(const Scope&); 30 | void prepare(Continuation*, shady::Node*); 31 | void emit_epilogue(Continuation*); 32 | // const shady::Node* emit_(const Def* def); 33 | void finalize(const Scope&); 34 | void finalize(Continuation*); 35 | 36 | const shady::Type* convert(const Type*); 37 | const shady::Node* emit_bb(BB&, const Def*); 38 | const shady::Node* emit_constant(const Def*); 39 | 40 | bool is_valid(const shady::Node* n) { 41 | return n; 42 | } 43 | 44 | const shady::Node* emit_fun_decl(Continuation*); 45 | protected: 46 | shady::AddressSpace convert_address_space(AddrSpace); 47 | shady::Node* emit_decl_head(Def*); 48 | shady::Node* get_decl(Def*); 49 | 50 | using NodeVec = std::vector; 51 | 52 | inline shady::Nodes vec2nodes(NodeVec& vec) { 53 | return shady::nodes(arena, vec.size(), vec.data()); 54 | } 55 | 56 | shady::IrArena* arena = nullptr; 57 | shady::Module* module = nullptr; 58 | 59 | void unimplemented(const Def* def); 60 | 61 | shady::Node* curr_fn; 62 | 63 | const Cont2Config& kernel_config_; 64 | 65 | }; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/thorin/be/spirv/spirv.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_SPIRV_H 2 | #define THORIN_SPIRV_H 3 | 4 | #include "thorin/analyses/schedule.h" 5 | #include "thorin/be/codegen.h" 6 | #include "thorin/be/emitter.h" 7 | 8 | namespace thorin::spirv { 9 | 10 | using Id = uint32_t; 11 | 12 | class CodeGen; 13 | 14 | struct FileBuilder; 15 | struct FnBuilder; 16 | 17 | struct Target { 18 | struct { 19 | // Either '4' or '8' 20 | size_t pointer_size = 8; 21 | } mem_layout; 22 | 23 | struct { 24 | bool broken_op_construct_composite = true; 25 | bool static_ac_indices_must_be_i32 = true; 26 | } bugs; 27 | 28 | enum Dialect { 29 | OpenCL, 30 | Vulkan 31 | }; 32 | 33 | Dialect dialect = OpenCL; 34 | }; 35 | 36 | struct ConvertedType { 37 | Id id; 38 | struct Layout { 39 | size_t size, alignment; 40 | }; 41 | std::optional layout; 42 | struct { 43 | std::optional payload_t; 44 | } variant; 45 | }; 46 | 47 | struct BasicBlockBuilder; 48 | 49 | class CodeGen : public thorin::CodeGen, public thorin::Emitter { 50 | public: 51 | CodeGen(Thorin& thorin, Target&, bool debug, const Cont2Config* = nullptr); 52 | 53 | void emit_stream(std::ostream& stream) override; 54 | const char* file_ext() const override { return ".spv"; } 55 | 56 | bool is_valid(Id id) { 57 | return id > 0; 58 | } 59 | 60 | uint32_t convert(AddrSpace); 61 | ConvertedType convert_maybe_void(const Type*); 62 | ConvertedType convert(const Type*); 63 | 64 | Id emit_fun_decl(Continuation*); 65 | 66 | FnBuilder* prepare(const Scope&); 67 | void prepare(Continuation*, FnBuilder*); 68 | void emit_epilogue(Continuation*); 69 | void finalize(const Scope&); 70 | void finalize(Continuation*); 71 | 72 | Id emit_constant(const Def*); 73 | Id emit_bb(BasicBlockBuilder* bb, const Def* def); 74 | protected: 75 | FnBuilder& get_fn_builder(Continuation*); 76 | std::vector emit_intrinsic(const App& app, const Continuation* intrinsic, BasicBlockBuilder* bb); 77 | std::vector emit_args(Defs); 78 | bool should_emit(const Type*); 79 | Id literal(uint32_t); 80 | 81 | Id emit_as_bb(Continuation*); 82 | Id emit_mathop(BasicBlockBuilder* bb, const MathOp& op); 83 | Id emit_composite(BasicBlockBuilder* bb, Id, Defs); 84 | Id emit_composite(BasicBlockBuilder* bb, Id, ArrayRef); 85 | Id emit_ptr_bitcast(BasicBlockBuilder* bb, const PtrType* from, const PtrType* to, Id); 86 | 87 | std::tuple, Id> get_dom_codom(const FnType* fn); 88 | Id get_codom_type(const FnType*); 89 | 90 | Target target_info_; 91 | FileBuilder* builder_; 92 | const Cont2Config* kernel_config_; 93 | DefSet scope_local_defs_; 94 | 95 | friend Target; 96 | }; 97 | 98 | } 99 | 100 | #endif //THORIN_SPIRV_H 101 | -------------------------------------------------------------------------------- /src/thorin/be/spirv/spirv_private.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_SPIRV_PRIVATE_H 2 | #define THORIN_SPIRV_PRIVATE_H 3 | 4 | #include "spirv.h" 5 | 6 | #include "thorin/be/spirv/spirv_builder.hpp" 7 | 8 | namespace thorin::spirv { 9 | 10 | struct BasicBlockBuilder : public builder::BasicBlockBuilder { 11 | explicit BasicBlockBuilder(FnBuilder& fn_builder); 12 | 13 | BasicBlockBuilder(const BasicBlockBuilder&) = delete; 14 | 15 | FnBuilder& fn_builder; 16 | FileBuilder& file_builder; 17 | std::unordered_map phis_map; 18 | 19 | bool semi_inline; 20 | }; 21 | 22 | struct FnBuilder : public builder::FnBuilder { 23 | explicit FnBuilder(FileBuilder& file_builder); 24 | 25 | FnBuilder(const FnBuilder&) = delete; 26 | 27 | FileBuilder& file_builder; 28 | std::vector> bbs; 29 | DefMap params; 30 | }; 31 | 32 | struct FileBuilder : public builder::FileBuilder { 33 | explicit FileBuilder(CodeGen* cg); 34 | FileBuilder(const FileBuilder&) = delete; 35 | 36 | CodeGen* cg; 37 | 38 | FnBuilder* current_fn_ = nullptr; 39 | ContinuationMap> fn_builders_; 40 | std::unordered_map builtins_; 41 | std::vector interface; 42 | 43 | Id u32_t(); 44 | Id u32_constant(uint32_t); 45 | 46 | private: 47 | Id u32_t_ { 0 }; 48 | }; 49 | 50 | } 51 | 52 | #endif // THORIN_SPIRV_PRIVATE_H 53 | -------------------------------------------------------------------------------- /src/thorin/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_CONFIG_H 2 | #define THORIN_CONFIG_H 3 | 4 | #cmakedefine01 THORIN_ENABLE_CHECKS 5 | #cmakedefine01 THORIN_ENABLE_PROFILING 6 | #cmakedefine01 THORIN_ENABLE_CREATION_CONTEXT 7 | #cmakedefine01 THORIN_ENABLE_LLVM 8 | #cmakedefine01 THORIN_ENABLE_JSON 9 | #cmakedefine01 THORIN_ENABLE_RV 10 | #cmakedefine01 THORIN_ENABLE_SHADY 11 | #cmakedefine01 THORIN_ENABLE_SPIRV 12 | #cmakedefine01 THORIN_ENABLE_RLIMITS 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/thorin/debug.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/debug.h" 2 | 3 | #include "thorin/world.h" 4 | 5 | namespace thorin { 6 | 7 | Loc::Loc(const Def* /*dbg*/) { 8 | #if 0 9 | if (dbg != nullptr) { 10 | auto [d_file, d_begin, d_finis] = dbg->out(1)->split<3>(); 11 | const_cast(file) = tuple2str(d_file); 12 | const_cast(begin.row) = as_lit(d_begin) >> 32_u64; 13 | const_cast(begin.col) = as_lit(d_begin); 14 | const_cast(finis.row) = as_lit(d_finis) >> 32_u64; 15 | const_cast(finis.col) = as_lit(d_finis); 16 | } 17 | #endif 18 | assert(false && "TODO"); 19 | } 20 | 21 | Stream& Loc::stream(Stream& s) const { 22 | s.fmt("{}:", file); 23 | 24 | if (begin.col == u32(-1) || finis.col == u32(-1)) { 25 | if (begin.row != finis.row) 26 | s.fmt("{} - {}", begin.row, finis.row); 27 | else 28 | s.fmt("{}", begin.row); 29 | } else if (begin.row != finis.row) { 30 | s.fmt("{} col {} - {} col {}", begin.row, begin.col, finis.row, finis.col); 31 | } else if (begin.col != finis.col) { 32 | s.fmt("{} col {} - {}", begin.row, begin.col, finis.col); 33 | } else { 34 | s.fmt("{} col {}", begin.row, begin.col); 35 | } 36 | 37 | return s; 38 | } 39 | 40 | #if 0 41 | Debug::Debug(const Def* dbg) 42 | : name(dbg ? tuple2str(dbg->out(0)) : std::string{}) 43 | , loc(dbg) 44 | , meta(dbg ? dbg->out(2) : nullptr) 45 | {} 46 | #endif 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/thorin/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_DEBUG_H 2 | #define THORIN_DEBUG_H 3 | 4 | #include 5 | #include 6 | 7 | #include "thorin/config.h" 8 | #include "thorin/util/stream.h" 9 | 10 | namespace thorin { 11 | 12 | class Def; 13 | class World; 14 | 15 | struct Pos { 16 | uint32_t row = -1; 17 | uint32_t col = -1; 18 | }; 19 | 20 | struct Loc : public Streamable { 21 | Loc() = default; 22 | Loc(std::string file, Pos begin, Pos finis) 23 | : file(file) 24 | , begin(begin) 25 | , finis(finis) 26 | {} 27 | Loc(std::string file, Pos pos) 28 | : Loc(file, pos, pos) 29 | {} 30 | Loc(const Def* dbg); 31 | 32 | Loc anew_begin() const { return {file, begin, begin}; } 33 | Loc anew_finis() const { return {file, finis, finis}; } 34 | 35 | std::string file; 36 | Pos begin = {uint32_t(-1), uint32_t(-1)}; 37 | Pos finis = {uint32_t(-1), uint32_t(-1)}; 38 | 39 | Stream& stream(Stream&) const; 40 | }; 41 | 42 | class Debug { 43 | public: 44 | Debug() = default; // TODO remove 45 | Debug(std::string name, Loc loc = {}, const Def* meta = nullptr) 46 | : name(name) 47 | #if THORIN_ENABLE_CREATION_CONTEXT 48 | , creation_context("") 49 | #endif 50 | , loc(loc) 51 | , meta(meta) 52 | {} 53 | Debug(const char* name, Loc loc = {}, const Def* meta = nullptr) 54 | : Debug(std::string(name), loc, meta) 55 | {} 56 | #if THORIN_ENABLE_CREATION_CONTEXT 57 | Debug(std::string name, std::string creation_context, Loc loc = {}, const Def* meta = nullptr) 58 | : name(name) 59 | , creation_context(creation_context) 60 | , loc(loc) 61 | , meta(meta) 62 | {} 63 | Debug(const char* name, const char* creation_context, Loc loc = {}, const Def* meta = nullptr) 64 | : Debug(std::string(name), std::string(creation_context), loc, meta) 65 | {} 66 | #endif 67 | Debug(Loc loc) 68 | : Debug("", loc) 69 | {} 70 | //Debug(const Def*); 71 | 72 | std::string name; 73 | #if THORIN_ENABLE_CREATION_CONTEXT 74 | std::string creation_context; 75 | #endif 76 | Loc loc; 77 | const Def* meta = nullptr; 78 | }; 79 | 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/thorin/def.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/def.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "thorin/transform/rewrite.h" 7 | #include "thorin/continuation.h" 8 | #include "thorin/primop.h" 9 | #include "thorin/type.h" 10 | #include "thorin/world.h" 11 | 12 | namespace thorin { 13 | 14 | //------------------------------------------------------------------------------ 15 | 16 | size_t Def::gid_counter_ = 1; 17 | 18 | Def::Def(World& world, NodeTag tag, const Type* type, Defs ops, Debug dbg) 19 | : tag_(tag) 20 | , ops_(ops.size()) 21 | , world_(world) 22 | , type_(type) 23 | , debug_(dbg) 24 | , gid_(gid_counter_++) 25 | , nom_(false) 26 | , dep_(tag == Node_Continuation ? Dep::Cont : 27 | tag == Node_Param ? Dep::Param : 28 | Dep::Bot ) 29 | { 30 | for (size_t i = 0, e = num_ops(); i != e; ++i) 31 | set_op(i, ops[i]); 32 | } 33 | 34 | Def::Def(World& world, NodeTag tag, const Type* type, size_t size, Debug dbg) 35 | : tag_(tag) 36 | , ops_(size) 37 | , world_(world) 38 | , type_(type) 39 | , debug_(dbg) 40 | , gid_(gid_counter_++) 41 | , nom_(true) 42 | , dep_(tag == Node_Continuation ? Dep::Cont : 43 | tag == Node_Param ? Dep::Param : 44 | Dep::Bot ) 45 | {} 46 | 47 | int Def::order() const { 48 | return type()->order(); 49 | } 50 | 51 | Debug Def::debug_history() const { 52 | #if THORIN_ENABLE_CHECKS 53 | return world().track_history() ? Debug(unique_name(), loc()) : debug(); 54 | #else 55 | return debug(); 56 | #endif 57 | } 58 | 59 | void Def::set_name(const std::string& name) const { debug_.name = name; } 60 | 61 | void Def::set_op(size_t i, const Def* def) { 62 | assert(!op(i) && "already set"); 63 | assert(def && "setting null pointer"); 64 | assert(&def->world() == &world()); 65 | ops_[i] = def; 66 | // A Param/Continuation should not have other bits than its own set. 67 | // (Right now, Param doesn't have ops, but this will change in the future). 68 | if (!isa_nom() && !isa()) 69 | dep_ |= def->dep(); // what about unset op then ? and cascading uses ? 70 | assert(!def->uses_.contains(Use(i, this))); 71 | const auto& p = def->uses_.emplace(i, this); 72 | assert_unused(p.second); 73 | } 74 | 75 | void Def::unregister_uses() const { 76 | for (size_t i = 0, e = num_ops(); i != e; ++i) 77 | unregister_use(i); 78 | } 79 | 80 | void Def::unregister_use(size_t i) const { 81 | auto def = ops_[i]; 82 | assert(def->uses_.contains(Use(i, this))); 83 | def->uses_.erase(Use(i, this)); 84 | assert(!def->uses_.contains(Use(i, this))); 85 | } 86 | 87 | void Def::unset_op(size_t i) { 88 | // Note: if replace() didn't touch the uses, we could assert for nominalness here ! 89 | assert(ops_[i] && "must be set"); 90 | unregister_use(i); 91 | ops_[i] = nullptr; 92 | } 93 | 94 | void Def::unset_ops() { 95 | for (size_t i = 0, e = num_ops(); i != e; ++i) 96 | unset_op(i); 97 | } 98 | 99 | std::string Def::unique_name() const { 100 | return name() + "_" + std::to_string(gid()); 101 | } 102 | 103 | bool is_unit(const Def* def) { 104 | return def->type() == def->world().unit_type(); 105 | } 106 | 107 | size_t vector_length(const Def* def) { return def->type()->as()->length(); } 108 | 109 | bool is_primlit(const Def* def, int64_t val) { 110 | if (auto lit = def->isa()) { 111 | switch (lit->primtype_tag()) { 112 | #define THORIN_I_TYPE(T, M) case PrimType_##T: return lit->value().get_##T() == T(val); 113 | #include "thorin/tables/primtypetable.h" 114 | case PrimType_bool: return lit->value().get_bool() == bool(val); 115 | default: ; // FALLTHROUGH 116 | } 117 | } 118 | 119 | if (auto vector = def->isa()) { 120 | for (auto op : vector->ops()) { 121 | if (!is_primlit(op, val)) 122 | return false; 123 | } 124 | return true; 125 | } 126 | return false; 127 | } 128 | 129 | bool is_minus_zero(const Def* def) { 130 | if (auto lit = def->isa()) { 131 | Box box = lit->value(); 132 | switch (lit->primtype_tag()) { 133 | #define THORIN_I_TYPE(T, M) case PrimType_##T: return box.get_##M() == M(0); 134 | #define THORIN_F_TYPE(T, M) case PrimType_##T: return box.get_##M() == M(-0.0); 135 | #include "thorin/tables/primtypetable.h" 136 | default: THORIN_UNREACHABLE; 137 | } 138 | } 139 | return false; 140 | } 141 | 142 | void Def::rebuild_from(Rewriter& rewriter, const Def* old) { 143 | assert(old->num_ops() == num_ops()); 144 | for (size_t i = 0; i < num_ops(); i++) 145 | set_op(i, rewriter.instantiate(old->op(i))); 146 | } 147 | 148 | void Def::replace_uses(const Def* with) const { 149 | world().DLOG("replace uses: {} -> {}", this, with); 150 | if (this != with) { 151 | assert(&with->world() == &this->world()); 152 | for (auto& use : copy_uses()) { 153 | auto def = const_cast(use.def()); 154 | if (def->isa()) 155 | continue; 156 | auto index = use.index(); 157 | def->unset_op(index); 158 | def->set_op(index, with); 159 | } 160 | 161 | uses_.clear(); 162 | } 163 | } 164 | 165 | uint64_t UseHash::hash(Use use) { 166 | assert(use->gid() != uint32_t(-1)); 167 | hash_t seed = hash_begin(use.index()); 168 | seed = hash_combine(seed, uint32_t(use->gid())); 169 | return seed; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/thorin/enums.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/enums.h" 2 | 3 | namespace thorin { 4 | 5 | #define THORIN_GLUE(pre, next) 6 | #define THORIN_NODE(node, abbr) static_assert(Node_##node == (NodeTag) zzzMarker_##node, "NodeTag value not equal zzzMarker"); 7 | #define THORIN_PRIMTYPE(T) static_assert(Node_PrimType_##T == (NodeTag) zzzMarker_PrimType_##T, "NodeTag value not equal zzzMarker"); 8 | #define THORIN_ARITHOP(op) static_assert(Node_##op == (NodeTag) zzzMarker_##op, "NodeTag value not equal zzzMarker"); 9 | #define THORIN_CMP(op) static_assert(Node_##op == (NodeTag) zzzMarker_##op, "NodeTag value not equal zzzMarker"); 10 | #define THORIN_MATHOP(op) static_assert(Node_##op == (NodeTag) zzzMarker_##op, "NodeTag value not equal zzzMarker"); 11 | #include "thorin/tables/allnodes.h" 12 | 13 | const char* tag2str(NodeTag tag) { 14 | switch (tag) { 15 | #define THORIN_GLUE(pre, next) 16 | #define THORIN_PRIMTYPE(T) case Node_PrimType_##T: return #T; 17 | #define THORIN_NODE(n, abbr) case Node_##n: return #n; 18 | #define THORIN_ARITHOP(n) case Node_##n: return #n; 19 | #define THORIN_CMP(n) case Node_##n: return #n; 20 | #define THORIN_MATHOP(n) case Node_##n: return #n; 21 | #include "thorin/tables/allnodes.h" 22 | default: THORIN_UNREACHABLE; 23 | } 24 | } 25 | 26 | int num_bits(PrimTypeTag tag) { 27 | switch (tag) { 28 | case PrimType_bool: return 1; 29 | case PrimType_ps8: case PrimType_qs8: case PrimType_pu8: case PrimType_qu8: return 8; 30 | case PrimType_ps16: case PrimType_qs16: case PrimType_pu16: case PrimType_qu16: case PrimType_pf16: case PrimType_qf16: return 16; 31 | case PrimType_ps32: case PrimType_qs32: case PrimType_pu32: case PrimType_qu32: case PrimType_pf32: case PrimType_qf32: return 32; 32 | case PrimType_ps64: case PrimType_qs64: case PrimType_pu64: case PrimType_qu64: case PrimType_pf64: case PrimType_qf64: return 64; 33 | } 34 | THORIN_UNREACHABLE; 35 | } 36 | 37 | CmpTag negate(CmpTag tag) { 38 | switch (tag) { 39 | case Cmp_eq: return Cmp_ne; 40 | case Cmp_ne: return Cmp_eq; 41 | case Cmp_lt: return Cmp_ge; 42 | case Cmp_le: return Cmp_gt; 43 | case Cmp_gt: return Cmp_le; 44 | case Cmp_ge: return Cmp_lt; 45 | } 46 | THORIN_UNREACHABLE; 47 | } 48 | 49 | } // namespace thorin 50 | -------------------------------------------------------------------------------- /src/thorin/enums.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ENUMS_H 2 | #define THORIN_ENUMS_H 3 | 4 | #include "thorin/util/types.h" 5 | 6 | namespace thorin { 7 | 8 | //------------------------------------------------------------------------------ 9 | 10 | 11 | enum NodeTag { 12 | #define THORIN_GLUE(pre, next) 13 | #define THORIN_NODE(node, abbr) Node_##node, 14 | #define THORIN_PRIMTYPE(T) Node_PrimType_##T, 15 | #define THORIN_ARITHOP(op) Node_##op, 16 | #define THORIN_CMP(op) Node_##op, 17 | #define THORIN_MATHOP(op) Node_##op, 18 | #include "thorin/tables/allnodes.h" 19 | }; 20 | 21 | enum Markers { 22 | #define THORIN_GLUE(pre, next) \ 23 | End_##pre, \ 24 | Begin_##next = End_##pre, \ 25 | zzz##Begin_##next = Begin_##next - 1, 26 | #define THORIN_NODE(node, abbr) zzzMarker_##node, 27 | #define THORIN_PRIMTYPE(T) zzzMarker_PrimType_##T, 28 | #define THORIN_ARITHOP(op) zzzMarker_##op, 29 | #define THORIN_CMP(op) zzzMarker_##op, 30 | #define THORIN_MATHOP(op) zzzMarker_##op, 31 | #include "thorin/tables/allnodes.h" 32 | End_MathOp, 33 | Begin_Node = 0, 34 | End_AllNodes = End_Cmp, 35 | 36 | Begin_AllNodes = Begin_Node, 37 | 38 | Begin_PrimType = Begin_PrimType_bool, 39 | End_PrimType = End_PrimType_qf, 40 | 41 | Num_AllNodes = End_AllNodes - Begin_AllNodes, 42 | Num_Nodes = End_Node - Begin_Node, 43 | 44 | Num_ArithOps = End_ArithOp - Begin_ArithOp, 45 | Num_Cmps = End_Cmp - Begin_Cmp, 46 | Num_MathOps = End_MathOp - Begin_MathOp, 47 | 48 | Num_PrimTypes = End_PrimType_qf - Begin_PrimType_bool, 49 | }; 50 | 51 | enum PrimTypeTag { 52 | #define THORIN_ALL_TYPE(T, M) PrimType_##T = Node_PrimType_##T, 53 | #include "thorin/tables/primtypetable.h" 54 | }; 55 | 56 | enum ArithOpTag { 57 | #define THORIN_ARITHOP(op) ArithOp_##op = Node_##op, 58 | #include "thorin/tables/arithoptable.h" 59 | }; 60 | 61 | enum CmpTag { 62 | #define THORIN_CMP(op) Cmp_##op = Node_##op, 63 | #include "thorin/tables/cmptable.h" 64 | }; 65 | 66 | enum MathOpTag { 67 | #define THORIN_MATHOP(op) MathOp_##op = Node_##op, 68 | #include "thorin/tables/mathoptable.h" 69 | }; 70 | 71 | inline bool is_type_ps(int tag) { return (int) Begin_PrimType_ps <= tag && tag < (int) End_PrimType_ps; } 72 | inline bool is_type_pu(int tag) { return (int) Begin_PrimType_pu <= tag && tag < (int) End_PrimType_pu; } 73 | inline bool is_type_qs(int tag) { return (int) Begin_PrimType_qs <= tag && tag < (int) End_PrimType_qs; } 74 | inline bool is_type_qu(int tag) { return (int) Begin_PrimType_qu <= tag && tag < (int) End_PrimType_qu; } 75 | inline bool is_type_pf(int tag) { return (int) Begin_PrimType_pf <= tag && tag < (int) End_PrimType_pf; } 76 | inline bool is_type_qf(int tag) { return (int) Begin_PrimType_qf <= tag && tag < (int) End_PrimType_qf; } 77 | 78 | inline bool is_type_q(int tag) { return is_type_qs(tag) || is_type_qu(tag) || is_type_qf(tag); } 79 | inline bool is_type_p(int tag) { return is_type_ps(tag) || is_type_pu(tag) || is_type_pf(tag); } 80 | inline bool is_type_s(int tag) { return is_type_ps(tag) || is_type_qs(tag); } 81 | inline bool is_type_u(int tag) { return is_type_pu(tag) || is_type_qu(tag); } 82 | inline bool is_type_i(int tag) { return is_type_s (tag) || is_type_u (tag); } 83 | inline bool is_type_f(int tag) { return is_type_pf(tag) || is_type_qf(tag); } 84 | 85 | inline bool is_primtype(int tag){ return (int) Begin_PrimType <= tag && tag < (int) End_PrimType; } 86 | inline bool is_arithop(int tag) { return (int) Begin_ArithOp <= tag && tag < (int) End_ArithOp; } 87 | inline bool is_cmp(int tag) { return (int) Begin_Cmp <= tag && tag < (int) End_Cmp; } 88 | inline bool is_mathop(int tag) { return (int) Begin_MathOp <= tag && tag < (int) End_MathOp; } 89 | 90 | inline bool is_bitop(int tag) { return tag == ArithOp_and || tag == ArithOp_or || tag == ArithOp_xor; } 91 | inline bool is_shift(int tag) { return tag == ArithOp_shl || tag == ArithOp_shr; } 92 | inline bool is_div_or_rem(int tag) { return tag == ArithOp_div || tag == ArithOp_rem; } 93 | inline bool is_commutative(int tag) { return tag == ArithOp_add || tag == ArithOp_mul 94 | || tag == ArithOp_and || tag == ArithOp_or || tag == ArithOp_xor; } 95 | inline bool is_associative(int tag) { return tag == ArithOp_add || tag == ArithOp_mul 96 | || tag == ArithOp_and || tag == ArithOp_or || tag == ArithOp_xor; } 97 | 98 | template struct tag2type {}; 99 | #define THORIN_ALL_TYPE(T, M) template<> struct tag2type { typedef T type; }; 100 | #include "thorin/tables/primtypetable.h" 101 | 102 | template struct type2tag {}; 103 | #define THORIN_ALL_TYPE(T, M) template<> struct type2tag { static const PrimTypeTag tag = PrimType_##T; }; 104 | #include "thorin/tables/primtypetable.h" 105 | 106 | const char* tag2str(NodeTag tag); 107 | int num_bits(PrimTypeTag); 108 | 109 | CmpTag negate(CmpTag tag); 110 | 111 | } // namespace thorin 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /src/thorin/tables/allnodes.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_GLUE 2 | #error "define THORIN_GLUE before including this file" 3 | #endif 4 | 5 | #ifndef THORIN_PRIMTYPE 6 | #error "define THORIN_PRIMTYPE before including this file" 7 | #endif 8 | 9 | #include "thorin/tables/nodetable.h" 10 | THORIN_GLUE(Node, PrimType_bool) 11 | #define THORIN_BOOL_TYPE(T, M) THORIN_PRIMTYPE(T) 12 | #include "thorin/tables/primtypetable.h" 13 | THORIN_GLUE(PrimType_bool, PrimType_ps) 14 | #define THORIN_PS_TYPE(T, M) THORIN_PRIMTYPE(T) 15 | #include "thorin/tables/primtypetable.h" 16 | THORIN_GLUE(PrimType_ps, PrimType_pu) 17 | #define THORIN_PU_TYPE(T, M) THORIN_PRIMTYPE(T) 18 | #include "thorin/tables/primtypetable.h" 19 | THORIN_GLUE(PrimType_pu, PrimType_qs) 20 | #define THORIN_QS_TYPE(T, M) THORIN_PRIMTYPE(T) 21 | #include "thorin/tables/primtypetable.h" 22 | THORIN_GLUE(PrimType_qs, PrimType_qu) 23 | #define THORIN_QU_TYPE(T, M) THORIN_PRIMTYPE(T) 24 | #include "thorin/tables/primtypetable.h" 25 | THORIN_GLUE(PrimType_qu, PrimType_pf) 26 | #define THORIN_PF_TYPE(T, M) THORIN_PRIMTYPE(T) 27 | #include "thorin/tables/primtypetable.h" 28 | THORIN_GLUE(PrimType_pf, PrimType_qf) 29 | #define THORIN_QF_TYPE(T, M) THORIN_PRIMTYPE(T) 30 | #include "thorin/tables/primtypetable.h" 31 | THORIN_GLUE(PrimType_qf, ArithOp) 32 | #include "thorin/tables/arithoptable.h" 33 | THORIN_GLUE(ArithOp, Cmp) 34 | #include "thorin/tables/cmptable.h" 35 | THORIN_GLUE(Cmp, MathOp) 36 | #include "thorin/tables/mathoptable.h" 37 | 38 | #undef THORIN_GLUE 39 | #undef THORIN_PRIMTYPE 40 | -------------------------------------------------------------------------------- /src/thorin/tables/arithoptable.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_ARITHOP 2 | #error "define THORIN_ARITHOP before including this file" 3 | #endif 4 | 5 | THORIN_ARITHOP(add) 6 | THORIN_ARITHOP(sub) 7 | THORIN_ARITHOP(mul) 8 | THORIN_ARITHOP(div) 9 | THORIN_ARITHOP(rem) 10 | 11 | THORIN_ARITHOP(and) 12 | THORIN_ARITHOP(or) 13 | THORIN_ARITHOP(xor) 14 | THORIN_ARITHOP(shl) 15 | THORIN_ARITHOP(shr) 16 | 17 | #undef THORIN_ARITHOP 18 | -------------------------------------------------------------------------------- /src/thorin/tables/cmptable.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_CMP 2 | #error "define THORIN_CMP before including this file" 3 | #endif 4 | 5 | THORIN_CMP(eq) 6 | THORIN_CMP(ne) 7 | THORIN_CMP(gt) 8 | THORIN_CMP(ge) 9 | THORIN_CMP(lt) 10 | THORIN_CMP(le) 11 | 12 | #undef THORIN_CMP 13 | -------------------------------------------------------------------------------- /src/thorin/tables/mathoptable.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_MATHOP 2 | #error "define THORIN_MATHOP before including this file" 3 | #endif 4 | 5 | THORIN_MATHOP(fabs) 6 | THORIN_MATHOP(copysign) 7 | THORIN_MATHOP(round) 8 | THORIN_MATHOP(floor) 9 | THORIN_MATHOP(ceil) 10 | THORIN_MATHOP(fmin) 11 | THORIN_MATHOP(fmax) 12 | THORIN_MATHOP(cos) 13 | THORIN_MATHOP(sin) 14 | THORIN_MATHOP(tan) 15 | THORIN_MATHOP(acos) 16 | THORIN_MATHOP(asin) 17 | THORIN_MATHOP(atan) 18 | THORIN_MATHOP(atan2) 19 | THORIN_MATHOP(sqrt) 20 | THORIN_MATHOP(cbrt) 21 | THORIN_MATHOP(pow) 22 | THORIN_MATHOP(exp) 23 | THORIN_MATHOP(exp2) 24 | THORIN_MATHOP(log) 25 | THORIN_MATHOP(log2) 26 | THORIN_MATHOP(log10) 27 | 28 | #undef THORIN_MATHOP 29 | -------------------------------------------------------------------------------- /src/thorin/tables/nodetable.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_NODE 2 | #error "define THORIN_NODE before including this file" 3 | #endif 4 | 5 | // Def 6 | THORIN_NODE(Continuation, continuation) 7 | // PrimOp 8 | // Literal 9 | THORIN_NODE(Top, top) 10 | THORIN_NODE(Bottom, bottom) 11 | THORIN_NODE(MemBlob, mem_blob) 12 | THORIN_NODE(BlobPtr, mem_blob) 13 | // MemOp 14 | THORIN_NODE(Alloc, alloc) 15 | // Access 16 | THORIN_NODE(Load, load) 17 | THORIN_NODE(Store, store) 18 | THORIN_NODE(Enter, enter) 19 | THORIN_NODE(Leave, leave) 20 | THORIN_NODE(Select, select) 21 | THORIN_NODE(AlignOf, align_of) 22 | THORIN_NODE(SizeOf, size_of) 23 | THORIN_NODE(Global, global) 24 | THORIN_NODE(Slot, slot) 25 | //THORIN_NODE(ArithOp) 26 | //THORIN_NODE(Cmp) 27 | //THORIN_NODE(Conv, conv) 28 | THORIN_NODE(Cast, cast) 29 | THORIN_NODE(Bitcast, bitcast) 30 | THORIN_NODE(DefiniteArray, definite_array) 31 | THORIN_NODE(IndefiniteArray, indefinite_array) 32 | THORIN_NODE(Tuple, tuple) 33 | THORIN_NODE(Variant, variant) 34 | THORIN_NODE(VariantIndex, variant_index) 35 | THORIN_NODE(VariantExtract, variant_extract) 36 | THORIN_NODE(StructAgg, struct_agg) 37 | THORIN_NODE(Vector, vector) 38 | THORIN_NODE(Closure, closure) 39 | THORIN_NODE(Extract, extract) 40 | THORIN_NODE(Insert, insert) 41 | THORIN_NODE(LEA, lea) 42 | THORIN_NODE(Hlt, hlt) 43 | THORIN_NODE(Known, known) 44 | THORIN_NODE(Run, run) 45 | THORIN_NODE(Assembly, asm) 46 | THORIN_NODE(Param, param) 47 | THORIN_NODE(Filter, filter) 48 | // Type 49 | // PrimType 50 | THORIN_NODE(Star, star) 51 | THORIN_NODE(App, app) 52 | THORIN_NODE(DefiniteArrayType, definite_array_type) 53 | THORIN_NODE(FnType, fn) 54 | THORIN_NODE(ClosureType, closure_type) 55 | THORIN_NODE(FrameType, frame) 56 | THORIN_NODE(IndefiniteArrayType, indefinite_array_type) 57 | THORIN_NODE(Lambda, lambda) 58 | THORIN_NODE(MemType, mem) 59 | THORIN_NODE(BotType, bot_ty) 60 | THORIN_NODE(PtrType, ptr) 61 | THORIN_NODE(StructType, struct_type) 62 | THORIN_NODE(VariantType, variant_type) 63 | THORIN_NODE(TupleType, tuple_type) 64 | THORIN_NODE(Var, var) 65 | 66 | #undef THORIN_NODE 67 | -------------------------------------------------------------------------------- /src/thorin/tables/primtypetable.h: -------------------------------------------------------------------------------- 1 | #ifdef THORIN_ALL_TYPE 2 | #define THORIN_P_TYPE(T, M) THORIN_ALL_TYPE(T, M) 3 | #define THORIN_Q_TYPE(T, M) THORIN_ALL_TYPE(T, M) 4 | #define THORIN_BOOL_TYPE(T, M) THORIN_ALL_TYPE(T, M) 5 | #endif 6 | 7 | #ifdef THORIN_P_TYPE 8 | #define THORIN_PS_TYPE(T, M) THORIN_P_TYPE(T, M) 9 | #define THORIN_PU_TYPE(T, M) THORIN_P_TYPE(T, M) 10 | #define THORIN_PF_TYPE(T, M) THORIN_P_TYPE(T, M) 11 | #endif 12 | 13 | #ifdef THORIN_Q_TYPE 14 | #define THORIN_QS_TYPE(T, M) THORIN_Q_TYPE(T, M) 15 | #define THORIN_QU_TYPE(T, M) THORIN_Q_TYPE(T, M) 16 | #define THORIN_QF_TYPE(T, M) THORIN_Q_TYPE(T, M) 17 | #endif 18 | 19 | #ifdef THORIN_I_TYPE 20 | #define THORIN_PS_TYPE(T, M) THORIN_I_TYPE(T, M) 21 | #define THORIN_PU_TYPE(T, M) THORIN_I_TYPE(T, M) 22 | #define THORIN_QS_TYPE(T, M) THORIN_I_TYPE(T, M) 23 | #define THORIN_QU_TYPE(T, M) THORIN_I_TYPE(T, M) 24 | #endif 25 | 26 | #ifdef THORIN_F_TYPE 27 | #define THORIN_PF_TYPE(T, M) THORIN_F_TYPE(T, M) 28 | #define THORIN_QF_TYPE(T, M) THORIN_F_TYPE(T, M) 29 | #endif 30 | 31 | #ifndef THORIN_BOOL_TYPE 32 | #define THORIN_BOOL_TYPE(T, M) 33 | #endif 34 | 35 | THORIN_BOOL_TYPE(bool, bool) 36 | 37 | #ifndef THORIN_PS_TYPE 38 | #define THORIN_PS_TYPE(T, M) 39 | #endif 40 | 41 | THORIN_PS_TYPE(ps8, s8) 42 | THORIN_PS_TYPE(ps16, s16) 43 | THORIN_PS_TYPE(ps32, s32) 44 | THORIN_PS_TYPE(ps64, s64) 45 | 46 | #ifndef THORIN_PU_TYPE 47 | #define THORIN_PU_TYPE(T, M) 48 | #endif 49 | 50 | THORIN_PU_TYPE(pu8, u8) 51 | THORIN_PU_TYPE(pu16, u16) 52 | THORIN_PU_TYPE(pu32, u32) 53 | THORIN_PU_TYPE(pu64, u64) 54 | 55 | #ifndef THORIN_QS_TYPE 56 | #define THORIN_QS_TYPE(T, M) 57 | #endif 58 | 59 | THORIN_QS_TYPE(qs8, s8) 60 | THORIN_QS_TYPE(qs16, s16) 61 | THORIN_QS_TYPE(qs32, s32) 62 | THORIN_QS_TYPE(qs64, s64) 63 | 64 | #ifndef THORIN_QU_TYPE 65 | #define THORIN_QU_TYPE(T, M) 66 | #endif 67 | 68 | THORIN_QU_TYPE(qu8, u8) 69 | THORIN_QU_TYPE(qu16, u16) 70 | THORIN_QU_TYPE(qu32, u32) 71 | THORIN_QU_TYPE(qu64, u64) 72 | 73 | #ifndef THORIN_PF_TYPE 74 | #define THORIN_PF_TYPE(T, M) 75 | #endif 76 | 77 | THORIN_PF_TYPE(pf16, f16) 78 | THORIN_PF_TYPE(pf32, f32) 79 | THORIN_PF_TYPE(pf64, f64) 80 | 81 | #ifndef THORIN_QF_TYPE 82 | #define THORIN_QF_TYPE(T, M) 83 | #endif 84 | 85 | THORIN_QF_TYPE(qf16, f16) 86 | THORIN_QF_TYPE(qf32, f32) 87 | THORIN_QF_TYPE(qf64, f64) 88 | 89 | #undef THORIN_PS_TYPE 90 | #undef THORIN_PU_TYPE 91 | #undef THORIN_QS_TYPE 92 | #undef THORIN_QU_TYPE 93 | #undef THORIN_PF_TYPE 94 | #undef THORIN_QF_TYPE 95 | #undef THORIN_BOOL_TYPE 96 | #undef THORIN_P_TYPE 97 | #undef THORIN_Q_TYPE 98 | #undef THORIN_I_TYPE 99 | #undef THORIN_F_TYPE 100 | #undef THORIN_ALL_TYPE 101 | -------------------------------------------------------------------------------- /src/thorin/transform/closure_conversion.h: -------------------------------------------------------------------------------- 1 | #include "thorin/world.h" 2 | 3 | namespace thorin { 4 | 5 | void closure_conversion(World& world); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/thorin/transform/codegen_prepare.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/world.h" 2 | #include "thorin/analyses/scope.h" 3 | #include "thorin/transform/rewrite.h" 4 | 5 | namespace thorin { 6 | 7 | struct CodegenPrepare : public Rewriter { 8 | CodegenPrepare(World& src, World& dst) : Rewriter(src, dst) {} 9 | 10 | Continuation* make_wrapper(const Def* old_return_param) { 11 | assert(old_return_param); 12 | auto npi = instantiate(old_return_param->type())->as(); 13 | npi = dst().fn_type(npi->types()); 14 | auto wrapper = dst().continuation(npi, old_return_param->debug()); 15 | return wrapper; 16 | } 17 | 18 | const Def* rewrite(const Def* odef) override { 19 | if (auto app = odef->isa()) { 20 | auto new_ops = Array(app->num_args(), [&](size_t i) -> const Def* { 21 | auto oarg = app->arg(i); 22 | if (auto oparam = oarg->isa()) { 23 | if (oparam == oparam->continuation()->ret_param()) { 24 | auto wrapped = make_wrapper(oarg); 25 | insert(oarg, wrapped); 26 | auto imported_param = instantiate(oparam->continuation())->as_nom()->ret_param(); 27 | wrapped->jump(imported_param, wrapped->params_as_defs(), imported_param->debug()); 28 | return wrapped; 29 | } 30 | } 31 | return instantiate(app->arg(i)); 32 | }); 33 | return dst().app(instantiate(app->callee()), new_ops); 34 | } 35 | return Rewriter::rewrite(odef); 36 | } 37 | }; 38 | 39 | /// this pass makes sure the return param is only called directly, by eta-expanding any uses where it appears in another position 40 | void codegen_prepare(Thorin& thorin) { 41 | thorin.world().VLOG("start codegen_prepare"); 42 | auto& src = thorin.world(); 43 | auto destination = std::make_unique(src); 44 | CodegenPrepare pass(src, *destination.get()); 45 | 46 | for (auto& external : src.externals()) 47 | pass.instantiate(external.second); 48 | 49 | thorin.world_container().swap(destination); 50 | thorin.world().VLOG("end codegen_prepare"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/thorin/transform/codegen_prepare.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_CODEGEN_PREPARE_H 2 | #define THORIN_TRANSFORM_CODEGEN_PREPARE_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | void codegen_prepare(Thorin&); 9 | 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/thorin/transform/dead_load_opt.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/world.h" 3 | #include "thorin/analyses/cfg.h" 4 | #include "thorin/analyses/scope.h" 5 | 6 | namespace thorin { 7 | 8 | static void dead_load_opt(const Scope& scope) { 9 | auto& world = scope.world(); 10 | for (auto n : scope.f_cfg().post_order()) { 11 | auto continuation = n->continuation(); 12 | if (!continuation->has_body()) continue; 13 | 14 | const Def* mem = nullptr; 15 | for (auto arg : continuation->body()->args()) { 16 | if (is_mem(arg)) { 17 | mem = arg; 18 | break; 19 | } 20 | } 21 | 22 | if (mem) { 23 | while (true) { 24 | if (auto memop = mem->isa()) { 25 | if (memop->isa() || memop->isa()) { 26 | if (memop->out(1)->num_uses() == 0) 27 | memop->replace_uses(world.tuple({ memop->mem(), world.bottom(memop->out(1)->type()) })); 28 | } 29 | mem = memop->mem(); 30 | } else if (auto extract = mem->isa()) { 31 | mem = extract->agg(); 32 | } else 33 | break; 34 | } 35 | } 36 | } 37 | } 38 | 39 | void dead_load_opt(World& world) { 40 | ScopesForest forest(world); 41 | forest.for_each([&] (const Scope& scope) { dead_load_opt(scope); }); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/thorin/transform/dead_load_opt.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_DEAD_LOAD_OPT_H 2 | #define THORIN_TRANSFORM_DEAD_LOAD_OPT_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | void dead_load_opt(World&); 8 | 9 | } 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/thorin/transform/flatten_tuples.h: -------------------------------------------------------------------------------- 1 | #include "thorin/world.h" 2 | 3 | namespace thorin { 4 | 5 | void flatten_tuples(Thorin& thorin); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/thorin/transform/hls_channels.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_HLS_CHANNELS_H 2 | #define THORIN_TRANSFORM_HLS_CHANNELS_H 3 | 4 | #include "thorin/be/kernel_config.h" 5 | #include "thorin/transform/importer.h" 6 | 7 | namespace thorin { 8 | 9 | enum class ChannelMode : uint8_t { 10 | Read, ///< Read-channel 11 | Write ///< Write-channe 12 | }; 13 | 14 | using Top2Kernel = std::vector>; 15 | using DeviceParams = std::vector; 16 | class World; 17 | 18 | /** 19 | * implements channels and interface parameters for kernels 20 | * creates a new kernel named hls_top 21 | * generates channels in hls_top 22 | * calls all kernels within hls_top 23 | * resolves all dependency requirements between kernel calls 24 | * provides hls_top parameters for hls runtime 25 | */ 26 | DeviceParams hls_channels(Thorin&, Importer&, Top2Kernel&, World&); 27 | void hls_annotate_top(World&, const Top2Kernel&, Cont2Config&); 28 | 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/thorin/transform/hls_kernel_launch.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_HLS_KERNEL_LAUNCH_H 2 | #define THORIN_TRANSFORM_HLS_KERNEL_LAUNCH_H 3 | #include "thorin/transform/importer.h" 4 | #include "thorin/transform/hls_channels.h" 5 | 6 | namespace thorin { 7 | 8 | class World; 9 | 10 | const auto hls_free_vars_offset = 4; // fn (mem, dev, kernel_ptr, cont, / free_vars /) 11 | 12 | /** 13 | * removes all calls to individual kernels in host code 14 | * and replaces them with a single call to a kernel named hls_top 15 | */ 16 | void hls_kernel_launch(World&, DeviceParams&); 17 | 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/thorin/transform/hoist_enters.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/world.h" 3 | #include "thorin/analyses/cfg.h" 4 | #include "thorin/analyses/scope.h" 5 | #include "thorin/analyses/verify.h" 6 | 7 | namespace thorin { 8 | 9 | static void find_enters(std::deque& enters, const Def* def) { 10 | if (auto enter = def->isa()) 11 | enters.push_back(enter); 12 | 13 | if (auto memop = def->isa()) 14 | def = memop->out_mem(); 15 | 16 | for (auto use : def->uses()) { 17 | if (auto memop = use->isa()) 18 | find_enters(enters, memop); 19 | } 20 | } 21 | 22 | static void find_enters(std::deque& enters, Continuation* continuation) { 23 | if (auto mem_param = continuation->mem_param()) 24 | find_enters(enters, mem_param); 25 | } 26 | 27 | static bool hoist_enters(const Scope& scope) { 28 | World& world = scope.world(); 29 | std::deque enters; 30 | 31 | for (auto n : scope.f_cfg().reverse_post_order()) 32 | find_enters(enters, n->continuation()); 33 | 34 | if (enters.empty() || enters[0]->mem() != scope.entry()->mem_param()) { 35 | world.VLOG("cannot optimize {} - didn't find entry enter", scope.entry()); 36 | return false; 37 | } 38 | 39 | auto entry_enter = enters[0]; 40 | auto frame = entry_enter->out_frame(); 41 | enters.pop_front(); 42 | 43 | bool todo = false; 44 | for (auto i = enters.rbegin(), e = enters.rend(); i != e; ++i) { 45 | auto old_enter = *i; 46 | for (auto use : old_enter->out_frame()->uses()) { 47 | auto slot = use->as(); 48 | if (slot->uses().size() > 0) 49 | todo = true; 50 | slot->replace_uses(world.slot(slot->alloced_type(), frame, slot->debug())); 51 | assert(slot->num_uses() == 0); 52 | } 53 | } 54 | return todo; 55 | } 56 | 57 | // TODO: rewrite this and put it out of its misery 58 | void hoist_enters(Thorin& thorin) { 59 | bool todo = false; 60 | do { 61 | todo = false; 62 | ScopesForest forest(thorin.world()); 63 | for (auto cont : thorin.world().copy_continuations()) { 64 | if (!cont->has_body()) 65 | continue; 66 | auto& scope = forest.get_scope(cont); 67 | if(!scope.has_free_params()) { 68 | if (!todo) todo = hoist_enters(scope); 69 | if (todo) 70 | break; 71 | } 72 | } 73 | } while (todo); 74 | thorin.cleanup(); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/thorin/transform/hoist_enters.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_HOIST_ENTERS_H 2 | #define THORIN_TRANSFORM_HOIST_ENTERS_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | void hoist_enters(Thorin&); 9 | 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/thorin/transform/importer.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/transform/importer.h" 2 | #include "thorin/transform/mangle.h" 3 | #include "thorin/primop.h" 4 | 5 | namespace thorin { 6 | 7 | const Def* Importer::rewrite(const Def* const odef) { 8 | assert(&odef->world() == &src()); 9 | if (auto memop = odef->isa()) { 10 | // Optimise out dead loads when importing 11 | if (memop->isa() || memop->isa()) { 12 | if (memop->out(1)->num_uses() == 0) { 13 | auto imported_mem = import(memop->mem()); 14 | auto imported_ty = import(memop->out(1)->type())->as(); 15 | todo_ = true; 16 | return(dst().tuple({ imported_mem, dst().bottom(imported_ty) })); 17 | } 18 | } 19 | } else if (auto app = odef->isa()) { 20 | // eat calls to known continuations that are only used once 21 | if (auto callee = app->callee()->isa_nom()) { 22 | if (callee->has_body() && !src().is_external(callee) && callee->can_be_inlined()) { 23 | todo_ = true; 24 | src().VLOG("simplify: inlining continuation {} because it is called exactly once", callee); 25 | for (size_t i = 0; i < callee->num_params(); i++) 26 | insert(callee->param(i), import(app->arg(i))); 27 | 28 | return instantiate(callee->body()); 29 | } 30 | } 31 | } else if (auto closure = odef->isa()) { 32 | bool only_called = true; 33 | for (auto use : closure->uses()) { 34 | if (use.def()->isa() && use.index() == 0) 35 | continue; 36 | only_called = false; 37 | break; 38 | } 39 | if (only_called) { 40 | bool self_param_ok = true; 41 | for (auto use: closure->fn()->params().back()->uses()) { 42 | // the closure argument can be used, but only to extract the environment! 43 | if (auto extract = use.def()->isa(); extract && is_primlit(extract->index(), 1)) 44 | continue; 45 | self_param_ok = false; 46 | break; 47 | } 48 | if (self_param_ok) { 49 | src().VLOG("simplify: eliminating closure {} as it is never passed as an argument, and is not recursive", closure); 50 | Array args(closure->fn()->num_params()); 51 | args.back() = closure; 52 | todo_ = true; 53 | return instantiate(drop(closure->fn(), args)); 54 | } 55 | } 56 | } else if (auto cont = odef->isa_nom()) { 57 | if (cont->has_body()) { 58 | auto body = cont->body(); 59 | // try to subsume continuations which call a def 60 | // (that is free within that continuation) with that def 61 | auto callee = body->callee(); 62 | auto& scope = forest_->get_scope(cont); 63 | if (!scope.contains(callee)) { 64 | if (src().is_external(cont) || callee->type()->tag() != Node_FnType) 65 | goto rebuild; 66 | 67 | if (body->args() == cont->params_as_defs()) { 68 | src().VLOG("simplify: continuation {} calls a free def: {}", cont->unique_name(), body->callee()); 69 | // We completely replace the original continuation 70 | // If we don't do so, then we miss some simplifications 71 | return instantiate(body->callee()); 72 | } else { 73 | // build the permutation of the arguments 74 | Array perm(body->num_args()); 75 | bool is_permutation = true; 76 | for (size_t i = 0, e = body->num_args(); i != e; ++i) { 77 | auto param_it = std::find(cont->params().begin(), 78 | cont->params().end(), 79 | body->arg(i)); 80 | 81 | if (param_it == cont->params().end()) { 82 | is_permutation = false; 83 | break; 84 | } 85 | 86 | perm[i] = param_it - cont->params().begin(); 87 | } 88 | 89 | if (!is_permutation) 90 | goto rebuild; 91 | } 92 | 93 | bool has_calls = false; 94 | // for every use of the continuation at a call site, 95 | // permute the arguments and call the parameter instead 96 | for (auto use : cont->copy_uses()) { 97 | auto uapp = use->isa(); 98 | if (uapp && use.index() == 0) { 99 | todo_ = true; 100 | has_calls = true; 101 | break; 102 | } 103 | } 104 | 105 | if (has_calls) { 106 | auto rebuilt = cont->stub(*this, instantiate(cont->type())->as()); 107 | src().VLOG("simplify: continuation {} calls a free def: {} (with permuted args), introducing a wrapper: {}", cont->unique_name(), body->callee(), rebuilt); 108 | auto wrapped = dst().run(rebuilt); 109 | insert(odef, wrapped); 110 | 111 | rebuilt->set_body(instantiate(body)->as()); 112 | return wrapped; 113 | } 114 | } 115 | } 116 | } 117 | 118 | rebuild: 119 | auto ndef = Rewriter::rewrite(odef); 120 | if (odef->isa_structural()) { 121 | // If some substitution took place 122 | // TODO: this might be dead code at the moment 123 | todo_ |= odef->tag() != ndef->tag(); 124 | } 125 | return ndef; 126 | } 127 | 128 | const Def* Importer::find_origin(const Def* ndef) { 129 | for (auto def : src().defs()) { 130 | if (ndef == lookup(def)) 131 | return def; 132 | } 133 | return nullptr; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/thorin/transform/importer.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_IMPORT_H 2 | #define THORIN_TRANSFORM_IMPORT_H 3 | 4 | #include "thorin/world.h" 5 | #include "thorin/config.h" 6 | #include "thorin/transform/rewrite.h" 7 | #include "thorin/analyses/scope.h" 8 | 9 | namespace thorin { 10 | 11 | class Importer : Rewriter { 12 | public: 13 | explicit Importer(World& src, World& dst) 14 | : Rewriter(src, dst), forest_(std::make_unique(src)) 15 | { 16 | assert(&src != &dst); 17 | if (src.is_pe_done()) 18 | dst.mark_pe_done(); 19 | } 20 | 21 | const Def* import(const Def* odef) { return instantiate(odef); } 22 | const Def* find_origin(const Def* ndef); 23 | bool todo() const { return todo_; } 24 | 25 | protected: 26 | const Def* rewrite(const Def* odef) override; 27 | 28 | private: 29 | std::unique_ptr forest_; 30 | bool todo_ = false; 31 | }; 32 | 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/thorin/transform/inliner.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/continuation.h" 2 | #include "thorin/world.h" 3 | #include "thorin/analyses/cfg.h" 4 | #include "thorin/analyses/scope.h" 5 | #include "thorin/analyses/verify.h" 6 | #include "thorin/transform/mangle.h" 7 | 8 | namespace thorin { 9 | 10 | void force_inline(Scope& scope, int threshold) { 11 | for (bool todo = true; todo && threshold-- != 0;) { 12 | todo = false; 13 | for (auto n : scope.f_cfg().post_order()) { 14 | auto continuation = n->continuation(); 15 | if (!continuation->has_body()) continue; 16 | if (auto callee = continuation->body()->callee()->isa_nom()) { 17 | if (callee->has_body() && !scope.contains(callee)) { 18 | Scope callee_scope(callee); 19 | continuation->jump(drop(callee_scope, continuation->body()->args()), {}, continuation->debug()); // TODO debug 20 | todo = true; 21 | } 22 | } 23 | } 24 | 25 | if (todo) 26 | scope.update(); 27 | } 28 | 29 | for (auto n : scope.f_cfg().reverse_post_order()) { 30 | auto continuation = n->continuation(); 31 | if (!continuation->has_body()) continue; 32 | if (auto callee = continuation->body()->callee()->isa_nom()) { 33 | if (callee->has_body() && !scope.contains(callee)) 34 | scope.world().WLOG("couldn't inline {} at {} within scope of {}", callee, continuation->loc(), scope.entry()); 35 | } 36 | } 37 | } 38 | 39 | void inliner(Thorin& thorin) { 40 | World& world = thorin.world(); 41 | world.VLOG("start inliner"); 42 | 43 | static const int factor = 8; 44 | static const int offset = 8; 45 | 46 | ContinuationMap> continuation2scope; 47 | 48 | auto get_scope = [&] (Continuation* continuation) -> Scope* { 49 | auto i = continuation2scope.find(continuation); 50 | if (i == continuation2scope.end()) 51 | i = continuation2scope.emplace(continuation, std::make_unique(continuation)).first; 52 | return i->second.get(); 53 | }; 54 | 55 | auto is_candidate = [&] (Continuation* continuation) -> Scope* { 56 | if (continuation->has_body() && continuation->order() > 1 && !continuation->is_external()) { 57 | auto scope = get_scope(continuation); 58 | if (scope->defs().size() < scope->entry()->num_params() * factor + offset) { 59 | // check that the function is not recursive to prevent inliner from peeling loops 60 | for (auto& use : continuation->uses()) { 61 | // note that if there was an edge from parameter to continuation, 62 | // we would need to check if the use is a parameter here. 63 | if (!use->isa() && scope->contains(use.def())) 64 | return nullptr; 65 | } 66 | return scope; 67 | } 68 | } 69 | return nullptr; 70 | }; 71 | 72 | ScopesForest(world).for_each([&] (Scope& scope) { 73 | bool dirty = false; 74 | for (auto n : scope.f_cfg().post_order()) { 75 | auto continuation = n->continuation(); 76 | if (!continuation->has_body()) continue; 77 | if (auto callee = continuation->body()->callee()->isa_nom()) { 78 | if (callee == scope.entry()) 79 | continue; // don't inline recursive calls 80 | world.DLOG("callee: {}", callee); 81 | if (auto callee_scope = is_candidate(callee)) { 82 | world.DLOG("- here: {}", continuation); 83 | continuation->jump(drop(*callee_scope, continuation->body()->args()), {}, continuation->debug()); // TODO debug 84 | dirty = true; 85 | } 86 | } 87 | } 88 | 89 | if (dirty) { 90 | scope.update(); 91 | 92 | if (auto s = get_scope(scope.entry())) 93 | s->update(); 94 | } 95 | }); 96 | 97 | world.VLOG("stop inliner"); 98 | debug_verify(world); 99 | thorin.cleanup(); 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/thorin/transform/inliner.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_INLINER_H 2 | #define THORIN_TRANSFORM_INLINER_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | /** 9 | * Forces inlining of all callees within @p scope that are not defined in @p scope. 10 | * There are at most @p threshold many inlining runs performed. 11 | * If there still remain functions to be inlined, warnings will be emitted 12 | */ 13 | void force_inline(Scope& scope, int threshold); 14 | void inliner(Thorin&); 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/thorin/transform/lift_builtins.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/world.h" 2 | #include "thorin/analyses/domtree.h" 3 | #include "thorin/analyses/free_defs.h" 4 | #include "thorin/analyses/scope.h" 5 | #include "thorin/transform/inliner.h" 6 | #include "thorin/transform/mangle.h" 7 | 8 | namespace thorin { 9 | 10 | void lift_pipeline(World& world) { 11 | for (auto cont : world.copy_continuations()) { 12 | if (!cont->has_body()) continue; 13 | auto body = cont->body(); 14 | auto callee = body->callee()->isa_nom(); 15 | // Binding to the number of arguments to avoid repeated optimization 16 | if (callee && callee->intrinsic() == Intrinsic::Pipeline && body->num_args() == 6) { 17 | auto cont_type = world.fn_type({ world.mem_type() }); 18 | auto p_cont_type = world.fn_type({ world.mem_type(), cont_type }); 19 | auto body_type = world.fn_type({ world.mem_type(), world.type_qs32() }); 20 | auto pipe_type = world.fn_type({ 21 | world.mem_type(), 22 | world.type_qs32(), 23 | world.type_qs32(), 24 | world.type_qs32(), 25 | body_type, 26 | cont_type, 27 | p_cont_type 28 | }); 29 | // Transform: 30 | // 31 | // f(...) 32 | // pipeline(..., pipeline_body, return) 33 | // 34 | // pipeline_body(mem: mem, i: i32, ret: fn(mem)) 35 | // ret(mem) 36 | // 37 | // Into: 38 | // 39 | // f(...) 40 | // new_pipeline(..., pipeline_body, return, pipeline_continue) 41 | // 42 | // pipeline_body(mem: mem, i: i32) 43 | // continue_wrapper(mem) 44 | // 45 | // continue_wrapper(mem: mem) 46 | // pipeline_continue(mem, return) 47 | // 48 | // Note the use of 'return' as the second argument to pipeline_continue. 49 | // This is required to encode the dependence of the loop body over the call to pipeline, 50 | // so that lift_builtins can extract the correct free variables. 51 | auto pipeline_continue = world.continuation(p_cont_type, Intrinsic::PipelineContinue, Debug("pipeline_continue")); 52 | auto continue_wrapper = world.continuation(cont_type, Debug("continue_wrapper")); 53 | auto new_pipeline = world.continuation(pipe_type, Intrinsic::Pipeline, callee->debug()); 54 | auto old_body = body->arg(4); 55 | auto body_cont = world.continuation(body_type, old_body->debug()); 56 | cont->jump(new_pipeline, thorin::Defs { body->arg(0), body->arg(1), body->arg(2), body->arg(3), body_cont, body->arg(5), pipeline_continue }); 57 | auto target = drop(old_body, {body_cont->param(0), body_cont->param(1), continue_wrapper}); 58 | assert(target->has_body()); 59 | continue_wrapper->jump(pipeline_continue, thorin::Defs { continue_wrapper->param(0), body->arg(5) }); 60 | body_cont->jump(target->body()->callee(), target->body()->args()); 61 | } 62 | } 63 | 64 | } 65 | 66 | void lift_builtins(Thorin& thorin) { 67 | // This must be run first 68 | lift_pipeline(thorin.world()); 69 | 70 | while (true) { 71 | World& world = thorin.world(); 72 | Continuation* cur = nullptr; 73 | ScopesForest forest(world); 74 | forest.for_each([&] (const Scope& scope) { 75 | if (cur) return; 76 | for (auto n : scope.f_cfg().post_order()) { 77 | if (n->continuation()->order() <= 1) 78 | continue; 79 | if (is_passed_to_accelerator(n->continuation(), false)) { 80 | cur = n->continuation(); 81 | break; 82 | } 83 | } 84 | }); 85 | 86 | if (!cur) break; 87 | 88 | static const int inline_threshold = 4; 89 | if (is_passed_to_intrinsic(cur, Intrinsic::Vectorize)) { 90 | Scope scope(cur); 91 | force_inline(scope, inline_threshold); 92 | } 93 | 94 | Scope scope(cur); 95 | 96 | // remove all continuations - they should be top-level functions and can thus be ignored 97 | std::vector defs; 98 | for (auto param : free_defs(scope)) { 99 | if (param->isa_nom()) { 100 | // TODO: assert is actually top level 101 | } else if (!param->isa()) { // don't lift the filter 102 | assert(!param->isa() && "an app should not be free"); 103 | assert(param->order() == 0 && "creating a higher-order function"); 104 | defs.push_back(param); 105 | } 106 | } 107 | 108 | Continuation * lifted; 109 | if (scope.entry()->has_body()) 110 | lifted = lift(scope, scope.entry(), defs); 111 | else { 112 | assert(defs.size() == 0 && "Scopes without body cannot have free defs."); 113 | lifted = scope.entry(); 114 | } 115 | for (auto use : cur->copy_uses()) { 116 | if (auto uapp = use->isa()) { 117 | if (auto callee = uapp->callee()->isa_nom()) { 118 | if (callee->is_intrinsic()) { 119 | auto old_ops = uapp->ops(); 120 | Array new_ops(old_ops.size() + defs.size()); 121 | std::copy(defs.begin(), defs.end(), std::copy(old_ops.begin(), old_ops.end(), new_ops.begin())); // old ops + former free defs 122 | assert(old_ops[use.index()] == cur); 123 | new_ops[use.index()] = world.global(lifted, false, lifted->debug()); // update to new lifted continuation 124 | 125 | // jump to new top-level dummy function with new args 126 | auto fn_type = world.fn_type(Array(new_ops.size()-1, [&] (auto i) { return new_ops[i+1]->type(); })); 127 | auto ncontinuation = world.continuation(fn_type, callee->attributes(), callee->debug()); 128 | 129 | new_ops[0] = ncontinuation; 130 | uapp->replace_uses(uapp->rebuild(world, uapp->type(), new_ops)); 131 | } 132 | } 133 | } 134 | } 135 | 136 | thorin.cleanup(); 137 | } 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/thorin/transform/lift_builtins.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_LIFT_BUILTINS_H 2 | #define THORIN_TRANSFORM_LIFT_BUILTINS_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | void lift_builtins(Thorin&); 9 | 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/thorin/transform/mangle.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/transform/mangle.h" 2 | 3 | #include "thorin/primop.h" 4 | #include "thorin/type.h" 5 | #include "thorin/world.h" 6 | #include "thorin/analyses/scope.h" 7 | 8 | namespace thorin { 9 | 10 | /// Mangles a continuation's scope 11 | /// @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 12 | /// @p lift lists defs that should be replaced by a fresh param, to be appended at the end of the signature 13 | Mangler::Mangler(const Scope& scope, Continuation* entry, Defs args, Defs lift) 14 | : Rewriter(scope.world()) 15 | , scope_(scope) 16 | , args_(args) 17 | , lift_(lift) 18 | , old_entry_(entry) 19 | , defs_(scope.defs().capacity()) 20 | { 21 | assert(old_entry()->has_body()); 22 | assert(args.size() == old_entry()->num_params()); 23 | 24 | // TODO correctly deal with continuations here 25 | std::queue queue; 26 | auto enqueue = [&](const Def* def) { 27 | if (!within(def)) { 28 | defs_.insert(def); 29 | queue.push(def); 30 | } 31 | }; 32 | 33 | for (auto def : lift) 34 | enqueue(def); 35 | 36 | while (!queue.empty()) { 37 | for (auto use : pop(queue)->uses()) 38 | enqueue(use); 39 | } 40 | 41 | is_dropping_ = std::any_of(args.begin(), args.end(), [&](const auto& item) { 42 | return item != nullptr; 43 | }); 44 | } 45 | 46 | Continuation* Mangler::mangle() { 47 | // create new_entry - but first collect and specialize all param types 48 | std::vector param_types; 49 | for (size_t i = 0, e = old_entry()->num_params(); i != e; ++i) { 50 | if (args_[i] == nullptr) 51 | param_types.emplace_back(old_entry()->param(i)->type()); // TODO reduce 52 | } 53 | 54 | auto fn_type = dst().fn_type(param_types); 55 | new_entry_ = dst().continuation(fn_type, old_entry()->debug()); 56 | 57 | for (size_t i = 0, j = 0, e = old_entry()->num_params(); i != e; ++i) { 58 | auto old_param = old_entry()->param(i); 59 | if (auto def = args_[i]) 60 | insert(old_param, def); 61 | else { 62 | // we recreate params that aren't specialized 63 | auto new_param = new_entry()->param(j++); 64 | insert(old_param, new_param); 65 | new_param->set_name(old_param->name()); 66 | } 67 | } 68 | 69 | for (auto def : lift_) 70 | insert(def, new_entry()->append_param(def->type())); 71 | 72 | // if we are dropping parameters, we can't necessarily rewrite the entry, see also note about applications in Mangler::rewrite() 73 | if (is_dropping_) 74 | insert(old_entry(), old_entry()); 75 | else { 76 | // if we're only adding parameters, we can replace the entry by a small wrapper calling into the lifted entry 77 | auto recursion_wrapper = dst().continuation(old_entry()->type()); 78 | insert(old_entry(), recursion_wrapper); 79 | std::vector args; 80 | for (auto p : recursion_wrapper->params_as_defs()) 81 | args.push_back(p); 82 | size_t i = 0; 83 | for ([[maybe_unused]] auto def : lift_) 84 | args.push_back(new_entry()->param(recursion_wrapper->num_params() + i++)); 85 | recursion_wrapper->jump(new_entry(), args); 86 | } 87 | 88 | // cut/widen filter 89 | if (!old_entry()->filter()->is_empty()) { 90 | Array new_conditions(new_entry()->num_params()); 91 | size_t j = 0; 92 | for (size_t i = 0, e = old_entry()->num_params(); i != e; ++i) { 93 | if (args_[i] == nullptr) 94 | new_conditions[j++] = instantiate(old_entry()->filter()->condition(i)); 95 | } 96 | 97 | for (size_t e = new_entry()->num_params(); j != e; ++j) 98 | new_conditions[j] = dst().literal_bool(false, Debug{}); 99 | 100 | new_entry()->set_filter(dst().filter(new_conditions, old_entry()->filter()->debug())); 101 | } 102 | 103 | new_entry()->set_body(instantiate(old_entry()->body())->as()); 104 | new_entry()->verify(); 105 | 106 | return new_entry(); 107 | } 108 | 109 | const Def* Mangler::rewrite(const Def* old_def) { 110 | if (!within(old_def)) 111 | return old_def; // we leave free variables alone 112 | if (auto param = old_def->isa()) 113 | assert(within(param->continuation()) && "if the param is not free, the continuation should not be either!"); 114 | if (auto app = old_def->isa()) { 115 | // HACK: only rebuild the branch we actually take 116 | // this is a hack because the uses can be stale (dead stuff can have transitive uses) 117 | if (auto br = app->callee()->isa_nom(); br && br->intrinsic() == Intrinsic::Branch) { 118 | auto condition = instantiate(app->arg(1)); 119 | if (auto lit = condition->isa()) { 120 | auto mem = instantiate(app->arg(0)); 121 | auto target = lit->value().get_bool() ? instantiate(app->arg(2)) : instantiate(app->arg(3)); 122 | return dst().app(target, { mem }); 123 | } 124 | } 125 | if (auto sw = app->callee()->isa_nom(); sw && sw->intrinsic() == Intrinsic::Match) { 126 | auto index = instantiate(app->arg(1)); 127 | if (auto lit = index->isa()) { 128 | for (size_t i = 3; i < app->num_args(); i++) { 129 | auto opattern = src().extract(app->arg(i), 0_s)->as(); 130 | if (instantiate(opattern) == lit) { 131 | auto mem = instantiate(app->arg(0)); 132 | auto target = dst().extract(instantiate(app->arg(i)), 1); 133 | return dst().app(target, { mem }, old_def->debug()); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | auto ndef = Rewriter::rewrite(old_def); 140 | if (auto app = ndef->isa()) { 141 | // 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 142 | // 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 143 | if (is_dropping_ && app->callee() == old_entry()) { 144 | auto oargs = app->args(); 145 | auto nargs = Array(oargs.size(), [&](size_t i) { return rewrite(oargs[i]); }); 146 | std::vector cut; 147 | bool substitute = true; 148 | for (size_t i = 0, e = args_.size(); i != e && substitute; ++i) { 149 | if (auto def = args_[i]) { 150 | substitute &= def == nargs[i]; 151 | cut.push_back(i); 152 | } 153 | } 154 | 155 | if (substitute) { 156 | const auto& args = concat(nargs.cut(cut), new_entry()->params().get_back(lift_.size())); 157 | return dst().app(new_entry(), args, old_def->debug()); // TODO debug 158 | } 159 | } 160 | } 161 | return ndef; 162 | } 163 | 164 | //------------------------------------------------------------------------------ 165 | 166 | Continuation* mangle(const Scope& scope, Continuation* entry, Defs args, Defs lift) { 167 | return Mangler(scope, entry, args, lift).mangle(); 168 | } 169 | 170 | Continuation* drop(const Def* callee, const Defs specialized_args) { 171 | Scope scope(callee->as_nom()); 172 | return drop(scope, specialized_args); 173 | } 174 | 175 | //------------------------------------------------------------------------------ 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/thorin/transform/mangle.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_MANGLE_H 2 | #define THORIN_TRANSFORM_MANGLE_H 3 | 4 | #include "thorin/type.h" 5 | #include "thorin/analyses/scope.h" 6 | #include "thorin/transform/rewrite.h" 7 | 8 | namespace thorin { 9 | 10 | class Mangler : Rewriter { 11 | public: 12 | Mangler(const Scope& scope, Continuation* entry, Defs args, Defs lift); 13 | 14 | const Scope& scope() const { return scope_; } 15 | Continuation* mangle(); 16 | Continuation* old_entry() const { return old_entry_; } 17 | Continuation* new_entry() const { return new_entry_; } 18 | 19 | private: 20 | const Def* rewrite(const Def* odef) override; 21 | bool within(const Def* def) { return scope().contains(def) || defs_.contains(def); } 22 | 23 | bool is_dropping_; 24 | 25 | const Scope& scope_; 26 | Defs args_; 27 | Defs lift_; 28 | Continuation* old_entry_; 29 | Continuation* new_entry_; 30 | DefSet defs_; 31 | }; 32 | 33 | 34 | Continuation* mangle(const Scope&, Continuation* entry, Defs args, Defs lift); 35 | 36 | inline Continuation* drop(const Scope& scope, Defs args) { 37 | return mangle(scope, scope.entry(), args, Array()); 38 | } 39 | 40 | Continuation* drop(const Def* callee, const Defs specialized_args); 41 | 42 | inline Continuation* lift(const Scope& scope, Continuation* entry, Defs defs) { 43 | return mangle(scope, entry, Array(entry->num_params()), defs); 44 | } 45 | 46 | inline Continuation* clone(const Scope& scope) { 47 | return mangle(scope, scope.entry(), Array(scope.entry()->num_params()), Defs()); 48 | } 49 | 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/thorin/transform/partial_evaluation.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/world.h" 3 | #include "thorin/transform/mangle.h" 4 | #include "thorin/util/hash.h" 5 | #include "partial_evaluation.h" 6 | 7 | namespace thorin { 8 | 9 | struct HashApp { 10 | inline static uint64_t hash(const App* app) { 11 | return murmur3(uint64_t(app)); 12 | } 13 | inline static bool eq(const App* a1, const App* a2) { return a1 == a2; } 14 | inline static const App* sentinel() { return static_cast((void*)size_t(-1)); } 15 | }; 16 | 17 | class PartialEvaluator { 18 | public: 19 | PartialEvaluator(World& world, bool lower2cff) 20 | : world_(world) 21 | , lower2cff_(lower2cff) 22 | , boundary_(Def::gid_counter()) 23 | {} 24 | 25 | World& world() { return world_; } 26 | bool run(); 27 | void enqueue(Continuation* continuation) { 28 | if (continuation->gid() < 2 * boundary_ && done_.emplace(continuation).second) 29 | queue_.push(continuation); 30 | } 31 | void eat_pe_info(Continuation*); 32 | 33 | private: 34 | World& world_; 35 | bool lower2cff_; 36 | HashMap cache_; 37 | ContinuationSet done_; 38 | unique_queue queue_; 39 | size_t boundary_; 40 | }; 41 | 42 | const Def* BetaReducer::rewrite(const Def* odef) { 43 | // leave nominal defs alone 44 | if (odef->isa_nom()) 45 | return odef; 46 | return Rewriter::rewrite(odef); 47 | } 48 | 49 | class CondEval { 50 | public: 51 | CondEval(Continuation* callee, ScopesForest& forest, Defs args) 52 | : reducer_(callee->world()) 53 | , callee_(callee) 54 | , forest_(forest) 55 | { 56 | assert(callee->filter()->is_empty() || callee->filter()->size() == args.size()); 57 | assert(callee->num_params() == args.size()); 58 | 59 | for (size_t i = 0, e = args.size(); i != e; ++i) 60 | reducer_.provide_arg(callee->param(i), args[i]); 61 | } 62 | 63 | World& world() { return callee_->world(); } 64 | 65 | bool eval(size_t i, bool lower2cff) { 66 | // the only higher order parameter that is allowed is a single 1st-order fn-parameter of a top-level continuation 67 | // all other parameters need specialization (lower2cff) 68 | auto order = callee_->param(i)->order(); 69 | if (lower2cff) 70 | if(order >= 2 || (order == 1 71 | && (!callee_->param(i)->type()->isa() 72 | || (!callee_->is_returning() || (!is_top_level(callee_)))))) { 73 | world().DLOG("bad param({}) {} of continuation {}", i, callee_->param(i), callee_); 74 | return true; 75 | } 76 | 77 | return ((!callee_->is_exported() || callee_->attributes().cc == CC::Thorin) && callee_->can_be_inlined()) || is_one(reducer_.instantiate(filter(i))); 78 | //return is_one(instantiate(filter(i))); 79 | } 80 | 81 | const Def* filter(size_t i) { 82 | return callee_->filter()->is_empty() ? world().literal_bool(false, {}) : callee_->filter()->condition(i); 83 | } 84 | 85 | bool is_top_level(Continuation* continuation) { 86 | return !forest_.get_scope(continuation).has_free_params(); 87 | } 88 | 89 | private: 90 | BetaReducer reducer_; 91 | Continuation* callee_; 92 | ScopesForest& forest_; 93 | }; 94 | 95 | void PartialEvaluator::eat_pe_info(Continuation* cur) { 96 | assert(cur->has_body()); 97 | auto body = cur->body(); 98 | assert(body->arg(1)->type() == world().ptr_type(world().indefinite_array_type(world().type_pu8()))); 99 | auto next = body->arg(3); 100 | 101 | if (!body->arg(2)->has_dep(Dep::Param)) { 102 | auto msg = body->arg(1)->as()->from()->as()->init()->as(); 103 | world().idef(body->callee(), "pe_info: {}: {}", msg->as_string(), body->arg(2)); 104 | cur->jump(next, {body->arg(0)}, cur->debug()); // TODO debug 105 | 106 | // always re-insert into queue because we've changed cur's jump 107 | queue_.push(cur); 108 | } else if (auto continuation = next->isa_nom()) { 109 | queue_.push(continuation); 110 | } 111 | } 112 | 113 | bool PartialEvaluator::run() { 114 | bool todo = false; 115 | 116 | for (auto&& [_, def] : world().externals()) { 117 | auto cont = def->isa(); 118 | if (!cont) continue; 119 | if (!cont->has_body()) continue; 120 | enqueue(cont); 121 | } 122 | 123 | while (!queue_.empty()) { 124 | auto continuation = queue_.pop(); 125 | 126 | bool force_fold = false; 127 | 128 | if (!continuation->has_body()) 129 | continue; 130 | const App* body = continuation->body(); 131 | const Def* callee_def = continuation->body()->callee(); 132 | 133 | if (auto run = callee_def->isa()) { 134 | force_fold = true; 135 | callee_def = run->def(); 136 | } 137 | 138 | if (auto callee = callee_def->isa_nom()) { 139 | if (callee->intrinsic() == Intrinsic::PeInfo) { 140 | eat_pe_info(continuation); 141 | continue; 142 | } 143 | 144 | if (callee->has_body()) { 145 | // TODO cache the forest and only rebuild it when we need to 146 | ScopesForest forest(world()); 147 | CondEval cond_eval(callee, forest, body->args()); 148 | 149 | std::vector specialize(body->num_args()); 150 | 151 | bool fold = false; 152 | for (size_t i = 0, e = body->num_args(); i != e; ++i) { 153 | if (force_fold || cond_eval.eval(i, lower2cff_)) { 154 | specialize[i] = body->arg(i); 155 | fold = true; 156 | } else 157 | specialize[i] = nullptr; 158 | } 159 | 160 | if (fold) { 161 | const auto& p = cache_.emplace(body, nullptr); 162 | Continuation*& target = p.first->second; 163 | // create new specialization if not found in cache 164 | if (p.second) { 165 | world_.ddef(continuation, "Specializing call to {}", callee); 166 | target = drop(callee, specialize); 167 | todo = true; 168 | } 169 | 170 | jump_to_dropped_call(continuation, target, specialize); 171 | 172 | while (callee && callee->never_called()) { 173 | if (callee->has_body()) { 174 | auto new_callee = const_cast(callee->body()->callee()->isa()); 175 | callee->destroy("partial_evaluation"); 176 | callee = new_callee; 177 | } else { 178 | callee = nullptr; 179 | } 180 | } 181 | 182 | if (lower2cff_ && fold) { 183 | // re-examine next iteration: 184 | // maybe the specialization is not top-level anymore which might need further specialization 185 | queue_.push(continuation); 186 | continue; 187 | } 188 | } 189 | } 190 | } 191 | 192 | for (auto succ : continuation->succs()) 193 | enqueue(succ); 194 | } 195 | 196 | return todo; 197 | } 198 | 199 | //------------------------------------------------------------------------------ 200 | 201 | bool partial_evaluation(World& world, bool lower2cff) { 202 | auto name = lower2cff ? "lower2cff" : "partial_evaluation"; 203 | world.VLOG("start {}", name); 204 | auto res = PartialEvaluator(world, lower2cff).run(); 205 | world.VLOG("end {}", name); 206 | return res; 207 | } 208 | 209 | //------------------------------------------------------------------------------ 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/thorin/transform/partial_evaluation.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_PARTIAL_EVALUATION_H 2 | #define THORIN_TRANSFORM_PARTIAL_EVALUATION_H 3 | 4 | #include "thorin/transform/rewrite.h" 5 | 6 | namespace thorin { 7 | 8 | class World; 9 | 10 | class BetaReducer : public Rewriter { 11 | public: 12 | BetaReducer(World& w) : Rewriter(w) {} 13 | 14 | void provide_arg(const Param* param, const Def* arg) { 15 | insert(param, arg); 16 | } 17 | 18 | protected: 19 | const Def* rewrite(const Def* odef) override; 20 | }; 21 | 22 | bool partial_evaluation(World&, bool lower2cff = false); 23 | 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/thorin/transform/resolve_loads.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_RESOLVE_LOADS_H 2 | #define THORIN_TRANSFORM_RESOLVE_LOADS_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | bool resolve_loads(World&); 9 | 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/thorin/transform/rewrite.cpp: -------------------------------------------------------------------------------- 1 | #include "rewrite.h" 2 | 3 | namespace thorin { 4 | 5 | Rewriter::Rewriter(World& src, World& dst) : src_(src), dst_(dst) { 6 | // TODO: rehash is slow-ish, especially in Debug mode 7 | // Many short-lived Rewriters are created that only rebuild a tiny portion of the 8 | // world, such as in CondEval. For these, we end up paying a significant amount 9 | // of time just running this, leading to bad performance. Be smarter or don't do this. 10 | //old2new_.rehash(src.defs().capacity()); 11 | } 12 | 13 | Rewriter::Rewriter(World& src, World& dst, Rewriter& parent) : Rewriter(src, dst) { 14 | old2new_ = parent.old2new_; 15 | } 16 | 17 | const Def* Rewriter::lookup(const thorin::Def* odef) { 18 | if (auto ndef = old2new_.lookup(odef)) return *ndef; 19 | 20 | // TODO maybe we want to deal with intrinsics in a more streamlined way 21 | if (odef == src().branch()) 22 | return dst().branch(); 23 | if (odef == src().end_scope()) 24 | return dst().end_scope(); 25 | return nullptr; 26 | } 27 | 28 | const Def* Rewriter::instantiate(const Def* odef) { 29 | auto found = lookup(odef); 30 | if (found) return found; 31 | 32 | return old2new_[odef] = rewrite(odef); 33 | } 34 | 35 | const Def* Rewriter::insert(const Def* odef, const Def* ndef) { 36 | assert(&odef->world() == &src()); 37 | assert(&ndef->world() == &dst()); 38 | return old2new_[odef] = ndef; 39 | } 40 | 41 | const Def* Rewriter::rewrite(const Def* odef) { 42 | if (odef == odef->world().star()) 43 | return insert(odef, dst().star()); 44 | 45 | auto ntype = instantiate(odef->type())->as(); 46 | 47 | Def* stub = nullptr; 48 | if (odef->isa_nom()) { 49 | stub = odef->stub(*this, ntype); 50 | insert(odef, stub); 51 | } 52 | 53 | if (odef->isa_structural()) { 54 | size_t size = odef->num_ops(); 55 | Array nops(size); 56 | for (size_t i = 0; i != size; ++i) { 57 | assert(odef->op(i) != odef); 58 | nops[i] = instantiate(odef->op(i)); 59 | assert(&nops[i]->world() == &dst()); 60 | } 61 | auto ndef = odef->rebuild(dst(), ntype, nops); 62 | return ndef; 63 | } else { 64 | assert(odef->isa_nom() && stub); 65 | stub->rebuild_from(*this, odef); 66 | return stub; 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/thorin/transform/rewrite.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_REWRITE_H 2 | #define THORIN_REWRITE_H 3 | 4 | #include "thorin/world.h" 5 | 6 | namespace thorin { 7 | 8 | class Rewriter { 9 | public: 10 | explicit Rewriter(World& src, World& dst); 11 | explicit Rewriter(World& world) : Rewriter(world, world) {} 12 | 13 | const Def* instantiate(const Def* odef); 14 | const Def* insert(const Def* odef, const Def* ndef); 15 | 16 | World& src() { return src_; } 17 | World& dst() { return dst_; } 18 | 19 | protected: 20 | explicit Rewriter(World& src, World& dst, Rewriter& parent); 21 | virtual const Def* lookup(const Def* odef); 22 | virtual const Def* rewrite(const Def* odef); 23 | 24 | private: 25 | Def2Def old2new_; 26 | World& src_; 27 | World& dst_; 28 | }; 29 | 30 | } 31 | 32 | #endif -------------------------------------------------------------------------------- /src/thorin/transform/split_slots.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/primop.h" 2 | #include "thorin/world.h" 3 | #include "thorin/analyses/scope.h" 4 | #include "thorin/analyses/schedule.h" 5 | #include "thorin/analyses/verify.h" 6 | #include "thorin/transform/split_slots.h" 7 | 8 | namespace thorin { 9 | 10 | struct IndexHash { 11 | static hash_t hash(u32 u) { return u; } // TODO bad hash function 12 | static bool eq(u32 a, u32 b) { return a == b; } 13 | static u32 sentinel() { return 0xFFFFFFFF; } 14 | }; 15 | 16 | #if 0 17 | static void split(const Slot* slot) { 18 | auto array_type = slot->alloced_type()->as(); 19 | auto dim = array_type->dim(); 20 | auto elem_type = array_type->elem_type(); 21 | 22 | HashMap new_slots; 23 | auto& world = slot->world(); 24 | 25 | auto elem_slot = [&] (u32 index) { 26 | if (!new_slots.contains(index)) 27 | new_slots[index] = world.slot(elem_type, slot->frame(), slot->debug()); 28 | return new_slots[index]; 29 | }; 30 | 31 | for (auto use : slot->copy_uses()) { 32 | if (auto lea = use->isa()) { 33 | lea->replace_uses(elem_slot(lea->index()->as()->value().get_u32())); 34 | } else if (auto store = use->isa()) { 35 | auto in_mem = store->op(0); 36 | for (size_t i = 0, e = dim; i != e; ++i) { 37 | auto elem = world.extract(store->op(2), i, store->debug()); 38 | in_mem = world.store(in_mem, elem_slot(i), elem, store->debug()); 39 | } 40 | store->replace_uses(in_mem); 41 | } else if (auto load = use->isa()) { 42 | auto in_mem = load->op(0); 43 | auto array = world.bottom(array_type, load->debug()); 44 | for (size_t i = 0, e = dim; i != e; ++i) { 45 | auto tuple = world.load(in_mem, elem_slot(i), load->debug()); 46 | auto elem = world.extract(tuple, 1_u32, load->debug()); 47 | in_mem = world.extract(tuple, 0_u32, load->debug()); 48 | array = world.insert(array, i, elem, load->debug()); 49 | } 50 | load->replace_uses(world.tuple({ in_mem, array }, load->debug())); 51 | } 52 | } 53 | } 54 | 55 | static bool can_split(const Slot* slot) { 56 | if (!slot->alloced_type()->isa()) 57 | return false; 58 | 59 | // only accept LEAs with constant indices and loads and stores 60 | for (auto use : slot->uses()) { 61 | if (auto lea = use->isa()) { 62 | if (!lea->index()->no_dep()) 63 | return false; 64 | } else if (!use->isa() && !use->isa()) { 65 | return false; 66 | } 67 | } 68 | 69 | return true; 70 | } 71 | #endif 72 | 73 | static bool split_slots(const Scope& /* scope */) { 74 | bool todo = false; 75 | // TODO 76 | #if 0 77 | for (const auto& block : schedule(scope, Schedule::Late)) { 78 | for (auto primop : block) { 79 | if (auto slot = primop->isa()) { 80 | if (can_split(slot)) { 81 | split(slot); 82 | todo = true; 83 | } 84 | } 85 | } 86 | } 87 | #endif 88 | return todo; 89 | } 90 | 91 | void split_slots(Thorin& thorin) { 92 | bool todo = true; 93 | while (todo) { 94 | todo = false; 95 | ScopesForest(thorin.world()).for_each([&] (const Scope& scope) { todo |= split_slots(scope); }); 96 | thorin.cleanup(); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/thorin/transform/split_slots.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_TRANSFORM_SPLIT_SLOTS_H 2 | #define THORIN_TRANSFORM_SPLIT_SLOTS_H 3 | 4 | namespace thorin { 5 | 6 | class World; 7 | 8 | /** 9 | * Tries to split @p Slot%s that are accessed through constant @p LEA%s. 10 | */ 11 | void split_slots(Thorin&); 12 | 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/thorin/type.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/type.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "thorin/transform/rewrite.h" 9 | #include "thorin/continuation.h" 10 | #include "thorin/primop.h" 11 | #include "thorin/world.h" 12 | 13 | namespace thorin { 14 | 15 | Type::Type(World& w, NodeTag tag, Defs args, Debug dbg) : Type(w, tag, w.star(), args, dbg) { 16 | // The overridden version of set_op is ignored in the Def ctor, because according to the C++ spec, virtuals are disabled in ctors (!) 17 | // So this is not actually duplicate code - for the nominal types Type::set_op will do what we want. 18 | for (auto& def : args) 19 | order_ = std::max(order_, def->order()); 20 | } 21 | Type::Type(World& w, NodeTag tag, size_t size, Debug dbg) : Type(w, tag, w.star(), size, dbg) {} 22 | 23 | void Type::set_op(size_t i, const Def* def) { 24 | Def::set_op(i, def); 25 | order_ = std::max(order_, def->order()); 26 | } 27 | 28 | Array types2defs(ArrayRef types) { 29 | Array defs(types.size()); 30 | size_t i = 0; 31 | for (auto type : types) 32 | defs[i++] = type->as(); 33 | return defs; 34 | } 35 | 36 | Array defs2types(ArrayRef defs) { 37 | Array types(defs.size()); 38 | size_t i = 0; 39 | for (auto type : defs) 40 | types[i++] = type->as(); 41 | return types; 42 | } 43 | 44 | //------------------------------------------------------------------------------ 45 | 46 | /* 47 | * rebuild 48 | */ 49 | 50 | const Type* NominalType::rebuild(World& , const Type* , Defs ) const { 51 | THORIN_UNREACHABLE; 52 | } 53 | 54 | const Type* BottomType ::rebuild(World& w, const Type* , Defs ) const { return w.bottom_type(); } 55 | const Type* ClosureType ::rebuild(World& w, const Type* , Defs o) const { return w.closure_type(defs2types(o)); } 56 | const Type* DefiniteArrayType ::rebuild(World& w, const Type* , Defs o) const { return w.definite_array_type(o[0]->as(), dim()); } 57 | const Type* FnType ::rebuild(World& w, const Type* , Defs o) const { return w.fn_type(defs2types(o)); } 58 | const Type* FrameType ::rebuild(World& w, const Type* , Defs ) const { return w.frame_type(); } 59 | const Type* IndefiniteArrayType::rebuild(World& w, const Type* , Defs o) const { return w.indefinite_array_type(o[0]->as()); } 60 | const Type* MemType ::rebuild(World& w, const Type* , Defs ) const { return w.mem_type(); } 61 | const Type* PrimType ::rebuild(World& w, const Type* , Defs ) const { return w.prim_type(primtype_tag(), length()); } 62 | const Type* PtrType ::rebuild(World& w, const Type* , Defs o) const { return w.ptr_type(o[0]->as(), length(), addr_space()); } 63 | const Type* TupleType ::rebuild(World& w, const Type* , Defs o) const { return w.tuple_type(defs2types(o)); } 64 | 65 | /* 66 | * stub 67 | */ 68 | 69 | StructType* StructType::stub(Rewriter& rewriter, const Type*) const { 70 | auto type = rewriter.dst().struct_type(name(), num_ops()); 71 | std::copy(op_names_.begin(), op_names_.end(), type->op_names().begin()); 72 | return type; 73 | } 74 | 75 | VariantType* VariantType::stub(Rewriter& rewriter, const Type*) const { 76 | auto type = rewriter.dst().variant_type(name(), num_ops()); 77 | std::copy(op_names_.begin(), op_names_.end(), type->op_names().begin()); 78 | return type; 79 | } 80 | 81 | //------------------------------------------------------------------------------ 82 | 83 | const VectorType* VectorType::scalarize() const { 84 | if (auto ptr = isa()) 85 | return world().ptr_type(ptr->pointee()); 86 | return world().prim_type(as()->primtype_tag()); 87 | } 88 | 89 | bool FnType::is_returning() const { 90 | bool ret = false; 91 | for (auto op : ops()) { 92 | switch (op->order()) { 93 | case 1: 94 | if (!ret) { 95 | ret = true; 96 | continue; 97 | } 98 | return false; 99 | default: continue; 100 | } 101 | } 102 | return ret; 103 | } 104 | 105 | bool VariantType::has_payload() const { 106 | return !std::all_of(types().begin(), types().end(), is_type_unit); 107 | } 108 | 109 | bool use_lea(const Type* type) { return type->isa() || type->isa(); } 110 | 111 | //------------------------------------------------------------------------------ 112 | 113 | /* 114 | * hash 115 | */ 116 | 117 | hash_t PtrType::vhash() const { 118 | return hash_combine(VectorType::vhash(), (hash_t)addr_space()); 119 | } 120 | 121 | //------------------------------------------------------------------------------ 122 | 123 | /* 124 | * equal 125 | */ 126 | 127 | bool PtrType::equal(const Def* other) const { 128 | if (!VectorType::equal(other)) 129 | return false; 130 | auto ptr = other->as(); 131 | return ptr->addr_space() == addr_space(); 132 | } 133 | 134 | TypeTable::TypeTable(World& world) 135 | : world_(world) 136 | , star_ (world.put((world))) 137 | , unit_ (world.put(world, Defs(), Debug())) 138 | , fn0_ (world.put(world, Defs(), Node_FnType, Debug())) 139 | , bottom_ty_(world.put(world, Debug())) 140 | , mem_ (world.put(world, Debug())) 141 | , frame_ (world.put(world, Debug())) 142 | { 143 | #define THORIN_ALL_TYPE(T, M) \ 144 | primtypes_[PrimType_##T - Begin_PrimType] = world.make(world, PrimType_##T, 1, Debug()); 145 | #include "thorin/tables/primtypetable.h" 146 | } 147 | 148 | const Type* World::tuple_type(Types ops) { 149 | return ops.size() == 1 ? ops.front()->as() : make(*this, types2defs(ops), Debug()); 150 | } 151 | 152 | StructType* World::struct_type(Symbol name, size_t size) { 153 | return put(*this, name, size, Debug()); 154 | } 155 | 156 | VariantType* World::variant_type(Symbol name, size_t size) { 157 | return put(*this, name, size, Debug()); 158 | } 159 | 160 | const PrimType* World::prim_type(PrimTypeTag tag, size_t length) { 161 | size_t i = tag - Begin_PrimType; 162 | assert(i < (size_t) Num_PrimTypes); 163 | return length == 1 ? types_.primtypes_[i] : make(*this, tag, length, Debug()); 164 | } 165 | 166 | const PtrType* World::ptr_type(const Type* pointee, size_t length, AddrSpace addr_space) { 167 | return make(*this, pointee, length, addr_space, Debug()); 168 | } 169 | 170 | const FnType* World::fn_type(Types args) { return make(*this, types2defs(args), Node_FnType, Debug()); } 171 | const ClosureType* World::closure_type(Types args) { return make(*this, types2defs(args), Debug()); } 172 | const DefiniteArrayType* World::definite_array_type(const Type* elem, u64 dim) { return make(*this, elem, dim, Debug()); } 173 | const IndefiniteArrayType* World::indefinite_array_type(const Type* elem) { return make(*this, elem, Debug()); } 174 | 175 | //------------------------------------------------------------------------------ 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/thorin/util/cast.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTIL_CAST_H 2 | #define THORIN_UTIL_CAST_H 3 | 4 | #include 5 | 6 | #include "thorin/util/utility.h" 7 | 8 | namespace thorin { 9 | 10 | /* 11 | * Watch out for the order of the template parameters in all of these casts. 12 | * All the casts use the order . 13 | * This order may seem irritating at first glance, as you read . 14 | * This is solely for the reason, that C++ type deduction does not work the other way round. 15 | * However, for these tags of cast you won't have to specify the template parameters explicitly anyway. 16 | * Thus, you do not have to care except for the bitcast -- so be warned. 17 | * Just read as instead, i.e., 18 | * L is the thing which is returned (the left-hand side of the function call), 19 | * R is the thing which goes in (the right-hand side of a call). 20 | */ 21 | 22 | /// A bitcast. 23 | template 24 | inline D bitcast(const S& src) { 25 | D dst; 26 | auto s = reinterpret_cast(&src); 27 | auto d = reinterpret_cast(&dst); 28 | 29 | if constexpr(sizeof(D) == sizeof(S)) memcpy(d, s, sizeof(D)); 30 | if constexpr(sizeof(D) < sizeof(S)) memcpy(d, s, sizeof(D)); 31 | if constexpr(sizeof(D) > sizeof(S)) { 32 | memset(d, 0, sizeof(D)); 33 | memcpy(d, s, sizeof(S)); 34 | } 35 | return dst; 36 | } 37 | 38 | /// @c static_cast checked in debug version 39 | template 40 | inline L* scast(R* r) { 41 | static_assert(std::is_base_of::value, "R is not a base type of L"); 42 | assert((!r || dynamic_cast(r)) && "cast not possible" ); 43 | return static_cast(r); 44 | } 45 | 46 | /// @c static_cast checked in debug version -- @c const version 47 | template 48 | inline const L* scast(const R* r) { return const_cast(scast(const_cast(r))); } 49 | 50 | /// shorthand for @c dynamic_cast 51 | template 52 | inline L* dcast(R* u) { 53 | static_assert(std::is_base_of::value, "R is not a base type of L"); 54 | return dynamic_cast(u); 55 | } 56 | 57 | /// shorthand for @c dynamic_cast -- @c const version 58 | template 59 | inline const L* dcast(const R* r) { return const_cast(dcast(const_cast(r))); } 60 | 61 | /** 62 | * Provides handy @p as and @p isa methods. 63 | * Inherit from this class in order to use 64 | * @code 65 | Bar* bar = foo->as(); 66 | if (Bar* bar = foo->isa()) { ... } 67 | * @endcode 68 | * instead of more combersume 69 | * @code 70 | Bar* bar = thorin::scast(foo); 71 | if (Bar* bar = thorin::dcast(foo)) { ... } 72 | * @endcode 73 | */ 74 | template 75 | class RuntimeCast { 76 | public: 77 | /** 78 | * Acts as static cast -- checked for correctness in the debug version. 79 | * Use if you @em know that @p this is of type @p To. 80 | * It is a program error (an assertion is raised) if this does not hold. 81 | */ 82 | template To* as() { return thorin::scast(static_cast(this)); } 83 | 84 | /** 85 | * Acts as dynamic cast. 86 | * @return @p this cast to @p To if @p this is a @p To, 0 otherwise. 87 | */ 88 | template To* isa() { return thorin::dcast(static_cast(this)); } 89 | 90 | ///< @c const version of @see RuntimeCast#as. 91 | template 92 | const To* as() const { return thorin::scast(static_cast(this)); } 93 | 94 | ///< @c const version of @see RuntimeCast#isa. 95 | template 96 | const To* isa() const { return thorin::dcast(static_cast(this)); } 97 | }; 98 | 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/thorin/util/hash.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/util/hash.h" 2 | 3 | #include "thorin/util/stream.h" 4 | 5 | namespace thorin { 6 | 7 | hash_t hash(const char* s) { 8 | hash_t seed = thorin::hash_begin(); 9 | for (const char* p = s; *p != '\0'; ++p) 10 | seed = thorin::hash_combine(seed, *p); 11 | return seed; 12 | } 13 | 14 | void debug_hash() { 15 | errf("debug with: break {}:{}", __FILE__, __LINE__); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/thorin/util/indexmap.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTIL_INDEXMAP_H 2 | #define THORIN_UTIL_INDEXMAP_H 3 | 4 | #include "thorin/util/array.h" 5 | #include "thorin/util/iterator.h" 6 | 7 | namespace thorin { 8 | 9 | template 10 | class IndexMap { 11 | private: 12 | template 13 | struct IsValidPred { 14 | static bool is_valid(T) { return true; } 15 | }; 16 | 17 | template 18 | struct IsValidPred { 19 | static bool is_valid(T* value) { return value != nullptr; } 20 | }; 21 | 22 | public: 23 | IndexMap(IndexMap&& other) 24 | : indexer_(std::move(other.indexer_)) 25 | , array_(std::move(other.array_)) 26 | {} 27 | IndexMap(const IndexMap& other) 28 | : indexer_(other.indexer_) 29 | , array_(other.array_) 30 | {} 31 | IndexMap(const Indexer& indexer, const Value& value = Value()) 32 | : indexer_(indexer) 33 | , array_(indexer.size(), value) 34 | {} 35 | IndexMap(const Indexer& indexer, ArrayRef array) 36 | : indexer_(indexer) 37 | , array_(array) 38 | {} 39 | template 40 | IndexMap(const Indexer& indexer, const I begin, const I end) 41 | : indexer_(indexer) 42 | , array_(begin, end) 43 | {} 44 | 45 | const Indexer& indexer() const { return indexer_; } 46 | size_t capacity() const { return array_.size(); } 47 | Value& operator[](Key key) { auto i = indexer().index(key); assert(i != size_t(-1)); return array_[i]; } 48 | const Value& operator[](Key key) const { return const_cast(this)->operator[](key); } 49 | Array& array() { return array_; } 50 | const Array& array() const { return array_; } 51 | Value& array(size_t i) { return array_[i]; } 52 | const Value& array(size_t i) const { return array_[i]; } 53 | 54 | typedef filter_iterator::const_iterator, bool (*)(Value)> const_iterator; 55 | const_iterator begin() const { return filter(array_.begin(), array_.end(), IsValidPred::is_valid); } 56 | const_iterator end() const { return filter(array_.end(), array_.end(), IsValidPred::is_valid); } 57 | 58 | friend void swap(IndexMap& map1, IndexMap& map2) { 59 | using std::swap; 60 | swap(map1.indexer_, map2.indexer_); 61 | swap(map1.array_, map2.array_); 62 | } 63 | 64 | private: 65 | const Indexer& indexer_; 66 | Array array_; 67 | }; 68 | 69 | template 70 | inline Value* find(IndexMap& map, Key key) { 71 | auto i = map.indexer().index(key); 72 | return i != size_t(-1) ? map.array()[i] : nullptr; 73 | } 74 | 75 | template 76 | inline const Value* find(const IndexMap& map, Key key) { 77 | return find(const_cast&>(map), key); 78 | } 79 | 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/thorin/util/indexset.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTIL_INDEXSET_H 2 | #define THORIN_UTIL_INDEXSET_H 3 | 4 | #include "thorin/util/array.h" 5 | 6 | namespace thorin { 7 | 8 | template 9 | class IndexSet { 10 | public: 11 | class reference { 12 | private: 13 | reference(uint64_t& word, uint64_t pos) 14 | : word_(word) 15 | , pos_(pos) 16 | {} 17 | 18 | public: 19 | reference operator=(bool b) { 20 | if (b) 21 | word_ |= uint64_t(1) << pos_; 22 | else 23 | word_ &= ~(uint64_t(1) << pos_); 24 | return *this; 25 | } 26 | operator bool() const { return word_ & (uint64_t(1) << pos_); } 27 | 28 | private: 29 | uint64_t word() const { return word_; } 30 | 31 | uint64_t& word_; 32 | uint64_t pos_; 33 | 34 | friend class IndexSet; 35 | }; 36 | 37 | // TODO write iterators 38 | // TODO add size 39 | 40 | IndexSet(const Indexer& indexer) 41 | : indexer_(indexer) 42 | , bits_((capacity()+63u) / 64u) 43 | {} 44 | IndexSet(IndexSet&& other) 45 | : IndexSet(indexer) 46 | { 47 | swap(*this, other); 48 | } 49 | IndexSet(const IndexSet& other) 50 | : indexer_(other.indexer()) 51 | , bits_(other.bits_) 52 | {} 53 | 54 | const Indexer& indexer() const { return indexer_; } 55 | size_t capacity() const { return indexer().size(); } 56 | size_t next(size_t pos = 0) { 57 | for (size_t i = pos, e = capacity(); i != e; ++i) { 58 | if (bits_[i]) 59 | return i; 60 | } 61 | return pos; 62 | } 63 | reference operator[](Key key) { 64 | auto i = indexer().index(key); 65 | assert(i != size_t(-1)); 66 | return reference(bits_[i / 64u], i % 64u); 67 | } 68 | bool operator[](Key key) const { return (*const_cast*>(this))[key]; } 69 | 70 | /// Depending on @p flag this method either inserts (true) or removes (false) @p key and returns true if successful. 71 | template 72 | bool set(Key key) { 73 | auto ref = (*this)[key]; 74 | auto old = ref.word(); 75 | ref = flag; 76 | return old != ref.word(); 77 | } 78 | bool insert(Key key) { return set(key); } ///< Inserts \p key and returns true if successful. 79 | bool erase(Key key) { return set(key); } ///< Erase \p key and returns true if successful. 80 | bool contains(Key key) const { return (*this)[key]; } 81 | void clear() { std::fill(bits_.begin(), bits_.end(), 0u); } 82 | 83 | private: 84 | const Indexer& indexer_; 85 | Array bits_; 86 | }; 87 | 88 | template 89 | bool visit(IndexSet& set, const Key& key) { 90 | return !set.insert(key); 91 | } 92 | 93 | template 94 | void visit_first(IndexSet& set, const Key& key) { 95 | assert(!set.contains(key)); 96 | visit(set, key); 97 | } 98 | 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/thorin/util/iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTIL_ITERATOR_H 2 | #define THORIN_UTIL_ITERATOR_H 3 | 4 | // we can nuke this when switching to C++20's range library 5 | 6 | #include 7 | #include 8 | 9 | namespace thorin { 10 | 11 | //------------------------------------------------------------------------------ 12 | 13 | template 14 | struct Range { 15 | using iterator = I; 16 | using difference_type = typename std::iterator_traits::difference_type; 17 | using value_type = typename std::iterator_traits::value_type; 18 | using reference = typename std::iterator_traits::reference; 19 | using pointer = typename std::iterator_traits::pointer; 20 | using iterator_category = typename std::iterator_traits::iterator_category; 21 | 22 | Range(I begin, I end) 23 | : begin_(begin) 24 | , end_(end) 25 | {} 26 | Range(const Range& other) 27 | : begin_(other.begin()) 28 | , end_ (other.end()) 29 | {} 30 | Range(Range&& other) 31 | : begin_(std::move(other.begin_)) 32 | , end_ (std::move(other.end_)) 33 | {} 34 | 35 | I begin() const { return begin_; } 36 | I end() const { return end_; } 37 | size_t distance() const { return std::distance(begin(), end()); } 38 | 39 | value_type operator*() const { return *begin_; } 40 | pointer operator->() const { return (pointer) &*begin_; } 41 | bool operator==(const Range& other) { assert(this->end_ == other.end_); return this->begin_ == other.begin_; } 42 | bool operator!=(const Range& other) { assert(this->end_ == other.end_); return this->begin_ != other.begin_; } 43 | Range& operator=(Range other) { swap(*this, other); return *this; } 44 | 45 | friend void swap(Range& r1, Range& r2) { 46 | using std::swap; 47 | swap(r1.begin_, r2.begin_); 48 | swap(r1.end_, r2.end_); 49 | } 50 | 51 | protected: 52 | I begin_; 53 | I end_; 54 | }; 55 | 56 | template 57 | auto range(I begin, I end) { return Range(begin, end); } 58 | 59 | template 60 | auto range(const C& container) { return Range(container.begin(), container.end()); } 61 | 62 | template 63 | auto reverse_range(const C& container) { return range(container.rbegin(), container.rend()); } 64 | 65 | template 66 | auto reverse_range(C& container) { return range(container.rbegin(), container.rend()); } 67 | 68 | //------------------------------------------------------------------------------ 69 | 70 | template::value_type> 71 | class filter_iterator : public Range { 72 | public: 73 | using super = Range; 74 | using value_type = typename super::value_type; 75 | using reference = typename super::reference; 76 | using pointer = typename super::pointer; 77 | using iterator_category = typename super::iterator_category; 78 | 79 | filter_iterator(I begin, I end, P predicate) 80 | : Range(begin, end) 81 | , predicate_(predicate) 82 | { 83 | skip(); 84 | } 85 | filter_iterator(const filter_iterator& other) 86 | : Range(other.begin(), other.end()) 87 | , predicate_(other.predicate()) 88 | { 89 | skip(); 90 | } 91 | filter_iterator(filter_iterator&& other) 92 | : Range(std::move(other.begin_), std::move(other.end_)) 93 | , predicate_(std::move(other.predicate_)) 94 | { 95 | skip(); 96 | } 97 | 98 | P predicate() const { return predicate_; } 99 | 100 | filter_iterator& operator++() { 101 | assert(super::begin_ != super::end_); 102 | ++super::begin_; 103 | skip(); 104 | return *this; 105 | } 106 | filter_iterator operator++(int) { filter_iterator res = *this; ++(*this); return res; } 107 | filter_iterator& operator=(filter_iterator other) { swap(*this, other); return *this; } 108 | 109 | friend void swap(filter_iterator& i1, filter_iterator& i2) { 110 | using std::swap; 111 | swap(static_cast(i1), static_cast(i2)); 112 | swap(i1.predicate_, i2.predicate_); 113 | } 114 | 115 | private: 116 | void skip() { 117 | while (super::begin_ != super::end() && !predicate_(*super::begin_)) 118 | ++super::begin_; 119 | } 120 | 121 | P predicate_; 122 | }; 123 | 124 | template 125 | auto filter(I begin, I end, P predicate) { return filter_iterator(begin, end, predicate); } 126 | 127 | template 128 | auto filter(const R& range, P predicate) { return filter_iterator(range.begin(), range.end(), predicate); } 129 | 130 | //------------------------------------------------------------------------------ 131 | 132 | template 133 | class map_iterator { 134 | public: 135 | using iterator = I; 136 | using difference_type = typename std::iterator_traits::difference_type; 137 | using value_type = typename std::result_of::value_type)>::type; 138 | using reference = value_type&; 139 | using pointer = value_type*; 140 | using iterator_category = typename std::iterator_traits::iterator_category; 141 | 142 | map_iterator(iterator iter, F map) 143 | : iter_(iter) 144 | , map_(map) 145 | {} 146 | map_iterator(const map_iterator& other) 147 | : iter_(other.iter_) 148 | , map_ (other.map_) 149 | {} 150 | map_iterator(map_iterator&& other) 151 | : iter_(std::move(other.iter_)) 152 | , map_(std::move(other.map_)) 153 | {} 154 | 155 | F map() const { return map_; } 156 | 157 | map_iterator& operator++() { ++iter_; return *this; } 158 | map_iterator operator++(int) { map_iterator res = *this; ++(*this); return res; } 159 | value_type operator*() const { return map_(*iter_); } 160 | pointer operator->() const { return (pointer) &map_(*iter_); } 161 | bool operator==(const map_iterator& other) { return this->iter_ == other.iter_; } 162 | bool operator!=(const map_iterator& other) { return this->iter_ != other.iter_; } 163 | map_iterator& operator=(map_iterator other) { swap(*this, other); return *this; } 164 | 165 | friend void swap(map_iterator& i1, map_iterator& i2) { 166 | using std::swap; 167 | swap(i1.iter_, i2.iter_); 168 | swap(i1.map_, i2.map_ ); 169 | } 170 | 171 | private: 172 | iterator iter_; 173 | F map_; 174 | }; 175 | 176 | template 177 | auto map(I begin, I end, F f) { return range(map_iterator(begin, f), map_iterator(end, f)); } 178 | 179 | template 180 | auto map(const R& r, F f) { return range(map_iterator(r.begin(), f), map_iterator(r.end(), f)); } 181 | 182 | //------------------------------------------------------------------------------ 183 | 184 | template 185 | struct is_range : std::false_type {}; 186 | template 187 | struct is_range().begin()), decltype(std::declval().end())>> : std::true_type {}; 188 | template static constexpr bool is_range_v = is_range::value; 189 | 190 | //------------------------------------------------------------------------------ 191 | 192 | } 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /src/thorin/util/scoped_dump.cpp: -------------------------------------------------------------------------------- 1 | #include "scoped_dump.h" 2 | 3 | namespace thorin { 4 | 5 | void ScopedWorld::stream_cont(thorin::Stream& s, Continuation* cont) const { 6 | s.fmt(Magenta); 7 | if (cont->is_external()) 8 | s.fmt("extern "); 9 | if (cont->is_intrinsic()) 10 | s.fmt("intrinsic "); 11 | 12 | switch (cont->cc()) { 13 | case CC::Thorin: break; 14 | case CC::C: s.fmt("cc(C) "); break; 15 | case CC::Device: s.fmt("cc(Device) "); break; 16 | default: s.fmt("cc(?)"); break; 17 | } 18 | 19 | s.fmt(Green); 20 | s.fmt("cont "); 21 | s.fmt(Reset); 22 | 23 | s.fmt(Red); 24 | s.fmt("{}", cont->unique_name()); 25 | s.fmt(Reset); 26 | s.fmt("("); 27 | const FnType* t = cont->type(); 28 | for (size_t i = 0; i < cont->num_params(); i++) { 29 | s.fmt(Yellow); 30 | s.fmt("{}: ", cont->param(i)->unique_name()); 31 | s.fmt(Blue); 32 | s.fmt("{}", t->types()[i]); 33 | s.fmt(Reset); 34 | if (i + 1 < cont->num_params()) 35 | s.fmt(", "); 36 | } 37 | s.fmt(")"); 38 | if (!cont->has_body()) { 39 | s.fmt(";"); 40 | return; 41 | } 42 | 43 | s.fmt(" = {{\t\n"); 44 | 45 | for (auto p : cont->params()) 46 | done_.insert(p); 47 | 48 | Scope& sc = forest_.get_scope(cont); 49 | scopes_to_defs_[cont] = std::make_unique>(); 50 | auto children = sc.children_scopes(); 51 | //size_t i = 0; 52 | for (auto child : children) { 53 | stream_cont(s, child); 54 | //if (i + 1 < children.size()) 55 | s.fmt("\n\n"); 56 | //i++; 57 | } 58 | 59 | prepare_def(cont, cont->body()); 60 | 61 | auto defs = *scopes_to_defs_[cont]; 62 | stream_defs(s, defs); 63 | s.fmt("{}", cont->body()->unique_name()); 64 | s.fmt("\b\n}}"); 65 | } 66 | 67 | void ScopedWorld::prepare_def(Continuation* in, const thorin::Def* def) const { 68 | if (done_.contains(def)) 69 | return; 70 | done_.insert(def); 71 | if (def->isa_nom()) 72 | return; 73 | 74 | while (in) { 75 | Scope* scope = &forest_.get_scope(in); 76 | if (scope->contains(def)) 77 | break; 78 | in = scope->parent_scope(); 79 | } 80 | 81 | for (auto op : def->ops()) 82 | prepare_def(in, op); 83 | 84 | if (!in) 85 | top_lvl_.push_back(def); 86 | else 87 | scopes_to_defs_[in]->push_back(def); 88 | } 89 | 90 | void ScopedWorld::stream_op(thorin::Stream& s, const thorin::Def* op) const { 91 | //if (is_mem(op)) 92 | // s.fmt(Gray); 93 | if (op->isa()) 94 | s.fmt(Cyan); 95 | if (op->isa()) 96 | s.fmt(Yellow); 97 | if (op->isa()) 98 | s.fmt(Red); 99 | s.fmt("{}", op); 100 | s.fmt(Reset); 101 | } 102 | 103 | void ScopedWorld::stream_ops(thorin::Stream& s, Defs ops) const { 104 | s.fmt("("); 105 | size_t j = 0; 106 | for (auto op : ops) { 107 | stream_op(s, op); 108 | if (j + 1 < ops.size()) 109 | s.fmt(", "); 110 | j++; 111 | } 112 | s.fmt(")"); 113 | } 114 | 115 | bool ScopedWorld::print_inline(const thorin::Def* def) const { 116 | if (def->isa()) 117 | return true; 118 | return false; 119 | } 120 | 121 | void ScopedWorld::stream_def(thorin::Stream& s, const thorin::Def* def) const { 122 | if (auto app = def->isa()) { 123 | stream_op(s, app->callee()); 124 | stream_ops(s, app->args()); 125 | return; 126 | } 127 | if (def->isa()) { 128 | def->stream1(s); 129 | return; 130 | } 131 | if (def->isa_nom()) { 132 | s.fmt("{}", def->unique_name()); 133 | return; 134 | } 135 | 136 | s.fmt(Green); 137 | s.fmt("{}", def->op_name()); 138 | s.fmt(Reset); 139 | stream_ops(s, def->ops()); 140 | } 141 | 142 | void ScopedWorld::stream_defs(thorin::Stream& s, std::vector& defs) const { 143 | for (auto def : defs) { 144 | if (print_inline(def)) 145 | continue; 146 | s.fmt("{}: ", def->unique_name()); 147 | s.fmt(Blue); 148 | s.fmt("{}", def->type()); 149 | s.fmt(Reset); 150 | s.fmt(" = "); 151 | stream_def(s, def); 152 | s.fmt("\n"); 153 | } 154 | } 155 | 156 | Stream& ScopedWorld::stream(thorin::Stream& s) const { 157 | auto tl = forest_.top_level_scopes(); 158 | //size_t i = 0; 159 | for (auto root : tl) { 160 | stream_cont(s, root); 161 | //if (i + 1 < tl.size()) 162 | s.fmt("\n\n"); 163 | //i++; 164 | } 165 | 166 | stream_defs(s, top_lvl_); 167 | 168 | return s; 169 | } 170 | 171 | void World::dump_scoped(bool use_color) const { 172 | ScopedWorld s(*const_cast(this), ScopedWorld::Config { use_color }); 173 | s.dump(); 174 | } 175 | 176 | void World::dump_scoped_to_disk() const { 177 | ScopedWorld s(*const_cast(this), ScopedWorld::Config { false }); 178 | auto name = this->name() + ".dump"; 179 | std::ofstream file(name); 180 | Stream st(file); 181 | s.stream(st); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/thorin/util/scoped_dump.h: -------------------------------------------------------------------------------- 1 | #include "thorin/world.h" 2 | #include "thorin/analyses/scope.h" 3 | #include "thorin/util/stream.h" 4 | 5 | namespace thorin { 6 | 7 | #define COLORS(C) \ 8 | C(Black, "\u001b[30m") \ 9 | C(Red, "\u001b[31m") \ 10 | C(Green, "\u001b[32m") \ 11 | C(Yellow, "\u001b[33m") \ 12 | C(Blue, "\u001b[34m") \ 13 | C(Magenta, "\u001b[35m") \ 14 | C(Cyan, "\u001b[36m") \ 15 | C(White, "\u001b[37m") \ 16 | C(Reset, "\u001b[0m") \ 17 | 18 | struct ScopedWorld : public Streamable { 19 | struct Config { 20 | bool use_color; 21 | }; 22 | 23 | ScopedWorld(World& w, Config cfg = { true }) : world_(w), forest_(w), config_(cfg) { 24 | #define T(n, c) n = cfg.use_color ? c : ""; 25 | COLORS(T) 26 | #undef T 27 | } 28 | 29 | World& world_; 30 | mutable ScopesForest forest_; 31 | 32 | mutable DefSet done_; 33 | mutable ContinuationMap>> scopes_to_defs_; 34 | mutable std::vector top_lvl_; 35 | Config config_; 36 | 37 | #define T(n, c) const char* n; 38 | COLORS(T) 39 | #undef T 40 | 41 | Stream& stream(Stream&) const; 42 | private: 43 | 44 | bool print_inline(const Def* def) const; 45 | void stream_cont(thorin::Stream& s, Continuation* cont) const; 46 | void prepare_def(Continuation* in, const Def* def) const; 47 | void stream_op(thorin::Stream&, const Def* op) const; 48 | void stream_ops(thorin::Stream& s, Defs defs) const; 49 | void stream_def(thorin::Stream& s, const Def* def) const; 50 | void stream_defs(thorin::Stream& s, std::vector& defs) const; 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/thorin/util/stream.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/util/stream.h" 2 | 3 | namespace thorin { 4 | 5 | Stream& Stream::endl() { 6 | ostream() << '\n'; 7 | for (size_t i = 0; i != level_; ++i) ostream() << tab_; 8 | return *this; 9 | } 10 | 11 | Stream& Stream::fmt(const char* s) { 12 | while (*s) { 13 | auto next = s + 1; 14 | 15 | switch (*s) { 16 | case '\n': s++; endl(); break; 17 | case '\t': s++; indent(); break; 18 | case '\b': s++; dedent(); break; 19 | case '{': 20 | if (match2nd(next, s, '{')) continue; 21 | 22 | while (*s && *s != '}') s++; 23 | 24 | assert(*s != '}' && "invalid format string for 'streamf': missing argument(s)"); 25 | assert(false && "invalid format string for 'streamf': missing closing brace and argument"); 26 | break; 27 | case '}': 28 | if (match2nd(next, s, '}')) continue; 29 | assert(false && "unmatched/unescaped closing brace '}' in format string"); 30 | break; 31 | default: 32 | (*this) << *s++; 33 | } 34 | } 35 | return *this; 36 | } 37 | 38 | bool Stream::match2nd(const char* next, const char*& s, const char c) { 39 | if (*next == c) { 40 | (*this) << c; 41 | s += 2; 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/thorin/util/symbol.cpp: -------------------------------------------------------------------------------- 1 | #include "thorin/util/symbol.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace thorin { 7 | 8 | Symbol::Table Symbol::table_; 9 | 10 | #ifdef _MSC_VER 11 | static const char* duplicate(const char* s) { return _strdup(s); } 12 | #else // _MSC_VER 13 | static const char* duplicate(const char* s) { return strdup(s); } 14 | #endif // _MSC_VER 15 | 16 | void Symbol::insert(const char* s) { 17 | auto i = table_.map.find(s); 18 | if (i == table_.map.end()) 19 | i = table_.map.emplace(duplicate(s)).first; 20 | str_ = *i; 21 | } 22 | 23 | std::string Symbol::remove_quotation() const { 24 | std::string str = str_; 25 | if (!str.empty() && str.front() == '"') { 26 | assert(str.size() >= 2 && str.back() == '"'); 27 | str = str.substr(1, str.size()-2); 28 | } 29 | return str; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/thorin/util/symbol.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTIL_SYMBOL_H 2 | #define THORIN_UTIL_SYMBOL_H 3 | 4 | #include 5 | 6 | #include "thorin/util/hash.h" 7 | 8 | namespace thorin { 9 | 10 | class Symbol { 11 | public: 12 | struct Hash { 13 | static hash_t hash(Symbol s) { return thorin::hash(s.c_str()); } 14 | static bool eq(Symbol s1, Symbol s2) { return s1 == s2; } 15 | static Symbol sentinel() { return Symbol(/*dummy*/23); } 16 | }; 17 | 18 | Symbol() { insert(""); } 19 | Symbol(const char* str) { insert(str); } 20 | Symbol(const std::string& str) { insert(str.c_str()); } 21 | 22 | const char* c_str() const { return str_; } 23 | std::string str() const { return str_; } 24 | operator bool() const { return *this != Symbol(""); } 25 | bool operator==(Symbol symbol) const { return c_str() == symbol.c_str(); } 26 | bool operator!=(Symbol symbol) const { return c_str() != symbol.c_str(); } 27 | bool operator==(const char* s) const { return c_str() == Symbol(s).c_str(); } 28 | bool operator!=(const char* s) const { return c_str() != Symbol(s).c_str(); } 29 | bool empty() const { return *str_ == '\0'; } 30 | bool is_anonymous() { return (*this) == "_"; } 31 | std::string remove_quotation() const; 32 | 33 | private: 34 | Symbol(int /* just a dummy */) 35 | : str_((const char*)(1)) 36 | {} 37 | 38 | struct Table { 39 | ~Table() { 40 | for (auto s : map) 41 | free((void*) const_cast(s)); 42 | } 43 | 44 | HashSet map; 45 | }; 46 | 47 | void insert(const char* str); 48 | 49 | const char* str_; 50 | static Table table_; 51 | }; 52 | 53 | inline Symbol operator+(Symbol s1, Symbol s2) { return std::string(s1.c_str()) + s2.str(); } 54 | inline Symbol operator+(Symbol s1, const char* s2) { return std::string(s1.c_str()) + s2; } 55 | inline Symbol operator+(Symbol s1, std::string s2) { return std::string(s1.c_str()) + s2; } 56 | inline std::ostream& operator<<(std::ostream& os, Symbol s) { return os << s.c_str(); } 57 | 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/thorin/util/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef THORIN_UTILITY_H 2 | #define THORIN_UTILITY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef _MSC_VER 11 | #include 12 | #endif 13 | 14 | 15 | [[noreturn]] inline void _thorin_unreachable() { abort(); } 16 | #define THORIN_UNREACHABLE do { assert(false && "unreachable"); ::_thorin_unreachable(); } while(0) 17 | 18 | #if (defined(__clang__) || defined(__GNUC__)) && (defined(__x86_64__) || defined(__i386__)) 19 | #define THORIN_BREAK asm("int3"); 20 | #else 21 | #define THORIN_BREAK { int* __p__ = nullptr; *__p__ = 42; } 22 | #endif 23 | 24 | #if defined(__clang__) || defined(__GNUC__) 25 | #define DONT_STRIP __attribute__((__used__)) 26 | #else 27 | // MSVC does not seem to offer a real intrinsic for this, please update this if you know of one 28 | #define DONT_STRIP __declspec(dllexport) 29 | #endif 30 | 31 | #ifndef NDEBUG 32 | #define assert_unused(x) assert(x) 33 | // prevents removal of useful methods in debug mode 34 | #define DEBUG_UTIL DONT_STRIP 35 | #else 36 | #define assert_unused(x) ((void) (0 && (x))) 37 | #define DEBUG_UTIL 38 | #endif 39 | 40 | namespace thorin { 41 | 42 | /** 43 | * A @c size_t literal. 44 | * Use @c 0_s to disambiguate @c 0 from @c nullptr. 45 | */ 46 | constexpr size_t operator""_s(unsigned long long int i) { return size_t(i); } 47 | 48 | /// A @c uint32_t literal. 49 | constexpr uint32_t operator""_u32(unsigned long long int i) { return uint32_t(i); } 50 | 51 | /// A @c uint64_t literal. 52 | constexpr uint64_t operator""_u64(unsigned long long int i) { return uint64_t(i); } 53 | 54 | /// Use to initialize an @c std::unique_ptr in a lazy way. 55 | template 56 | inline T& lazy_init(const This* self, std::unique_ptr& ptr) { 57 | return *(ptr ? ptr : ptr = std::make_unique(*self)); 58 | } 59 | 60 | template 61 | auto pop(S& s) -> decltype(s.top(), typename S::value_type()) { 62 | auto val = s.top(); 63 | s.pop(); 64 | return val; 65 | } 66 | 67 | template 68 | auto pop(Q& q) -> decltype(q.front(), typename Q::value_type()) { 69 | auto val = q.front(); 70 | q.pop(); 71 | return val; 72 | } 73 | 74 | template 75 | I binary_find(I begin, I end, T val, Cmp cmp) { 76 | auto i = std::lower_bound(begin, end, val, cmp); 77 | return (i != end && !(cmp(val, *i))) ? i : end; 78 | } 79 | 80 | template 81 | class unique_stack { 82 | public: 83 | using T = typename std::remove_reference_t::value_type; 84 | 85 | bool push(T val) { 86 | if (done_.emplace(val).second) { 87 | stack_.emplace(val); 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | bool empty() const { return stack_.empty(); } 94 | const T& top() { return stack_.top(); } 95 | T pop() { return thorin::pop(stack_); } 96 | void clear() { done_.clear(); stack_ = {}; } 97 | 98 | private: 99 | Set done_; 100 | std::stack stack_; 101 | }; 102 | 103 | template 104 | class unique_queue { 105 | public: 106 | using T = typename std::remove_reference_t::value_type; 107 | 108 | unique_queue() = default; 109 | unique_queue(Set set) 110 | : done_(set) 111 | {} 112 | 113 | bool push(T val) { 114 | if (done_.emplace(val).second) { 115 | queue_.emplace(val); 116 | return true; 117 | } 118 | return false; 119 | } 120 | 121 | bool empty() const { return queue_.empty(); } 122 | T pop() { return thorin::pop(queue_); } 123 | T& front() { return queue_.front(); } 124 | T& back() { return queue_.back(); } 125 | void clear() { done_.clear(); queue_ = {}; } 126 | 127 | private: 128 | Set done_; 129 | std::queue queue_; 130 | }; 131 | 132 | template 133 | struct Push { 134 | Push(T& t, T new_val) 135 | : old_(t) 136 | , ref_(t) 137 | { 138 | t = new_val; 139 | } 140 | ~Push() { ref_ = old_; } 141 | 142 | private: 143 | T old_; 144 | T& ref_; 145 | }; 146 | 147 | /** 148 | * A tagged pointer: first 16 bits is tag (index), remaining 48 bits is the actual pointer. 149 | * For non-x86_64 there is a fallback implementation. 150 | */ 151 | template 152 | class TaggedPtr { 153 | public: 154 | TaggedPtr() {} 155 | #if defined(__x86_64__) || (_M_X64) 156 | TaggedPtr(T* ptr, I index) 157 | : ptr_(reinterpret_cast(ptr)) 158 | , index_(int16_t(index)) 159 | {} 160 | #else 161 | TaggedPtr(T* ptr, I index) 162 | : ptr_(ptr) 163 | , index_(I(index)) 164 | {} 165 | #endif 166 | 167 | T* ptr() const { return reinterpret_cast(ptr_); } 168 | T* operator->() const { return ptr(); } 169 | operator T*() const { return ptr(); } 170 | void index(I index) { index_ = index; } 171 | I index() const { return I(index_); } 172 | bool operator==(TaggedPtr other) const { return this->ptr() == other.ptr() && this->index() == other.index(); } 173 | 174 | #if defined(__x86_64__) || (_M_X64) 175 | TaggedPtr& operator=(T* ptr) { ptr_ = reinterpret_cast(ptr); return *this; } 176 | TaggedPtr& operator=(I index) { index_ = int16_t(index); return *this; } 177 | #else 178 | TaggedPtr& operator=(T* ptr) { ptr_ = ptr; return *this; } 179 | TaggedPtr& operator=(I index) { index_ = index; return *this; } 180 | #endif 181 | void set(T* ptr, I index) { (*this = ptr) = index; } 182 | 183 | private: 184 | #if defined(__x86_64__) || (_M_X64) 185 | int64_t ptr_ : 48; // sign extend to make pointer canonical 186 | int64_t index_ : 16; 187 | #else 188 | T* ptr_; 189 | I index_; 190 | #endif 191 | }; 192 | 193 | #if defined(__x86_64__) || (_M_X64) 194 | static_assert(sizeof(TaggedPtr) == 8, "a tagged ptr on x86_64 is supposed to be 8 bytes big"); 195 | #endif 196 | 197 | //@{ bit fiddling 198 | 199 | /// Determines whether @p i is a power of two. 200 | constexpr uint64_t is_power_of_2(uint64_t i) { return ((i != 0) && !(i & (i - 1))); } 201 | 202 | constexpr uint64_t log2(uint64_t n, uint64_t p = 0) { return (n <= 1_u64) ? p : log2(n / 2_u64, p + 1_u64); } 203 | 204 | inline uint64_t round_to_power_of_2(uint64_t i) { 205 | i--; 206 | i |= i >> 1_u64; 207 | i |= i >> 2_u64; 208 | i |= i >> 4_u64; 209 | i |= i >> 8_u64; 210 | i |= i >> 16_u64; 211 | i |= i >> 32_u64; 212 | i++; 213 | return i; 214 | } 215 | 216 | inline size_t bitcount(uint64_t v) { 217 | #if defined(__GNUC__) | defined(__clang__) 218 | return __builtin_popcountll(v); 219 | #elif defined(_MSC_VER) 220 | return __popcnt64(v); 221 | #else 222 | // see https://stackoverflow.com/questions/3815165/how-to-implement-bitcount-using-only-bitwise-operators 223 | auto c = v - ((v >> 1_u64) & 0x5555555555555555_u64); 224 | c = ((c >> 2_u64) & 0x3333333333333333_u64) + (c & 0x3333333333333333_u64); 225 | c = ((c >> 4_u64) + c) & 0x0F0F0F0F0F0F0F0F_u64; 226 | c = ((c >> 8_u64) + c) & 0x00FF00FF00FF00FF_u64; 227 | c = ((c >> 16_u64) + c) & 0x0000FFFF0000FFFF_u64; 228 | return ((c >> 32_u64) + c) & 0x00000000FFFFFFFF_u64; 229 | #endif 230 | } 231 | 232 | inline uint64_t pad(uint64_t offset, uint64_t align) { 233 | auto mod = offset % align; 234 | if (mod != 0) offset += align - mod; 235 | return offset; 236 | } 237 | 238 | //@} 239 | 240 | } 241 | 242 | #endif 243 | --------------------------------------------------------------------------------