diff --git a/easy-jit/.gitignore b/easy-jit/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e63dd861f04ebe81d84380ab5cd6a3d66f26e197 --- /dev/null +++ b/easy-jit/.gitignore @@ -0,0 +1,16 @@ +*.ll +*.ninja_deps +*.ninja_log +*/CMakeCache.txt +*/CMakeFiles/ +*/bin/ +*/build.ninja +*/cmake_install.cmake +*/rules.ninja +*/.ninja_log +*/__pycache__ +*.py[cod] +build/ +.vscode +.venv +/.cache \ No newline at end of file diff --git a/easy-jit/.travis-old.yml b/easy-jit/.travis-old.yml new file mode 100644 index 0000000000000000000000000000000000000000..00853c787ba639997556a6eb9ff3f264f949a86e --- /dev/null +++ b/easy-jit/.travis-old.yml @@ -0,0 +1,30 @@ +language: cpp + +dist: trusty + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-6.0 + + packages: + - ninja-build + - g++-6 + - llvm-6.0-dev + - llvm-6.0-tools + - clang-6.0 + - libopencv-dev + +before_install: + - pip install --user --upgrade pip + - pip install --user lit + +script: + - mkdir _build && cd _build + - git clone --depth=1 https://github.com/google/benchmark.git && git clone --depth=1 https://github.com/google/googletest.git benchmark/googletest && mkdir benchmark/_build && cd benchmark/_build && cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=`pwd`/../_install && ninja && ninja install && cd ../.. + - cmake -DLLVM_DIR=/usr/lib/llvm-6.0/cmake -DCMAKE_CXX_COMPILER=clang++-6.0 -DCMAKE_C_COMPILER=clang-6.0 -DEASY_JIT_EXAMPLE=ON -DEASY_JIT_BENCHMARK=ON -DBENCHMARK_DIR=`pwd`/benchmark/_install -DCMAKE_INSTALL_PREFIX=`pwd`/../_install .. -G Ninja + - ninja + - ninja install + - ninja check + diff --git a/easy-jit/CMakeLists.txt b/easy-jit/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ed8ba7dd0d76ca9235a8346f99eed5c9c1dd5bbe --- /dev/null +++ b/easy-jit/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.12) +set(LLVM_SUBPROJECT_TITLE "EasyJIT") + +# Check for a standalone build and configure as appropriate from +# there. +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + message("Building EasyJIT as a standalone project.") + project(EasyJIT) + set(EASY_JIT_STANDALONE_BUILD ON) + option(EASY_JIT_INCLUDE_TESTS "Generate build targets for the EasyJIT tests." ON) + option(EASY_JIT_INCLUDE_DOCS "Generate build targets for the EasyJIT docs." OFF) + option(EASY_JIT_EXAMPLE "Generate build targets for the EasyJIT examples." OFF) + option(EASY_JIT_INCLUDE_BENCHMARKS "Generate build targets for the EasyJIT benchmarks." OFF) + option(EASY_JIT_BENCHMARK "Build the EasyJIT benchmarks." OFF) +else() + set(EASY_JIT_STANDALONE_BUILD OFF) + option(EASY_JIT_INCLUDE_TESTS "Generate build targets for the EasyJIT tests." ${LLVM_INCLUDE_TESTS}) + option(EASY_JIT_INCLUDE_DOCS "Generate build targets for the EasyJIT docs." ${LLVM_INCLUDE_DOCS}) + option(EASY_JIT_EXAMPLE "Generate build targets for the EasyJIT examples." ${LLVM_BUILD_EXAMPLES}) + option(EASY_JIT_INCLUDE_BENCHMARKS "Generate build targets for the EasyJIT benchmarks." ${LLVM_INCLUDE_BENCHMARKS}) + option(EASY_JIT_BENCHMARK "Build the EasyJIT benchmarks." ${LLVM_BUILD_BENCHMARKS}) +endif() + +# set(CMAKE_SHARED_LIBRARY_PREFIX_CXX "") + +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + message(FATAL_ERROR "Do not set the build directory equal to the source directory!") +endif() + +message(STATUS "CPU Architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}") +if (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64") + add_definitions(-D_X86) +elseif (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64") + add_definitions(-D_AARCH64) +endif() + +if(EASY_JIT_STANDALONE_BUILD) + # We need a pre-built/installed version of LLVM. + find_package(LLVM 19.1 REQUIRED HINTS ${LLVM_DIR}) + message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") + message(STATUS "Using LLVMConfig.cmake in ${LLVM_DIR}") + message(STATUS "LLVM Root: ${LLVM_TOOLS_BINARY_DIR}") + message(STATUS "LLVM Include dirs: ${LLVM_INCLUDE_DIRS}") + message(STATUS "LLVM Definitions: ${LLVM_DEFINITIONS}") + # Find llvm-lit if LLVM_DIR did not provide it + if(NOT LLVM_EXTERNAL_LIT AND EASY_JIT_INCLUDE_TESTS) + find_program(LIT_EXECUTABLE NAMES llvm-lit lit + HINTS ${LLVM_TOOLS_BINARY_DIR}) + if(LIT_EXECUTABLE) + set(LLVM_EXTERNAL_LIT ${LIT_EXECUTABLE} CACHE FILEPATH "Path to lit executable") + message(STATUS "Found lit executable: ${LLVM_EXTERNAL_LIT}") + else() + message(FATAL_ERROR "lit or llvm-lit not found. Please set LLVM_EXTERNAL_LIT to the path of lit.") + endif() + endif() +endif() + +list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") +include(AddLLVM) + +add_definitions(${LLVM_DEFINITIONS}) +include_directories(${LLVM_INCLUDE_DIRS}) +link_directories(${LLVM_LIBRARY_DIRS}) +include_directories(include) +add_compile_options(-fno-rtti) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(EASY_JIT_STANDALONE_BUILD) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +endif() + +set(EASY_JIT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) + +# Suppress superfluous randlib warnings about "*.a" having no symbols on MacOSX. +if (APPLE) + set(CMAKE_C_ARCHIVE_CREATE " Scr ") + set(CMAKE_CXX_ARCHIVE_CREATE " Scr ") + set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") + set(CMAKE_CXX_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") +endif() + +add_subdirectory(cmake) +add_subdirectory(include) +add_subdirectory(pass) +add_subdirectory(runtime) + +add_custom_target(easy-jit-core DEPENDS EasyJitPass EasyJitRuntime) + +if(EASY_JIT_INCLUDE_DOCS) + add_subdirectory(doc) +endif() + +if(EASY_JIT_INCLUDE_BENCHMARKS) + add_subdirectory(benchmark) +endif() + +if(EASY_JIT_INCLUDE_TESTS) + include(CMakeTests.txt) +endif() diff --git a/easy-jit/CMakeTests.txt b/easy-jit/CMakeTests.txt new file mode 100644 index 0000000000000000000000000000000000000000..52d568b0e4ccff9f0772bbf82d35002fe3da0758 --- /dev/null +++ b/easy-jit/CMakeTests.txt @@ -0,0 +1,24 @@ +# Collect test dependencies that should be built before running lit +set(EASYJIT_TEST_DEPS + EasyJitPass + EasyJitRuntime +) + +# Generate lit.site.cfg files into the binary tree +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/tests/lit.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/tests/lit.cfg +) +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/tests/doc/lit.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/tests/doc/lit.cfg +) + +# Avoid picking up arbitrary compile flags from parents (optional but common pattern) +file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/compile_flags.txt) + +# Define the lit testsuite target; works for both standalone and monorepo builds +add_lit_testsuite(check-easy-jit "Running EasyJIT tests" + ${CMAKE_CURRENT_BINARY_DIR}/tests + DEPENDS ${EASYJIT_TEST_DEPS} +) diff --git a/easy-jit/LICENSE b/easy-jit/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..399eaccd54d0c579370ae3d009271448d1c10d3f --- /dev/null +++ b/easy-jit/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, Juan Manuel Martinez Caamaño and Serge Guelton and Quarkslab. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/easy-jit/README.md b/easy-jit/README.md new file mode 100644 index 0000000000000000000000000000000000000000..92bfa9f00d4c29fec3a95f1205d48be1f1ccd092 --- /dev/null +++ b/easy-jit/README.md @@ -0,0 +1,202 @@ +Easy::jit: A just-in-time compiler for C++ +========================================== + +About +----- + +Easy::jit is a _compiler-assisted_ library that enables simple Just-In-Time +code generation for C++ codes. + +### Talks + +* [Easy::jit at EuroLLVM'18](https://www.youtube.com/watch?v=sFxqI6Z_bhE) +* [Easy::jit at FOSDEM'18](https://www.youtube.com/watch?v=5_rydTiB32I) + +Building +-------- + +First, install clang and LLVM. + +```bash +apt install llvm-19-dev llvm-19-tools clang-19 +## or in OpenEuler +dnf install llvm-toolset-19 llvm-toolset-19-llvm-devel llvm-toolset-19-mlir-devel +``` + +Then, configure and compile the project. + +```bash +cmake -DLLVM_DIR=/usr/lib/llvm-19/cmake +cmake --build . +## or in OpenEuler +cmake -DLLVM_DIR=/opt/openEuler/llvm-toolset-19/root/usr/lib64/cmake +cmake --build . +``` + +To build the examples, install the [opencv](https://opencv.org/) library, +and add the flags ```-DEASY_JIT_EXAMPLE=1``` to the cmake command. + +To enable benchmarking, install the [google benchmark](https://github.com/google/benchmark) framework, +and add the flags ```-DEASY_JIT_BENCHMARK=1 -DBENCHMARK_DIR=``` to the cmake command. + +Everything is ready to go! + +### Docker + +If you want to give only a quick test to the project, everything is provided to use it with docker. +To do this, generate a Dockerfile from the current directory using the scripts in ```/misc/docker```, +then generate your docker instance. + +```bash +python3 /misc/docker/GenDockerfile.py /.travis.yml > Dockerfile +docker build -t easy/test -f Dockerfile +docker run -ti easy/test /bin/bash +``` + +Basic usage +----------- + +### Compiling my project with Easy::Jit + +Since the Easy::Jit library relies on assistance from the compiler, its +mandatory to load a compiler plugin in order to use it. +The flag ```-Xclang -load -Xclang /bin/EasyJitPass.so -Xclang -fpass-plugin=/bin/EasyJitPass.so``` +loads the plugin. + +The included headers require C++14 support, and remember to add the include directories! +Use ```--std=c++14 -I/cpplib/include```. + +Finaly, the binary must be linked against the Easy::Jit runtime library, using +```-L/bin -lEasyJitRuntime```. + +Putting all together we get the command bellow. + +```bash +clang++-19 --std=c++14 \ + -Xclang -load -Xclang /path/to/easy/jit/build/bin/bin/EasyJitPass.so \ + -I/cpplib/include \ + -L/bin -lEasyJitRuntime +``` + +### Using Easy::Jit inside my project + +Consider the code below from a software that applies image filters on a video stream. +In the following sections we are going to adapt it to use the Easy::jit library. +The function to optimize is ```kernel```, which applies a mask on the entire image. + +The mask, its dimensions and area do not change often, so specializing the function for +these parameters seems reasonable. +Moreover, the image dimensions and number of channels typically remain constant during +the entire execution; however, it is impossible to know their values as they depend on the stream. + +```cpp +static void kernel(const char* mask, unsigned mask_size, unsigned mask_area, + const unsigned char* in, unsigned char* out, + unsigned rows, unsigned cols, unsigned channels) { + unsigned mask_middle = (mask_size/2+1); + unsigned middle = (cols+1)*mask_middle; + + for(unsigned i = 0; i != rows-mask_size; ++i) { + for(unsigned j = 0; j != cols-mask_size; ++j) { + for(unsigned ch = 0; ch != channels; ++ch) { + + long out_val = 0; + for(unsigned ii = 0; ii != mask_size; ++ii) { + for(unsigned jj = 0; jj != mask_size; ++jj) { + out_val += mask[ii*mask_size+jj] * in[((i+ii)*cols+j+jj)*channels+ch]; + } + } + out[(i*cols+j+middle)*channels+ch] = out_val / mask_area; + } + } + } +} + +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + kernel(mask, mask_size, mask_area, image.ptr(0,0), out->ptr(0,0), image.rows, image.cols, image.channels()); +} +``` + +The main header for the library is ```easy/jit.h```, where the only core function +of the library is exported. This function is called -- guess how? -- ```easy::jit```. +We add the corresponding include directive them in the top of the file. + +```cpp +#include +``` + +With the call to ```easy::jit```, we specialize the function and obtain a new +one taking only two parameters (the input and the output frame). + +```cpp +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + using namespace std::placeholders; + + auto kernel_opt = easy::jit(kernel, mask, mask_size, mask_area, _1, _2, image.rows, image.cols, image.channels()); + kernel_opt(image.ptr(0,0), out->ptr(0,0)); +} +``` + +#### Deducing which functions to expose at runtime + +Easy::jit embeds the [LLVM bitcode](https://llvm.org/docs/LangRef.html) +representation of the functions to specialize at runtime in the binary code. +To perform this, the library requires access to the implementation of these +functions. +Easy::jit does an effort to deduce which functions are specialized at runtime, +still in many cases this is not possible. + +In this case, it's possible to use the ```EASY_JIT_EXPOSE``` macro, as shown in +the following code, + +```cpp +void EASY_JIT_EXPOSE kernel() { /* ... */ } +``` + +or using a regular expression during compilation. +The command bellow exports all functions whose name starts with "^kernel". + +```bash +clang++ ... -mllvm -easy-export="^kernel.*" ... +``` + +#### Caching + +In parallel to the ```easy/jit.h``` header, there is ```easy/code_cache.h``` which +provides a code cache to avoid recompilation of functions that already have been +generated. + +Bellow we show the code from previous section, but adapted to use a code cache. + +```cpp +#include +``` + +```cpp +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + using namespace std::placeholders; + + static easy::Cache<> cache; + auto const &kernel_opt = cache.jit(kernel, mask, mask_size, mask_area, _1, _2, image.rows, image.cols, image.channels()); + kernel_opt(image.ptr(0,0), out->ptr(0,0)); +} +``` + +License +------- + +See file `LICENSE` at the top-level directory of this project. + +Thanks +------ + +Special thanks to Quarkslab for their support on working in personal projects. + +Warriors +-------- + +Serge Guelton (serge_sans_paille) + +Juan Manuel Martinez Caamaño (jmmartinez) + +Kavon Farvardin (kavon) author of [atJIT](https://github.com/kavon/atJIT) diff --git a/easy-jit/benchmark/CMakeLists.txt b/easy-jit/benchmark/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..55738109cf1563d5b5af164bf9d1e4fd969d2ef8 --- /dev/null +++ b/easy-jit/benchmark/CMakeLists.txt @@ -0,0 +1,31 @@ +if(EASY_JIT_BENCHMARK) + set(CMAKE_CXX_COMPILER ${LLVM_TOOLS_BINARY_DIR}/clang++) + set(CMAKE_CXX_FLAGS "") + set(EASYJIT_PLUGIN_FLAGS -Xclang -disable-O0-optnone -Xclang -load -Xclang ${EASY_JIT_PASS} -Xclang -fpass-plugin=${EASY_JIT_PASS}) + + if(EASY_JIT_STANDALONE_BUILD) + add_executable(easyjit-benchmark benchmark.cpp) + target_compile_options(easyjit-benchmark PRIVATE ${EASYJIT_PLUGIN_FLAGS}) + find_package(benchmark CONFIG REQUIRED) + link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + target_link_libraries (easyjit-benchmark benchmark::benchmark) + target_link_libraries (easyjit-benchmark EasyJitRuntime pthread) + target_link_options (easyjit-benchmark PRIVATE "-Wl,-rpath,$") + set_output_directory(easyjit-benchmark BINARY_DIR ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}) + else() + add_benchmark(easyjit-benchmark benchmark.cpp) + target_compile_options(easyjit-benchmark PRIVATE ${EASYJIT_PLUGIN_FLAGS}) + target_link_libraries (easyjit-benchmark PRIVATE EasyJitRuntime pthread) + target_link_options (easyjit-benchmark PRIVATE "-Wl,-rpath,$") + set_output_directory(easyjit-benchmark BINARY_DIR ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}) + endif() + + add_dependencies(easyjit-benchmark easy-jit-core) + + # Put the benchmark executable alongside libraries in monorepo builds. + if(NOT EASY_JIT_STANDALONE_BUILD) + set_target_properties(easyjit-benchmark PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${LLVM_LIBRARY_OUTPUT_INTDIR}") + endif() + +endif() diff --git a/easy-jit/benchmark/benchmark.cpp b/easy-jit/benchmark/benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..13be036360f8d922ae8321d16f0ab4b323701170 --- /dev/null +++ b/easy-jit/benchmark/benchmark.cpp @@ -0,0 +1,154 @@ + +#include +#include +#include +#include +#include + +void __attribute__((noinline)) kernel(int n, int m, int * image, int const * mask, int* out) { + for(int i = 0; i < n - m; ++i) + for(int j = 0; j < n - m; ++j) + for(int k = 0; k < m; ++k) + for(int l = 0; l < m; ++l) + out[i * (n-m+1) + j] += image[(i+k) * n + j+l] * mask[k *m + l]; +} + +/* To sort array elemets */ + +int int_cmp(int a, int b) +{ + if (a > b) + return 1; + else + { + if (a == b) + return 0; + else + return -1; + } +} + +// https://github.com/ctasims/The-C-Programming-Language--Kernighan-and-Ritchie/blob/master/ch04-functions-and-program-structure/qsort.c +void __attribute__((noinline)) Qsort(int v[], int left, int right, int (*cmp)(int, int)) +{ + int i, last; + void swap(int v[], int i, int j); + + if (left >= right) // do nothing if array contains < 2 elems + return; + // move partition elem to v[0] + swap(v, left, (left + right)/2); + last = left; + + for (i = left+1; i <= right; i++) // partition + if (cmp(v[i], v[left])) + swap(v, ++last, i); + + swap(v, left, last); // restore partition elem + Qsort(v, left, last-1, cmp); + Qsort(v, last+1, right, cmp); +} + +/* swap: interchange v[i] and v[j] */ +void swap(int v[], int i, int j) +{ + int temp; + temp = v[i]; + v[i] = v[j]; + v[j] = temp; +} + +static const int mask[3][3] = {{1,2,3},{0,0,0},{3,2,1}}; + +static void BM_convolve(benchmark::State& state) { + using namespace std::placeholders; + + bool jit = state.range(1); + int n = state.range(0); + + std::vector image(n*n,0); + std::vector out((n-3)*(n-3),0); + + auto my_kernel = easy::jit(kernel, n, 3, _1, &mask[0][0], _2); + + benchmark::ClobberMemory(); + + for (auto _ : state) { + if(jit) { + my_kernel(image.data(), out.data()); + } else { + kernel(n, 3, image.data(), &mask[0][0], out.data()); + } + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_convolve)->Ranges({{16,1024}, {0,1}}); + +static void BM_convolve_compile_jit(benchmark::State& state) { + using namespace std::placeholders; + for (auto _ : state) { + auto my_kernel = easy::jit(kernel, 11, 3, _1, &mask[0][0], _2); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_convolve_compile_jit); + +static void BM_convolve_cache_hit_jit(benchmark::State& state) { + using namespace std::placeholders; + static easy::Cache<> cache; + cache.jit(kernel, 11, 3, _1, &mask[0][0], _2); + benchmark::ClobberMemory(); + + for (auto _ : state) { + auto const &my_kernel = cache.jit(kernel, 11, 3, _1, &mask[0][0], _2); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_convolve_cache_hit_jit); + +static void BM_qsort(benchmark::State& state) { + using namespace std::placeholders; + + bool jit = state.range(1); + int n = state.range(0); + + std::vector vec(n); + std::iota(vec.begin(), vec.end(), 0); + std::shuffle(vec.begin(), vec.end(), std::default_random_engine()); + + auto my_qsort = easy::jit(Qsort, _1, _2, _3, int_cmp); + benchmark::ClobberMemory(); + + for (auto _ : state) { + if(jit) { + my_qsort(vec.data(), 0, vec.size()-1); + } else { + Qsort(vec.data(), 0, vec.size()-1, int_cmp); + } + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_qsort)->Ranges({{16,1024}, {0,1}}); + +static void BM_qsort_compile_jit(benchmark::State& state) { + using namespace std::placeholders; + for (auto _ : state) { + auto my_qsort = easy::jit(Qsort, _1, _2, _3, int_cmp); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_qsort_compile_jit); + +static void BM_qsort_cache_hit_jit(benchmark::State& state) { + using namespace std::placeholders; + static easy::Cache<> cache; + cache.jit(Qsort, _1, _2, _3, int_cmp); + benchmark::ClobberMemory(); + for (auto _ : state) { + auto const &my_qsort = cache.jit(Qsort, _1, _2, _3, int_cmp); + benchmark::ClobberMemory(); + } +} +BENCHMARK(BM_qsort_cache_hit_jit); + +BENCHMARK_MAIN(); diff --git a/easy-jit/cmake/CMakeLists.txt b/easy-jit/cmake/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4ee8b898f2c8b112e3433b2c94215c8ef90f8ebc --- /dev/null +++ b/easy-jit/cmake/CMakeLists.txt @@ -0,0 +1,3 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/EasyJitConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/EasyJitConfig.cmake @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/EasyJitConfig.cmake + DESTINATION lib/cmake) diff --git a/easy-jit/cmake/EasyJitConfig.cmake.in b/easy-jit/cmake/EasyJitConfig.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..230a475aac20af3d6084e860e891268c555324ab --- /dev/null +++ b/easy-jit/cmake/EasyJitConfig.cmake.in @@ -0,0 +1,10 @@ +# Try to find the easy::jit library, headers and compiler plugin. +# EasyJit_INCLUDE_DIRS - the easy::jit include directory +# EasyJit_LIBRARY_DIRS - library directory needed to use easy::jit +# EasyJit_LIBRARY - library needed to use easy::jit +# EasyJit_PLUGIN - compiler plugin + +set(EasyJit_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/include") +set(EasyJit_LIBRARY_DIRS "@CMAKE_INSTALL_PREFIX@/lib") +set(EasyJit_LIBRARY "EasyJitRuntime") +set(EasyJit_PLUGIN "@CMAKE_INSTALL_PREFIX@/lib/EasyJitPass@CMAKE_SHARED_LIBRARY_SUFFIX@") diff --git a/easy-jit/doc/CMakeLists.txt b/easy-jit/doc/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..303a069fdcfcb9617c9a0a2c9b1c2e3a79f85a51 --- /dev/null +++ b/easy-jit/doc/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(readme) diff --git a/easy-jit/doc/readme/CMakeLists.txt b/easy-jit/doc/readme/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e37f961fec035084bae68604c1618e7d27aac3d8 --- /dev/null +++ b/easy-jit/doc/readme/CMakeLists.txt @@ -0,0 +1,19 @@ +option(EASY_JIT_EXAMPLE "Build Examples" OFF) + +if(EASY_JIT_EXAMPLE) + find_package(OpenCV REQUIRED) + + set(CMAKE_CXX_COMPILER ${LLVM_TOOLS_BINARY_DIR}/clang++) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -disable-O0-optnone -Xclang -load -Xclang ${EASY_JIT_PASS} -Xclang -fpass-plugin=${EASY_JIT_PASS}") + + add_executable(easyjit-example camfilter.cpp) + add_dependencies(easyjit-example easy-jit-core) + + include_directories(${OpenCV_INCLUDE_DIRS}) + target_link_libraries(easyjit-example ${OpenCV_LIBS}) + + link_directories(${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + + target_link_libraries (easyjit-example ${Benchmark_LIBRARIES}) + target_link_libraries (easyjit-example EasyJitRuntime pthread) +endif() diff --git a/easy-jit/doc/readme/README.md.in b/easy-jit/doc/readme/README.md.in new file mode 100644 index 0000000000000000000000000000000000000000..a169a01d57a59c2fa90808dc54557a662d78127a --- /dev/null +++ b/easy-jit/doc/readme/README.md.in @@ -0,0 +1,167 @@ +Easy::jit: A just-in-time compiler for C++ +========================================== + +About +----- + +Easy::jit is a _compiler-assisted_ library that enables simple Just-In-Time +code generation for C++ codes. + +### Talks + +* [Easy::jit at EuroLLVM'18](https://www.youtube.com/watch?v=sFxqI6Z_bhE) +* [Easy::jit at FOSDEM'18](https://www.youtube.com/watch?v=5_rydTiB32I) + +Building +-------- + +First, install clang and LLVM. + +```bash +apt install llvm-19-dev llvm-19-tools clang-19 +## or in OpenEuler +dnf install llvm-toolset-19 llvm-toolset-19-llvm-devel llvm-toolset-19-mlir-devel +``` + +Then, configure and compile the project. + +```bash +cmake -DLLVM_DIR=/usr/lib/llvm-19/cmake +cmake --build . +## or in OpenEuler +cmake -DLLVM_DIR=/opt/openEuler/llvm-toolset-19/root/usr/lib64/cmake +cmake --build . +``` + +To build the examples, install the [opencv](https://opencv.org/) library, +and add the flags ```-DEASY_JIT_EXAMPLE=1``` to the cmake command. + +To enable benchmarking, install the [google benchmark](https://github.com/google/benchmark) framework, +and add the flags ```-DEASY_JIT_BENCHMARK=1 -DBENCHMARK_DIR=``` to the cmake command. + +Everything is ready to go! + +### Docker + +If you want to give only a quick test to the project, everything is provided to use it with docker. +To do this, generate a Dockerfile from the current directory using the scripts in ```/misc/docker```, +then generate your docker instance. + +```bash +python3 /misc/docker/GenDockerfile.py /.travis.yml > Dockerfile +docker build -t easy/test -f Dockerfile +docker run -ti easy/test /bin/bash +``` + +Basic usage +----------- + +### Compiling my project with Easy::Jit + +Since the Easy::Jit library relies on assistance from the compiler, its +mandatory to load a compiler plugin in order to use it. +The flag ```-Xclang -load -Xclang /bin/EasyJitPass.so -Xclang -fpass-plugin=/bin/EasyJitPass.so``` +loads the plugin. + +The included headers require C++14 support, and remember to add the include directories! +Use ```--std=c++14 -I/cpplib/include```. + +Finaly, the binary must be linked against the Easy::Jit runtime library, using +```-L/bin -lEasyJitRuntime```. + +Putting all together we get the command bellow. + +```bash +clang++-19 --std=c++14 \ + -Xclang -load -Xclang /path/to/easy/jit/build/bin/bin/EasyJitPass.so \ + -I/cpplib/include \ + -L/bin -lEasyJitRuntime +``` + +### Using Easy::Jit inside my project + +Consider the code below from a software that applies image filters on a video stream. +In the following sections we are going to adapt it to use the Easy::jit library. +The function to optimize is ```kernel```, which applies a mask on the entire image. + +The mask, its dimensions and area do not change often, so specializing the function for +these parameters seems reasonable. +Moreover, the image dimensions and number of channels typically remain constant during +the entire execution; however, it is impossible to know their values as they depend on the stream. + +```cpp + +``` + +The main header for the library is ```easy/jit.h```, where the only core function +of the library is exported. This function is called -- guess how? -- ```easy::jit```. +We add the corresponding include directive them in the top of the file. + +```cpp + +``` + +With the call to ```easy::jit```, we specialize the function and obtain a new +one taking only two parameters (the input and the output frame). + +```cpp + +``` + +#### Deducing which functions to expose at runtime + +Easy::jit embeds the [LLVM bitcode](https://llvm.org/docs/LangRef.html) +representation of the functions to specialize at runtime in the binary code. +To perform this, the library requires access to the implementation of these +functions. +Easy::jit does an effort to deduce which functions are specialized at runtime, +still in many cases this is not possible. + +In this case, it's possible to use the ```EASY_JIT_EXPOSE``` macro, as shown in +the following code, + +```cpp +void EASY_JIT_EXPOSE kernel() { /* ... */ } +``` + +or using a regular expression during compilation. +The command bellow exports all functions whose name starts with "^kernel". + +```bash +clang++ ... -mllvm -easy-export="^kernel.*" ... +``` + +#### Caching + +In parallel to the ```easy/jit.h``` header, there is ```easy/code_cache.h``` which +provides a code cache to avoid recompilation of functions that already have been +generated. + +Bellow we show the code from previous section, but adapted to use a code cache. + +```cpp + +``` + +```cpp + +``` + +License +------- + +See file `LICENSE` at the top-level directory of this project. + +Thanks +------ + +Special thanks to Quarkslab for their support on working in personal projects. + +Warriors +-------- + +Serge Guelton (serge_sans_paille) + +Juan Manuel Martinez Caamaño (jmmartinez) + +Kavon Farvardin (kavon) author of [atJIT](https://github.com/kavon/atJIT) diff --git a/easy-jit/doc/readme/camfilter.cpp b/easy-jit/doc/readme/camfilter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09c47920be40567c17d5704057283f2c42187da8 --- /dev/null +++ b/easy-jit/doc/readme/camfilter.cpp @@ -0,0 +1,244 @@ +// REQUIRES: example +// test that it compiles, links and loads correctly. +// RUN: %bin/easyjit-example 1 1 2 3 4 s 1 2 3 4 s 1 2 3 4 q + +#include +#include +#include +#include +#include +#include + +// INLINE FROM HERE #INCLUDE_EASY# +#include +// TO HERE #INCLUDE_EASY# + +// INLINE FROM HERE #INCLUDE_EASY_CACHE# +#include +// TO HERE #INCLUDE_EASY_CACHE# + +struct timeval; + +static double get_fps() { + static struct timeval before; + struct timeval now; + + gettimeofday(&now, nullptr); + + long secs = now.tv_sec - before.tv_sec; + long usec = now.tv_usec - before.tv_usec; + double diff = secs + ((double)usec)/1000000.0; + before = now; + + return 1.0/diff; +} + +namespace original { +// INLINE FROM HERE #ORIGINAL# +static void kernel(const char* mask, unsigned mask_size, unsigned mask_area, + const unsigned char* in, unsigned char* out, + unsigned rows, unsigned cols, unsigned channels) { + unsigned mask_middle = (mask_size/2+1); + unsigned middle = (cols+1)*mask_middle; + + for(unsigned i = 0; i != rows-mask_size; ++i) { + for(unsigned j = 0; j != cols-mask_size; ++j) { + for(unsigned ch = 0; ch != channels; ++ch) { + + long out_val = 0; + for(unsigned ii = 0; ii != mask_size; ++ii) { + for(unsigned jj = 0; jj != mask_size; ++jj) { + out_val += mask[ii*mask_size+jj] * in[((i+ii)*cols+j+jj)*channels+ch]; + } + } + out[(i*cols+j+middle)*channels+ch] = out_val / mask_area; + } + } + } +} + +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + kernel(mask, mask_size, mask_area, image.ptr(0,0), out->ptr(0,0), image.rows, image.cols, image.channels()); +} +// TO HERE #ORIGINAL# +} + +namespace jit { + using original::kernel; +// INLINE FROM HERE #EASY# +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + using namespace std::placeholders; + + auto kernel_opt = easy::jit(kernel, mask, mask_size, mask_area, _1, _2, image.rows, image.cols, image.channels()); + kernel_opt(image.ptr(0,0), out->ptr(0,0)); +} +// TO HERE #EASY# +} + +namespace cache { + using original::kernel; +// INLINE FROM HERE #EASY_CACHE# +static void apply_filter(const char *mask, unsigned mask_size, unsigned mask_area, cv::Mat &image, cv::Mat *&out) { + using namespace std::placeholders; + + static easy::Cache<> cache; + auto const &kernel_opt = cache.jit(kernel, mask, mask_size, mask_area, _1, _2, image.rows, image.cols, image.channels()); + kernel_opt(image.ptr(0,0), out->ptr(0,0)); +} +// TO HERE #EASY_CACHE# +} + +static const char mask_no_filter[1] = {1}; +static const char mask_gauss_3[9] = {1,1,1,1,1,1,1,1,1}; +static const char mask_gauss_5[25] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; +static const char mask_gauss_7[49] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; + +static const char* masks[] = { + mask_no_filter, + mask_gauss_3, + mask_gauss_5, + mask_gauss_7 +}; + +static unsigned mask_size[] = { + 1, + 3, + 5, + 7 +}; + +static unsigned mask_area[] = { + 1, + 9, + 25, + 49 +}; + +static void (*apply_filter)(const char*, unsigned, unsigned, cv::Mat &, cv::Mat *&) = original::apply_filter; + +static char get_keystroke(bool test, char** argv, int argc) { + if(!test) + return cv::waitKey(1); + + // read the first character of each argument and use it as the key stroke + static int key_count = 2; + if(key_count >= argc) + return 'q'; + + char key = argv[key_count][0]; + + key_count++; + return key; +} + +static bool get_next_frame(bool test, cv::Mat &frame) { + if(test) { + // return a black frame + frame = cv::Mat(cv::Size(640, 480), CV_8UC3, cv::Scalar(0)); + return true; + } else { + // capture from the video camara + static cv::VideoCapture video(0); + + if(!video.isOpened()) { + std::cerr << "cannot open camara.\n"; + return false; + } + + video.read(frame); + return true; + } +} + +int main(int argc, char** argv) { + bool test = 0; + if(argc > 2) + test = atoi(argv[1]); + + std::cerr << "\npress 1, 2, 3, 4 to change the filter.\n" + "s to switch the implementation, from original to easy-jit(no cache), and to easy-jit(cache).\n" + "q to exit.\n\n"; + + int mask_no = 0; + + cv::Mat Frame; + static cv::Mat Output; + cv::Mat *Out = nullptr; + + unsigned time = 0; + std::stringstream fps_message; + + double fps_history[4]; + + while (true) { + if(!get_next_frame(test, Frame)) + return -1; + + // allocate the output frame + if(!Out) { + Output = Frame.clone(); + Out = &Output; + } + + get_fps(); + + // apply the filter + apply_filter(masks[mask_no], mask_size[mask_no], mask_area[mask_no], Frame, Out); + + double fps = get_fps(); + fps_history[time%4] = fps; + + if(time % 4 == 0) { + double fps_avg = std::accumulate(std::begin(fps_history), std::end(fps_history), 0.0)/4.0; + fps_message.str(""); + fps_message << "fps: " << fps_avg; + } + + // show the fps, updated every 4 iterations + cv::putText(*Out, fps_message.str().c_str(), cv::Point(30,30), + cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, + cv::Scalar(200,200,250), 1, cv::LINE_AA); + + if(!test) { + cv::imshow("camera", *Out); + } + ++time; + + // user input + char key = get_keystroke(test, argv, argc); + if (key < 0) + continue; + + switch(key) { + case 'q': + return 0; + case 's': + { + if(apply_filter == original::apply_filter) { + std::cerr << "using easy::jit (no-cache) implementation\n"; + apply_filter = jit::apply_filter; + } else if (apply_filter == jit::apply_filter) { + std::cerr << "using easy::jit (cache) implementation\n"; + apply_filter = cache::apply_filter; + } else { + std::cerr << "using original implementation\n"; + apply_filter = original::apply_filter; + } + break; + } + case '1': + case '2': + case '3': + case '4': + { + mask_no = key-'1'; + std::cerr << "change mask to " << mask_no+1 << "!\n"; + break; + } + default: + std::cerr << "unknown stroke " << key << "\n"; + } + } + + return 0; +} diff --git a/easy-jit/doc/readme/readme_is_up-to-date.test b/easy-jit/doc/readme/readme_is_up-to-date.test new file mode 100644 index 0000000000000000000000000000000000000000..ffcd6bb675c92549d701c61ee14eb7d88c4ba414 --- /dev/null +++ b/easy-jit/doc/readme/readme_is_up-to-date.test @@ -0,0 +1,4 @@ +// RUN: cd %S +// RUN: bash update_doc.sh > %t.new +// RUN: cp %S/../../README.md %t.old +// RUN: diff %t.new %t.old diff --git a/easy-jit/doc/readme/update_doc.sh b/easy-jit/doc/readme/update_doc.sh new file mode 100644 index 0000000000000000000000000000000000000000..6ae92cd2636f4eb1200b2589a54713a0f9e133d4 --- /dev/null +++ b/easy-jit/doc/readme/update_doc.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +pushd $DIR >> /dev/null + +easy_jit_dir=../../ +export PYTHONIOENCODING="utf-8" + +cat README.md.in | \ + python3 ${easy_jit_dir}/misc/doc/python.py | \ + python3 ${easy_jit_dir}/misc/doc/include.py diff --git a/easy-jit/doc/slides/cppcon'18.pdf b/easy-jit/doc/slides/cppcon'18.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dcf515e34141acee1c9cec2f1cab27b58cf18ad6 Binary files /dev/null and b/easy-jit/doc/slides/cppcon'18.pdf differ diff --git a/easy-jit/include/CMakeLists.txt b/easy-jit/include/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ad252203ea389c4c6260b052fc00bc2c0e3f28ea --- /dev/null +++ b/easy-jit/include/CMakeLists.txt @@ -0,0 +1,2 @@ +install(DIRECTORY easy + DESTINATION include FILES_MATCHING PATTERN "*.h") diff --git a/easy-jit/include/easy/attributes.h b/easy-jit/include/easy/attributes.h new file mode 100644 index 0000000000000000000000000000000000000000..469d595ea54147e15c2a90b7516ebe6e16782a19 --- /dev/null +++ b/easy-jit/include/easy/attributes.h @@ -0,0 +1,19 @@ +#ifndef NOINLINE +#define NOINLINE + +#define CI_SECTION "segment,compiler-if" +#define JIT_SECTION "segment,easy-jit" +#define LAYOUT_SECTION "segment,layout" + +// mark functions in the easy::jit interface as no inline. +// it's easier for the pass to find the original functions to be jitted. +#define EASY_JIT_COMPILER_INTERFACE \ + __attribute__((noinline)) __attribute__((section(CI_SECTION))) + +#define EASY_JIT_EXPOSE \ + __attribute__((section(JIT_SECTION))) + +#define EASY_JIT_LAYOUT\ + __attribute__((section(LAYOUT_SECTION))) + +#endif // NOINLINE diff --git a/easy-jit/include/easy/code_cache.h b/easy-jit/include/easy/code_cache.h new file mode 100644 index 0000000000000000000000000000000000000000..ba1b8d6f2adb07f43560b9bb91479703a5d74078 --- /dev/null +++ b/easy-jit/include/easy/code_cache.h @@ -0,0 +1,87 @@ +#ifndef CACHE +#define CACHE + +#include +#include + +namespace easy { + +namespace { +using AutoKey = std::pair; + +template +class CacheBase { + + public: + + using Key = KeyTy; + + protected: + + std::unordered_map Cache_; + using iterator = typename std::unordered_map::iterator; + + template + auto const & compile_if_not_in_cache(std::pair &CacheEntry, T &&Fun, Args&& ... args) { + using wrapper_ty = decltype(easy::jit(std::forward(Fun), std::forward(args)...)); + + FunctionWrapperBase &FWB = CacheEntry.first->second; + if(CacheEntry.second) { + auto FW = easy::jit(std::forward(Fun), std::forward(args)...); + FWB = std::move(FW); + } + return reinterpret_cast(FWB); + } +}; +} + +template +class Cache : public CacheBase { + public: + + template + auto const& EASY_JIT_COMPILER_INTERFACE jit(Key const &K, T &&Fun, Args&& ... args) { + auto CacheEntry = CacheBase::Cache_.emplace(K, FunctionWrapperBase()); + return CacheBase::compile_if_not_in_cache(CacheEntry, std::forward(Fun), std::forward(args)...); + } + + template + auto const& EASY_JIT_COMPILER_INTERFACE jit(Key &&K, T &&Fun, Args&& ... args) { + auto CacheEntry = CacheBase::Cache_.emplace(K, FunctionWrapperBase()); + return CacheBase::compile_if_not_in_cache(CacheEntry, std::forward(Fun), std::forward(args)...); + } + + bool has(Key const &K) const { + auto const CacheEntry = CacheBase::Cache_.find(K); + return CacheEntry != CacheBase::Cache_.end(); + } +}; + + +template<> +class Cache : public CacheBase { + public: + + template + auto const& EASY_JIT_COMPILER_INTERFACE jit(T &&Fun, Args&& ... args) { + void* FunPtr = reinterpret_cast(meta::get_as_pointer(Fun)); + auto CacheEntry = + CacheBase::Cache_.emplace( + Key(FunPtr, get_context_for(std::forward(args)...)), + FunctionWrapperBase()); + return CacheBase::compile_if_not_in_cache(CacheEntry, std::forward(Fun), std::forward(args)...); + } + + template + bool has(T &&Fun, Args&& ... args) const { + void* FunPtr = reinterpret_cast(meta::get_as_pointer(Fun)); + auto const CacheEntry = + CacheBase::Cache_.find(Key(FunPtr, + get_context_for(std::forward(args)...))); + return CacheEntry != Cache_.end(); + } +}; + +} + +#endif // CACHE diff --git a/easy-jit/include/easy/exceptions.h b/easy-jit/include/easy/exceptions.h new file mode 100644 index 0000000000000000000000000000000000000000..2fad51aab34f1f529ae6c2cbfca7486dbf916a05 --- /dev/null +++ b/easy-jit/include/easy/exceptions.h @@ -0,0 +1,24 @@ +#ifndef EXCEPTIONS +#define EXCEPTIONS + +#include +#include +#include + +namespace easy { + struct exception + : public std::runtime_error { + exception(std::string const &Message, std::string const &Reason) + : std::runtime_error(Message + Reason) {} + virtual ~exception() = default; + }; +} + +#define DefineEasyException(Exception, Message) \ + struct Exception : public easy::exception { \ + Exception() : easy::exception(Message, "") {} \ + Exception(std::string const &Reason) : easy::exception(Message, Reason) {} \ + virtual ~Exception() = default; \ + } + +#endif diff --git a/easy-jit/include/easy/function_wrapper.h b/easy-jit/include/easy/function_wrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..68e4bf475e3665e5d2f4e163bbaf52395f83cb71 --- /dev/null +++ b/easy-jit/include/easy/function_wrapper.h @@ -0,0 +1,132 @@ +#ifndef FUNCTION_WRAPPER +#define FUNCTION_WRAPPER + +#include +#include +#include +#include + +#ifndef _MSC_VER +#define __cdecl +#endif + +namespace easy { + +class FunctionWrapperBase { + + protected: + std::unique_ptr Fun_; + + public: + // null object + FunctionWrapperBase() = default; + + // default constructor + FunctionWrapperBase(std::unique_ptr F) + : Fun_(std::move(F)) {} + + // steal the implementation + FunctionWrapperBase(FunctionWrapperBase &&FW) + : Fun_(std::move(FW.Fun_)) {} + FunctionWrapperBase& operator=(FunctionWrapperBase &&FW) { + Fun_ = std::move(FW.Fun_); + return *this; + } + + Function const& getFunction() const { + return *Fun_; + } + + void* getRawPointer() const { + return getFunction().getRawPointer(); + } + + void serialize(std::ostream& os) const { + getFunction().serialize(os); + } + + static FunctionWrapperBase deserialize(std::istream& is) { + std::unique_ptr Fun = Function::deserialize(is); + return FunctionWrapperBase{std::move(Fun)}; + } +}; + +template +class FunctionWrapper; + +template +class FunctionWrapper : + public FunctionWrapperBase { + public: + FunctionWrapper(std::unique_ptr F) + : FunctionWrapperBase(std::move(F)) {} + + template + Ret operator()(Args&& ... args) const { + return getFunctionPointer()(std::forward(args)...); + } + + auto getFunctionPointer() const { + return reinterpret_cast(getRawPointer()); + } + + static FunctionWrapper deserialize(std::istream& is) { + std::unique_ptr Fun = Function::deserialize(is); + return FunctionWrapper{std::move(Fun)}; + } +}; + +// specialization for void return +template +class FunctionWrapper : + public FunctionWrapperBase { + public: + FunctionWrapper(std::unique_ptr F) + : FunctionWrapperBase(std::move(F)) {} + + template + void operator()(Args&& ... args) const { + return getFunctionPointer()(std::forward(args)...); + } + + auto getFunctionPointer() const { + return ((void(__cdecl *)(Params...))getRawPointer()); + } + + static FunctionWrapper deserialize(std::istream& is) { + std::unique_ptr Fun = Function::deserialize(is); + return FunctionWrapper{std::move(Fun)}; + } +}; + +template +struct is_function_wrapper { + + template + struct is_function_wrapper_helper { + static constexpr bool value = false; + }; + + template + struct is_function_wrapper_helper> { + static constexpr bool value = true; + using return_type = Ret; + using params = meta::type_list; + }; + + using helper = is_function_wrapper_helper>; + + static constexpr bool value = helper::value; +}; + +template +struct is_function_wrapper> { + static constexpr bool value = true; + using return_type = Ret; + using params = meta::type_list; +}; + + +} + +#endif diff --git a/easy-jit/include/easy/jit.h b/easy-jit/include/easy/jit.h new file mode 100644 index 0000000000000000000000000000000000000000..f9e48908618be62d3135db1534940f13b662d6d6 --- /dev/null +++ b/easy-jit/include/easy/jit.h @@ -0,0 +1,68 @@ +#ifndef EASY +#define EASY + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace easy { + +namespace { +template +FunctionWrapper +WrapFunction(std::unique_ptr F, meta::type_list) { + return FunctionWrapper(std::move(F)); +} + +template +auto jit_with_context(easy::Context const &C, T &&Fun) { + + auto* FunPtr = meta::get_as_pointer(Fun); + using FunOriginalTy = std::remove_pointer_t>; + + using new_type_traits = meta::new_function_traits>; + using new_return_type = typename new_type_traits::return_type; + using new_parameter_types = typename new_type_traits::parameter_list; + + auto CompiledFunction = + Function::Compile(reinterpret_cast(FunPtr), C); + + auto Wrapper = + WrapFunction(std::move(CompiledFunction), + typename new_parameter_types::template push_front ()); + return Wrapper; +} + +template +easy::Context get_context_for(Args&& ... args) { + using FunOriginalTy = std::remove_pointer_t>; + static_assert(std::is_function::value, + "easy::jit: supports only on functions and function pointers"); + + using parameter_list = typename meta::function_traits::parameter_list; + + static_assert(parameter_list::size <= sizeof...(Args), + "easy::jit: not providing enough argument to actual call"); + + easy::Context C; + easy::set_parameters(parameter_list(), C, + std::forward(args)...); + return C; +} +} + +template +auto EASY_JIT_COMPILER_INTERFACE jit(T &&Fun, Args&& ... args) { + auto C = get_context_for(std::forward(args)...); + return jit_with_context(C, std::forward(Fun)); +} + +} + +#endif diff --git a/easy-jit/include/easy/meta.h b/easy-jit/include/easy/meta.h new file mode 100644 index 0000000000000000000000000000000000000000..c095fa87ae975e3800082d427b425ba368acef88 --- /dev/null +++ b/easy-jit/include/easy/meta.h @@ -0,0 +1,211 @@ +#ifndef META +#define META + +#include +#include +#include +#include + +namespace easy { +namespace meta { + +template +struct type_list { + + template + using push_front = struct type_list; + template + using push_back = struct type_list; + template + using remove = type_list<>; + template + static constexpr bool has = false; + + // undefined + template + using at = void; + template + using set = meta::type_list<>; + + static constexpr size_t size = 0; + static constexpr bool empty = true; +}; + +template +struct type_list { + using head = Head; + using tail = struct type_list; + + template + using push_front = struct type_list; + template + using push_back = struct type_list; + template + using at = std::conditional_t>; + + template + using set = std::conditional_t, + typename tail::template set::template push_front>; + + template + using remove = std::conditional_t::value, + typename tail::template remove, + typename tail::template remove::template push_front>; + template + static constexpr bool has = std::is_same::value || tail:: template has; + + static constexpr size_t size = 1+tail::size; + static constexpr bool empty = false; +}; + +template +struct init_list { + + template + struct helper { + using type = typename helper::type::template push_front; + }; + + template + struct helper<0,TT> { + using type = meta::type_list<>; + }; + + using type = typename helper::type; +}; + +template +T* get_as_pointer(T &&A) { return &A; } + +template +T* get_as_pointer(T* A) { return A; } + + +namespace { + +template +using is_ph = std::is_placeholder>; + +template +struct discard_options { + + template + struct helper { + using type = meta::type_list<>; + }; + + template + struct helper { + using head = typename AL::head; + using tail = typename AL::tail; + static bool constexpr is_opt = + options::is_option>::value; + using recursive = typename helper::type; + using type = std::conditional_t>; + }; + + using type = typename helper::type; +}; + +template +struct max_placeholder { + + template + struct helper { + static constexpr size_t max = Max; + }; + + template + struct helper { + using head = typename AL::head; + using tail = typename AL::tail; + static constexpr size_t max = helper(is_ph::value, Max)>::max; + }; + + static constexpr size_t max = helper::max; +}; + +template +struct map_placeholder_to_type { + + template + struct helper { + static_assert(Seen::size == N, "Seen::size != N"); + static_assert(Result::size == N, "Result::size != N"); + static_assert(!Result::template has, "Void cannot appear in the resulting type"); + using type = Result; + }; + + template + struct helper{ + using al_head = typename AL::head; + using al_tail = typename AL::tail; + + static bool constexpr parse_placeholder = is_ph::value && !Seen::template has; + static size_t constexpr result_idx = parse_placeholder?is_ph::value-1:0; + static size_t constexpr arg_idx = PL::size - AL::size; + using pl_at_idx = typename PL::template at; + + // for + // foo(int, bool, float) and specialization foo(int(4), _2, _1) + // [int,bool,float] [int,_2,_1] [void,void] [] 2 + // [int,bool,float] [_2,_1] [void,void] [] 2 + // [int,bool,float] [_1] [void,bool] [_2] 2 + // [int,bool,float] [] [float,bool] [_2,_1] 2 + // yields new foo'(float _1, bool _2) = foo(int(4), _2, _1); + + static_assert(PL::size >= AL::size, "easy::jit: More parameters than arguments specified"); + static_assert(result_idx < Result::size, "easy::jit: Cannot have a placeholder outside the maximum"); + using new_result = std::conditional_t, Result>; + using new_seen = std::conditional_t, Seen>; + + using type = typename helper::type; + }; + template + struct helper, Result, Seen, N, false>{ + static_assert(N==-1 /*just to make this context dependent*/, "easy::jit: Invalid bind, placeholder cannot be bound to a formal argument"); + }; + + using default_param = void; + static constexpr size_t N = max_placeholder::max; + using type = typename helper::type, meta::type_list<>, N, N == 0>::type; +}; + +template +struct get_new_param_list { + using type = typename map_placeholder_to_type::type; +}; + +template +Ret get_return_type(Ret(*)(Args...)); + +template +type_list get_parameter_list(Ret(*)(Args...)); + +} + +template +struct function_traits { + static_assert(std::is_function::value, "function expected."); + using ptr_ty = std::decay_t; + using return_type = decltype(get_return_type(std::declval())); + using parameter_list = decltype(get_parameter_list(std::declval())); +}; + +template +struct new_function_traits { + using OriginalTraits = function_traits; + using return_type = typename OriginalTraits::return_type; + using clean_arg_list = typename discard_options::type; + using parameter_list = typename get_new_param_list::type; +}; + +} +} + + + +#endif diff --git a/easy-jit/include/easy/options.h b/easy-jit/include/easy/options.h new file mode 100644 index 0000000000000000000000000000000000000000..f01648a5562fa6f6e1c8d9db0919ab11e6bd99ca --- /dev/null +++ b/easy-jit/include/easy/options.h @@ -0,0 +1,50 @@ +#ifndef OPTIONS +#define OPTIONS + +#include + +#define EASY_NEW_OPTION_STRUCT(Name) \ + struct Name; \ + template<> struct is_option { \ + static constexpr bool value = true; }; \ + struct Name + +#define EASY_HANDLE_OPTION_STRUCT(Name, Ctx) \ + void handle(easy::Context &Ctx) const + + +namespace easy { +namespace options{ + + template + struct is_option { + static constexpr bool value = false; + }; + + EASY_NEW_OPTION_STRUCT(opt_level) + : public std::pair { + + opt_level(unsigned OptLevel, unsigned OptSize) + : std::pair(OptLevel,OptSize) {} + + EASY_HANDLE_OPTION_STRUCT(opt_level, C) { + C.setOptLevel(first, second); + } + }; + + // option used for writing the ir to a file, useful for debugging + EASY_NEW_OPTION_STRUCT(dump_ir) { + dump_ir(std::string const &file) + : file_(file) {} + + EASY_HANDLE_OPTION_STRUCT(dump_ir, C) { + C.setDebugFile(file_); + } + + private: + std::string file_; + }; +} +} + +#endif // OPTIONS diff --git a/easy-jit/include/easy/param.h b/easy-jit/include/easy/param.h new file mode 100644 index 0000000000000000000000000000000000000000..1ea63de6a6c6d8bf6d8a36e7d0d224ec8b464033 --- /dev/null +++ b/easy-jit/include/easy/param.h @@ -0,0 +1,162 @@ +#ifndef PARAM +#define PARAM + +#include +#include +#include +#include +#include + +namespace easy { + +namespace layout { + + // the address of this function is used as id :) + template + char* serialize_arg(Arg a); + + template + layout_id __attribute__((noinline)) EASY_JIT_LAYOUT get_layout() { + // horrible hack to get the ptr of get_struct_layout_internal as void* + union { + void* as_void_ptr; + decltype(serialize_arg)* as_fun_ptr; + } dummy; + dummy.as_fun_ptr = serialize_arg; + return dummy.as_void_ptr; + } + + template + void set_layout(easy::Context &C) { + C.setArgumentLayout(get_layout()); + } +} + +namespace { + +// special types +template +struct set_parameter_helper { + + template + struct function_wrapper_specialization_is_possible { + + template + static std::true_type can_assign_fun_pointer(std::remove_pointer_t); + + template + static std::false_type can_assign_fun_pointer (...); + + using type = decltype(can_assign_fun_pointer( + *std::declval().getFunctionPointer())); + + static constexpr bool value { type::value }; + }; + + template + using _if = std::enable_if_t; + + template + static void set_param(Context &C, + _if<(bool)std::is_placeholder>::value, Arg>) { + C.setParameterIndex(std::is_placeholder>::value-1); + } + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + static_assert(function_wrapper_specialization_is_possible::value, + "easy::jit composition is not possible. Incompatible types."); + C.setParameterModule(arg.getFunction()); + } +}; + +template<> +struct set_parameter_helper { + + template + using _if = std::enable_if_t; + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + Param arg_as_param = arg; + C.setParameterInt(arg_as_param); + } + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + Param arg_as_param = arg; + C.setParameterFloat(arg_as_param); + } + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + Param arg_as_param = arg; + C.setParameterTypedPointer(arg_as_param); + } + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + C.setParameterTypedPointer(std::addressof(arg)); + } + + template + static void set_param(Context &C, + _if::value, Arg> &&arg) { + C.setParameterStruct(layout::serialize_arg(arg)); + } +}; + +template +struct set_parameter { + + static constexpr bool is_ph = std::is_placeholder>::value; + static constexpr bool is_fw = easy::is_function_wrapper::value; + static constexpr bool is_special = is_ph || is_fw; + + using help = set_parameter_helper; +}; + +} + +template +void set_options(Context &, NoOptions&& ...) { + static_assert(meta::type_list::empty, "Remaining options to be processed!"); +} + +template +void set_options(Context &C, Option0&& Opt, Options&& ... Opts) { + using OptTy = std::decay_t; + OptTy& OptRef = std::ref(Opt); + static_assert(options::is_option::value, "An easy::jit option is expected"); + + OptRef.handle(C); + set_options(C, std::forward(Opts)...); +} + +template +std::enable_if_t +set_parameters(ParameterList, + Context& C, Options&& ... opts) { + set_options(C, std::forward(opts)...); +} + +template +std::enable_if_t +set_parameters(ParameterList, + Context &C, Arg0 &&arg0, Args&& ... args) { + using Param0 = typename ParameterList::head; + using ParametersTail = typename ParameterList::tail; + + layout::set_layout(C); + set_parameter::help::template set_param(C, std::forward(arg0)); + set_parameters(ParametersTail(), C, std::forward(args)...); +} + +} + +#endif // PARAM diff --git a/easy-jit/include/easy/runtime/BitcodeTracker.h b/easy-jit/include/easy/runtime/BitcodeTracker.h new file mode 100644 index 0000000000000000000000000000000000000000..30804a910c8df2c90dbe93e99d16b2a8a07a0b37 --- /dev/null +++ b/easy-jit/include/easy/runtime/BitcodeTracker.h @@ -0,0 +1,71 @@ +#ifndef BITCODETRACKER +#define BITCODETRACKER + +#include +#include +#include + +#include + +#include +#include + +namespace easy { + +struct GlobalMapping { + const char* Name; + void* Address; +}; + +struct FunctionInfo { + const char* Name; + GlobalMapping* Globals; + const char* Bitcode; + size_t BitcodeLen; + + FunctionInfo(const char* N, GlobalMapping* G, const char* B, size_t BL) + : Name(N), Globals(G), Bitcode(B), BitcodeLen(BL) + { } +}; + +struct LayoutInfo { + size_t NumFields; +}; + +class BitcodeTracker { + + // map function to all the info required for jit compilation + std::unordered_map Functions; + std::unordered_map NameToAddress; + + // map the addresses of the layout_id with the number of parameters + std::unordered_map Layouts; + + public: + + void registerFunction(void* FPtr, const char* Name, GlobalMapping* Globals, const char* Bitcode, size_t BitcodeLen) { + // llvm::dbgs() << "[RUNTIME] " __FILE__ ":" << __LINE__<< " Register function " << Name << "\n"; + Functions.emplace(FPtr, FunctionInfo{Name, Globals, Bitcode, BitcodeLen}); + NameToAddress.emplace(Name, FPtr); + } + + void registerLayout(layout_id Id, size_t N) { + Layouts.emplace(Id, LayoutInfo{N}); + } + + void* getAddress(std::string const &Name); + std::tuple getNameAndGlobalMapping(void* FPtr); + bool hasGlobalMapping(void* FPtr) const; + LayoutInfo const & getLayoutInfo(easy::layout_id id) const; + + using ModuleContextPair = std::pair, std::unique_ptr>; + ModuleContextPair getModule(void* FPtr); + std::unique_ptr getModuleWithContext(void* FPtr, llvm::LLVMContext &C); + + // get the singleton object + static BitcodeTracker& GetTracker(); +}; + +} + +#endif diff --git a/easy-jit/include/easy/runtime/Context.h b/easy-jit/include/easy/runtime/Context.h new file mode 100644 index 0000000000000000000000000000000000000000..b5a06a8a78dc6dc98d994c88718a505125fddc37 --- /dev/null +++ b/easy-jit/include/easy/runtime/Context.h @@ -0,0 +1,235 @@ +#ifndef CONTEXT +#define CONTEXT + +#include +#include +#include +#include +#include +#include + +#include + +namespace easy { + +struct serialized_arg { + std::vector buf; + + serialized_arg(char* serialized) { + uint32_t size = *reinterpret_cast(serialized); + const char* data = serialized + sizeof(uint32_t); + buf.insert(buf.end(), data, data+size); + + free(serialized); + } +}; + +typedef void* layout_id; + +struct ArgumentBase { + + enum ArgumentKind { + AK_Forward, + AK_Int, + AK_Float, + AK_Ptr, + AK_Struct, + AK_Module, + }; + + ArgumentBase() = default; + virtual ~ArgumentBase() = default; + + bool operator==(ArgumentBase const &Other) const { + return this->kind() == Other.kind() && + this->compareWithSameType(Other); + } + + template + std::enable_if_t::value, ArgTy const*> + as() const { + if(kind() == ArgTy::Kind) return static_cast(this); + else return nullptr; + } + + friend std::hash; + + virtual ArgumentKind kind() const noexcept = 0; + + protected: + virtual bool compareWithSameType(ArgumentBase const&) const = 0; + virtual size_t hash() const noexcept = 0; +}; + +#define DeclareArgument(Name, Type) \ + class Name##Argument \ + : public ArgumentBase { \ + \ + using HashType = std::remove_const_t>; \ + \ + Type Data_; \ + public: \ + Name##Argument(Type D) : ArgumentBase(), Data_(D) {} \ + virtual ~Name ## Argument() override = default ;\ + Type get() const { return Data_; } \ + static constexpr ArgumentKind Kind = AK_##Name;\ + ArgumentKind kind() const noexcept override { return Kind; } \ + \ + protected: \ + bool compareWithSameType(ArgumentBase const& Other) const override { \ + auto const &OtherCast = static_cast(Other); \ + return Data_ == OtherCast.Data_; \ + } \ + \ + size_t hash() const noexcept override { return std::hash{}(Data_); } \ + } + +DeclareArgument(Forward, unsigned); +DeclareArgument(Int, int64_t); +DeclareArgument(Float, double); +DeclareArgument(Ptr, void const*); +DeclareArgument(Module, easy::Function const&); + +class StructArgument + : public ArgumentBase { + serialized_arg Data_; + + public: + StructArgument(serialized_arg &&arg) + : ArgumentBase(), Data_(arg) {} + virtual ~StructArgument() override = default; + std::vector const & get() const { return Data_.buf; } + static constexpr ArgumentKind Kind = AK_Struct; + ArgumentKind kind() const noexcept override { return Kind; } + + protected: + bool compareWithSameType(ArgumentBase const& Other) const override { + auto const &OtherCast = static_cast(Other); + return get() == OtherCast.get(); + } + + size_t hash() const noexcept override { + std::hash hash{}; + size_t R = 0; + for (char c : get()) + R ^= hash(c); + return R; + } +}; + +// class that holds information about the just-in-time context +class Context { + + std::vector> ArgumentMapping_; + unsigned OptLevel_ = 2, OptSize_ = 0; + std::string DebugFile_; + + // describes how the arguments of the function are passed + // struct arguments can be packed in a single int, or passed field by field, + // keep track of how many arguments a parameter takes + std::vector ArgumentLayout_; + + template + inline Context& setArg(Args && ... args) { + ArgumentMapping_.emplace_back(new ArgTy(std::forward(args)...)); + return *this; + } + + public: + + Context() = default; + + bool operator==(const Context&) const; + + // set the mapping between + Context& setParameterIndex(unsigned); + Context& setParameterInt(int64_t); + Context& setParameterFloat(double); + Context& setParameterPointer(void const*); + Context& setParameterStruct(serialized_arg); + Context& setParameterModule(easy::Function const&); + + Context& setArgumentLayout(layout_id id) { + ArgumentLayout_.push_back(id); // each layout id is associated with a number of fields in the bitcode tracker + return *this; + } + + decltype(ArgumentLayout_) const & getLayout() const { + return ArgumentLayout_; + } + + template + Context& setParameterTypedPointer(T* ptr) { + return setParameterPointer(reinterpret_cast(ptr)); + } + + Context& setOptLevel(unsigned OptLevel, unsigned OptSize) { + OptLevel_ = OptLevel; + OptSize_ = OptSize; + return *this; + } + + Context& setDebugFile(std::string const &File) { + DebugFile_ = File; + return *this; + } + + std::pair getOptLevel() const { + return std::make_pair(OptLevel_, OptSize_); + } + + std::string const& getDebugFile() const { + return DebugFile_; + } + + auto begin() const { return ArgumentMapping_.begin(); } + auto end() const { return ArgumentMapping_.end(); } + size_t size() const { return ArgumentMapping_.size(); } + + ArgumentBase const& getArgumentMapping(size_t i) const { + return *ArgumentMapping_[i]; + } + + friend bool operator<(easy::Context const &C1, easy::Context const &C2); +}; + +} + +namespace std +{ + template struct hash> + { + typedef std::pair argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& s) const noexcept { + return std::hash{}(s.first) ^ std::hash{}(s.second); + } + }; + + template<> struct hash + { + typedef easy::ArgumentBase argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& s) const noexcept { + return s.hash(); + } + }; + + template<> struct hash + { + typedef easy::Context argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& C) const noexcept { + size_t H = 0; + std::hash ArgHash; + std::hash> OptHash; + for(auto const &Arg : C) + H ^= ArgHash(*Arg); + H ^= OptHash(C.getOptLevel()); + return H; + } + }; +} + + +#endif diff --git a/easy-jit/include/easy/runtime/Function.h b/easy-jit/include/easy/runtime/Function.h new file mode 100644 index 0000000000000000000000000000000000000000..4251f44e403d90713ee52be4661f55ed2106adb9 --- /dev/null +++ b/easy-jit/include/easy/runtime/Function.h @@ -0,0 +1,58 @@ +#ifndef FUNCTION +#define FUNCTION + +#include "LLVMHolder.h" +#include + +namespace easy { + class Function; +} + +namespace llvm { + class Module; +} + +namespace std { + template<> struct hash + { + typedef easy::Function argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& F) const noexcept; + }; +} + +namespace easy { + +class Context; +struct GlobalMapping; + +class Function { + + // do not reorder the fields and do not add virtual methods! + void* Address; + std::unique_ptr Holder; + + public: + + Function(void* Addr, std::unique_ptr H); + + void* getRawPointer() const { + return Address; + } + + void serialize(std::ostream&) const; + static std::unique_ptr deserialize(std::istream&); + + bool operator==(easy::Function const&) const; + + llvm::Module const& getLLVMModule() const; + + static std::unique_ptr Compile(void *Addr, easy::Context const &C); + + friend + std::hash::result_type std::hash::operator()(argument_type const& F) const noexcept; +}; + +} + +#endif diff --git a/easy-jit/include/easy/runtime/LLVMHolder.h b/easy-jit/include/easy/runtime/LLVMHolder.h new file mode 100644 index 0000000000000000000000000000000000000000..9ce5cf717bb670f81bd719d14f9464b1b2125820 --- /dev/null +++ b/easy-jit/include/easy/runtime/LLVMHolder.h @@ -0,0 +1,10 @@ +#ifndef LLVMHOLDER +#define LLVMHOLDER +namespace easy { +class LLVMHolder { + public: + virtual ~LLVMHolder() = default; +}; +} + +#endif diff --git a/easy-jit/include/easy/runtime/LLVMHolderImpl.h b/easy-jit/include/easy/runtime/LLVMHolderImpl.h new file mode 100644 index 0000000000000000000000000000000000000000..6b1b4b537aac0d4a9992c3ab57d5bc0783cbcc1d --- /dev/null +++ b/easy-jit/include/easy/runtime/LLVMHolderImpl.h @@ -0,0 +1,25 @@ +#ifndef LLVMHOLDER_IMPL +#define LLVMHOLDER_IMPL + +#include + +#include +#include + +namespace easy { +class LLVMHolderImpl : public easy::LLVMHolder { + public: + + std::unique_ptr Context_; + std::unique_ptr Engine_; + llvm::Module* M_; // the execution engine has the ownership + + LLVMHolderImpl(std::unique_ptr EE, std::unique_ptr C, llvm::Module* M) + : Context_(std::move(C)), Engine_(std::move(EE)), M_(M) { + } + + virtual ~LLVMHolderImpl() = default; +}; +} + +#endif diff --git a/easy-jit/include/easy/runtime/RuntimePasses.h b/easy-jit/include/easy/runtime/RuntimePasses.h new file mode 100644 index 0000000000000000000000000000000000000000..48e3c88e52d26718bce9b26baded7e6c1a5396b9 --- /dev/null +++ b/easy-jit/include/easy/runtime/RuntimePasses.h @@ -0,0 +1,58 @@ +#ifndef RUNTIME_PASSES +#define RUNTIME_PASSES + +#include +#include +#include +#include + +namespace easy { + + struct ContextAnalysisResult { + private: + easy::Context const *C; + public: + explicit ContextAnalysisResult(easy::Context const &C); + ContextAnalysisResult(); + + easy::Context const &getContext() const { return *C; } + bool invalidate(llvm::Module &M, const llvm::PreservedAnalyses &PA, + llvm::ModuleAnalysisManager::Invalidator &Inv) { return false; } + }; + + class ContextAnalysisPass : public llvm::AnalysisInfoMixin { + friend llvm::AnalysisInfoMixin; + static llvm::AnalysisKey Key; + + public: + explicit ContextAnalysisPass(easy::Context const &C); + ContextAnalysisPass(); + using Result = ContextAnalysisResult; + Result run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM); + + private: + Result Result_; + }; + + + class InlineParametersPass : public llvm::PassInfoMixin { + public: + explicit InlineParametersPass(llvm::StringRef TargetName); + InlineParametersPass(); + llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM); + private: + llvm::StringRef TargetName_; + }; + + class DevirtualizeConstantPass : public llvm::PassInfoMixin { + public: + explicit DevirtualizeConstantPass(llvm::StringRef TargetName); + DevirtualizeConstantPass(); + llvm::PreservedAnalyses run(llvm::Function &F, llvm::FunctionAnalysisManager &FAM); + private: + llvm::StringRef TargetName_; + }; + +} + +#endif diff --git a/easy-jit/include/easy/runtime/Utils.h b/easy-jit/include/easy/runtime/Utils.h new file mode 100644 index 0000000000000000000000000000000000000000..3619c2a9a3998942bf5634b4fa7b8f7b0324e669 --- /dev/null +++ b/easy-jit/include/easy/runtime/Utils.h @@ -0,0 +1,25 @@ +#ifndef UTILS +#define UTILS + +#include +#include +#include + +namespace llvm { + class LLVMContext; + class Module; + class Function; +} + +namespace easy { + +llvm::StringRef GetEntryFunctionName(llvm::Module const &M); +void MarkAsEntry(llvm::Function &F); +void UnmarkEntry(llvm::Module &M); + +std::unique_ptr +CloneModuleWithContext(llvm::Module const &LM, llvm::LLVMContext &C); + +} + +#endif diff --git a/easy-jit/misc/doc/generate.py b/easy-jit/misc/doc/generate.py new file mode 100644 index 0000000000000000000000000000000000000000..71ab10d41a30be81ca736e37596b08e72744de3d --- /dev/null +++ b/easy-jit/misc/doc/generate.py @@ -0,0 +1,32 @@ +import re +import sys + +def get_split(tag): + start = re.compile("") + def split(contents): + start_match = start.search(contents, pos=0) + if not start_match: + return contents, "", "", False + end_match = end.search(contents, pos=start_match.end()) + if not end_match: + return contents, "", "", False + pre = contents[0 : start_match.start()] + args = contents[start_match.end() : end_match.start()] + post = contents[end_match.end() : ] + return pre, args, post, True + + return split + +def match_and_expand(tag, expand_with): + do_match_and_expand(sys.stdin.read(), get_split(tag), expand_with) + return + +def do_match_and_expand(contents, split, expand_with): + pre, code, post, match = split(contents) + if match : + do_match_and_expand(pre, split, expand_with) + expand_with(code) + do_match_and_expand(post, split, expand_with) + else: + print(contents, end='') diff --git a/easy-jit/misc/doc/include.py b/easy-jit/misc/doc/include.py new file mode 100644 index 0000000000000000000000000000000000000000..e464e4790cf2c89cff45d79dfa4ca960444622eb --- /dev/null +++ b/easy-jit/misc/doc/include.py @@ -0,0 +1,19 @@ +import generate +import re + + +def on_include(args): + args = args.split() + filename = args[0]; + label = args[1]; + + all_code = open(filename.strip()).read() + + inline = re.compile(".*// INLINE FROM HERE #"+ label +"#(?P.*)// TO HERE #" + label + "#.*", flags=re.DOTALL) + code = inline.match(all_code) + code = code.group(1).rstrip().lstrip() + + print(code, end='') + return + +generate.match_and_expand("include", on_include) diff --git a/easy-jit/misc/doc/python.py b/easy-jit/misc/doc/python.py new file mode 100644 index 0000000000000000000000000000000000000000..40703b2c54d27358524d33f191adcf5a376a155d --- /dev/null +++ b/easy-jit/misc/doc/python.py @@ -0,0 +1,7 @@ +import generate + +def on_python(python_code): + exec (python_code) in {'__builtins__':{}}, {} + return + +generate.match_and_expand("python", on_python) diff --git a/easy-jit/misc/docker/GenDockerfile.py b/easy-jit/misc/docker/GenDockerfile.py new file mode 100644 index 0000000000000000000000000000000000000000..aa9d22f49b1a70b8f8deeea2e5a7cf8725fabcb2 --- /dev/null +++ b/easy-jit/misc/docker/GenDockerfile.py @@ -0,0 +1,54 @@ +import yaml +import sys + +Head = "# Dockerfile derived from easy::jit's .travis.yml" +From = "ubuntu:latest" +Manteiner = "Juan Manuel Martinez Caamaño jmartinezcaamao@gmail.com" +base_packages = ['build-essential', 'python', 'python-pip', 'git', 'wget', 'unzip', 'cmake'] + +travis = yaml.load(open(sys.argv[1])) +travis_sources = travis['addons']['apt']['sources'] +travis_packages = travis['addons']['apt']['packages'] +before_install = travis['before_install'] +script = travis['script'] + +# I could not get a better way to do this +AddSourceCmd = { + "llvm-toolchain-trusty-6.0" : "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-6.0 main | tee -a /etc/apt/sources.list > /dev/null", + "ubuntu-toolchain-r-test" : "apt-add-repository -y \"ppa:ubuntu-toolchain-r/test\"" +} + +Sources = ["RUN {cmd} \n".format(cmd=AddSourceCmd[source]) for source in travis_sources] + +Apt = """# add sources +RUN apt-get update +RUN apt-get install -y software-properties-common +{AddSources} +# install apt packages, base first, then travis +RUN apt-get update +RUN apt-get upgrade -y +RUN apt-get install -y {base_packages} && \\ + apt-get install -y {travis_packages} +""".format(AddSources = "".join(Sources), base_packages = " ".join(base_packages), travis_packages=" ".join(travis_packages)) + +Checkout = "RUN git clone --depth=50 --branch=${branch} https://github.com/jmmartinez/easy-just-in-time.git easy-just-in-time && cd easy-just-in-time\n" +BeforeInstall = "".join(["RUN cd /easy-just-in-time && {0} \n".format(cmd) for cmd in before_install]) +Run = "RUN cd easy-just-in-time && \\\n" + "".join([" {cmd} && \\ \n".format(cmd=cmd) for cmd in script]) + " echo ok!" + +Template = """{Head} + +FROM {From} + +LABEL manteiner {Manteiner} + +ARG branch=master + +{Apt} +# checkout +{Checkout} +# install other deps +{BeforeInstall} +# compile and test! +{Run}""" + +print(Template.format(Head=Head, From=From, Manteiner=Manteiner, Apt=Apt, BeforeInstall=BeforeInstall, Checkout=Checkout, Run=Run)) diff --git a/easy-jit/misc/docker/build_docker.sh b/easy-jit/misc/docker/build_docker.sh new file mode 100644 index 0000000000000000000000000000000000000000..0601295861aa9ed1c0c7dcbc05a70aa7052beb19 --- /dev/null +++ b/easy-jit/misc/docker/build_docker.sh @@ -0,0 +1,3 @@ +#!/bin/bash +python3 GenDockerfile.py ../../.travis.yml > Dockerfile.easy && + docker build -t easy/test -f Dockerfile.easy . diff --git a/easy-jit/pass/CMakeLists.txt b/easy-jit/pass/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..dd840a53e46ad518ee8ec05e82ec020c19a8dd73 --- /dev/null +++ b/easy-jit/pass/CMakeLists.txt @@ -0,0 +1,22 @@ + +add_llvm_pass_plugin(EasyJitPass SHARED + RegisterPasses.cpp + Easy.cpp + Layout.cpp + Utils.cpp + MayAliasTracer.cpp + DEPENDS intrinsics_gen + ) + +set(EASY_JIT_PASS ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/EasyJitPass${CMAKE_SHARED_LIBRARY_SUFFIX} PARENT_SCOPE) + +install(TARGETS EasyJitPass + LIBRARY DESTINATION lib) + +# Get proper shared-library behavior (where symbols are not necessarily +# resolved when the shared library is linked) on OS X. +if(APPLE) + set_target_properties(EasyJitPass PROPERTIES + LINK_FLAGS "-undefined dynamic_lookup" + ) +endif(APPLE) diff --git a/easy-jit/pass/Easy.cpp b/easy-jit/pass/Easy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97451def69603125c0e861f0991934845c584279 --- /dev/null +++ b/easy-jit/pass/Easy.cpp @@ -0,0 +1,526 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include + +#define DEBUG_TYPE "easy-register-bitcode" +#include + +#include +#include + +#include +#include +#include + +#include + +#include "MayAliasTracer.h" +#include "StaticPasses.h" +#include "Utils.h" + +#include + +using namespace llvm; + +static cl::opt RegexString("easy-export", + cl::desc("A regular expression to describe functions to expose at runtime."), + cl::init("")); + +namespace easy { + + struct RegisterBitcodeMixin : public PassInfoMixin { + public: + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) { + // execute the rest of the easy::jit passes + LLVM_DEBUG(dbgs() << "RegisterBitcode run on module " << M.getName() << "\n"); + + SmallVector ObjectsToJIT; + + collectObjectsToJIT(M, ObjectsToJIT); + + if(ObjectsToJIT.empty()) + return PreservedAnalyses::all(); + + SmallVector LocalVariables; + collectLocalGlobals(M, LocalVariables); + nameGlobals(LocalVariables, "unnamed_local_global"); + + auto Bitcode = embedBitcode(M, ObjectsToJIT); + GlobalVariable* GlobalMapping = getGlobalMapping(M, LocalVariables); + + Function* RegisterBitcodeFun = declareRegisterBitcode(M, GlobalMapping); + registerBitcode(M, ObjectsToJIT, Bitcode, GlobalMapping, RegisterBitcodeFun); + + LLVM_DEBUG(WriteIntermediateToFile(M, (M.getName() + "_pass.ll").str())); + + return PreservedAnalyses::none(); + } + static bool isRequired() { return true; } + private: + + static void WriteIntermediateToFile(llvm::Module const &M, std::string const& File) { + if(File.empty()) + return; + std::error_code Error; + llvm::raw_fd_ostream Out(File, Error, llvm::sys::fs::OF_None); + + if(Error) { + dbgs() << "WHAT\n"; + return; + } + + Out << M; + llvm::dbgs() << "Wrote to " << File << "\n"; + } + + static bool canExtractBitcode(GlobalObject &GO, std::string &Reason) { + if(GO.isDeclaration()) { + Reason = "Can't extract a declaration."; + return false; + } + return true; + } + + static auto compilerInterface(Module &M) { + SmallVector, 4> Funs; + std::copy_if(M.begin(), M.end(), std::back_inserter(Funs), + [](Function &F) {return F.getSection() == CI_SECTION;}); + return Funs; + } + + static bool mayBeOverload(Function *FOverload, Function *F) { + auto *FOverloadTy = FOverload->getFunctionType(); + auto *FTy = F->getFunctionType(); + if (FTy->getReturnType() != FOverloadTy->getReturnType()) + return false; + if (FTy->getNumParams() != FOverloadTy->getNumParams()) + return false; + if (FOverloadTy->isVarArg()) + return false; + + auto FParamIter = FTy->param_begin(); + auto FOverloadParamIter = FOverloadTy->param_begin(); + + // TODO: check that first parameter type are compatible? + if (!isa(*FOverloadParamIter)) + return false; + + for (++FParamIter, ++FOverloadParamIter; FParamIter != FTy->param_end(); + ++FParamIter, ++FOverloadParamIter) { + if (*FParamIter != *FOverloadParamIter) + return false; + } + + // an overload must be registered in a virtual table + for(User* U : F->users()) { + auto* CE = dyn_cast(U); + if(!CE || !CE->isCast()) + continue; + + for(User* CEU : CE->users()) { + if(auto* Init = dyn_cast(CEU)) { + return true; // probably a vtable + } + } + } + + return false; + } + + void collectObjectsToJIT(Module &M, SmallVectorImpl &ObjectsToJIT) { + + // get **all** functions passed as parameter to easy jit calls + // not only the target function, but also its parameters + deduceObjectsToJIT(M); + regexFunctionsToJIT(M); + deduceVirtualMethodsToJIT(M); + + // get functions in section jit section + for(GlobalObject &GO : M.global_objects()) { + if(GO.getSection() != JIT_SECTION) + continue; + + GO.setSection(""); // drop the identifier + + std::string Reason; + if(!canExtractBitcode(GO, Reason)) { + LLVM_DEBUG(dbgs() << "Could not extract global '" << GO.getName() << "'. " << Reason << "\n"); + continue; + } + LLVM_DEBUG(dbgs() << "Global '" << GO.getName() << "' marked for extraction.\n"); + + ObjectsToJIT.push_back(&GO); + } + + // also collect virtual functions in two steps + + } + + static bool isConstant(GlobalObject const& GO) { + if(isa(GO)) + return true; + return cast(GO).isConstant(); + } + + void deduceVirtualMethodsToJIT(Module &M) { + // First collect all loaded types we could monitor stores but stores + // could be done at call site, outside of ObjectsToJIT's scopes + + SmallPtrSet VirtualMethodTys; + for(Function& F: M) { + if(F.getSection() != JIT_SECTION) + continue; + for(auto& I : instructions(F)) { + auto* CI = dyn_cast(&I); + if(!CI) + continue; + + if (CI->getCalledFunction()) continue; + + auto* LI = dyn_cast(CI->getCalledOperand()); + if (!LI) + continue; + auto* LLI = dyn_cast(LI->getOperand(0)); + if (!LLI) + continue; + + MDNode *Tag = LLI->getMetadata(LLVMContext::MD_tbaa); + if(!Tag || !Tag->isTBAAVtableAccess()) + continue; + + llvm::dbgs() << "Found virtual: " << *CI << " from " << *LI << " from " << *LLI << ", type = " << *CI->getFunctionType() << "\n"; + dbgs() << "--------------------\n"; + dbgs() << F; + dbgs() << "--------------------\n"; + VirtualMethodTys.insert(CI->getFunctionType()); + } + } + + // Second look at functions that have a type compatible with the virtual one + for(GlobalObject &GO : M.global_objects()) { + GlobalVariable* GV = dyn_cast(&GO); + if(!GV || ! GV->hasInitializer()) + continue; + + ConstantStruct* CS = dyn_cast(GV->getInitializer()); + if(!CS || CS->getNumOperands() != 1) + continue; + + ConstantAggregate* CA = dyn_cast(CS->getOperand(0)); + if(!CA) + continue; + + for(Use& U : CA->operands()) { + Constant* C = dyn_cast(U.get()); + if(!C) + continue; + if(auto* CE = dyn_cast(C)) { + if(CE->isCast()) { + C = CE->getOperand(0); + } + } + auto* F = dyn_cast(C); + if(!F) + continue; + + auto* FTy = F->getFunctionType(); + if(VirtualMethodTys.count(FTy)) { + F->setSection(JIT_SECTION); + // also look for overloads + for(auto& FOverload: M) { + if(&FOverload == F) + continue; + if(mayBeOverload(&FOverload, F)) + FOverload.setSection(JIT_SECTION); + } + } + } + } + } + + void deduceObjectsToJIT(Module &M) { + for(Function &EasyJitFun : compilerInterface(M)) { + for(Use& U : EasyJitFun.uses()) { + if(AbstractCallSite CS{&U}) { + for (int i = 0; i < CS.getNumArgOperands(); i++) { + Value* O = CS.getCallArgOperand(i); + O = O->stripPointerCasts(); + MayAliasTracer Tracer(O); + for(GlobalObject& GO: M.global_objects()) { + if(isConstant(GO) and Tracer.count(GO)) { + GO.setSection(JIT_SECTION); + } + } + } + } + } + } + } + + static void regexFunctionsToJIT(Module &M) { + if(RegexString.empty()) + return; + llvm::Regex Match(RegexString); + for(GlobalObject &GO : M.global_objects()) + if(Match.match(GO.getName())) + GO.setSection(JIT_SECTION); + } + + static void collectLocalGlobals(Module &M, SmallVectorImpl &Globals) { + for(GlobalVariable &GV : M.globals()) + if(GV.hasLocalLinkage()) { + LLVM_DEBUG(dbgs() << "Found local global: " << GV << "\n"); + Globals.push_back(&GV); + } + } + + static void nameGlobals(SmallVectorImpl &Globals, Twine Name) { + for(GlobalValue *GV : Globals) + if(!GV->hasName()) + GV->setName(Name); + } + + static GlobalVariable* + getGlobalMapping(Module &M, SmallVectorImpl &Globals) { + LLVMContext &C = M.getContext(); + SmallVector Entries; + + Type* PtrTy = PointerType::get(C, 0); + StructType *EntryTy = StructType::get(C, {PtrTy, PtrTy}, true); + + for(GlobalValue* GV : Globals) { + GlobalVariable* Name = getStringGlobal(M, GV->getName()); + Constant* NameCast = ConstantExpr::getPointerCast(Name, PtrTy); + Constant* GVCast = GV; + if(GV->getType() != PtrTy) + GVCast = ConstantExpr::getPointerCast(GV, PtrTy); + Constant* Entry = ConstantStruct::get(EntryTy, {NameCast, GVCast}); + Entries.push_back(Entry); + } + Entries.push_back(Constant::getNullValue(EntryTy)); + + Constant* Init = ConstantArray::get(ArrayType::get(EntryTy, Entries.size()), Entries); + LLVM_DEBUG(dbgs() << "Global mapping: " << *Init << "\n"); + return new GlobalVariable(M, Init->getType(), true, + GlobalVariable::PrivateLinkage, + Init, "global_mapping"); + } + + static SmallVector + embedBitcode(Module &M, SmallVectorImpl &Objs) { + SmallVector Bitcode(Objs.size()); + for(size_t i = 0, n = Objs.size(); i != n; ++i) { + LLVM_DEBUG(dbgs() << "Embedding bitcode for " << Objs[i]->getName() << "\n"); + Bitcode[i] = embedBitcode(M, *Objs[i]); + } + return Bitcode; + } + + static GlobalVariable* embedBitcode(Module &M, GlobalObject& GO) { + std::unique_ptr Embed = CloneModule(M); + + GlobalValue *FEmbed = Embed->getNamedValue(GO.getName()); + assert(FEmbed && "global value with that name exists"); + cleanModule(*FEmbed, *Embed); + + Twine ModuleName = GO.getName() + "_bitcode"; + Embed->setModuleIdentifier(ModuleName.str()); + + return writeModuleToGlobal(M, *Embed, FEmbed->getName() + "_bitcode"); + } + + static std::string moduleToString(Module &M) { + std::string s; + raw_string_ostream so(s); + WriteBitcodeToFile(M, so); + so.flush(); + return s; + } + + static GlobalVariable* writeModuleToGlobal(Module &M, Module &Embed, Twine Name) { + std::string Bitcode = moduleToString(Embed); + Constant* BitcodeInit = ConstantDataArray::getString(M.getContext(), Bitcode, true); + return new GlobalVariable(M, BitcodeInit->getType(), true, + GlobalVariable::PrivateLinkage, + BitcodeInit, Name); + } + + static void cleanModule(GlobalValue &Entry, Module &M) { + + llvm::StripDebugInfo(M); + + bool ForFunction = isa(Entry); + + LLVM_DEBUG(dbgs() << "Cleaning module" << M.getName() << " for " << Entry.getName() << ", ForFunction = " << ForFunction << "\n"); + + auto Referenced = getReferencedFromEntry(Entry); + Referenced.push_back(&Entry); + + if(ForFunction) { + Entry.setLinkage(GlobalValue::ExternalLinkage); + } + + //clean the cloned module + + ModulePassManager PM; + ModuleAnalysisManager AM; + AM.registerPass([]() { return PassInstrumentationAnalysis(); }); + PM.addPass(GlobalDCEPass()); + PM.addPass(StripDeadDebugInfoPass()); + PM.addPass(StripDeadPrototypesPass()); + PM.run(M, AM); + + if(ForFunction) { + fixLinkages(Entry, M); + } + } + + static std::vector getReferencedFromEntry(GlobalValue &Entry) { + std::vector Funs; + + SmallPtrSet Visited; + SmallVector ToVisit; + ToVisit.push_back(&Entry); + + while(!ToVisit.empty()) { + User* U = ToVisit.pop_back_val(); + if(!Visited.insert(U).second) + continue; + if(Function* UF = dyn_cast(U)) { + Funs.push_back(UF); + + for(Instruction &I : instructions(UF)) + for(Value* Op : I.operands()) + if(User* OpU = dyn_cast(Op)) + ToVisit.push_back(OpU); + } + else if(GlobalVariable* GV = dyn_cast(U)) { + if(GV->hasInitializer()) { + ToVisit.push_back(GV->getInitializer()); + } + } + + for(Value* Op : U->operands()) + if(User* OpU = dyn_cast(Op)) + ToVisit.push_back(OpU); + } + + return Funs; + } + + static void fixLinkages(GlobalValue &Entry, Module &M) { + for(GlobalValue &GV : M.global_values()) { + if(GV.getName().starts_with("llvm.")) + continue; + + if(GlobalObject* GO = dyn_cast(&GV)) { + GO->setComdat(nullptr); + } + + if(auto* GVar = dyn_cast(&GV)) { + if (!GVar->isConstant()) { + GVar->setInitializer(nullptr); + GVar->setVisibility(GlobalValue::DefaultVisibility); + GVar->setLinkage(GlobalValue::ExternalLinkage); + LLVM_DEBUG(dbgs() << "fix bitcode var linkage for " << *GVar << "\n"); + } + } else if(auto* F = dyn_cast(&GV)) { + // f becomes private + F->removeFnAttr(Attribute::NoInline); + if(F == &Entry) + continue; + + if(!F->isDeclaration() && + (F->getVisibility() != GlobalValue::DefaultVisibility || + F->getLinkage() != GlobalValue::PrivateLinkage)) { + F->setVisibility(GlobalValue::DefaultVisibility); + F->setLinkage(GlobalValue::PrivateLinkage); + } + } else llvm::report_fatal_error("Easy::Jit [not yet implemented]: handle aliases, ifuncs."); + } + } + + Function* declareRegisterBitcode(Module &M, GlobalVariable *GlobalMapping) { + StringRef Name = "easy_register"; + if(Function* F = M.getFunction(Name)) + return F; + + LLVMContext &C = M.getContext(); + DataLayout const &DL = M.getDataLayout(); + + Type* Void = Type::getVoidTy(C); + Type* I8Ptr = PointerType::get(C, 0); + Type* GMTy = GlobalMapping->getType(); + Type* SizeT = DL.getLargestLegalIntType(C); + + assert(SizeT); + + FunctionType* FTy = + FunctionType::get(Void, {I8Ptr, I8Ptr, GMTy, I8Ptr, SizeT}, false); + return Function::Create(FTy, Function::ExternalLinkage, Name, &M); + } + + static void + registerBitcode(Module &M, SmallVectorImpl &Objs, + SmallVectorImpl &Bitcodes, + Value* GlobalMapping, + Function* RegisterBitcodeFun) { + // Create static initializer with low priority to register everything + Type* FPtr = RegisterBitcodeFun->getFunctionType()->getParamType(0); + Type* StrPtr = RegisterBitcodeFun->getFunctionType()->getParamType(1); + Type* BitcodePtr = RegisterBitcodeFun->getFunctionType()->getParamType(3); + Type* SizeTy = RegisterBitcodeFun->getFunctionType()->getParamType(4); + + Function *Ctor = GetCtor(M, "register_bitcode"); + IRBuilder<> B(Ctor->getEntryBlock().getTerminator()); + + for(size_t i = 0, n = Objs.size(); i != n; ++i) { + GlobalVariable* Name = getStringGlobal(M, Objs[i]->getName()); + ArrayType* ArrTy = cast(Bitcodes[i]->getInitializer()->getType()); + size_t Size = ArrTy->getNumElements()-1; /*-1 for the 0 terminator*/ + + Value* Fun = B.CreatePointerCast(Objs[i], FPtr); + Value* NameCast = B.CreatePointerCast(Name, StrPtr); + Value* Bitcode = B.CreatePointerCast(Bitcodes[i], BitcodePtr); + Value* BitcodeSize = ConstantInt::get(SizeTy, Size, false); + + // fun, name, gm, bitcode, bitcode size + auto* CI = B.CreateCall(RegisterBitcodeFun, + {Fun, NameCast, GlobalMapping, Bitcode, BitcodeSize}, ""); + LLVM_DEBUG(dbgs() << "Call:::: " << *CI << "\n"); + } + } + + static GlobalVariable* getStringGlobal(Module& M, StringRef Name) { + Constant* Init = ConstantDataArray::getString(M.getContext(), Name, true); + return new GlobalVariable(M, Init->getType(), true, + GlobalVariable::PrivateLinkage, + Init, Name + "_name"); + } + }; + + void registerBitcodePass(llvm::ModulePassManager &PM) { + PM.addPass(RegisterBitcodeMixin()); + } +} diff --git a/easy-jit/pass/Layout.cpp b/easy-jit/pass/Layout.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e327fd180053b17daa0736740058f54d01c0ce07 --- /dev/null +++ b/easy-jit/pass/Layout.cpp @@ -0,0 +1,205 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "easy-register-layout" +#include + +#include + +#include "Utils.h" +#include +#include + +using namespace llvm; + +namespace easy { + struct RegisterLayoutMixin : public PassInfoMixin { + public: + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) { + LLVM_DEBUG(dbgs() << "RegisterLayout run on module " << M.getName() << "\n"); + + SmallVector LayoutFunctions; + collectLayouts(M, LayoutFunctions); + + if(LayoutFunctions.empty()) + return PreservedAnalyses::all(); + + Function* Register = declareRegisterLayout(M); + registerLayouts(LayoutFunctions, Register); + + return PreservedAnalyses::none(); + } + static bool isRequired() { return true; } + + static void collectLayouts(Module &M, SmallVectorImpl &LayoutFunctions) { + for(Function &F : M) + if(F.getSection() == LAYOUT_SECTION) + LayoutFunctions.push_back(&F); + } + + static Function* GetSerializeStruct(Function* F) { + // fragile! + for(Instruction &I : llvm::instructions(F)) { + if(isa(I)) + continue; + for(Value* Op : I.operands()) { + if(ConstantExpr* OpCE = dyn_cast(Op)) + if(OpCE->getOpcode() == Instruction::BitCast) + Op = OpCE->getOperand(0); + if(Function* F = dyn_cast(Op)) + return F; + } + } + assert(false && "unreachable"); + return nullptr; + } + + //Can this be simplified with emitMalloc ? + static Function* DeclareMalloc(Module &M) { + if(Function* Malloc = M.getFunction("malloc")) + return Malloc; + Type* IntPtrTy = M.getDataLayout().getIntPtrType(M.getContext()); + Type* OpqPtrTy = PointerType::get(M.getContext(), 0); + FunctionType* FTy = FunctionType::get(OpqPtrTy, {IntPtrTy}, false); + return Function::Create(FTy, Function::ExternalLinkage, "malloc", &M); + } + + template + static size_t GetStructSize(TypeIterator begin, TypeIterator end, DataLayout const &DL) { + return std::accumulate(begin, end, 0ul, + [&DL](size_t A, Type* T) { return A + DL.getTypeStoreSize(T); } ); + } + + static void SerializeStruct(IRBuilder<> &B, size_t &Offset, DataLayout const & DL, + Value* Buf, Value* ByVal, Type* CurLevelTy, Type* RootTy, SmallVectorImpl &GEPOffset) { + StructType* Struct = dyn_cast(CurLevelTy); + if(!Struct) { + // Compute the address of the current field relative to the root struct + Value* ArgPtr = B.CreateGEP(RootTy, ByVal, GEPOffset, "struct.ptr"); + // Load the field value with the correct type + Value* Argument = B.CreateLoad(CurLevelTy, ArgPtr); + + // Store the field into the serialization buffer at current byte offset + Value* DstI8 = B.CreateConstGEP1_32(Type::getInt8Ty(B.getContext()), Buf, Offset); + Value* DstTyped = B.CreatePointerCast(DstI8, PointerType::getUnqual(B.getContext())); + B.CreateStore(Argument, DstTyped); + + Offset += DL.getTypeStoreSize(Argument->getType()); + return; + } + + Type* I32Ty = Type::getInt32Ty(B.getContext()); + GEPOffset.push_back(nullptr); + for(size_t Arg = 0; Arg != Struct->getNumElements(); Arg++) { + GEPOffset.back() = ConstantInt::get(I32Ty, Arg); + SerializeStruct(B, Offset, DL, Buf, ByVal, Struct->getElementType(Arg), RootTy, GEPOffset); + } + GEPOffset.pop_back(); + } + + static void SerializeArguments(IRBuilder<> &B, size_t &Offset, DataLayout const & DL, Value* Buf, Function* F) { + for(size_t Arg = 0; Arg != F->arg_size(); Arg++) { + Value *Argument = F->arg_begin()+Arg; + Type* ArgTy = Argument->getType(); + + Value* Ptr = B.CreateConstGEP1_32(Type::getInt8Ty(B.getContext()), Buf, Offset); + Ptr = B.CreatePointerCast(Ptr, PointerType::getUnqual(B.getContext()), Argument->getName() + ".ptr"); + B.CreateStore(Argument, Ptr); + + LLVM_DEBUG(dbgs() << "Store at " << Offset << "\n"); + + Offset += DL.getTypeStoreSize(Argument->getType()); + } + } + + static void DefineSerializeStruct(Function *F) { + if(!F->isDeclaration()) + return; + + LLVMContext &C = F->getContext(); + + Module &M = *F->getParent(); + Function *Malloc = DeclareMalloc(M); + + DataLayout const &DL = M.getDataLayout(); + + //Why PassedAsAPointer can be decided only by the first argument? + FunctionType *FTy = F->getFunctionType(); + StructType *STy = F->arg_begin()->hasByValAttr() ? cast(F->arg_begin()->getParamByValType()) : nullptr; + bool PassedAsAPointer = STy; + + Type* I32 = Type::getInt32Ty(C); + Type* OpqPtr = PointerType::getUnqual(C); + size_t I32Size = DL.getTypeStoreSize(I32); + + BasicBlock *BB = BasicBlock::Create(C, "entry", F); + IRBuilder<> B(BB); + + size_t ArgSize; + if(PassedAsAPointer) ArgSize = GetStructSize(STy->element_begin(), STy->element_end(), DL); + else ArgSize = GetStructSize(FTy->param_begin(), FTy->param_end(), DL); + + LLVM_DEBUG(dbgs() << F->getName() << ": I32Size = " << I32Size << ", ArgSize = " << ArgSize << "\n"); + + // buf = malloc(sizeof(int32_t) + ArgSize) + Value* Buf = B.CreateCall(Malloc, {ConstantInt::get(Malloc->getFunctionType()->getParamType(0), I32Size + ArgSize)}, "buf"); + // *buf = ArgSize + B.CreateStore(ConstantInt::get(I32, ArgSize), B.CreatePointerCast(Buf, OpqPtr, "size.ptr")); + + SmallVector Offset = { ConstantInt::getNullValue(I32) }; + if(PassedAsAPointer) SerializeStruct(B, I32Size, DL, Buf, F->arg_begin(), STy, STy, Offset); + else SerializeArguments(B, I32Size, DL, Buf, F); + + B.CreateRet(Buf); + } + + static void registerLayouts(SmallVectorImpl &LayoutFunctions, Function* Register) { + + FunctionType* RegisterTy = Register->getFunctionType(); + Type* IdTy = RegisterTy->getParamType(0); + Type* NTy = RegisterTy->getParamType(1); + + // register the layout info in a constructor + Function* Ctor = GetCtor(*Register->getParent(), "register_layout"); + IRBuilder<> B(Ctor->getEntryBlock().getTerminator()); + + for(Function *F : LayoutFunctions) { + Function* SerializeStructFun = GetSerializeStruct(F); + + DefineSerializeStruct(SerializeStructFun); + + size_t N = SerializeStructFun->getFunctionType()->getNumParams(); + Value* Id = B.CreatePointerCast(SerializeStructFun, IdTy); + B.CreateCall(Register, {Id, ConstantInt::get(NTy, N, false)}); + } + } + + static Function* declareRegisterLayout(Module &M) { + StringRef Name = "easy_register_layout"; + if(Function* F = M.getFunction(Name)) + return F; + + LLVMContext &C = M.getContext(); + DataLayout const &DL = M.getDataLayout(); + + Type* Void = Type::getVoidTy(C); + Type* OpqPtr = PointerType::get(C, 0); + Type* SizeT = DL.getLargestLegalIntType(C); + + FunctionType* FTy = + FunctionType::get(Void, {OpqPtr, SizeT}, false); + return Function::Create(FTy, Function::ExternalLinkage, Name, &M); + } + }; + + void registerLayoutPass(llvm::ModulePassManager &PM) { + PM.addPass(RegisterLayoutMixin()); + } +} diff --git a/easy-jit/pass/MayAliasTracer.cpp b/easy-jit/pass/MayAliasTracer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..589232f46ad5957a2189fcc5756c3ae5c505051e --- /dev/null +++ b/easy-jit/pass/MayAliasTracer.cpp @@ -0,0 +1,75 @@ +#include "MayAliasTracer.h" + +#include +#include +#include +#include + +using namespace llvm; + +void easy::MayAliasTracer::mayAliasWithStoredValues(Value* V, VSet &Loaded, VSet &Stored) { + if(!Stored.insert(V).second) + return; + if(auto* GO = dyn_cast(V)) + GOs_.insert(GO); + + if(auto * II = dyn_cast(V)) { + if(II->getIntrinsicID() == Intrinsic::memcpy) { + mayAliasWithLoadedValues(II->getArgOperand(1), Loaded, Stored); + } + } + + if(auto* SI = dyn_cast(V)) { + mayAliasWithLoadedValues(SI->getValueOperand(), Loaded, Stored); + } + + if(isa(V)||isa(V)||isa(V)) { + for(User* U : V->users()) { + mayAliasWithStoredValues(U, Loaded, Stored); + } + } +} + +void easy::MayAliasTracer::mayAliasWithLoadedValues(Value * V, VSet &Loaded, VSet &Stored) { + if(!Loaded.insert(V).second) + return; + if(auto* GO = dyn_cast(V)) + GOs_.insert(GO); + + auto mayAliasWithLoadedOperand = [this, &Loaded, &Stored](Value* V) { mayAliasWithLoadedValues(V, Loaded, Stored);}; + + //TODO: generalize that + if(auto* PHI = dyn_cast(V)) { + std::for_each(PHI->op_begin(), PHI->op_end(), mayAliasWithLoadedOperand); + } + if(auto* Select = dyn_cast(V)) { + mayAliasWithLoadedValues(Select->getTrueValue(), Loaded, Stored); + mayAliasWithLoadedValues(Select->getFalseValue(), Loaded, Stored); + } + if(auto* Alloca = dyn_cast(V)) { + mayAliasWithStoredValues(Alloca, Loaded, Stored); + } + if(auto *GEP = dyn_cast(V)) { + mayAliasWithLoadedValues(GEP->getPointerOperand(), Loaded, Stored); + } + if(auto *BC = dyn_cast(V)) { + mayAliasWithLoadedValues(BC->getOperand(0), Loaded, Stored); + } + if(auto const* CE = dyn_cast(V)) { + switch(CE->getOpcode()) { + case Instruction::GetElementPtr: + case Instruction::BitCast: + return mayAliasWithLoadedValues(CE->getOperand(0), Loaded, Stored); + default: + ; + } + } + if(auto* OtherGV = dyn_cast(V)) { + if(OtherGV->hasInitializer()) + mayAliasWithLoadedValues(OtherGV->getInitializer(), Loaded, Stored); + mayAliasWithStoredValues(OtherGV, Loaded, Stored); + } + if(auto* CA = dyn_cast(V)) { + std::for_each(CA->op_begin(), CA->op_end(), mayAliasWithLoadedOperand); + } +} diff --git a/easy-jit/pass/MayAliasTracer.h b/easy-jit/pass/MayAliasTracer.h new file mode 100644 index 0000000000000000000000000000000000000000..2e3148e6b869cd0c63274f259f1faaecb017ca5c --- /dev/null +++ b/easy-jit/pass/MayAliasTracer.h @@ -0,0 +1,32 @@ +#ifndef MAY_ALIAS_TRACER +#define MAY_ALIAS_TRACER + +#include + +namespace llvm { + class Value; + class GlobalObject; +} + +namespace easy { + class MayAliasTracer { + llvm::SmallPtrSet GOs_; + + using VSet = llvm::SmallPtrSetImpl; + void mayAliasWithStoredValues(llvm::Value* V, VSet &Loaded, VSet &Stored); + void mayAliasWithLoadedValues(llvm::Value* V, VSet &Loaded, VSet &Stored); + + + public: + + MayAliasTracer(llvm::Value* V) { + llvm::SmallPtrSet VLoaded; + llvm::SmallPtrSet VStored; + mayAliasWithLoadedValues(V, VLoaded, VStored); + } + auto count(llvm::GlobalObject& GO) const { return GOs_.count(&GO);} + }; + +} + +#endif diff --git a/easy-jit/pass/RegisterPasses.cpp b/easy-jit/pass/RegisterPasses.cpp new file mode 100644 index 0000000000000000000000000000000000000000..47883d7c7a5a30be5f2242a670d09fb10339ae77 --- /dev/null +++ b/easy-jit/pass/RegisterPasses.cpp @@ -0,0 +1,28 @@ +#include "StaticPasses.h" + +#include +#include +#include + +#include +#include + +using namespace llvm; +using namespace easy; + +// The old pass manager inserts RegisterBitcodePass after last of optimization. +//@TODO: figure out the insertion point of these passes. +llvm::PassPluginLibraryInfo getEasyJitPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "RegisterBitcode", LLVM_VERSION_STRING, + [](PassBuilder &PB) { + PB.registerPipelineEarlySimplificationEPCallback( + [](llvm::ModulePassManager &PM, llvm::OptimizationLevel) { + easy::registerLayoutPass(PM); + easy::registerBitcodePass(PM); + }); + }}; +} +extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo +llvmGetPassPluginInfo() { + return getEasyJitPassPluginInfo(); +} \ No newline at end of file diff --git a/easy-jit/pass/StaticPasses.h b/easy-jit/pass/StaticPasses.h new file mode 100644 index 0000000000000000000000000000000000000000..8b402f5fb8d4084875459f00cf574ff7fdd8457e --- /dev/null +++ b/easy-jit/pass/StaticPasses.h @@ -0,0 +1,13 @@ +#ifndef STATIC_PASSES +#define STATIC_PASSES + +#include + +#include + +namespace easy { + void registerBitcodePass(llvm::ModulePassManager &PM); + void registerLayoutPass(llvm::ModulePassManager &PM); +} + +#endif diff --git a/easy-jit/pass/Utils.cpp b/easy-jit/pass/Utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..63a91c74fc355b9aa77e527758cdce4ce5a0cb6e --- /dev/null +++ b/easy-jit/pass/Utils.cpp @@ -0,0 +1,22 @@ +#include "Utils.h" + +#include +#include +#include + +#include + +using namespace llvm; + +Function* GetCtor(Module &M, Twine Name, unsigned Priority) { + LLVMContext &C = M.getContext(); + Type* Void = Type::getVoidTy(C); + FunctionType* VoidFun = FunctionType::get(Void, false); + Function* Ctor = Function::Create(VoidFun, Function::PrivateLinkage, Name, &M); + BasicBlock* Entry = BasicBlock::Create(C, "entry", Ctor); + ReturnInst::Create(C, Entry); + + llvm::appendToGlobalCtors(M, Ctor, Priority); + + return Ctor; +} diff --git a/easy-jit/pass/Utils.h b/easy-jit/pass/Utils.h new file mode 100644 index 0000000000000000000000000000000000000000..e492af30f3323ec5f7ff417bc924560cf71de94a --- /dev/null +++ b/easy-jit/pass/Utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +namespace llvm { + class Module; + class Function; +} + +llvm::Function* GetCtor(llvm::Module &M, llvm::Twine Name, unsigned Priority = 65535); + +#endif // UTILS_H diff --git a/easy-jit/runtime/BitcodeTracker.cpp b/easy-jit/runtime/BitcodeTracker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8f88ca99ee6160fdd8ecd3514aa3edd8d41c4001 --- /dev/null +++ b/easy-jit/runtime/BitcodeTracker.cpp @@ -0,0 +1,84 @@ +#include + +#include +#include + +#include + +using namespace easy; +using namespace llvm; + +namespace easy { + DefineEasyException(BitcodeNotRegistered, "Cannot find bitcode."); + DefineEasyException(BitcodeParseError, "Cannot parse bitcode for: "); +} + +BitcodeTracker& BitcodeTracker::GetTracker() { + static BitcodeTracker TheTracker; + return TheTracker; +} + +bool BitcodeTracker::hasGlobalMapping(void* FPtr) const { + auto InfoPtr = Functions.find(FPtr); + return InfoPtr != Functions.end(); +} + +LayoutInfo const & BitcodeTracker::getLayoutInfo(easy::layout_id id) const { + auto InfoPair = Layouts.find(id); + assert(InfoPair != Layouts.end()); + return InfoPair->second; +} + +void* BitcodeTracker::getAddress(std::string const &Name) { + auto Addr = NameToAddress.find(Name); + if(Addr == NameToAddress.end()) + return nullptr; + return Addr->second; +} + +std::tuple BitcodeTracker::getNameAndGlobalMapping(void* FPtr) { + auto InfoPtr = Functions.find(FPtr); + if(InfoPtr == Functions.end()) { + throw easy::BitcodeNotRegistered(); + } + + return std::make_tuple(InfoPtr->second.Name, InfoPtr->second.Globals); +} + +std::unique_ptr BitcodeTracker::getModuleWithContext(void* FPtr, llvm::LLVMContext &C) { + auto InfoPtr = Functions.find(FPtr); + if(InfoPtr == Functions.end()) { + throw easy::BitcodeNotRegistered(); + } + + auto &Info = InfoPtr->second; + + llvm::StringRef BytecodeStr(Info.Bitcode, Info.BitcodeLen); + std::unique_ptr Buf(llvm::MemoryBuffer::getMemBuffer(BytecodeStr)); + auto ModuleOrErr = + llvm::parseBitcodeFile(Buf->getMemBufferRef(), C); + + if (ModuleOrErr.takeError()) { + throw easy::BitcodeParseError(Info.Name); + } + + return std::move(ModuleOrErr.get()); +} + +BitcodeTracker::ModuleContextPair BitcodeTracker::getModule(void* FPtr) { + + std::unique_ptr Context(new llvm::LLVMContext()); + auto Module = getModuleWithContext(FPtr, *Context); + return ModuleContextPair(std::move(Module), std::move(Context)); +} + +// function to interface with the generated code +extern "C" { +void easy_register(void* FPtr, const char* Name, GlobalMapping* Globals, const char* Bitcode, size_t BitcodeLen) { + BitcodeTracker::GetTracker().registerFunction(FPtr, Name, Globals, Bitcode, BitcodeLen); +} +void easy_register_layout(layout_id Id, size_t N) { + BitcodeTracker::GetTracker().registerLayout(Id, N); +} +} + diff --git a/easy-jit/runtime/CMakeLists.txt b/easy-jit/runtime/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3096881c9010bf73a18925971dcf06d4eea3e3c9 --- /dev/null +++ b/easy-jit/runtime/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(EasyJitRuntime SHARED + BitcodeTracker.cpp + Context.cpp + Function.cpp + InitNativeTarget.cpp + Utils.cpp + pass/ContextAnalysis.cpp + pass/DevirtualizeConstant.cpp + pass/InlineParameters.cpp + pass/InlineParametersHelper.cpp +) + +llvm_config(EasyJitRuntime core codegen interpreter support mcjit native executionengine passes objcarcopts) + + +set(EASY_JIT_RUNTIME ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_SHARED_LIBRARY_PREFIX}EasyJitRuntime${CMAKE_SHARED_LIBRARY_SUFFIX} PARENT_SCOPE) + +install(TARGETS EasyJitRuntime + LIBRARY DESTINATION lib) diff --git a/easy-jit/runtime/Context.cpp b/easy-jit/runtime/Context.cpp new file mode 100644 index 0000000000000000000000000000000000000000..725cd0142af6f78deb832197b539272d81246efb --- /dev/null +++ b/easy-jit/runtime/Context.cpp @@ -0,0 +1,44 @@ +#include "easy/runtime/Context.h" + +using namespace easy; + +Context& Context::setParameterIndex(unsigned param_idx) { + return setArg(param_idx); +} + +Context& Context::setParameterInt(int64_t val) { + return setArg(val); +} + +Context& Context::setParameterFloat(double val) { + return setArg(val); +} + +Context& Context::setParameterPointer(const void* val) { + return setArg(val); +} + +Context& Context::setParameterStruct(serialized_arg arg) { + return setArg(std::move(arg)); +} + +Context& Context::setParameterModule(easy::Function const &F) { + return setArg(F); +} + +bool Context::operator==(const Context& Other) const { + if(getOptLevel() != Other.getOptLevel()) + return false; + if(size() != Other.size()) + return false; + + for(auto this_it = begin(), other_it = Other.begin(); + this_it != end(); ++this_it, ++other_it) { + ArgumentBase &ThisArg = **this_it; + ArgumentBase &OtherArg = **other_it; + if(!(ThisArg == OtherArg)) + return false; + } + + return true; +} diff --git a/easy-jit/runtime/Function.cpp b/easy-jit/runtime/Function.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce48f9a58109a5c26930e6d9b11a84363b82adfb --- /dev/null +++ b/easy-jit/runtime/Function.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef NDEBUG +#include +#endif + + +using namespace easy; + +namespace easy { + DefineEasyException(ExecutionEngineCreateError, "Failed to create execution engine for:"); + DefineEasyException(CouldNotOpenFile, "Failed to file to dump intermediate representation."); +} + +Function::Function(void* Addr, std::unique_ptr H) + : Address(Addr), Holder(std::move(H)) { +} + +static std::unique_ptr GetHostTargetMachine() { + std::unique_ptr TM(llvm::EngineBuilder().selectTarget()); + return TM; +} + +static void Optimize(llvm::Module& M, const char* Name, const easy::Context& C, llvm::OptimizationLevel OptLevel) { + + llvm::LoopAnalysisManager LAM; + llvm::FunctionAnalysisManager FAM; + llvm::CGSCCAnalysisManager CGAM; + llvm::ModuleAnalysisManager MAM; + + MAM.registerPass([&C]{return ContextAnalysisPass(C);}); + + std::unique_ptr TM = GetHostTargetMachine(); + assert(TM); + + llvm::PassBuilder PB(TM.get()); + + PB.registerModuleAnalyses(MAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerFunctionAnalyses(FAM); + PB.registerLoopAnalyses(LAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + + llvm::ModulePassManager MPM; + + MPM.addPass(easy::InlineParametersPass(Name)); + MPM.addPass(PB.buildPerModuleDefaultPipeline(OptLevel)); + MPM.addPass(llvm::createModuleToFunctionPassAdaptor(easy::DevirtualizeConstantPass(Name))); + +#ifdef NDEBUG + MPM.addPass(llvm::VerifierPass()); +#endif + + MPM.addPass(PB.buildPerModuleDefaultPipeline(OptLevel)); + + MPM.run(M, MAM); +} + +static std::unique_ptr GetEngine(std::unique_ptr M, const char *Name) { + llvm::EngineBuilder ebuilder(std::move(M)); + std::string eeError; + + std::unique_ptr EE(ebuilder.setErrorStr(&eeError) + .setMCPU(llvm::sys::getHostCPUName()) + .setEngineKind(llvm::EngineKind::JIT) + .setOptLevel(llvm::CodeGenOptLevel::Aggressive) + .create()); + + if(!EE) { + throw easy::ExecutionEngineCreateError(Name); + } + + return EE; +} + +static void MapGlobals(llvm::ExecutionEngine& EE, GlobalMapping* Globals) { + for(GlobalMapping *GM = Globals; GM->Name; ++GM) { + EE.addGlobalMapping(GM->Name, (uint64_t)GM->Address); + } + EE.addGlobalMapping("__dso_handle", (uint64_t)&EE); + EE.finalizeObject(); +} + +static void WriteOptimizedToFile(llvm::Module const &M, std::string const& File) { + if(File.empty()) + return; + std::error_code Error; + llvm::raw_fd_ostream Out(File, Error, llvm::sys::fs::OF_None); + + if(Error) + throw CouldNotOpenFile(Error.message()); + + Out << M; +} + +std::unique_ptr +CompileAndWrap(const char*Name, GlobalMapping* Globals, + std::unique_ptr Ctx, + std::unique_ptr M) { + + llvm::Module* MPtr = M.get(); + std::unique_ptr EE = GetEngine(std::move(M), Name); + + if(Globals) { + MapGlobals(*EE, Globals); + } + + void *Address = (void*)EE->getFunctionAddress(Name); + + std::unique_ptr Holder(new easy::LLVMHolderImpl{std::move(EE), std::move(Ctx), MPtr}); + return std::unique_ptr(new Function(Address, std::move(Holder))); +} + +llvm::Module const& Function::getLLVMModule() const { + return *static_cast(*this->Holder).M_; +} + +static llvm::OptimizationLevel getOptimizationLevel(const std::pair & OptLevelPair) { + unsigned OptLevel = OptLevelPair.first; + unsigned OptSize = OptLevelPair.second; + assert(OptLevel <= 3 && "Optimization level for speed should be 0, 1, 2, or 3"); + assert(OptSize <= 2 && "Optimization level for size should be 0, 1, or 2"); + assert((OptSize == 0 || OptLevel == 2) && "Optimize for size should be encoded with speedup level == 2"); + if(OptLevel == 0) + return llvm::OptimizationLevel::O0; + if(OptLevel == 1) + return llvm::OptimizationLevel::O1; + if(OptLevel == 2) { + if(OptSize == 0) + return llvm::OptimizationLevel::O2; + else if (OptSize == 1) + return llvm::OptimizationLevel::Os; + else // OptSize == 2 + return llvm::OptimizationLevel::Oz; + } + if(OptLevel == 3) + return llvm::OptimizationLevel::O3; + return llvm::OptimizationLevel::O2; +} + +std::unique_ptr Function::Compile(void *Addr, easy::Context const& C) { + // llvm::DebugFlag = true; + // llvm::setCurrentDebugType("jit"); + + auto &BT = BitcodeTracker::GetTracker(); + + const char* Name; + GlobalMapping* Globals; + std::tie(Name, Globals) = BT.getNameAndGlobalMapping(Addr); + + std::unique_ptr M; + std::unique_ptr Ctx; + std::tie(M, Ctx) = BT.getModule(Addr); + + llvm::OptimizationLevel OptimizationLevel = getOptimizationLevel(C.getOptLevel()); + + Optimize(*M, Name, C, OptimizationLevel); + + WriteOptimizedToFile(*M, C.getDebugFile()); + + return CompileAndWrap(Name, Globals, std::move(Ctx), std::move(M)); +} + +void easy::Function::serialize(std::ostream& os) const { + std::string buf; + llvm::raw_string_ostream stream(buf); + + LLVMHolderImpl const *H = reinterpret_cast(Holder.get()); + llvm::WriteBitcodeToFile(*H->M_, stream); + stream.flush(); + + os << buf; +} + +std::unique_ptr easy::Function::deserialize(std::istream& is) { + + auto &BT = BitcodeTracker::GetTracker(); + + std::string buf(std::istreambuf_iterator(is), {}); // read the entire istream + auto MemBuf = llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(buf)); + + std::unique_ptr Ctx(new llvm::LLVMContext()); + auto ModuleOrError = llvm::parseBitcodeFile(*MemBuf, *Ctx); + if(ModuleOrError.takeError()) { + return nullptr; + } + + auto M = std::move(ModuleOrError.get()); + + std::string FunName = easy::GetEntryFunctionName(*M).str(); + + GlobalMapping* Globals = nullptr; + if(void* OrigFunPtr = BT.getAddress(FunName)) { + std::tie(std::ignore, Globals) = BT.getNameAndGlobalMapping(OrigFunPtr); + } + + return CompileAndWrap(FunName.c_str(), Globals, std::move(Ctx), std::move(M)); +} + +bool Function::operator==(easy::Function const& other) const { + LLVMHolderImpl& This = static_cast(*this->Holder); + LLVMHolderImpl& Other = static_cast(*other.Holder); + return This.M_ == Other.M_; +} + +std::hash::result_type +std::hash::operator()(argument_type const& F) const noexcept { + LLVMHolderImpl& This = static_cast(*F.Holder); + return std::hash{}(This.M_); +} diff --git a/easy-jit/runtime/InitNativeTarget.cpp b/easy-jit/runtime/InitNativeTarget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..85232e88439b926952bb0314f883453b32cb1213 --- /dev/null +++ b/easy-jit/runtime/InitNativeTarget.cpp @@ -0,0 +1,28 @@ +#include + +#include +#include +#include + +using namespace llvm; + +namespace { +class InitNativeTarget { + public: + InitNativeTarget() { +#if defined(_X86) + LLVMInitializeX86Target(); + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86AsmPrinter(); +#elif defined(__aarch64__) + LLVMInitializeAArch64Target(); + LLVMInitializeAArch64TargetInfo(); + LLVMInitializeAArch64TargetMC(); + LLVMInitializeAArch64AsmPrinter(); +#endif + + sys::DynamicLibrary::LoadLibraryPermanently(nullptr); + } +} Init; +} diff --git a/easy-jit/runtime/Utils.cpp b/easy-jit/runtime/Utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b4b472110441c6489358576f957cc4e148e987b2 --- /dev/null +++ b/easy-jit/runtime/Utils.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace llvm; + +static const char EasyJitMD[] = "easy::jit"; +static const char EntryTag[] = "entry"; + +llvm::StringRef easy::GetEntryFunctionName(Module const &M) { + NamedMDNode* MD = M.getNamedMetadata(EasyJitMD); + + for(MDNode *Operand : MD->operands()) { + if(Operand->getNumOperands() != 2) + continue; + MDString* Entry = dyn_cast(Operand->getOperand(0)); + MDString* Name = dyn_cast(Operand->getOperand(1)); + + if(!Entry || !Name || Entry->getString() != EntryTag) + continue; + + return Name->getString(); + } + + llvm_unreachable("No entry function in easy::jit module!"); + return ""; +} + +void easy::MarkAsEntry(llvm::Function &F) { + Module &M = *F.getParent(); + LLVMContext &Ctx = F.getContext(); + NamedMDNode* MD = M.getOrInsertNamedMetadata(EasyJitMD); + MDNode* Node = MDNode::get(Ctx, { MDString::get(Ctx, EntryTag), + MDString::get(Ctx, F.getName())}); + MD->addOperand(Node); +} + +void easy::UnmarkEntry(llvm::Module &M) { + NamedMDNode* MD = M.getOrInsertNamedMetadata(EasyJitMD); + M.eraseNamedMetadata(MD); +} + +std::unique_ptr +easy::CloneModuleWithContext(llvm::Module const &LM, llvm::LLVMContext &C) { + // I have not found a better way to do this withouth having to fully reimplement + // CloneModule + + std::string buf; + + // write module + { + llvm::raw_string_ostream stream(buf); + llvm::WriteBitcodeToFile(LM, stream); + stream.flush(); + } + + // read the module + auto MemBuf = llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(buf)); + auto ModuleOrError = llvm::parseBitcodeFile(*MemBuf, C); + if(ModuleOrError.takeError()) + return nullptr; + + auto LMCopy = std::move(ModuleOrError.get()); + return LMCopy; +} diff --git a/easy-jit/runtime/pass/ContextAnalysis.cpp b/easy-jit/runtime/pass/ContextAnalysis.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a071ab212454eb75b4cf233bc7113d3531ddbb52 --- /dev/null +++ b/easy-jit/runtime/pass/ContextAnalysis.cpp @@ -0,0 +1,17 @@ +#include + +using namespace llvm; +using namespace easy; + +ContextAnalysisResult::ContextAnalysisResult(easy::Context const &C) : C(&C) {} +ContextAnalysisResult::ContextAnalysisResult() : C(nullptr) {} + +AnalysisKey ContextAnalysisPass::Key; + +ContextAnalysisPass::ContextAnalysisPass(easy::Context const &C) : Result_(C) {} +ContextAnalysisPass::ContextAnalysisPass() : Result_() {} + +ContextAnalysisPass::Result ContextAnalysisPass::run(Module &M, ModuleAnalysisManager &MAM) { + return Result_; +} + diff --git a/easy-jit/runtime/pass/DevirtualizeConstant.cpp b/easy-jit/runtime/pass/DevirtualizeConstant.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81bd8438e712ca1474bb2e43ab5e932a80aa7e50 --- /dev/null +++ b/easy-jit/runtime/pass/DevirtualizeConstant.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +static ConstantInt* getVTableHostAddress(Value& V) { + auto* VTable = dyn_cast(&V); + if(!VTable) + return nullptr; + MDNode *Tag = VTable->getMetadata(LLVMContext::MD_tbaa); + if(!Tag || !Tag->isTBAAVtableAccess()) + return nullptr; + + // that's a vtable + auto* Location = dyn_cast(VTable->getPointerOperand()->stripPointerCasts()); + if(!Location) + return nullptr; + + if(auto* CE = dyn_cast(Location)) { + if(CE->getOpcode() == Instruction::IntToPtr) { + Location = CE->getOperand(0); + } + } + auto* CLocation = dyn_cast(Location); + if(!CLocation) + return nullptr; + return CLocation; +} + +static Function* findFunctionAndLinkModules(Module& M, void* HostValue) { + auto &BT = easy::BitcodeTracker::GetTracker(); + const char* FName = std::get<0>(BT.getNameAndGlobalMapping(HostValue)); + + if(!FName) + return nullptr; + + std::unique_ptr LM = BT.getModuleWithContext(HostValue, M.getContext()); + + if(!Linker::linkModules(M, std::move(LM), Linker::OverrideFromSrc, + [](Module &, const StringSet<> &){})) + { + GlobalValue *GV = M.getNamedValue(FName); + if(Function* F = dyn_cast(GV)) { + F->setLinkage(Function::PrivateLinkage); + return F; + } + else { + assert(false && "wtf"); + } + } + return nullptr; +} + +template +bool Devirtualize(IIter it, IIter end) { + bool Changed = false; + + // We are trying to match %1 from CallInsts like %3 + // Matching %1 means we are doing a virtualized call in %3. + + // %1 = load ptr, ptr inttoptr (i64 187650338298944 to ptr), align 64, !tbaa !9 + // %2 = load ptr, ptr %1, align 8 + // %3 = tail call noundef i32 %2(ptr noundef nonnull align 8 dereferenceable(8) inttoptr (i64 187650338298944 to ptr)) + for (; it != end; ++it) { + Instruction &I = *it; + CallInst* CI = dyn_cast(&I); // %3 + if(!CI) + continue; + + // Try to take us to where we load the VTable + // This only happens when we are calling a temp, not a function + if (CI->getCalledFunction()) + continue; + LoadInst* LI = dyn_cast(CI->getCalledOperand()); // %2 + + // must come from a pointer load + if (!LI) + continue; + + LoadInst* LLI = dyn_cast(LI->getOperand(0)); // %1 + if (!LLI) + continue; + + auto* VTable = getVTableHostAddress(*LLI); + if(!VTable) + continue; + + void** RuntimeLoadedValue = *(void***)(uintptr_t)(VTable->getZExtValue()); + + void* CalledPtrHostValue = *RuntimeLoadedValue; + llvm::Function* F = findFunctionAndLinkModules(*LLI->getParent()->getParent()->getParent(), CalledPtrHostValue); + if(!F) + continue; + + LI->replaceAllUsesWith(F); + + Changed = true; + } + return Changed; +} + + +easy::DevirtualizeConstantPass::DevirtualizeConstantPass(llvm::StringRef Name) : TargetName_(Name) {} +easy::DevirtualizeConstantPass::DevirtualizeConstantPass() : TargetName_("") {} +PreservedAnalyses easy::DevirtualizeConstantPass::run(llvm::Function &F, FunctionAnalysisManager &FAM) { + const auto &MPMProxy = FAM.getResult(F); + + if(F.getName() != TargetName_) + return PreservedAnalyses::all(); + + if(Devirtualize(inst_begin(F), inst_end(F))) { + return PreservedAnalyses::all(); + } + + return PreservedAnalyses::none(); +} diff --git a/easy-jit/runtime/pass/InlineParameters.cpp b/easy-jit/runtime/pass/InlineParameters.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9104cea6a42a416f238178388799926b1520a9b --- /dev/null +++ b/easy-jit/runtime/pass/InlineParameters.cpp @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InlineParametersHelper.h" + +using namespace llvm; +using easy::HighLevelLayout; + +HighLevelLayout GetNewLayout(easy::Context const &C, HighLevelLayout &HLL) { + + assert(C.size() == HLL.Args_.size()); + + size_t NNewArgs = 0; + for(auto const &Arg : C) + if(auto const *Map = Arg->as()) + NNewArgs = std::max(NNewArgs, Map->get()+1); + + HighLevelLayout NewHLL(HLL); + NewHLL.Args_.clear(); + NewHLL.Args_.resize(NNewArgs, HighLevelLayout::HighLevelArg()); + + SmallSet VisitedArgs; + + // only forwarded params are kept + for(size_t arg = 0; arg != HLL.Args_.size(); ++arg) { + if(auto const *Map = C.getArgumentMapping(arg).as()) { + if(!VisitedArgs.insert(Map->get()).second) + continue; + NewHLL.Args_[Map->get()] = HLL.Args_[arg]; + } + } + + // set the param_idx once all the parameter sizes are known + for(size_t new_arg = 0, ParamIdx = 0; new_arg != NewHLL.Args_.size(); ++new_arg) { + NewHLL.Args_[new_arg].FirstParamIdx_ = ParamIdx; + ParamIdx += NewHLL.Args_[new_arg].Types_.size(); + } + return NewHLL; +} + +FunctionType* GetWrapperTy(HighLevelLayout &HLL, LLVMContext& C) { + SmallVector Args; + // Reserve a opaque pointer here, its type is recorded in attr sret. + if(HLL.StructReturn_) + Args.push_back(PointerType::getUnqual(C)); + for(auto &HLArg : HLL.Args_) + Args.insert(Args.end(), HLArg.Types_.begin(), HLArg.Types_.end()); + return FunctionType::get(HLL.Return_, Args, false); +} + +void GetInlineArgs(easy::Context const &C, + Function& F, HighLevelLayout &FHLL, + Function &Wrapper, HighLevelLayout &WrapperHLL, + SmallVectorImpl &Args, IRBuilder<> &B, + SmallVectorImpl + &PostLinkageSymbols) { + + LLVMContext &Ctx = F.getContext(); + DataLayout const &DL = F.getParent()->getDataLayout(); + + if(FHLL.StructReturn_) + Args.push_back(&*Wrapper.arg_begin()); + + for(size_t i = 0, n = C.size(); i != n; ++i) { + auto const &Arg = C.getArgumentMapping(i); + auto &ArgInF = FHLL.Args_[i]; + + switch(Arg.kind()) { + + case easy::ArgumentBase::AK_Forward: { + auto Forward = GetForwardArgs(ArgInF, FHLL, Wrapper, WrapperHLL); + Args.insert(Args.end(), Forward.begin(), Forward.end()); + } break; + case easy::ArgumentBase::AK_Int: + case easy::ArgumentBase::AK_Float: { + Args.push_back(easy::GetScalarArgument(Arg, ArgInF.Types_[0])); + } break; + + case easy::ArgumentBase::AK_Ptr: { + auto const *Ptr = Arg.as(); + Type* PtrTy = FHLL.Args_[i].Types_[0]; + + Constant* PtrVal = easy::GetScalarArgument(Arg, PtrTy); + // This linkage should be postponed as later as possible, as it will change the memory layout + // of current module, and cause severe dangling pointer problems. + // If this pointer is a global variable, it should be linked after linkage. + StringRef GlobalName = easy::GetGlobalName(*Wrapper.getParent(), *Ptr); + if (!GlobalName.empty()) + PostLinkageSymbols.push_back({GlobalName, easy::ArgumentBase::AK_Ptr, i}); + + Args.push_back(PtrVal); + } break; + + case easy::ArgumentBase::AK_Struct: { + auto const *Struct = Arg.as(); + auto &ArgInF = FHLL.Args_[i]; + + if(ArgInF.StructByPointer_) { + // struct is passed trough a pointer + // StructType should be extracted from origin Function. + Type* StructType = F.getParamByValType(ArgInF.FirstParamIdx_); + AllocaInst* ParamAlloc = easy::GetStructAlloc(B, DL, *Struct, StructType); + Args.push_back(ParamAlloc); + } else if (ArgInF.StructByArray_) { + // struct is passed as an array + Type* ArrayTy = ArgInF.Types_[0]; + size_t N = ArrayTy->getArrayNumElements(); + Type* FieldTy = ArrayTy->getArrayElementType(); + SmallVector ArrayValues; + + for(size_t ParamIdx = 0, RawOffset = 0; ParamIdx != N; ++ParamIdx) { + const char* RawField = &Struct->get()[RawOffset]; + + Constant* FieldValue; + size_t RawSize; + std::tie(FieldValue, RawSize) = easy::GetConstantFromRaw(DL, FieldTy, (uint8_t const*)RawField); + + ArrayValues.push_back(FieldValue); + RawOffset += RawSize; + } + + Constant* ArrayConst = ConstantArray::get(cast(ArrayTy), ArrayValues); + Args.push_back(ArrayConst); + } else { + // struct is passed by value (may be many values) + size_t N = ArgInF.Types_.size(); + for(size_t ParamIdx = 0, RawOffset = 0; ParamIdx != N; ++ParamIdx) { + Type* FieldTy = ArgInF.Types_[ParamIdx]; + const char* RawField = &Struct->get()[RawOffset]; + + Constant* FieldValue; + size_t RawSize; + std::tie(FieldValue, RawSize) = easy::GetConstantFromRaw(DL, FieldTy, (uint8_t const*)RawField); + + Args.push_back(FieldValue); + RawOffset += RawSize; + } + } + } break; + + case easy::ArgumentBase::AK_Module: { + + auto &ArgInF = FHLL.Args_[i]; + assert(ArgInF.Types_.size() == 1); + + easy::Function const &Function = Arg.as()->get(); + llvm::Module const& FunctionModule = Function.getLLVMModule(); + auto FunctionName = easy::GetEntryFunctionName(FunctionModule); + + // Linking is postponed after creation of WrapperFun. + PostLinkageSymbols.push_back({FunctionName, easy::ArgumentBase::AK_Module, i}); + llvm::FunctionType* FTy = F.getFunctionType(); + + // Just a placeholder + llvm::Function* FunctionInWrapper = Function::Create(FTy, Function::PrivateLinkage, FunctionName, Wrapper.getParent()); + + Args.push_back(FunctionInWrapper); + + } break; + } + } +} + +void RemapAttributes(Function const &F, HighLevelLayout const& HLL, Function &Wrapper, HighLevelLayout const& NewHLL) { + auto FAttributes = F.getAttributes(); + + auto FunAttrs = FAttributes.getFnAttrs(); + for(Attribute Attr : FunAttrs) + Wrapper.addFnAttr(Attr); + + for(size_t new_arg = 0; new_arg != NewHLL.Args_.size(); ++new_arg) { + auto const &NewArg = NewHLL.Args_[new_arg]; + auto const &OrgArg = HLL.Args_[NewArg.Position_]; + + for(size_t field = 0; field != NewArg.Types_.size(); ++field) { + Wrapper.addParamAttrs(field + NewArg.FirstParamIdx_, + AttrBuilder(F.getContext(), FAttributes.getParamAttrs(field + OrgArg.FirstParamIdx_))); + } + } +} + +Function* CreateWrapperFun(Module &M, Function &F, HighLevelLayout &HLL, easy::Context const &C, + SmallVectorImpl &PostLinkageSymbols, Value* &Call) { + LLVMContext &CC = M.getContext(); + + HighLevelLayout NewHLL(GetNewLayout(C, HLL)); + FunctionType *WrapperTy = GetWrapperTy(NewHLL,CC); + + Function* Wrapper = Function::Create(WrapperTy, Function::ExternalLinkage, "", &M); + + BasicBlock* BB = BasicBlock::Create(CC, "", Wrapper); + IRBuilder<> B(BB); + + SmallVector Args; + GetInlineArgs(C, F, HLL, *Wrapper, NewHLL, Args, B, PostLinkageSymbols); + + // The call will be updated after linking + Call = B.CreateCall(&F, Args); + + if(Call->getType()->isVoidTy()) { + B.CreateRetVoid(); + } else { + B.CreateRet(Call); + } + + RemapAttributes(F, HLL, *Wrapper, NewHLL); + + return Wrapper; +} + + +easy::InlineParametersPass::InlineParametersPass(llvm::StringRef Name) : TargetName_(Name) {} +easy::InlineParametersPass::InlineParametersPass() : TargetName_("") {} +PreservedAnalyses easy::InlineParametersPass::run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM) { + easy::Context const &C = MAM.getResult(M).getContext(); + SmallVector PostLinkageSymbols; + + llvm::Function* F = M.getFunction(TargetName_); + assert(F); + + llvm::Value* CallToUpdate = nullptr; + + HighLevelLayout HLL(C, *F); + llvm::Function* WrapperFun = CreateWrapperFun(M, *F, HLL, C, PostLinkageSymbols, CallToUpdate); + // Give it a temporary name to be discoverable after linkage. + Twine TempName = "__easy_wrapper_" + TargetName_; + WrapperFun->setName(TempName); + // After linking, the place of Function F and WrapperFun may change + if(LinkAndUpdateSymbol(M, TargetName_, TempName.str(), PostLinkageSymbols, C, CallToUpdate)){ + F = M.getFunction(TargetName_); + WrapperFun = M.getFunction(TempName.str()); + assert(F); + assert(WrapperFun); + } + + // privatize F, steal its name, copy its attributes, and its cc + F->setLinkage(llvm::Function::PrivateLinkage); + WrapperFun->takeName(F); + WrapperFun->setCallingConv(CallingConv::C); + + // add metadata to identify the entry function + easy::MarkAsEntry(*WrapperFun); + + return PreservedAnalyses::none(); +} + \ No newline at end of file diff --git a/easy-jit/runtime/pass/InlineParametersHelper.cpp b/easy-jit/runtime/pass/InlineParametersHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d1615bbcbb3443d942400c563b2afeaa733f7a12 --- /dev/null +++ b/easy-jit/runtime/pass/InlineParametersHelper.cpp @@ -0,0 +1,260 @@ +#include "InlineParametersHelper.h" +#include "easy/runtime/Utils.h" +#include + +#include + +#include + +using namespace llvm; +using namespace easy; + +HighLevelLayout::HighLevelLayout(easy::Context const& C, llvm::Function &F) { + StructReturn_ = nullptr; + + FunctionType* FTy = F.getFunctionType(); + if(F.arg_begin()->hasStructRetAttr()) + StructReturn_ = F.getParamStructRetType(0); + + Return_ = FTy->getReturnType(); + + auto &BT = easy::BitcodeTracker::GetTracker(); + + size_t ParamIdx = 0; + size_t ArgIdx = 0; + if(StructReturn_) + ParamIdx++; + + for(easy::layout_id lid : C.getLayout()) { + size_t N = BT.getLayoutInfo(lid).NumFields; + + Args_.emplace_back(ArgIdx, ParamIdx); + HighLevelArg& Arg = Args_.back(); + + size_t ArgEnd = (ParamIdx+N); + + bool SingleArg = (ParamIdx + 1) == ArgEnd; + if(SingleArg) { + Type* ParamTy = FTy->getParamType(ParamIdx); + Arg.Types_.push_back(ParamTy); + Arg.StructByPointer_ = ParamTy->isPointerTy(); + Arg.StructByArray_ = ParamTy->isArrayTy(); + ++ParamIdx; + ++ArgIdx; + } else { + for(; ParamIdx != ArgEnd; ++ParamIdx) + Arg.Types_.push_back(FTy->getParamType(ParamIdx)); + ++ArgIdx; + } + } +} + +llvm::SmallVector +easy::GetForwardArgs(easy::HighLevelLayout::HighLevelArg &ArgInF, easy::HighLevelLayout &FHLL, + llvm::Function &Wrapper, easy::HighLevelLayout &WrapperHLL) { + + llvm::SmallVector Args; + + auto GetArg = [&Wrapper, &FHLL](size_t i) -> Argument* + { return &*(Wrapper.arg_begin()+i+(FHLL.StructReturn_ ? 1 : 0)); }; + + // find layout in the new wrapper + size_t ArgPosition = ArgInF.Position_; + auto &ArgInWrapper = *std::find_if(WrapperHLL.Args_.begin(), WrapperHLL.Args_.end(), + [ArgPosition](HighLevelLayout::HighLevelArg &ArgInWrapper) + { return ArgInWrapper.Position_ == ArgPosition; }); + + for(size_t j = 0; j != ArgInF.Types_.size(); ++j) { + Args.push_back(GetArg(ArgInWrapper.FirstParamIdx_ + j)); + } + return Args; +} + +Constant* easy::GetScalarArgument(ArgumentBase const& Arg, Type* T) { + switch(Arg.kind()) { + case easy::ArgumentBase::AK_Int: { + auto const *Int = Arg.as(); + return ConstantInt::get(T, Int->get(), true); + } + case easy::ArgumentBase::AK_Float: { + auto const *Float = Arg.as(); + return ConstantFP::get(T, Float->get()); + } + case easy::ArgumentBase::AK_Ptr: { + auto const *Ptr = Arg.as(); + uintptr_t Addr = (uintptr_t)Ptr->get(); + return ConstantExpr::getIntToPtr( + ConstantInt::get(Type::getInt64Ty(T->getContext()), Addr, false), + T); + } + default: + return nullptr; + } +} + +llvm::StringRef easy::GetGlobalName(llvm::Module &M, easy::PtrArgument const &Ptr) { + auto &BT = easy::BitcodeTracker::GetTracker(); + void* PtrValue = const_cast(Ptr.get()); + if(BT.hasGlobalMapping(PtrValue)) { + return std::get<0>(BT.getNameAndGlobalMapping(PtrValue)); + } + return ""; +} + +static +void UpdateCallArg(llvm::Value* Call, easy::Context const &C, Value* newArg, size_t argNo) { + assert(Call != nullptr && "CallToUpdate is null."); + if(auto *CI = dyn_cast(Call)) { + CI->setArgOperand(argNo, newArg); + } + else { + report_fatal_error("CallToUpdate is not a call instruction.", true); + } +} + +bool easy::LinkAndUpdateSymbol(llvm::Module &M, llvm::StringRef FName, llvm::StringRef WrapperName, llvm::SmallVectorImpl &Symbols, easy::Context const &C, llvm::Value* CallToUpdate) { + assert(CallToUpdate != nullptr); + assert(llvm::isa(CallToUpdate) && "CallToUpdate is not a call instruction."); + + auto &BT = easy::BitcodeTracker::GetTracker(); + SmallVector,8> ModulesToLink; + + // Collect modules to link + for (auto& Symbol : Symbols) { + auto const &Arg = C.getArgumentMapping(Symbol.ArgNo); + switch (Arg.kind()) { + case easy::ArgumentBase::AK_Ptr: { + auto const *Ptr = Arg.as(); + Constant* PtrVal = GetScalarArgument(Arg, PointerType::getUnqual(M.getContext())); + void * PtrValue = const_cast(Ptr->get()); + if(BT.hasGlobalMapping(PtrValue)) { + std::unique_ptr LM = BT.getModuleWithContext(PtrValue, M.getContext()); + ModulesToLink.push_back(std::move(LM)); + } + } break; + case easy::ArgumentBase::AK_Module: { + easy::Function const &Function = Arg.as()->get(); + auto const &Module = Function.getLLVMModule(); + std::unique_ptr LM = + easy::CloneModuleWithContext(Module, M.getContext()); + assert(LM); + easy::UnmarkEntry(*LM); + ModulesToLink.push_back(std::move(LM)); + } break; + default: + break; + } + } + + // Link modules + for(auto &LM : ModulesToLink) { + if(Linker::linkModules(M, std::move(LM), Linker::OverrideFromSrc, + [](Module &, const StringSet<> &){})) { + llvm::report_fatal_error("Failed to link with module!", true); + } + } + + if (ModulesToLink.empty()) + return false; + + // Look up the symbol + for (auto& Symbol : Symbols) { + StringRef Name = Symbol.Name; + assert(!Name.empty() && "Unnamed symbol shouldn't reach here."); + auto Kind = Symbol.Kind; + auto *GV = M.getNamedValue(Name); + switch (Kind) { + case easy::ArgumentBase::AK_Ptr: { + if (GlobalVariable *G = dyn_cast(GV)) { + GV->setLinkage(llvm::Function::PrivateLinkage); + assert(GV->getType()->isPointerTy() && "Global variable passed as ptr arg is not a pointer"); + UpdateCallArg(CallToUpdate, C, GV, Symbol.ArgNo); + } + else if (llvm::Function *F = dyn_cast(GV)) { + F->setLinkage(llvm::Function::PrivateLinkage); + UpdateCallArg(CallToUpdate, C, F, Symbol.ArgNo); + } + else { + report_fatal_error("Global varialbe passed as ptr but is not a pointer nor a function", true); + } + } break; + case easy::ArgumentBase::AK_Module: { + llvm::Function* FunctionInM = M.getFunction(Name); + FunctionInM->setLinkage(llvm::Function::PrivateLinkage); + UpdateCallArg(CallToUpdate, C, FunctionInM, Symbol.ArgNo); + } break; + default: + break; + } + + } + + return true; +} + +std::pair easy::GetConstantFromRaw(llvm::DataLayout const& DL, + llvm::Type* T, const uint8_t* Raw) { + // pack in a I8 constant vector and cast + Type* I8 = Type::getInt8Ty(T->getContext()); + size_t Size = DL.getTypeStoreSize(T); // TODO: not sure about this + + SmallVector Elements(Size, nullptr); + for(size_t i = 0; i != Size; ++i) { + Elements[i] = ConstantInt::get(I8, Raw[i]); + } + + Constant* DataAsI8 = ConstantVector::get(Elements); + Constant* DataAsT; + if(T->isPointerTy()) { + Type* TInt = DL.getIntPtrType(T->getContext()); + Constant* DataAsTSizedInt = ConstantExpr::getBitCast(DataAsI8, TInt); + DataAsT = ConstantExpr::getIntToPtr(DataAsTSizedInt, T); + } else { + DataAsT = ConstantExpr::getBitCast(DataAsI8, T); + } + return {DataAsT, Size}; +} + +static +size_t StoreStructField(llvm::IRBuilder<> &B, + llvm::DataLayout const &DL, + Type* Ty, + uint8_t const* Raw, + AllocaInst* Alloc, SmallVectorImpl &GEP) { + + StructType* STy = dyn_cast(Ty); + size_t RawOffset = 0; + if(STy) { + errs() << "struct " << *STy << "\n"; + size_t Fields = STy->getNumContainedTypes(); + for(size_t Field = 0; Field != Fields; ++Field) { + GEP.push_back(B.getInt32(Field)); + size_t Size = StoreStructField(B, DL, STy->getElementType(Field), Raw+RawOffset, Alloc, GEP); + RawOffset += Size; + GEP.pop_back(); + } + } else { + Constant* FieldValue; + std::tie(FieldValue, RawOffset) = easy::GetConstantFromRaw(DL, Ty, (uint8_t const*)Raw); + + Value* FieldPtr = B.CreateGEP(Alloc->getAllocatedType(), Alloc, GEP, "field.gep"); + B.CreateStore(FieldValue, FieldPtr); + } + return RawOffset; +} + +llvm::AllocaInst* easy::GetStructAlloc(llvm::IRBuilder<> &B, + llvm::DataLayout const &DL, + easy::StructArgument const &Struct, + llvm::Type* StructTy) { + AllocaInst* Alloc = B.CreateAlloca(StructTy); + + SmallVector GEP = {B.getInt32(0)}; + + // TODO: Data points to the data structure or holds the data structure itself ? + // Check that size matches the .data() + + size_t Size = StoreStructField(B, DL, StructTy, (uint8_t const*)Struct.get().data(), Alloc, GEP); + + return Alloc; +} diff --git a/easy-jit/runtime/pass/InlineParametersHelper.h b/easy-jit/runtime/pass/InlineParametersHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..a4182196947f3d1b34b4cd3a859a6a036117011f --- /dev/null +++ b/easy-jit/runtime/pass/InlineParametersHelper.h @@ -0,0 +1,58 @@ +#ifndef INLINEPARAMETERSHELPER_H +#define INLINEPARAMETERSHELPER_H + +#include +#include +#include +#include +#include + +#include + +namespace easy { + +struct HighLevelLayout { + struct HighLevelArg { + size_t Position_; + size_t FirstParamIdx_; + llvm::SmallVector Types_; + bool StructByPointer_ = false; + bool StructByArray_ = false; + + HighLevelArg(size_t Pos, size_t FirstParamIdx) : + Position_(Pos), FirstParamIdx_(FirstParamIdx) { } + explicit HighLevelArg() = default; + }; + + //StructReturn_ now is just an indicator of sret, can be opt to bool. + llvm::Type* StructReturn_; + llvm::SmallVector Args_; + llvm::Type* Return_; + + HighLevelLayout(easy::Context const& C, llvm::Function &F); +}; + +struct PostLinkageSymbol { + llvm::StringRef Name; + ArgumentBase::ArgumentKind Kind; + size_t ArgNo; +}; + +llvm::SmallVector GetForwardArgs(easy::HighLevelLayout::HighLevelArg &ArgInF, easy::HighLevelLayout &FHLL, + llvm::Function &Wrapper, easy::HighLevelLayout &WrapperHLL); +llvm::Constant* GetScalarArgument(easy::ArgumentBase const& Arg, llvm::Type* T); + +llvm::StringRef GetGlobalName(llvm::Module &M, easy::PtrArgument const &Ptr); + +// Return true if any linkage happened +bool LinkAndUpdateSymbol(llvm::Module &M, llvm::StringRef FName, llvm::StringRef WrapperName, llvm::SmallVectorImpl &Symbols, easy::Context const &C, llvm::Value* CallToUpdate); + +llvm::AllocaInst* GetStructAlloc(llvm::IRBuilder<> &B, llvm::DataLayout const &DL, easy::StructArgument const &Struct, llvm::Type* StructTy); + +std::pair GetConstantFromRaw(llvm::DataLayout const& DL, llvm::Type* T, const uint8_t* Raw); + +std::pair GetConstantFromRaw(llvm::DataLayout const& DL, llvm::Type* T, const uint8_t* Raw); + +} + +#endif // INLINEPARAMETERSHELPER_H diff --git a/easy-jit/tests/doc/lit.cfg.in b/easy-jit/tests/doc/lit.cfg.in new file mode 100644 index 0000000000000000000000000000000000000000..6954458e638eb754201dc0e3cd90570f3175c2f7 --- /dev/null +++ b/easy-jit/tests/doc/lit.cfg.in @@ -0,0 +1,12 @@ +import lit.formats +import lit.util + +from subprocess import call + +lit_config.load_config(config, os.path.join("@CMAKE_CURRENT_BINARY_DIR@", "./tests/lit.cfg")) +config.test_source_root = "@CMAKE_CURRENT_SOURCE_DIR@/doc" +config.test_exec_root = "@CMAKE_CURRENT_BINARY_DIR@/tests/doc" + +# for the documentation example +if "@EASY_JIT_EXAMPLE@" in ["1", "ON"]: + config.available_features.add('example') diff --git a/easy-jit/tests/inlineparam/convolve.cpp b/easy-jit/tests/inlineparam/convolve.cpp new file mode 100644 index 0000000000000000000000000000000000000000..73aa6ba7343212a5396c29fb973d32137edac050 --- /dev/null +++ b/easy-jit/tests/inlineparam/convolve.cpp @@ -0,0 +1,45 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out + +#include + +#include +#include +#include + +using namespace std::placeholders; + +void __attribute__((noinline)) kernel(int n, int m, int * image, int const * mask, int* out) { + int i = 0; + int j = 0; + int k = 0; + int l = 0; + out[i * (n-m+1) + j] += image[(i+k) * n + j+l] * mask[k *m + l]; +} + +void test_convolve() { + std::vector image(16*16,0); + std::vector out((16-3)*(16-3),0); + std::vector out_nonjit((16-3)*(16-3), 0); + + static const int mask[3][3] = {{1,2,3},{0,0,0},{3,2,1}}; + + auto my_kernel = easy::jit(kernel, 16, 3, _1, &mask[0][0], _2); + + my_kernel(image.data(), out.data()); + + kernel(16, 3, image.data(), &mask[0][0], out_nonjit.data()); + + for(int i = 0; i != out.size(); ++i) { + if(out[i] != out_nonjit[i]) { + printf("%d != %d\n", out[i], out_nonjit[i]); + exit(-1); + } + } +} + +int main() { + test_convolve(); + return 0; +} + \ No newline at end of file diff --git a/easy-jit/tests/install/CMakeLists.txt b/easy-jit/tests/install/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2506ae2b972c63e3d875f1e24c90112ba81bff13 --- /dev/null +++ b/easy-jit/tests/install/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.4.3) + +project(test) + +find_package(EasyJit REQUIRED CONFIG) + +message("Easy::Jit include dir: " ${EasyJit_INCLUDE_DIRS}) +message("Easy::Jit lib dir: " ${EasyJit_LIBRARY_DIRS}) +message("Easy::Jit runtime: " ${EasyJit_LIBRARY}) +message("Easy::Jit plugin: " ${EasyJit_PLUGIN}) + +include_directories(${EasyJit_INCLUDE_DIRS}) +link_directories(${EasyJit_LIBRARY_DIRS}) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++14 -Xclang -disable-O0-optnone -Xclang -load -Xclang ${EasyJit_PLUGIN} -Xclang -fpass-plugin=${EasyJit_PLUGIN}") + +add_executable(InstallTest + test.cpp +) + +target_link_libraries(InstallTest ${EasyJit_LIBRARY}) diff --git a/easy-jit/tests/install/test.cpp b/easy-jit/tests/install/test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75674407b7e4fb05d09f24d72a7fb5bb115485c7 --- /dev/null +++ b/easy-jit/tests/install/test.cpp @@ -0,0 +1,34 @@ +// REQUIRES: install +// +// clean before, if not there may be unconsistencies +// RUN: rm -fr build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake InstallTest rules.ninja +// +// RUN: cmake -DCMAKE_CXX_COMPILER=%clang++ -DEasyJit_DIR=%install_dir/lib/cmake %S -G Ninja +// RUN: cmake --build . +// RUN: ./InstallTest > %t.out +// RUN: %FileCheck %s < %t.out + +#include +#include + +#include +#include + +using namespace std::placeholders; + +void test(int a) { + printf("this is a test %d!\n", a); +} + +int main() { + easy::Cache<> C; + auto test_jit0 = easy::jit(test, 0); + auto const &test_jit1 = C.jit(test, 1); + + // CHECK: this is a test 0! + // CHECK: this is a test 1! + test_jit0(); + test_jit1(); + + return 0; +} diff --git a/easy-jit/tests/lit.cfg.in b/easy-jit/tests/lit.cfg.in new file mode 100644 index 0000000000000000000000000000000000000000..463be704d29a5227594f70e865b1b4d3db19d72e --- /dev/null +++ b/easy-jit/tests/lit.cfg.in @@ -0,0 +1,54 @@ +import lit.formats +import lit.util +import os + +config.name = 'easy_jit' +config.suffixes = ['.c', '.cpp', '.ll', '.test'] + +config.test_format = lit.formats.ShTest(True) + +config.test_source_root = "@CMAKE_CURRENT_SOURCE_DIR@/tests" +config.test_exec_root = "@CMAKE_CURRENT_BINARY_DIR@/tests" + +config.environment['PATH'] = os.pathsep.join(["@LLVM_TOOLS_BINARY_DIR@"] + [ config.environment['PATH'] ]) + +runtime_lib = os.path.basename("@EASY_JIT_RUNTIME@").split('.')[0].replace("lib", "", 1) +runtime_lib_dir = os.path.dirname("@EASY_JIT_RUNTIME@") +llvm_lib_dir = os.path.join(os.path.dirname("@LLVM_TOOLS_BINARY_DIR@"), "lib") + +includes = ["@EASY_JIT_ROOT@"] +include_flags = " ".join(["-I'" + os.path.abspath(dir) + "'" for dir in "@LLVM_INCLUDE_DIRS@".split()] + ["-I'" + os.path.join(dir, "include") + "'" for dir in includes] ) + +ld_paths = [runtime_lib_dir, llvm_lib_dir] +ld_flags = "" +for ld_path in ld_paths: + ld_flags = ld_flags + " -L'" + os.path.abspath(ld_path) + "' -rpath '" + os.path.abspath(ld_path) + "' " + +ld_flags = ld_flags + " -l" + runtime_lib + +# substitutions +config.substitutions.append(('%bin', "@CMAKE_ARCHIVE_OUTPUT_DIRECTORY@")) +config.substitutions.append(('%install_dir', "@CMAKE_INSTALL_PREFIX@")) +config.substitutions.append(('%llvm_tools_dir', "@LLVM_TOOLS_BINARY_DIR@")) + +common_flags = "-g -Xclang -disable-O0-optnone " + +config.substitutions.append(('%clangxx', os.path.join("@LLVM_TOOLS_BINARY_DIR@", "clang++"))) +config.substitutions.append(('%clang', os.path.join("@LLVM_TOOLS_BINARY_DIR@", "clang"))) +config.substitutions.append(('%opt', os.path.join("@LLVM_TOOLS_BINARY_DIR@", "opt"))) +config.substitutions.append(('%cxxflags', common_flags + "--std=c++14")) +config.substitutions.append(('%cflags', common_flags)) +config.substitutions.append(('%include_flags', include_flags)) +config.substitutions.append(('%lib_pass', "@EASY_JIT_PASS@")) +config.substitutions.append(('%lib_runtime', "@EASY_JIT_RUNTIME@")) +config.substitutions.append(('%ld_flags', ld_flags)) + +config.substitutions.append(('%not', "!")) + +config.substitutions.append(('%FileCheck', os.path.join("@LLVM_TOOLS_BINARY_DIR@", "FileCheck"))) + +if "@EASY_JIT_BENCHMARK@" in ["1", "ON"] : + config.available_features.add('benchmark') + +if "@CMAKE_INSTALL_PREFIX@" and os.path.exists(os.path.join("@CMAKE_INSTALL_PREFIX@", "include", "easy")): + config.available_features.add('install') diff --git a/easy-jit/tests/meta/bad_signature_a.cpp b/easy-jit/tests/meta/bad_signature_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ee030f0599fbdb0df03a0aaad472304e14b3979a --- /dev/null +++ b/easy-jit/tests/meta/bad_signature_a.cpp @@ -0,0 +1,19 @@ +// RUN: %not %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t 2> %t.log +// RUN: %FileCheck %s < %t.log + +#include +#include + +// CHECK: An easy::jit option is expected + +using namespace std::placeholders; + +int foo(int) { + return 0; +} + +int main(int, char** argv) { + + auto foo_ = easy::jit(foo, 1, 2); + return 0; +} diff --git a/easy-jit/tests/meta/bad_signature_b.cpp b/easy-jit/tests/meta/bad_signature_b.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92602b3c947e68e9eda8ce0cbbeed54414c488c8 --- /dev/null +++ b/easy-jit/tests/meta/bad_signature_b.cpp @@ -0,0 +1,18 @@ +// RUN: %not %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t 2> %t.log +// RUN: %FileCheck %s < %t.log + +#include +#include + +using namespace std::placeholders; + +int foo(int, int, int) { + return 0; +} + +int main(int, char** argv) { + + auto foo_ = easy::jit(foo, 1, 2); + foo_(); // CHECK: easy::jit: not providing enough argument to actual call + return 0; +} diff --git a/easy-jit/tests/meta/bad_signature_c.cpp b/easy-jit/tests/meta/bad_signature_c.cpp new file mode 100644 index 0000000000000000000000000000000000000000..278f5337e5f7372987ddb51960563b500dbd3588 --- /dev/null +++ b/easy-jit/tests/meta/bad_signature_c.cpp @@ -0,0 +1,19 @@ +// RUN: %not %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t 2> %t.log +// RUN: %FileCheck %s < %t.log + +#include +#include + +// CHECK: Invalid bind, placeholder cannot be bound to a formal argument + +using namespace std::placeholders; + +int foo(float) { + return 0; +} + +int main(int, char** argv) { + + auto foo_ = easy::jit(foo, _2); + return 0; +} diff --git a/easy-jit/tests/meta/new_func_traits.cpp b/easy-jit/tests/meta/new_func_traits.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0355ff2a6f1a46f7eb3b403cdf1388eea192c183 --- /dev/null +++ b/easy-jit/tests/meta/new_func_traits.cpp @@ -0,0 +1,66 @@ +// RUN: %clangxx %cxxflags %include_flags %s -o /dev/null + +#include +#include +#include + +using namespace easy; +using namespace easy::meta; +using namespace std::placeholders; + +int foo(int, bool, float, int); +int baz(int, bool); + +int main() { + + using foo_type = decltype(foo); + using baz_type = decltype(baz); + using new_foo_traits_a = new_function_traits>; + using new_foo_traits_b = new_function_traits>; + using new_foo_traits_c = new_function_traits>; + using new_foo_traits_d = new_function_traits>; + using new_foo_traits_e = new_function_traits>; + using new_foo_traits_f = new_function_traits>; + using new_baz_traits_g = new_function_traits>; + using new_baz_traits_h = new_function_traits>; + + // full specialization + static_assert(new_foo_traits_a::parameter_list::empty, "fail A not empty"); + static_assert(new_foo_traits_b::parameter_list::empty, "fail B not empty"); + static_assert(new_foo_traits_c::parameter_list::empty, "fail C not empty"); + + // two int parameters + static_assert(new_foo_traits_d::parameter_list::size == 2, "fail D size"); + static_assert(std::is_same< + typename new_foo_traits_d::parameter_list, + meta::type_list + >::value, "fail D types"); + + // one int parameter + static_assert(new_foo_traits_e::parameter_list::size == 1, "fail E size"); + static_assert(new_foo_traits_f::parameter_list::size == 2, "fail F size"); + + static_assert(std::is_same< + typename new_foo_traits_e::parameter_list, + meta::type_list + >::value, "fail E types"); + static_assert(std::is_same< + typename new_foo_traits_f::parameter_list, + meta::type_list + >::value, "fail F types"); + + static_assert(new_baz_traits_g::parameter_list::size == 2, "fail G size"); + static_assert(new_baz_traits_h::parameter_list::size == 2, "fail H size"); + + static_assert(std::is_same< + typename new_baz_traits_g::parameter_list, + meta::type_list + >::value, "fail G types"); + + static_assert(std::is_same< + typename new_baz_traits_h::parameter_list, + meta::type_list + >::value, "fail H types"); + + return 0; +} diff --git a/easy-jit/tests/meta/type_list+func_traits.cpp b/easy-jit/tests/meta/type_list+func_traits.cpp new file mode 100644 index 0000000000000000000000000000000000000000..483db38eaa49080730c276f99dd3fa67c19fee77 --- /dev/null +++ b/easy-jit/tests/meta/type_list+func_traits.cpp @@ -0,0 +1,58 @@ +// RUN: %clangxx %cxxflags %include_flags %s -o /dev/null + +#include +#include + +using namespace easy; +using namespace easy::meta; + +int foo(int, bool, float); + +int main() { + static_assert(std::is_same< + type_list::head, + int>::value, + "not same type"); + + static_assert(std::is_same< + type_list::at<0>, + int>::value, + "not same type"); + static_assert(std::is_same< + type_list::at<1>, + bool>::value, + "not same type"); + static_assert(std::is_same< + type_list::at<2>, + float>::value, + "not same type"); + static_assert(type_list::size == 3, + "not correct size"); + static_assert(!type_list::empty, + "detected as empty"); + static_assert(std::is_same< + type_list::tail::head, + bool>::value, + "not same type"); + + using foo_type = decltype(foo); + using foo_traits = function_traits; + + static_assert(std::is_same::value, + "not same type"); + static_assert(std::is_same< + foo_traits::parameter_list, + type_list>::value, + "not same type"); + + static_assert(std::is_same< + typename meta::init_list<3,void>::type, + type_list>::value, + "not same type"); + static_assert(std::is_same< + typename meta::init_list<0,void>::type, + type_list<>>::value, + "not same type"); + + return 0; +} diff --git a/easy-jit/tests/simple/cache.cpp b/easy-jit/tests/simple/cache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06a99171ecfcb57fc1aab6e3ba9f13b2d32910a4 --- /dev/null +++ b/easy-jit/tests/simple/cache.cpp @@ -0,0 +1,37 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +int main() { + easy::Cache<> C; + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + + for(int i = 0; i != 16; ++i) { + auto const &inc = C.jit(add, _1, 1); + + if(!C.has(add, _1, 1)) { + printf("code not in cache!\n"); + return -1; + } + + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + } + + return 0; +} diff --git a/easy-jit/tests/simple/compose_bad.cpp b/easy-jit/tests/simple/compose_bad.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac8208789cdf285b06d77e2ff8326d27051ea21b --- /dev/null +++ b/easy-jit/tests/simple/compose_bad.cpp @@ -0,0 +1,28 @@ +// RUN: %not %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t + +#include + +#include +#include +#include + +using namespace std::placeholders; + +float mul(float a, float b) { + return a*b; +} + +int accumulate(std::vector const &vec, int acum, int(*fun)(int)) { + int a = acum; + for(int e : vec) + a += fun(e); + return a; +} + +int main(int argc, char** argv) { + + easy::FunctionWrapper mul_by_two = easy::jit(mul, _1, 2.0); + easy::FunctionWrapper const&)> mul_vector_by_two = easy::jit(accumulate, _1, 0, mul_by_two); + + return 0; +} diff --git a/easy-jit/tests/simple/compose_ptr.cpp b/easy-jit/tests/simple/compose_ptr.cpp new file mode 100644 index 0000000000000000000000000000000000000000..064a5a97111c82f15eaef1a17380ed259a4102b1 --- /dev/null +++ b/easy-jit/tests/simple/compose_ptr.cpp @@ -0,0 +1,56 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t 8 1 2 3 4 5 6 7 8 %t.ll > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll +// +// CHECK: 72 +// +// only one function in the final IR +// CHECK-IR: define + +#include + +#include +#include +#include + +using namespace std::placeholders; + +int mul(int a, int b) { + return a*b; +} + +int accumulate(std::vector const &vec, int acum, int(*fun)(int)) { + int a = acum; + for(int e : vec) + a += fun(e); + return a; +} + +int main(int argc, char** argv) { + + int n = atoi(argv[1]); + + // read input + std::vector vec; + for(int i = 0; i != n; ++i) + vec.emplace_back(atoi(argv[i+2])); + + // generate code + easy::FunctionWrapper mul_by_two = easy::jit(mul, _1, 2); + + static_assert(easy::is_function_wrapper::value, "Value not detected as function wrapper!"); + static_assert(easy::is_function_wrapper::value, "Reference not detected as function wrapper!"); + static_assert(easy::is_function_wrapper::value, "RReference not detected as function wrapper!"); + + easy::FunctionWrapper const&)> mul_vector_by_two = easy::jit(accumulate, _1, 0, mul_by_two, + easy::options::dump_ir(argv[argc-1])); + + // kernel! + int result = mul_vector_by_two(vec); + + // output + printf("%d\n", result); + + return 0; +} diff --git a/easy-jit/tests/simple/compose_ref.cpp b/easy-jit/tests/simple/compose_ref.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c01be958bbbf536d1b00e762cf7339f7da4542a4 --- /dev/null +++ b/easy-jit/tests/simple/compose_ref.cpp @@ -0,0 +1,52 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t 8 1 2 3 4 5 6 7 8 %t.ll > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll +// +// CHECK: 72 +// +// only one function in the final IR +// CHECK-IR: define +// CHECK-IR: define + +#include + +#include +#include +#include + +using namespace std::placeholders; + +int mul(int a, int b) { + return a*b; +} + +int accumulate(std::vector const &vec, int acum, int(&fun)(int)) { + int a = acum; + for(int e : vec) + a += fun(e); + return a; +} + +int main(int argc, char** argv) { + + int n = atoi(argv[1]); + + // read input + std::vector vec; + for(int i = 0; i != n; ++i) + vec.emplace_back(atoi(argv[i+2])); + + // generate code + easy::FunctionWrapper mul_by_two = easy::jit(mul, _1, 2); + easy::FunctionWrapper const&)> mul_vector_by_two = easy::jit(accumulate, _1, 0, mul_by_two, + easy::options::dump_ir(argv[argc-1])); + + // kernel! + int result = mul_vector_by_two(vec); + + // output + printf("%d\n", result); + + return 0; +} diff --git a/easy-jit/tests/simple/custom_key_cache.cpp b/easy-jit/tests/simple/custom_key_cache.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfea3398e0e0e519e748926063e79941576c2be4 --- /dev/null +++ b/easy-jit/tests/simple/custom_key_cache.cpp @@ -0,0 +1,65 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +void test_int() { + easy::Cache C; + + for(int i = 0; i != 6; ++i) { + auto const &inc = C.jit(i, add, _1, i); + + if(!C.has(i)) { + printf("code not in cache!\n"); + } + + // CHECK-NOT: code not in cache + // CHECK: inc.int(0) is 0 + // CHECK: inc.int(0) is 1 + // CHECK: inc.int(0) is 2 + // CHECK: inc.int(0) is 3 + // CHECK: inc.int(0) is 4 + // CHECK: inc.int(0) is 5 + + printf("inc.int(%d) is %d\n", 0, inc(0)); + } +} + +void test_string() { + easy::Cache C; + + for(int i = 0; i != 6; ++i) { + auto const &inc = C.jit(std::to_string(i), add, _1, i); + + if(!C.has(std::to_string(i))) { + printf("code not in cache!\n"); + } + + // CHECK-NOT: code not in cache + // CHECK: inc.str(0) is 0 + // CHECK: inc.str(0) is 1 + // CHECK: inc.str(0) is 2 + // CHECK: inc.str(0) is 3 + // CHECK: inc.str(0) is 4 + // CHECK: inc.str(0) is 5 + + printf("inc.str(%d) is %d\n", 0, inc(0)); + } +} + +int main() { + test_int(); + test_string(); + return 0; +} diff --git a/easy-jit/tests/simple/devirtualization.cpp b/easy-jit/tests/simple/devirtualization.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab1a3cb99b0117ca334680d8b2e21b2b3d6cc872 --- /dev/null +++ b/easy-jit/tests/simple/devirtualization.cpp @@ -0,0 +1,42 @@ +// RUN: %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out + +#include +#include + +#include +#include + + +using namespace std::placeholders; + +struct Foo { + virtual int EASY_JIT_EXPOSE doit() { return 1; } + virtual ~Foo() = default; +}; + +struct Bar : Foo { + int EASY_JIT_EXPOSE doit() override { return 2; } +}; + +int doit(Foo* f) { + return f->doit(); +} + +int main(int argc, char** argv) { + Foo* f = nullptr; + if(argc == 1) + f = new Foo(); + else + f = new Bar(); + + easy::FunctionWrapper easy_doit = easy::jit(doit, f, easy::options::dump_ir(argv[1])); + + // CHECK: doit() is 2 + printf("doit() is %d\n", easy_doit()); + + delete f; + + return 0; +} diff --git a/easy-jit/tests/simple/devirtualization_nohint.cpp b/easy-jit/tests/simple/devirtualization_nohint.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7c5275a6da699d4f42969d0cfa860908a97d7e9c --- /dev/null +++ b/easy-jit/tests/simple/devirtualization_nohint.cpp @@ -0,0 +1,41 @@ +// RUN: %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out + +#include +#include + +#include +#include + +using namespace std::placeholders; + +struct Foo { + virtual int doit() { return 1; } + virtual ~Foo() = default; +}; + +struct Bar : Foo { + int doit() override { return 2; } +}; + +int doit(Foo* f) { + return f->doit(); +} + +int main(int argc, char** argv) { + Foo* f = nullptr; + if(argc == 1) + f = new Foo(); + else + f = new Bar(); + + easy::FunctionWrapper easy_doit = easy::jit(doit, f, easy::options::dump_ir(argv[1])); + + // CHECK: doit() is 2 + printf("doit() is %d\n", easy_doit()); + + delete f; + + return 0; +} diff --git a/easy-jit/tests/simple/double_a.cpp b/easy-jit/tests/simple/double_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..693a98c87012195c2a7c60e9950d954b629aa834 --- /dev/null +++ b/easy-jit/tests/simple/double_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +double add (double a, double b) { + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4.00) is 5.00 + // CHECK: inc(5.00) is 6.00 + // CHECK: inc(6.00) is 7.00 + // CHECK: inc(7.00) is 8.00 + for(int v = 4; v != 8; ++v) + printf("inc(%.2f) is %.2f\n", (double)v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/exception_a.cpp b/easy-jit/tests/simple/exception_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e08de0d140292739196f169ef24f5655f889f087 --- /dev/null +++ b/easy-jit/tests/simple/exception_a.cpp @@ -0,0 +1,36 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + if(a == 8) + throw std::runtime_error{"an expected error occured"}; + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + // CHECK: inc(8) is exception: an expected error occured + // CHECK: inc(9) is 10 + for(int v = 4; v != 10; ++v) { + try { + printf("inc(%d) is %d\n", v, inc(v)); + } catch(std::runtime_error &e) { + printf("inc(%d) is exception: %s\n", v, e.what()); + } + } + + return 0; +} diff --git a/easy-jit/tests/simple/float_a.cpp b/easy-jit/tests/simple/float_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2951af1053885eea02bf370f35626320d378a56c --- /dev/null +++ b/easy-jit/tests/simple/float_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +float add (float a, float b) { + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4.00) is 5.00 + // CHECK: inc(5.00) is 6.00 + // CHECK: inc(6.00) is 7.00 + // CHECK: inc(7.00) is 8.00 + for(int v = 4; v != 8; ++v) + printf("inc(%.2f) is %.2f\n", (float)v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_a.cpp b/easy-jit/tests/simple/fun_ptr_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8baf94dda274c8611b3ef7e3b781c93b4cceea32 --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_a.cpp @@ -0,0 +1,42 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function +// reading from a global variable +// CHECK-IR: @[[GLOBAL:.+]] = external +// CHECK-IR: define +// CHECK-IR: add +// CHECK-IR: ret + + +using namespace std::placeholders; + +static int bubu() { + static int v = 0; + return v++; +} + +static int add (int a, int (*f)()) { + return a+f(); +} + +int main(int argc, char** argv) { + easy::FunctionWrapper inc = easy::jit(add, _1, bubu, easy::options::dump_ir(argv[1])); + + // CHECK: inc(4) is 4 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 8 + // CHECK: inc(7) is 10 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_b.cpp b/easy-jit/tests/simple/fun_ptr_b.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bae22d1361d0f710144f4ca1d531c3a88b0b0ab1 --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_b.cpp @@ -0,0 +1,41 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function, without any call +// CHECK-IR: call{{.*@.*}} + + +using namespace std::placeholders; + +static void foo(void* dat) { + (*(int*)dat) += 1; +} + +static void map (void* data, unsigned nmemb, unsigned size, void (*f)(void*)) { + for(unsigned i = 0; i < nmemb; ++i) + f((char*)data + i * size); +} + +int main(int argc, char** argv) { + easy::FunctionWrapper map_w = easy::jit(map, _1, _2, _3, foo, easy::options::dump_ir(argv[1])); + + int data[] = {1,2,3,4}; + map_w(data, sizeof(data)/sizeof(data[0]), sizeof(data[0])); + + // CHECK: data[0] is 2 + // CHECK: data[1] is 3 + // CHECK: data[2] is 4 + // CHECK: data[3] is 5 + for(int v = 0; v != 4; ++v) + printf("data[%d] is %d\n", v, data[v]); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_c.cpp b/easy-jit/tests/simple/fun_ptr_c.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2590bf653cfcc818a8499424235a44a6688ffe45 --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_c.cpp @@ -0,0 +1,47 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function, without any call +// CHECK-IR: call{{ }} + + +using namespace std::placeholders; + +static void foo(void* dat) { + (*(int*)dat) += 1; +} +static void bar(void* dat) { + (*(int*)dat) += 2; +} + +static void map (void* data, unsigned nmemb, unsigned size, void (*f)(void*)) { + for(unsigned i = 0; i < nmemb; ++i) + f((char*)data + i * size); +} + +int main(int argc, char** argv) { + + static void (*(come_and_get_some[]))(void*dat) = {foo, bar}; + + easy::FunctionWrapper map_w = easy::jit(map, _1, _2, _3, come_and_get_some[argc?1:0], easy::options::dump_ir(argv[1])); + + int data[] = {1,2,3,4}; + map_w(data, sizeof(data)/sizeof(data[0]), sizeof(data[0])); + + // CHECK: data[0] is 3 + // CHECK: data[1] is 4 + // CHECK: data[2] is 5 + // CHECK: data[3] is 6 + for(int v = 0; v != 4; ++v) + printf("data[%d] is %d\n", v, data[v]); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_d.cpp b/easy-jit/tests/simple/fun_ptr_d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..34ea293c47e38099b2c95bc9dc6a5dfcdf29e7fb --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_d.cpp @@ -0,0 +1,45 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function +// reading from a global variable +// CHECK-IR: @[[GLOBAL:.+]] = external +// CHECK-IR: define +// CHECK-IR: add +// CHECK-IR: ret + + +using namespace std::placeholders; + +static int bubu() { + static int v = 0; + return v++; +} +static int bibi() { + return 0; +} + +static int add (int a, int (*f)()) { + return a+f(); +} + +int main(int argc, char** argv) { + easy::FunctionWrapper inc = easy::jit(add, _1, argc?bubu:bibi, easy::options::dump_ir(argv[1])); + + // CHECK: inc(4) is 4 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 8 + // CHECK: inc(7) is 10 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_e.cpp b/easy-jit/tests/simple/fun_ptr_e.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ae0bde95baf3f03691029be4d46380b5e6ae52c --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_e.cpp @@ -0,0 +1,49 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function, without any call +// CHECK-IR: call{{ }} + + +using namespace std::placeholders; + +static void foo(void* dat) { + (*(int*)dat) += 1; +} +static void bar(void* dat) { + (*(int*)dat) += 2; +} + +static void map (void* data, unsigned nmemb, unsigned size, void (*f)(void*)) { + for(unsigned i = 0; i < nmemb; ++i) + f((char*)data + i * size); +} + +int main(int argc, char** argv) { + + void (*(come_and_get_some[2]))(void*dat); + come_and_get_some[0] = foo; + come_and_get_some[1] = bar; + + easy::FunctionWrapper map_w = easy::jit(map, _1, _2, _3, come_and_get_some[argc?1:0], easy::options::dump_ir(argv[1])); + + int data[] = {1,2,3,4}; + map_w(data, sizeof(data)/sizeof(data[0]), sizeof(data[0])); + + // CHECK: data[0] is 3 + // CHECK: data[1] is 4 + // CHECK: data[2] is 5 + // CHECK: data[3] is 6 + for(int v = 0; v != 4; ++v) + printf("data[%d] is %d\n", v, data[v]); + + return 0; +} diff --git a/easy-jit/tests/simple/fun_ptr_f.cpp b/easy-jit/tests/simple/fun_ptr_f.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f51b2b6706d6dedbde19095f44c2025c9da5eaa9 --- /dev/null +++ b/easy-jit/tests/simple/fun_ptr_f.cpp @@ -0,0 +1,47 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function, without any call +// CHECK-IR: call{{ }} + + +using namespace std::placeholders; + +static void foo(void* dat) { + (*(int*)dat) += 1; +} +static void bar(void* dat) { + (*(int*)dat) += 2; +} + +static void map (void* data, unsigned nmemb, unsigned size, void (*f)(void*)) { + for(unsigned i = 0; i < nmemb; ++i) + f((char*)data + i * size); +} + +int main(int argc, char** argv) { + + void (*(come_and_get_some[2]))(void*dat) = {foo, bar}; + + easy::FunctionWrapper map_w = easy::jit(map, _1, _2, _3, come_and_get_some[argc?1:0], easy::options::dump_ir(argv[1])); + + int data[] = {1,2,3,4}; + map_w(data, sizeof(data)/sizeof(data[0]), sizeof(data[0])); + + // CHECK: data[0] is 3 + // CHECK: data[1] is 4 + // CHECK: data[2] is 5 + // CHECK: data[3] is 6 + for(int v = 0; v != 4; ++v) + printf("data[%d] is %d\n", v, data[v]); + + return 0; +} diff --git a/easy-jit/tests/simple/int_a.cpp b/easy-jit/tests/simple/int_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9258a6b5a598ec28cc6715bfa7171641fe4f3240 --- /dev/null +++ b/easy-jit/tests/simple/int_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/int_ptr_a.cpp b/easy-jit/tests/simple/int_ptr_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7bd542d482efa0eafde23703e1073cc2a44c3ad --- /dev/null +++ b/easy-jit/tests/simple/int_ptr_a.cpp @@ -0,0 +1,36 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// verify that the variable 'b' is not loaded, and the addition is performed using its constant value +// CHECK-IR-NOT: inttoptr +// CHECK-IR-NOT: load i32 +// CHECK-IR: add{{.*}}4321 + +using namespace std::placeholders; + +static int add (int a, int const *b) { + return a+*b; +} + +int const b = 4321; + +int main(int argc, char** argv) { + easy::FunctionWrapper inc = easy::jit(add, _1, &b, easy::options::dump_ir(argv[1])); + + // CHECK: inc(4) is 4325 + // CHECK: inc(5) is 4326 + // CHECK: inc(6) is 4327 + // CHECK: inc(7) is 4328 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/long_a.cpp b/easy-jit/tests/simple/long_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8cdb1ac9f9739409b563cd513d07195de6cb155b --- /dev/null +++ b/easy-jit/tests/simple/long_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (long a, long b) { + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, -1); + + // CHECK: inc(4) is 3 + // CHECK: inc(5) is 4 + // CHECK: inc(6) is 5 + // CHECK: inc(7) is 6 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/multi_file+regexp.cpp b/easy-jit/tests/simple/multi_file+regexp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a775f94a900515902a754948435e84eb8584f4f3 --- /dev/null +++ b/easy-jit/tests/simple/multi_file+regexp.cpp @@ -0,0 +1,39 @@ +// RUN: %clangxx %cxxflags %include_flags %s -Xclang -load -Xclang %lib_pass -Xclang -fpass-plugin=%lib_pass -DMAIN -c -o %t.main.o +// RUN: %clangxx %cxxflags %include_flags %s -Xclang -load -Xclang %lib_pass -Xclang -fpass-plugin=%lib_pass -DLIB -c -o %t.lib.o -mllvm -easy-export="add" +// RUN: %clangxx %ld_flags %t.main.o %t.lib.o -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#ifdef LIB + +extern "C" int add (int a, int b) { + return a+b; +} + +#endif + +#ifdef MAIN + +#include + +#include +#include + +using namespace std::placeholders; + +extern "C" int add (int a, int b); + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} + +#endif diff --git a/easy-jit/tests/simple/nortti_a.cpp b/easy-jit/tests/simple/nortti_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..faabc5e544c7f2bd55ee42a51279d60761ba8e02 --- /dev/null +++ b/easy-jit/tests/simple/nortti_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t -fno-rtti +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, 1); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/opt_level.cpp b/easy-jit/tests/simple/opt_level.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2d9a55db3925f53145e727fb2d4229600b788987 --- /dev/null +++ b/easy-jit/tests/simple/opt_level.cpp @@ -0,0 +1,90 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +void test_level_O0 (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(0, 0)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +void test_level_O1 (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(1, 0)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +void test_level_O2 (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(2, 0)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +void test_level_O3 (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(3, 0)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +void test_level_Os (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(2, 1)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +void test_level_Oz (){ + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::opt_level(2, 2)); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); +} + +int main() { + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_O0(); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_O1(); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_O2(); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_O3(); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_Os(); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + test_level_Oz(); + + return 0; +} diff --git a/easy-jit/tests/simple/ptr_a.cpp b/easy-jit/tests/simple/ptr_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dc8547a6efdfd5a121c3ebdb8f907329faf908cb --- /dev/null +++ b/easy-jit/tests/simple/ptr_a.cpp @@ -0,0 +1,28 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +int add (int a, int *b) { + return a+*b; +} + +int main() { + int b = 1; + easy::FunctionWrapper inc = easy::jit(add, _1, &b); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/serialize.cpp b/easy-jit/tests/simple/serialize.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9a12c6a781f607365820b54a20f3a13cb6e8b152 --- /dev/null +++ b/easy-jit/tests/simple/serialize.cpp @@ -0,0 +1,41 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +int main() { + auto inc_store = easy::jit(add, _1, 1); + + std::stringstream out; + inc_store.serialize(out); + out.flush(); + + std::string buffer = out.str(); + + assert(buffer.size()); + printf("buffer.size() = %lu\n", buffer.size()); + + std::stringstream in(buffer); + auto inc_load = easy::FunctionWrapper::deserialize(in); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc_load(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/serialize_multifile.cpp b/easy-jit/tests/simple/serialize_multifile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cfebac7fac5d59d39ebacb69a9e9c5384e85f568 --- /dev/null +++ b/easy-jit/tests/simple/serialize_multifile.cpp @@ -0,0 +1,57 @@ +// RUN: %clangxx %cxxflags %include_flags %s -DMAIN -c -o %t.main.o +// RUN: %clangxx %cxxflags %include_flags %s -Xclang -load -Xclang %lib_pass -Xclang -fpass-plugin=%lib_pass -DLIB -c -o %t.lib.o -mllvm -easy-export="add" +// RUN: %clangxx %ld_flags %t.main.o %t.lib.o -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include +#include +#include + +using namespace std::placeholders; + +#ifdef LIB + +static int var = 0; + +static int add (int a, int b) { + return a+b+(var++); +} + +std::string get_add(int b) { + auto inc_store = easy::jit(add, _1, 1); + + std::ostringstream out; + inc_store.serialize(out); + out.flush(); + + return out.str(); +} + +#endif + +#ifdef MAIN + +std::string get_add(int b); + +int main() { + + std::string bitcode = get_add(1); + std::istringstream in(bitcode); + auto inc_load = easy::FunctionWrapper::deserialize(in); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 7 + // CHECK: inc(6) is 9 + // CHECK: inc(7) is 11 + for(int v = 4; v != 8; ++v) { + printf("inc(%d) is %d\n", v, inc_load(v)); + } + + return 0; +} + +#endif diff --git a/easy-jit/tests/simple/serialize_static.cpp b/easy-jit/tests/simple/serialize_static.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a3a2a6d828f829960552bbd0e808584ac635a66a --- /dev/null +++ b/easy-jit/tests/simple/serialize_static.cpp @@ -0,0 +1,45 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include +#include +#include + +using namespace std::placeholders; + +static int var = 0; + +int add (int a, int b) { + return a+b+var; +} + +int main() { + auto inc_store = easy::jit(add, _1, 1); + + std::stringstream out; + inc_store.serialize(out); + out.flush(); + + std::string buffer = out.str(); + + assert(buffer.size()); + printf("buffer.size() = %lu\n", buffer.size()); + + std::stringstream in(buffer); + auto inc_load = easy::FunctionWrapper::deserialize(in); + + // CHECK: inc(4) is 6 + // CHECK: inc(5) is 8 + // CHECK: inc(6) is 10 + // CHECK: inc(7) is 12 + for(int v = 4; v != 8; ++v) { + var++; + printf("inc(%d) is %d\n", v, inc_load(v)); + } + + return 0; +} diff --git a/easy-jit/tests/simple/small_struct.cpp b/easy-jit/tests/simple/small_struct.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2ff6474308f0acf56d26536e2b5939af8d3cecc3 --- /dev/null +++ b/easy-jit/tests/simple/small_struct.cpp @@ -0,0 +1,32 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +struct Point { + int x; + int y; +}; + +int add (Point a, Point b) { + return a.x+b.x+a.y+b.y; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, _1, Point{1,1}); + + // CHECK: inc(4,4) is 10 + // CHECK: inc(5,5) is 12 + // CHECK: inc(6,6) is 14 + // CHECK: inc(7,7) is 16 + for(int v = 4; v != 8; ++v) + printf("inc(%d,%d) is %d\n", v, v, inc(Point{v,v})); + + return 0; +} diff --git a/easy-jit/tests/simple/static_var_a.cpp b/easy-jit/tests/simple/static_var_a.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4e79826035985127f42d1e39ba493759d5c58767 --- /dev/null +++ b/easy-jit/tests/simple/static_var_a.cpp @@ -0,0 +1,27 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +void add (int a, int b) { + printf("inc(%d) is %d\n", a, a+b); +} + +int main(int argc, char** argv) { + easy::FunctionWrapper inc = easy::jit(add, _1, 1, easy::options::dump_ir(argv[1])); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + inc(v); + + return 0; +} diff --git a/easy-jit/tests/simple/static_var_b.cpp b/easy-jit/tests/simple/static_var_b.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f0d0bfe35d00762d9245434eee57f2f7e8fe088 --- /dev/null +++ b/easy-jit/tests/simple/static_var_b.cpp @@ -0,0 +1,29 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +void add (int b) { + static int a = 4; + printf("inc(%d) is %d\n", a, a+b); + a++; +} + +int main() { + easy::FunctionWrapper inc = easy::jit(add, 1); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + for(int v = 4; v != 8; ++v) + inc(); + + return 0; +} diff --git a/easy-jit/tests/simple/struct_arg.cpp b/easy-jit/tests/simple/struct_arg.cpp new file mode 100644 index 0000000000000000000000000000000000000000..01ba1992cd3755ebc9a15de9a3b63e0db5f9f98e --- /dev/null +++ b/easy-jit/tests/simple/struct_arg.cpp @@ -0,0 +1,102 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +template +struct Point { + T x; + T y; + + Point(T _x, T _y) { + x = _x; + y = _y; + } +}; + +template +long whopie (Point a, T b) { + return a.x * b + a.y * b; +} + +template +void test_swap() { + T val = 1; + auto whop = easy::jit(whopie, _2, _1); + + for(int v = 4; v != 8; ++v) { + long z = whop(val, Point(v*2, v*3)); + printf(" swap(%d) %ld\n", v, z); + + if(z != whopie(Point(v*2, v*3), val)) + exit(-1); + } +} + +template +void test_specialzie_a() { + T val = 1; + auto whop = easy::jit(whopie, _1, val); + + for(int v = 4; v != 8; ++v) { + long z = whop(Point(v*2, v*3)); + printf(" spec_a(%d) %ld\n", v, z); + + if(z != whopie(Point(v*2, v*3), val)) + exit(-1); + } +} + +template +void test_specialzie_b() { + T val = 1; + auto whop = easy::jit(whopie, Point(val*2, val*3), _1); + + for(int v = 4; v != 8; ++v) { + long z = whop(v); + printf(" spec_b(%d) %ld\n", v, z); + + if(z != whopie(Point(val*2, val*3), v)) + exit(-1); + } +} + +template +void test_specialzie_ab() { + T val = 1; + auto whop = easy::jit(whopie, Point(val*2, val*3), val); + + for(int v = 4; v != 8; ++v) { + long z = whop(); + printf(" spec_ab() %ld\n", z); + + if(z != whopie(Point(val*2, val*3), val)) + exit(-1); + } +} + +template +void test() { + printf("== %s ==\n", typeid(T).name()); + test_swap(); + test_specialzie_a(); + test_specialzie_b(); + test_specialzie_ab(); +} + +int main() { + + test(); + test(); + test(); + test(); + test(); + test(); + + return 0; +} diff --git a/easy-jit/tests/simple/struct_return.cpp b/easy-jit/tests/simple/struct_return.cpp new file mode 100644 index 0000000000000000000000000000000000000000..22d1cbf03eb0c6f40d72a0bb64617602e4b560a1 --- /dev/null +++ b/easy-jit/tests/simple/struct_return.cpp @@ -0,0 +1,73 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include + +using namespace std::placeholders; + +template +struct Point { + T x; + T y; +}; + +template +Point build (T a, T b) { + return Point{(T)(a-7), (T)(2*b)}; +} + +template +void test() { + T val = 1; + easy::FunctionWrapper(T)> build_val = easy::jit(build, _1, val); + + for(int v = 4; v != 8; ++v) { + Point xy = build_val((T)v); + printf("point = %d %d \n", (int)xy.x, (int)xy.y); + } +} + +int main() { + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + // CHECK: point = -3 2 + // CHECK: point = -2 2 + // CHECK: point = -1 2 + // CHECK: point = 0 2 + test(); + + return 0; +} diff --git a/easy-jit/tests/simple/thread.cpp b/easy-jit/tests/simple/thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fc1e845cd0f94194eff6b85ee54a8a54a3010997 --- /dev/null +++ b/easy-jit/tests/simple/thread.cpp @@ -0,0 +1,30 @@ +// RUN: %clangxx %cxxflags %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -lpthread -o %t +// RUN: %t > %t.out +// RUN: %FileCheck %s < %t.out + +#include + +#include +#include +#include + +using namespace std::placeholders; + +int add (int a, int b) { + return a+b; +} + +int main() { + auto inc_future = std::async(std::launch::async, + [](){ return easy::jit(add, _1, 1);}); + + // CHECK: inc(4) is 5 + // CHECK: inc(5) is 6 + // CHECK: inc(6) is 7 + // CHECK: inc(7) is 8 + auto inc = inc_future.get(); + for(int v = 4; v != 8; ++v) + printf("inc(%d) is %d\n", v, inc(v)); + + return 0; +} diff --git a/easy-jit/tests/simple/unroll.cpp b/easy-jit/tests/simple/unroll.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f6a739ea63b45d2a08d8e5171436da6f529b05e1 --- /dev/null +++ b/easy-jit/tests/simple/unroll.cpp @@ -0,0 +1,41 @@ +// RUN: %clangxx %cxxflags -O2 %include_flags %ld_flags %s -Xclang -fpass-plugin=%lib_pass -o %t +// RUN: %t "%t.ll" > %t.out +// RUN: %FileCheck %s < %t.out +// RUN: %FileCheck --check-prefix=CHECK-IR %s < %t.ll + +#include +#include + +#include +#include + +// only one function +// with only one block (~no branch) +// CHECK-IR: define +// CHECK-IR-NOT: define +// CHECK-IR-NOT: br +// CHECK-IR: ret + +using namespace std::placeholders; + +int dot(int *a, std::vector b, int n) { + int x = 0; + for(size_t i = 0; i != n; ++i) { + x += a[i]*b[i]; + } + return x; +} + +int main(int, char** argv) { + + std::vector a = {1,2,3,4}, + b = {4,3,2,1}; + + auto dot_a = easy::jit(dot, a.data(), _1, 4, easy::options::dump_ir(argv[1])); + int x = dot_a(b); + + // CHECK: dot is 20 + printf("dot is %d\n", x); + + return 0; +} diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt index 12618966c4adfd250b6d0ce82d752b7ab6056d6a..2eb21ad0e52dfde206e3731651418ab26272e289 100644 --- a/llvm/CMakeLists.txt +++ b/llvm/CMakeLists.txt @@ -116,7 +116,7 @@ endif() # one for llvm+clang+... using the same sources. set(LLVM_ALL_PROJECTS "bolt;clang;clang-tools-extra;compiler-rt;cross-project-tests;libc;libclc;lld;lldb;mlir;openmp;polly;pstl") # The flang project is not yet part of "all" projects (see C++ requirements) -set(LLVM_EXTRA_PROJECTS "flang") +set(LLVM_EXTRA_PROJECTS "flang;easy-jit") # List of all known projects in the mono repo set(LLVM_KNOWN_PROJECTS "${LLVM_ALL_PROJECTS};${LLVM_EXTRA_PROJECTS}") set(LLVM_ENABLE_PROJECTS "" CACHE STRING diff --git a/llvm/tools/CMakeLists.txt b/llvm/tools/CMakeLists.txt index db66dad5dc0dbd7110c8f52c5f6827413cab8313..6c3be258a12f4d713be8d17941be1b2d1307ccf0 100644 --- a/llvm/tools/CMakeLists.txt +++ b/llvm/tools/CMakeLists.txt @@ -45,6 +45,7 @@ add_llvm_external_project(clang) add_llvm_external_project(flang) add_llvm_external_project(lldb) add_llvm_external_project(bolt) +add_llvm_external_project(easy-jit) # Automatically add remaining sub-directories containing a 'CMakeLists.txt' # file as external projects.