diff --git a/.xo-jit/.gitignore b/.xo-jit/.gitignore deleted file mode 100644 index f3b23fc3..00000000 --- a/.xo-jit/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# emacs projectile config -.projectile -# clangd working space (see emacs+lsp) -.cache -# typical cmake build directory (source-tree-nephew) -.build* -# symlink to builddir/compile_commands.json; should be set manually in dev sandbox -compile_commands.json diff --git a/.xo-jit/CMakeLists.txt b/.xo-jit/CMakeLists.txt deleted file mode 100644 index 7243123a..00000000 --- a/.xo-jit/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -# jit/CMakeLists.txt - -cmake_minimum_required(VERSION 3.10) - -project(xo_jit VERSION 0.1) - -include(GNUInstallDirs) -include(cmake/xo-bootstrap-macros.cmake) - -xo_cxx_toplevel_options3() - -# ---------------------------------------------------------------- -# c++ settings - -set(PROJECT_CXX_FLAGS "") -#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! -add_definitions(${PROJECT_CXX_FLAGS}) - -# ---------------------------------------------------------------- - -# must complete definition of jit lib before configuring examples -add_subdirectory(src/jit) -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- - -add_subdirectory(example) -add_subdirectory(utest) - -# ---------------------------------------------------------------- - -if (XO_ENABLE_EXAMPLES) - install(TARGETS xo_jit_ex1 DESTINATION bin/xo/example/jit) - install(TARGETS xo_jit_ex2 DESTINATION bin/xo/example/jit) - install(TARGETS xo_fptr_ex3 DESTINATION bin/xo/example/jit) - install(TARGETS xo_kaleidoscope4 DESTINATION bin/xo/example/jit) -endif() - -# ---------------------------------------------------------------- - -# reminder: must come last: docs targets depend on all the other library/utest targets -add_subdirectory(docs) - -# end CMakeLists.txt diff --git a/.xo-jit/HOWTO b/.xo-jit/HOWTO deleted file mode 100644 index c3eed238..00000000 --- a/.xo-jit/HOWTO +++ /dev/null @@ -1,72 +0,0 @@ -* How to add an llvm intrinsic - - - add enum value llvmintrinsic::foo to llvmintrinsic in xo-expression - - also add foo to llvmintrinsic2str in xo-expression llvmintrinsic.hpp - - if we have a built-in Primitive for the same functionality, - want Primitive::intrinsic_ = llvmintrinsic::foo - - in MachPipeline::codegen_apply(), look for switch(intrinsic), - add case llvmintrinsic::foo - - substitute codegen for the intrinsic - in place of the catch-all IRBuilder::CreateCall - -** To test from python: - - 1. install xo-pyjit and deps somewhere (~/local2 in this example) - - 2. PYTHONPATH=~/local2:$PYTHONPATH python - - 3. python: - - from xo_pyreflect import * - from xo_pyexpression import * - from xo_pyjit import * - i32_t=TypeDescr.lookup_by_name('double') - x=make_var('x',i32_t) - f=make_mul_i32_pm() - c=make_apply(f,[x,x]) - lm=make_lambda('sq',[x],c) - - mp=MachPipeline.make() - code=mp.codegen(lm) - print(code.print()) - - 4. in our example, get output like: - - define i32 @sq(i32 %x) { - entry: - %0 = mul i32 %x, %x - ret i32 %0 - } - - 5. to compile+run via JIT: - - mp.machgen_current_module() - fn=mp.lookup_fn('int (*)(int)', 'sq') - fn(16) # -> 256 - -** to figure out what 'IR should look like' for something simple - - write some c++: - - #include - - struct env_type { - env_type * parent; - env_type * (*unwind)(env_type *, int); - }; - - double wrap_sqrt(env_type * env, double x) { - return ::sqrt(x); - } - - int main() { - wrap_sqrt(nullptr, 2.0); - } - - compile to emit IR - - $ clang -cc1 ex_cpp.cpp -emit-llvm - - inspect - - ex_cpp.ll diff --git a/.xo-jit/LICENSE b/.xo-jit/LICENSE deleted file mode 100644 index cae3cb5d..00000000 --- a/.xo-jit/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2024 Roland Conybeare , All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. 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. - -3. 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. - -Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of -external contributions to this project including patches, pull requests, etc. diff --git a/.xo-jit/README.md b/.xo-jit/README.md deleted file mode 100644 index a8a7457d..00000000 --- a/.xo-jit/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# xo-jit library - -A library for representing abstract syntax trees for EGAD (a small jit-based language). - -## Links - -- [new pass manager (26mar2021)](https://blog.llvm.org/posts/2021-03-26-the-new-pass-manager) -- [life of an llvm instruction (24nov2012)](https://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm) - -## Getting Started - -### build + install `xo-cmake` dependency - -- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) - -Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. - -### build + install other necessary XO dependencies -``` -$ xo-build --clone --configure --build --install xo-indentlog -$ xo-build --clone --configure --build --install xo-refnct -$ xo-build --clone --configure --build --install xo-subsys -$ xo-build --clone --configure --build --install xo-reflect -$ xo-build --clone --configure --build --install xo-expression -``` - -Note: can use `xo-build -n` to dry-run here - -### copy `xo-jit` repository locally -``` -$ xo-build --clone xo-jit -``` - -or equivalently -``` -$ git clone git@github.com:Rconybea/xo-jit.git -``` - -### build + install xo-jit -``` -$ xo-build --configure --build --install xo-jit -``` - -or equivalently: -``` -$ PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-jit -B xo-jit/.build -$ cmake --build xo-jit/.build -$ cmake --install xo-jit/.build -``` - -### build for unit test coverage -``` -$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-jit/.build-ccov -$ cmake --build xo-jit/.build-ccov -``` - -### LSP support -``` -$ cd xo-jit -$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree -``` diff --git a/.xo-jit/cmake/xo-bootstrap-macros.cmake b/.xo-jit/cmake/xo-bootstrap-macros.cmake deleted file mode 100755 index 592272c0..00000000 --- a/.xo-jit/cmake/xo-bootstrap-macros.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# ---------------------------------------------------------------- -# for example: -# $ PREFIX=/usr/local # for example -# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build -# -# will get -# CMAKE_MODULE_PATH -# from xo-cmake-config --cmake-module-path -# -# and expect .cmake macros in -# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake -# ---------------------------------------------------------------- - -find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) - -if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") - message(FATAL "could not find xo-cmake-config executable") -endif() - -message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") - -if (XO_SUBMODULE_BUILD) - if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) - # local version of xo-cmake macros - set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/xo-cmake/cmake") - message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") - endif() -else() - if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) - # default to typical install location for xo-project-macros - execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) - message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") - endif() -endif() - -# needs to have been installed somewhere on CMAKE_MODULE_PATH, -# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) -# -include(xo_macros/xo_cxx) - -xo_cxx_bootstrap_message() diff --git a/.xo-jit/cmake/xo_jitConfig.cmake.in b/.xo-jit/cmake/xo_jitConfig.cmake.in deleted file mode 100644 index a9203adb..00000000 --- a/.xo-jit/cmake/xo_jitConfig.cmake.in +++ /dev/null @@ -1,7 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) -find_dependency(xo_expression) -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") -check_required_components("@PROJECT_NAME@") diff --git a/.xo-jit/docs/CMakeLists.txt b/.xo-jit/docs/CMakeLists.txt deleted file mode 100644 index bf9c8563..00000000 --- a/.xo-jit/docs/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# xo-jit/docs/CMakeLists.txt - -xo_doxygen_collect_deps() -xo_docdir_doxygen_config() -xo_docdir_sphinx_config( - index.rst glossary.rst -) diff --git a/.xo-jit/docs/README b/.xo-jit/docs/README deleted file mode 100644 index 2fab6399..00000000 --- a/.xo-jit/docs/README +++ /dev/null @@ -1,70 +0,0 @@ -build - - +-----------------------------------------------+ - | cmake | - | CMakeLists.txt | - | $PREFIX/share/cmake/xo_macros/xo_cxx.cmake | - +-----------------------------------------------+ - | - | +----------------------+ - +------------------------------------------------->| .build/docs/Doxyfile | - | +----------------------+ - | | - | /------------/ - | | - | v - | +---------------------------------------+ +-----------------+ - +---->| doxygen |--->| .build/docs/dox | - | | $PREFIX/share/xo-macros/Doxyfile.in | | +- html/ | - | +---------------------------------------+ | +- xml/ | - | +-----------------+ - | | - | /------------/ - | | - | v - | +---------------------------------------+ +--------------------+ - \---->| sphinx |--->| .build/docs/sphinx | - | +- conf.py | | +- html/ | - | +- _static/ | +--------------------+ - | +- *.rst | - +---------------------------------------+ - -files - - README this file - CMakeLists.txt build entry point - conf.py sphinx config - _static static files for sphinx - -map - - index.rst - +- install.rst - +- examples.rst - +- unit-quantities.rst - +- classes.rst - +- glossary.rst - ... - -examples - -.. doxygenclass:: ${c++ class name} - :project: - :path: - :members: - :protected-members: - :private-members: - :undoc-members: - :member-groups: - :members-only: - :outline: - :no-link: - :allow-dot-graphs: - -.. doxygendefine:: ${c preprocessor define} - -.. doxygenconcept:: ${c++ concept definition} - -.. doxygenenum:: ${c++ enum definition} - -.. doxygenfunction:: ${c++ function name} diff --git a/.xo-jit/docs/conf.py b/.xo-jit/docs/conf.py deleted file mode 100644 index 31acd35e..00000000 --- a/.xo-jit/docs/conf.py +++ /dev/null @@ -1,39 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = 'xo jit documentation' -copyright = '2024, Roland Conybeare' -author = 'Roland Conybeare' - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -#extensions = [] -extensions = [ "breathe", - "sphinx.ext.mathjax", # inline math - "sphinx.ext.autodoc", # generate info from docstrings - "sphinxcontrib.ditaa", # diagrams-through-ascii-art - "sphinxcontrib.plantuml" # text -> uml diagrams - ] - -# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in -# match project name in Doxyfile.in -breathe_default_project = "xodoxxml" - -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -pygments_style = 'sphinx' - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -#html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] -html_favicon = '_static/img/favicon.ico' diff --git a/.xo-jit/docs/glossary.rst b/.xo-jit/docs/glossary.rst deleted file mode 100644 index 6af7073a..00000000 --- a/.xo-jit/docs/glossary.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _glossary: - -Glossary --------- - -.. glossary:: - ABI - | Short for Application Binary Interface. - | In this context applies to conventions adopted by `xo-jit`. - - c.foo - | llvm typename for automatically-generated closure type for a lambda - | with name `foo`. - - e.foo - | llvm typename for automatically-generated local environment for a - | lambda with name `foo`. - - w.foo - | llvm typename for automatically-generated wrapper function for a - | primitive function `foo`. The wrapper function accepts and ignores - | an extra initial argument supplying an environment pointer. - | - | We apply this practice so that lambdas and primitives support the - | same ABI, so that we can support pointers to abstract functions - | that might at runtime turn out to be either primitives or lambdas - - lambda - | Common use is for lambda to refer to an anonymous function. - | In llvm we need all functions to be named, and those names - | have to be unique. - | - | Since all functions have to be named, we cheerfully adopt - | the oxymoron 'named lambda' - | - | Practices: - | 1. Automatically generate unique names for anonymous lambdas. - | 2. Incorporate user-provided names for convenience, when provided. - | 3. Still have to uniqueify names for user-provided nested lambdas. - - localenv - | Shorthand for local environment. - | Represents an explicit runtime repsentation for a struct that - | holds captured function arguments with the ability to be persisted - | (for example, moved to the the heap). - | - | Note that library `xo-expression` also uses the term environment, but differently. - | In that context describes all function arguments. - - lvtype - | Shorthand for `llvm::Type`: - | llvm-owned representation for a datatype - - xsession - | Shorthand for `llvm::orc::ExecutionSession`. - | Manages running JIT-generated machine code in the host process - - td - | Short for `xo::reflect::TypeDescr`. - - XO - A set of integrated c++ libraries for complex event processing, with browser and python integration. - `xo documentation here`_ - -.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html - -.. toctree:: diff --git a/.xo-jit/docs/index.rst b/.xo-jit/docs/index.rst deleted file mode 100644 index ea337854..00000000 --- a/.xo-jit/docs/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. xo-jit documentation master file, created by - sphinx-quickstart on Wed Mar 6 23:32:27 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -xo-jit documentation -==================== - -xo-jit compiles xo-expression AST's to runnable-in-this-process machine code. - -* uses C++ reflection to simplify making c++ type-equivalents available in LLVM -* uses C++ reflection to simplify making c++ functions available in LLVM -* integration with python (see sister project xo-pyjit) - -.. toctree:: - :maxdepth: 2 - :caption: xo-jit contents - - glossary - genindex - search diff --git a/.xo-jit/example/CMakeLists.txt b/.xo-jit/example/CMakeLists.txt deleted file mode 100644 index 168ffa7c..00000000 --- a/.xo-jit/example/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_subdirectory(ex1) -add_subdirectory(ex2_jit) -add_subdirectory(ex3_fptr) -add_subdirectory(ex_kaleidoscope4) diff --git a/.xo-jit/example/ex1/CMakeLists.txt b/.xo-jit/example/ex1/CMakeLists.txt deleted file mode 100644 index 9406c959..00000000 --- a/.xo-jit/example/ex1/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# xo-jit/example/ex1/CMakeLists.txt - -set(SELF_EXE xo_jit_ex1) -set(SELF_SRCS ex1.cpp) - -if (XO_ENABLE_EXAMPLES) - xo_add_executable(${SELF_EXE} ${SELF_SRCS}) - xo_self_dependency(${SELF_EXE} xo_jit) - #xo_dependency(${SELF_EXE} xo_expression) -endif() - -# end CMakeLists.txt diff --git a/.xo-jit/example/ex1/ex1.cpp b/.xo-jit/example/ex1/ex1.cpp deleted file mode 100644 index 017f19ec..00000000 --- a/.xo-jit/example/ex1/ex1.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/** @file ex1.cpp **/ - -#include "xo/jit/MachPipeline.hpp" -#include "xo/expression/Constant.hpp" -#include "xo/expression/PrimitiveExpr.hpp" -#include "xo/expression/Apply.hpp" -#include "xo/expression/Lambda.hpp" -#include "xo/expression/Variable.hpp" -#include - -#include "llvm/ADT/APFloat.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Constants.h" -#include "llvm/IR/DerivedTypes.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/IR/Module.h" -#include "llvm/IR/PassManager.h" -#include "llvm/IR/Type.h" -#include "llvm/IR/Verifier.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/Passes/StandardInstrumentations.h" -#include "llvm/Support/TargetSelect.h" -#include "llvm/Target/TargetMachine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include "llvm/Transforms/Scalar/GVN.h" -#pragma GCC diagnostic pop -#include "llvm/Transforms/Scalar/Reassociate.h" -#include "llvm/Transforms/Scalar/SimplifyCFG.h" -#include - -namespace { - // need wrappers to fix type signature for osx/clang15. Perhaps sqrt() is a macro or template (?) - double - xo_sqrt(double x) - { - return ::sqrt(x); - } - - double - xo_sin(double x) - { - return ::sin(x); - } - - double - xo_cos(double x) - { - return ::cos(x); - } -} - -int -main() { - using xo::scope; - using xo::jit::MachPipeline; - using xo::scm::make_constant; - using xo::scm::make_primitive; - using xo::scm::llvmintrinsic; - using xo::scm::make_apply; - using xo::scm::make_var; - using xo::scm::make_lambda; - using xo::reflect::Reflect; - using xo::xtag; - using std::cerr; - using std::endl; - - //using xo::scm::make_constant; - - static llvm::ExitOnError llvm_exit_on_err; - - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - - //auto jit = llvm_exit_on_err(Jit::make_aux()); - auto jit = MachPipeline::make(); - - //static_assert(std::is_function_v); - - scope log(XO_DEBUG(true)); - - { - auto expr = make_constant(7.0); - - log && log(xtag("expr", expr)); - - auto llvm_ircode = jit->codegen_toplevel(expr); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", expr) - << endl; - } - } - - { - auto expr = make_primitive("sqrt", &xo_sqrt, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - - log && log(xtag("expr", expr)); - - auto llvm_ircode = jit->codegen_toplevel(expr); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", expr) - << endl; - } - } - - { - /* (sqrt 2) */ - - auto fn = make_primitive("sqrt", &xo_sqrt, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - auto arg = make_constant(2.0); - - auto call = make_apply(fn, {arg}); - - log && log(xtag("expr", call)); - - auto llvm_ircode = jit->codegen_toplevel(call); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", call) - << endl; - } - } - - { - /* (lambda (x) (sin (cos x))) */ - - auto sin = make_primitive("sin", - &xo_sin, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sin); - auto cos = make_primitive("cos", - &xo_cos, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_cos); - - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(cos, {x_var}); /* (cos x) */ - auto call2 = make_apply(sin, {call1}); /* (sin (cos x)) */ - - /* (define (lm_1 x) (sin (cos x))) */ - auto lambda = make_lambda("lm_1", - {x_var}, - call2, - nullptr /*parent_env*/); - - log && log(xtag("expr", lambda)); - - auto llvm_ircode = jit->codegen_toplevel(lambda); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", lambda) - << endl; - } - - /* is this in module? */ - cerr << "ex1: jit execution session" << endl; - jit->dump_execution_session(); - } -} - -/** end ex1.cpp **/ diff --git a/.xo-jit/example/ex2_jit/CMakeLists.txt b/.xo-jit/example/ex2_jit/CMakeLists.txt deleted file mode 100644 index 1a6d1e88..00000000 --- a/.xo-jit/example/ex2_jit/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# xo-jit/example/ex2_jit/CMakeLists.txt - -set(SELF_EXE xo_jit_ex2) -set(SELF_SRCS ex2_jit.cpp) - -if (XO_ENABLE_EXAMPLES) - xo_add_executable(${SELF_EXE} ${SELF_SRCS}) - xo_self_dependency(${SELF_EXE} xo_jit) - #xo_dependency(${SELF_EXE} xo_expression) -endif() - -# end CMakeLists.txt diff --git a/.xo-jit/example/ex2_jit/ex2_jit.cpp b/.xo-jit/example/ex2_jit/ex2_jit.cpp deleted file mode 100644 index 0e8e68d3..00000000 --- a/.xo-jit/example/ex2_jit/ex2_jit.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/** @file ex2_jit.cpp **/ - -#include "xo/jit/MachPipeline.hpp" -#include "xo/jit/activation_record.hpp" -#include "xo/expression/Constant.hpp" -#include "xo/expression/PrimitiveExpr.hpp" -#include "xo/expression/Apply.hpp" -#include "xo/expression/Lambda.hpp" -#include "xo/expression/Variable.hpp" -#include - -#include "llvm/ADT/APFloat.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Constants.h" -#include "llvm/IR/DerivedTypes.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/IR/Module.h" -#include "llvm/IR/PassManager.h" -#include "llvm/IR/Type.h" -#include "llvm/IR/Verifier.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/Passes/StandardInstrumentations.h" -#include "llvm/Support/TargetSelect.h" -#include "llvm/Target/TargetMachine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#include "llvm/Transforms/Scalar/GVN.h" -#pragma GCC diagnostic pop -#include "llvm/Transforms/Scalar/Reassociate.h" -#include "llvm/Transforms/Scalar/SimplifyCFG.h" - -//double foo(double x) { return x; } - -namespace { - // need wrappers to pin type signature for osx/clang15 - double - xo_sqrt(double x) - { - return ::sqrt(x); - } -} - -int -main() { - using xo::scope; - using xo::jit::MachPipeline; - using xo::jit::activation_record; - using xo::scm::make_constant; - using xo::scm::make_primitive; - using xo::scm::llvmintrinsic; - using xo::scm::make_apply; - using xo::scm::make_var; - using xo::scm::make_lambda; - using xo::reflect::Reflect; - using xo::xtag; - using std::cerr; - using std::endl; - - //using xo::scm::make_constant; - - static llvm::ExitOnError llvm_exit_on_err; - - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - - //auto jit = llvm_exit_on_err(Jit::make_aux()); - auto jit = MachPipeline::make(); - - //static_assert(std::is_function_v); - - scope log(XO_DEBUG(true)); - - /* try spelling everything out */ - - { - auto sqrt = make_primitive("sqrt", - &xo_sqrt, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - - { - auto llvm_ircode = jit->codegen_toplevel(sqrt); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", sqrt) - << endl; - } - } - -#define CHOICE 2 - -#if CHOICE == 0 -#define FUNCTION_SYMBOL "callit" - /* def callit(f :: double->double, x :: double) { f(x); } */ - - auto f_var = make_var("f", Reflect::require()); - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(f_var, {x_var}); /* (f x) */ - //auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ - - auto lambda = make_lambda("callit", - {f_var, x_var}, - call1, - nullptr /*parent_env*/); -#elif CHOICE == 1 -#define FUNCTION_SYMBOL "root4" - /* def root4(x : double) { sqrt(sqrt(x)) } */ - - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(sqrt, {x_var}); - auto call2 = make_apply(sqrt, {call1}); - - auto lambda = make_lambda("root4", - {x_var}, - call2, - nullptr /*parent_env*/); -#elif CHOICE == 2 -#define FUNCTION_SYMBOL "twice" - auto root = make_primitive("sqrt", - &xo_sqrt, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - - /* def twice(f :: int->int, x :: int) { f(f(x)) } */ - auto f_var = make_var("f", Reflect::require()); - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(f_var, {x_var}); /* (f x) */ - auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ - - /* (define (twice f ::int->int x ::int) (f (f x))) */ - auto lambda = make_lambda("twice", - {f_var, x_var}, - call2, - nullptr /*parent_env*/); -#endif - - log && log(xtag("lambda", lambda)); - - auto llvm_ircode = jit->codegen_toplevel(lambda); - - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "ex1 llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "ex1: code generation failed" - << xtag("expr", lambda) - << endl; - } - - jit->machgen_current_module(); - - /* is this in module? */ - cerr << "ex2: jit execution session" << endl; - jit->dump_execution_session(); - - auto fn = jit->lookup_symbol(FUNCTION_SYMBOL); - - if (!fn) { - cerr << "ex2: lookup: symbol not found" - << xtag("symbol", FUNCTION_SYMBOL) - << endl; - } else { - cerr << "ex2: lookup: symbol found" - << xtag("fn", fn.get().getValue()) - << xtag("symbol", FUNCTION_SYMBOL) - << endl; - } - } -} - -/** end ex2_jit.cpp **/ diff --git a/.xo-jit/example/ex3_fptr/CMakeLists.txt b/.xo-jit/example/ex3_fptr/CMakeLists.txt deleted file mode 100644 index b66e7f86..00000000 --- a/.xo-jit/example/ex3_fptr/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# xo-jit/example/ex3_fptr/CMakeLists.txt - -set(SELF_EXE xo_fptr_ex3) -set(SELF_SRCS ex3_fptr.cpp) - -if (XO_ENABLE_EXAMPLES) - xo_add_executable(${SELF_EXE} ${SELF_SRCS}) - xo_self_dependency(${SELF_EXE} xo_jit) - #xo_dependency(${SELF_EXE} xo_expression) -endif() - -# end CMakeLists.txt diff --git a/.xo-jit/example/ex3_fptr/ex3_fptr.cpp b/.xo-jit/example/ex3_fptr/ex3_fptr.cpp deleted file mode 100644 index ef6de58c..00000000 --- a/.xo-jit/example/ex3_fptr/ex3_fptr.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "llvm/IR/LLVMContext.h" -#include "llvm/IR/IRBuilder.h" - -#include "llvm/Support/raw_ostream.h" - -int main() { - llvm::LLVMContext context; - llvm::IRBuilder<> builder(context); - llvm::Module *module = new llvm::Module("top", context); - - // Create main function and basic block - llvm::FunctionType *functionType = llvm::FunctionType::get(builder.getInt32Ty(), false); - llvm::Function *mainFunction = llvm::Function::Create(functionType, llvm::Function::ExternalLinkage, "main", module); - llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunction); - builder.SetInsertPoint(entry); - - // Create a global string pointer - llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world\n"); - - // Create function pointer for puts - std::vector putArgs; - putArgs.push_back(builder.getInt8Ty()->getPointerTo()); - llvm::ArrayRef argsRef(putArgs); - llvm::FunctionType *putsType = llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false); - /* = FunctionType + Callee-pointer */ - llvm::FunctionCallee putFunction_callee = module->getOrInsertFunction("puts", putsType); - -#ifdef NOT_YET - llvm::Constant * putFunction = llvm::Constant - - // Allocate memory for the function pointer - llvm::Value *p = builder.CreateAlloca(putFunction->getType(), nullptr, "p"); - builder.CreateStore(putFunction, p, false); - - // Load the function pointer and call it - llvm::Value *temp = builder.CreateLoad(p); - builder.CreateCall(temp, helloWorld); - - // Return 0 to complete the main function - builder.CreateRet(llvm::ConstantInt::get(builder.getInt32Ty(), 0)); - - // Print the module (IR code) - module->print(llvm::errs(), nullptr); -#endif -} diff --git a/.xo-jit/example/ex_cpp/README b/.xo-jit/example/ex_cpp/README deleted file mode 100644 index b67ad2e8..00000000 --- a/.xo-jit/example/ex_cpp/README +++ /dev/null @@ -1,6 +0,0 @@ -Not including this in build for now. -Instead, use to manually generate .ll output: - -$ clang -cc1 ex_cpp.cpp -emit-llvm - -and inspect ex_cpp.ll diff --git a/.xo-jit/example/ex_cpp/ex_cpp.cpp b/.xo-jit/example/ex_cpp/ex_cpp.cpp deleted file mode 100644 index 65ce9934..00000000 --- a/.xo-jit/example/ex_cpp/ex_cpp.cpp +++ /dev/null @@ -1,40 +0,0 @@ -struct env_type; - -struct closure_type { - double (*fnptr)(env_type * env, double x); - env_type * envptr; -}; - -double -sqrt(double x) { - return x/100; -} - -double -wrap_sqrt(env_type * env, double x) { - return ::sqrt(x); -} - -double twice(env_type * env, closure_type fnclosure, double x) { - double tmp1 = (*fnclosure.fnptr)(fnclosure.envptr, x); - double tmp2 = (*fnclosure.fnptr)(fnclosure.envptr, tmp1); - - return tmp2; -} - -closure_type make_some_closure() -{ - closure_type closure; - closure.fnptr = &wrap_sqrt; - closure.envptr = nullptr; - - return closure; -} - -int main() { - closure_type closure = make_some_closure(); - - double y = twice(nullptr, closure, 4.0); - - //std::cout << "y=" << y << std::endl; -} diff --git a/.xo-jit/example/ex_cpp/ex_cpp.ll b/.xo-jit/example/ex_cpp/ex_cpp.ll deleted file mode 100644 index 28434d62..00000000 --- a/.xo-jit/example/ex_cpp/ex_cpp.ll +++ /dev/null @@ -1,108 +0,0 @@ -; ModuleID = 'ex_cpp.cpp' -source_filename = "ex_cpp.cpp" -target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" -target triple = "x86_64-unknown-linux-gnu" - -%struct.closure_type = type { ptr, ptr } - -; Function Attrs: mustprogress noinline nounwind optnone -define dso_local noundef double @_Z4sqrtd(double noundef %x) #0 { -entry: - %x.addr = alloca double, align 8 - store double %x, ptr %x.addr, align 8 - %0 = load double, ptr %x.addr, align 8 - %div = fdiv double %0, 1.000000e+02 - ret double %div -} - -; Function Attrs: mustprogress noinline nounwind optnone -define dso_local noundef double @_Z9wrap_sqrtP8env_typed(ptr noundef %env, double noundef %x) #0 { -entry: - %env.addr = alloca ptr, align 8 - %x.addr = alloca double, align 8 - store ptr %env, ptr %env.addr, align 8 - store double %x, ptr %x.addr, align 8 - %0 = load double, ptr %x.addr, align 8 - %call = call noundef double @_Z4sqrtd(double noundef %0) - ret double %call -} - -; Function Attrs: mustprogress noinline nounwind optnone -define dso_local noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef %env, ptr %fnclosure.coerce0, ptr %fnclosure.coerce1, double noundef %x) #0 { -entry: - %fnclosure = alloca %struct.closure_type, align 8 - %env.addr = alloca ptr, align 8 - %x.addr = alloca double, align 8 - %tmp1 = alloca double, align 8 - %tmp2 = alloca double, align 8 - %0 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 0 - store ptr %fnclosure.coerce0, ptr %0, align 8 - %1 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 1 - store ptr %fnclosure.coerce1, ptr %1, align 8 - store ptr %env, ptr %env.addr, align 8 - store double %x, ptr %x.addr, align 8 - %fnptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 - %2 = load ptr, ptr %fnptr, align 8 - %envptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 - %3 = load ptr, ptr %envptr, align 8 - %4 = load double, ptr %x.addr, align 8 - %call = call noundef double %2(ptr noundef %3, double noundef %4) - store double %call, ptr %tmp1, align 8 - %fnptr1 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 - %5 = load ptr, ptr %fnptr1, align 8 - %envptr2 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 - %6 = load ptr, ptr %envptr2, align 8 - %7 = load double, ptr %tmp1, align 8 - %call3 = call noundef double %5(ptr noundef %6, double noundef %7) - store double %call3, ptr %tmp2, align 8 - %8 = load double, ptr %tmp2, align 8 - ret double %8 -} - -; Function Attrs: mustprogress noinline nounwind optnone -define dso_local { ptr, ptr } @_Z17make_some_closurev() #0 { -entry: - %retval = alloca %struct.closure_type, align 8 - %fnptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 0 - store ptr @_Z9wrap_sqrtP8env_typed, ptr %fnptr, align 8 - %envptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 1 - store ptr null, ptr %envptr, align 8 - %0 = load { ptr, ptr }, ptr %retval, align 8 - ret { ptr, ptr } %0 -} - -; Function Attrs: mustprogress noinline norecurse nounwind optnone -define dso_local noundef i32 @main() #1 { -entry: - %closure = alloca %struct.closure_type, align 8 - %y = alloca double, align 8 - %agg.tmp = alloca %struct.closure_type, align 8 - %call = call { ptr, ptr } @_Z17make_some_closurev() - %0 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 0 - %1 = extractvalue { ptr, ptr } %call, 0 - store ptr %1, ptr %0, align 8 - %2 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 1 - %3 = extractvalue { ptr, ptr } %call, 1 - store ptr %3, ptr %2, align 8 - call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %closure, i64 16, i1 false) - %4 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 0 - %5 = load ptr, ptr %4, align 8 - %6 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 1 - %7 = load ptr, ptr %6, align 8 - %call1 = call noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef null, ptr %5, ptr %7, double noundef 4.000000e+00) - store double %call1, ptr %y, align 8 - ret i32 0 -} - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) -declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2 - -attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } -attributes #1 = { mustprogress noinline norecurse nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } - -!llvm.module.flags = !{!0} -!llvm.ident = !{!1} - -!0 = !{i32 1, !"wchar_size", i32 4} -!1 = !{!"clang version 18.1.5"} diff --git a/.xo-jit/example/ex_cpp/tmp.ll b/.xo-jit/example/ex_cpp/tmp.ll deleted file mode 100644 index b8b8993a..00000000 --- a/.xo-jit/example/ex_cpp/tmp.ll +++ /dev/null @@ -1,58 +0,0 @@ -define double @root4(ptr %.env, double %x) { -entry: - %x1 = alloca double, align 8 - store double %x, ptr %x1, align 8 - %x2 = load double, ptr %x1, align 8 - %calltmp = call double @w.sqrt(ptr null, double %x2) - %calltmp3 = call double @w.sqrt(ptr null, double %calltmp) - ret double %calltmp3 -} - -; ---------------------------------------------------------------- - -define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { -entry: - %f1 = alloca { ptr, ptr }, align 8 - store { ptr, ptr } %f, ptr %f1, align 8 - %x2 = alloca double, align 8 - store double %x, ptr %x2, align 8 - %f3 = load { ptr, ptr }, ptr %f1, align 8 - %fnptr = extractvalue { ptr, ptr } %f3, 0 - %envptr = extractvalue { ptr, ptr } %f3, 1 - %f4 = load { ptr, ptr }, ptr %f1, align 8 - %fnptr5 = extractvalue { ptr, ptr } %f4, 0 - %envptr6 = extractvalue { ptr, ptr } %f4, 1 - %x7 = load double, ptr %x2, align 8 - %calltmp = call double %fnptr5(ptr %envptr6, double %x7) - %calltmp8 = call double %fnptr(ptr %envptr, double %calltmp) - ret double %calltmp8 -} - - -define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { -entry: - %f1 = alloca { ptr, ptr }, align 8 - %f.elt = extractvalue { ptr, ptr } %f, 0 - store ptr %f.elt, ptr %f1, align 8 - %f1.repack9 = getelementptr inbounds { ptr, ptr }, ptr %f1, i64 0, i32 1 - %f.elt10 = extractvalue { ptr, ptr } %f, 1 - store ptr %f.elt10, ptr %f1.repack9, align 8 - %calltmp = call double %f.elt(ptr %f.elt10, double %x) - %calltmp8 = call double %f.elt(ptr %f.elt10, double %calltmp) - ret double %calltmp8 -} - - -;; ---------------------------------------------------------------- - -define double @root_2x(ptr %.env, double %x2) { -entry: - %x21 = alloca double, align 8 - store double %x2, ptr %x21, align 8 - %envptr = insertvalue { ptr, ptr } { ptr @twice, ptr undef }, ptr %.env, 1 - %fnptr = extractvalue { ptr, ptr } %envptr, 0 - %envptr2 = extractvalue { ptr, ptr } %envptr, 1 - %x23 = load double, ptr %x21, align 8 - %calltmp = call double %fnptr(ptr %envptr2, { ptr, ptr } { ptr @w.sqrt, ptr null }, double %x23) - ret double %calltmp -} diff --git a/.xo-jit/example/ex_kaleidoscope4/CMakeLists.txt b/.xo-jit/example/ex_kaleidoscope4/CMakeLists.txt deleted file mode 100644 index 4e9476c3..00000000 --- a/.xo-jit/example/ex_kaleidoscope4/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# xo-jit/example/ex1/CMakeLists.txt - -set(SELF_EXE xo_kaleidoscope4) -set(SELF_SRCS ex_kaleidoscope4.cpp) - -if (XO_ENABLE_EXAMPLES) - xo_add_executable(${SELF_EXE} ${SELF_SRCS}) - xo_self_dependency(${SELF_EXE} xo_jit) - #xo_dependency(${SELF_EXE} xo_expression) -endif() - -# end CMakeLists.txt diff --git a/.xo-jit/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/.xo-jit/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp deleted file mode 100644 index 46f0c81d..00000000 --- a/.xo-jit/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/** ex_kaleidoscop4.cpp **/ - -#include "xo/jit/Jit.hpp" -#include - -int -main() { - using std::cerr; - using std::endl; - - //auto jit = xo::jit::KaleidoscopeJIT::Create(); - - //cerr << "created kaleidoscope jit successfully" << endl; -} - -/** end ex_kaleidoscope4.cpp **/ diff --git a/.xo-jit/include/xo/jit/IrPipeline.hpp b/.xo-jit/include/xo/jit/IrPipeline.hpp deleted file mode 100644 index 15338f44..00000000 --- a/.xo-jit/include/xo/jit/IrPipeline.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/** @file IrPipeline.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "xo/refcnt/Refcounted.hpp" -#include "LlvmContext.hpp" - -/* stuff from kaleidoscope.cpp */ -#pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-parameter" -# pragma GCC diagnostic ignored "-Wcpp" -# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -# include "llvm/ADT/APFloat.h" -# include "llvm/ADT/STLExtras.h" -# include "llvm/IR/BasicBlock.h" -# include "llvm/IR/Constants.h" -# include "llvm/IR/DerivedTypes.h" -# include "llvm/IR/Function.h" -# include "llvm/IR/IRBuilder.h" -# include "llvm/IR/LLVMContext.h" -# include "llvm/IR/Module.h" -# include "llvm/IR/PassManager.h" -# include "llvm/IR/Type.h" -# include "llvm/IR/Verifier.h" -# include "llvm/Passes/PassBuilder.h" -# include "llvm/Passes/StandardInstrumentations.h" -# include "llvm/Support/TargetSelect.h" -# include "llvm/Target/TargetMachine.h" -# include "llvm/Transforms/InstCombine/InstCombine.h" -# include "llvm/Transforms/Scalar.h" -# include "llvm/Transforms/Scalar/GVN.h" -# include "llvm/Transforms/Utils/Mem2Reg.h" -# include "llvm/Transforms/Scalar/Reassociate.h" -# include "llvm/Transforms/Scalar/SimplifyCFG.h" -#pragma GCC diagnostic pop - -//#include - -namespace xo { - namespace jit { - /** @class IrPipeline - * @brief represent an LLVM IR pipeline - * - * Represents analysis/transformation short of generating - * machine-code. For now pipeline stages are hardwired; - * adapted from the LLVM Kaleidoscope example project. - * - * Conversely, pipeline *starts* with code already that has - * already been expressed in LLVM IR - **/ - class IrPipeline : public ref::Refcount { - public: - explicit IrPipeline(rp llvm_cx); - - void run_pipeline(llvm::Function & fn); - - private: - // ----- transforms (also adapted from kaleidescope.cpp) ------ - - /** keepalive for contained llvm::LLVMContext **/ - rp llvm_cx_; - - /** manages all the passes+analaysis (?) **/ - std::unique_ptr llvm_fpmgr_; - /** loop analysis (?) **/ - std::unique_ptr llvm_lamgr_; - /** function-level analysis (?) **/ - std::unique_ptr llvm_famgr_; - /** cgscc (?) analysis **/ - std::unique_ptr llvm_cgamgr_; - /** module analsyis (?) **/ - std::unique_ptr llvm_mamgr_; - /** pass instrumentation **/ - std::unique_ptr llvm_pic_; - /** standard instrumentation **/ - std::unique_ptr llvm_si_; - }; /*IrPipeline*/ - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end IrPipeline.hpp **/ diff --git a/.xo-jit/include/xo/jit/Jit.hpp b/.xo-jit/include/xo/jit/Jit.hpp deleted file mode 100644 index e725fbeb..00000000 --- a/.xo-jit/include/xo/jit/Jit.hpp +++ /dev/null @@ -1,172 +0,0 @@ -/** @file Jit.hpp **/ - -/** Adapted from LLVM KaleidoscopeJIT.h **/ - -#pragma once - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wredundant-move" -# include "llvm/ADT/StringRef.h" -# include "llvm/ExecutionEngine/JITSymbol.h" -# include "llvm/ExecutionEngine/Orc/CompileUtils.h" -# include "llvm/ExecutionEngine/Orc/Core.h" -# include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" -# include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" -# include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" -# include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" -# include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" -# include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" // need llvm18 -# include "llvm/ExecutionEngine/SectionMemoryManager.h" -# include "llvm/IR/DataLayout.h" -# include "llvm/IR/LLVMContext.h" -#pragma GCC diagnostic pop -#include - -namespace xo { - namespace jit { - - class Jit { - private: - using StringRef = llvm::StringRef; - using SectionMemoryManager = llvm::SectionMemoryManager; - using DynamicLibrarySearchGenerator = llvm::orc::DynamicLibrarySearchGenerator; - using ConcurrentIRCompiler = llvm::orc::ConcurrentIRCompiler; - using ExecutionSession = llvm::orc::ExecutionSession; - using DataLayout = llvm::DataLayout; - using MangleAndInterner = llvm::orc::MangleAndInterner; - using RTDyldObjectLinkingLayer = llvm::orc::RTDyldObjectLinkingLayer; - using IRCompileLayer = llvm::orc::IRCompileLayer; - using JITDylib = llvm::orc::JITDylib; - using JITTargetMachineBuilder = llvm::orc::JITTargetMachineBuilder; - using ThreadSafeModule = llvm::orc::ThreadSafeModule; - using ResourceTrackerSP = llvm::orc::ResourceTrackerSP; - using ExecutorSymbolDef = llvm::orc::ExecutorSymbolDef; - using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; - - private: - /** execution session - represents a currently-running jit program **/ - std::unique_ptr xsession_; - - /** (?) needed for name mangling (?) **/ - DataLayout data_layout_; - /** symbol mangling and unique-ifying */ - MangleAndInterner mangler_; - - /** in-process linking layer - * (? specialized for jit in running process ?) - **/ - RTDyldObjectLinkingLayer object_layer_; - /** compilation layer (sits above linking layer) **/ - IRCompileLayer compile_layer_; - - /** destination library **/ - JITDylib & dest_dynamic_lib_; //MainJD; - - public: - Jit(std::unique_ptr xsession, - JITTargetMachineBuilder jtmb, - DataLayout data_layout) - : xsession_{std::move(xsession)}, - data_layout_(std::move(data_layout)), - mangler_(*this->xsession_, this->data_layout_), - object_layer_(*this->xsession_, - []() { return std::make_unique(); }), - compile_layer_(*this->xsession_, object_layer_, - std::make_unique(std::move(jtmb))), - dest_dynamic_lib_(this->xsession_->createBareJITDylib("
")) - { - dest_dynamic_lib_.addGenerator - (cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess - (data_layout_.getGlobalPrefix()))); - - if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { - object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); - object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); - } - } - - ~Jit() { - if (auto Err = this->xsession_->endSession()) - this->xsession_->reportError(std::move(Err)); - } - - static llvm::Expected> Create() { - auto EPC = SelfExecutorProcessControl::Create(); - if (!EPC) - return EPC.takeError(); - - auto xsession = std::make_unique(std::move(*EPC)); - - JITTargetMachineBuilder jtmb - (xsession->getExecutorProcessControl().getTargetTriple()); - - auto data_layout = jtmb.getDefaultDataLayoutForTarget(); - if (!data_layout) - return data_layout.takeError(); - - return std::make_unique(std::move(xsession), - std::move(jtmb), - std::move(*data_layout)); - } - - /* exposing this for printing */ - const ExecutionSession * xsession() const { return xsession_.get(); } - const DataLayout & data_layout() const { return data_layout_; } - - JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } - const std::string & target_triple() const { - return xsession_->getTargetTriple().getTriple(); - } - - - /** compile module to machine code that's runnable from this process; - * incorporate into @ref dest_dynamic_lib_ - **/ - llvm::Error - add_llvm_module(ThreadSafeModule ts_module, - ResourceTrackerSP rtracker = nullptr) { - if (!rtracker) - rtracker = dest_dynamic_lib_.getDefaultResourceTracker(); - - return compile_layer_.add(rtracker, - std::move(ts_module)); - } - - /** intern @p symbol, binding it to address @p dest **/ - template - llvm::Error intern_symbol(const std::string & symbol, T * dest) { - auto mangled_sym = mangler_(symbol); - - llvm::orc::SymbolMap symbol_map; - symbol_map[mangled_sym] - = llvm::orc::ExecutorSymbolDef(llvm::orc::ExecutorAddr::fromPtr(dest), - llvm::JITSymbolFlags()); - - auto materializer = llvm::orc::absoluteSymbols(symbol_map); - - return dest_dynamic_lib_.define(materializer); - } /*intern_symbol*/ - - /** report mangled symbol name **/ - std::string_view mangle(StringRef name) { - auto tmp = *(this->mangler_(name.str())); - - return std::string_view(tmp.data(), tmp.size()); - } - - llvm::Expected lookup(StringRef name) { - return this->xsession_->lookup({&dest_dynamic_lib_}, - this->mangle(name)); - } - - /* dump */ - void dump_execution_session() { - this->xsession_->dump(llvm::errs()); - } - }; /*Jit*/ - - } /*namespace jit*/ -} /*namespace xo*/ - -/** end Jit.hpp **/ diff --git a/.xo-jit/include/xo/jit/LlvmContext.hpp b/.xo-jit/include/xo/jit/LlvmContext.hpp deleted file mode 100644 index ac577b9e..00000000 --- a/.xo-jit/include/xo/jit/LlvmContext.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/** @file LlvmContext.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "xo/refcnt/Refcounted.hpp" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -# include "llvm/IR/LLVMContext.h" -#pragma GCC diagnostic pop - -//#include - -namespace xo { - namespace jit { - /** @class LlvmContext - * @brief Keepalive for a llvm::LLVMContext instance. - * - * For example IrPipeline holds an rp - * to help ensure validity of embedded llvm::LLVMContext reference - **/ - class LlvmContext : public ref::Refcount { - public: - static rp make(); - - llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; } - std::unique_ptr & llvm_cx() { return llvm_cx_; } - - private: - LlvmContext(); - - private: - /** Llvm context. Ties together fragments of code generation - * for AST subtrees that go into the same module. - **/ - std::unique_ptr llvm_cx_; - }; /*LlvmContext*/ - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end LlvmContext.hpp **/ diff --git a/.xo-jit/include/xo/jit/MachPipeline.hpp b/.xo-jit/include/xo/jit/MachPipeline.hpp deleted file mode 100644 index 640f8264..00000000 --- a/.xo-jit/include/xo/jit/MachPipeline.hpp +++ /dev/null @@ -1,281 +0,0 @@ -/** @file MachPipeline.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -//#include - -#include "xo/refcnt/Refcounted.hpp" -#include "IrPipeline.hpp" -#include "LlvmContext.hpp" -#include "Jit.hpp" -#include "activation_record.hpp" - -#include "xo/expression/Expression.hpp" -#include "xo/expression/ConstantInterface.hpp" -#include "xo/expression/PrimitiveExprInterface.hpp" -#include "xo/expression/Apply.hpp" -#include "xo/expression/Lambda.hpp" -#include "xo/expression/Variable.hpp" -#include "xo/expression/IfExpr.hpp" -#include "xo/expression/GlobalSymtab.hpp" - -/* stuff from kaleidoscope.cpp */ -#include "llvm/ADT/APFloat.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Constants.h" -#include "llvm/IR/DerivedTypes.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/IR/Module.h" -#include "llvm/IR/PassManager.h" -#include "llvm/IR/Type.h" -#include "llvm/IR/Verifier.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/Passes/StandardInstrumentations.h" -#include "llvm/Support/TargetSelect.h" -#include "llvm/Target/TargetMachine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#include "llvm/Transforms/Scalar/GVN.h" -#include "llvm/Transforms/Scalar/Reassociate.h" -#include "llvm/Transforms/Scalar/SimplifyCFG.h" -#include - - -namespace xo { - namespace jit { - /** @class MachPipeline - * @brief just-in-time compiler for EGAD - * - * TODO: make module name a parameter? - **/ - class MachPipeline : public ref::Refcount { - public: - using Expression = xo::scm::Expression; - using Lambda = xo::scm::Lambda; - using GlobalEnv = xo::scm::GlobalSymtab; - using TypeDescr = xo::reflect::TypeDescr; - using ExecutionSession = llvm::orc::ExecutionSession; - using DataLayout = llvm::DataLayout; - //using ConstantInterface = xo::scm::ConstantInterface; - - public: - /* tracking KaleidoscopeJIT::Create() here.. */ - static llvm::Expected> make_aux(); - static rp make(); - - // ----- access ----- - - llvm::Module * current_module() { return llvm_module_.get(); } - bp llvm_cx() { return llvm_cx_; } - llvm::IRBuilder<> * llvm_current_ir_builder() { return llvm_toplevel_ir_builder_.get(); } - - /** target triple = string describing target host for codegen **/ - const std::string & target_triple() const; - /** execution session (run jit-generated machine code in this process) **/ - const ExecutionSession * xsession() const; - /** data layout = rules for alignment/padding; specific to target host **/ - const DataLayout & data_layout() const; - /** append function names defined in attached module to *p_v - * - * (RC 15jun2024 - this part is working) - **/ - std::vector get_function_name_v(); - - /** write state of execution session (all the associated dynamic libraries) **/ - void dump_execution_session(); - - // ----- code generation ----- - - /** establish llvm IR corresponding to a c++ type. - * Handles - * T := bool|char|short|int|long|float|double - * | T1(*)(T2..Tn) - * | struct{T1,..,Tn} - * - * Not supported yet: - * - vector - * - string - * - map - * - unions - * - pointers (except function pointers) - * - * Idempotent: multiple calls with the same @p td produce the same @c llvm::Type pointer. - * @c llvm::Type instances are *immortal* (llvm interns them into opaque global lookup tables) - **/ - llvm::Type * codegen_type(TypeDescr td); - llvm::Value * codegen_constant(bp expr); - llvm::Function * codegen_primitive(bp expr); - - /** like @ref codegen_primitive , but create wrapper function that accepts (and discards) - * environment pointer as first argument. - * - * Implementation consists of tail call to natural primitive, that skips the unused - * environment pointer - **/ - llvm::Function * codegen_primitive_wrapper(bp expr, - llvm::IRBuilder<> & ir_builder); - - /** Generate closure for invoking a primitive function. - * Primitives don't benefit from a closure, but we need a consistent ABI - * to support function-pointer-like behavior for a target function - * that may resolve to primitive-or-lambda at runtime - **/ - llvm::Value * codegen_primitive_closure(bp expr, - llvm::IRBuilder<> & ir_builder); - - llvm::Value * codegen_apply(bp expr, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder); - /* NOTE: codegen_lambda() needs to be reentrant too. - * for example can have a lambda in apply position. - */ - llvm::Function * codegen_lambda_decl(bp expr); - llvm::Function * codegen_lambda_defn(bp expr, llvm::IRBuilder<> & ir_builder); - /** Generate closure for invoking a lambda (user-defined function). - * See @ref MachPipeline::codegen_apply for invocation - * Same ABI as @ref MachPipeline::codegen_primitive_closure - * - * @param envptr. Environment from surrounding lexical scope. - * This will be captured as envptr member by - * the IR code for creating a closure. - * @ref MachPipeline::codegen_toplevel and friends are responsible for - * assembling and propagating this. - **/ - llvm::Value * codegen_lambda_closure(bp lambda, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_variable(bp var, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_ifexpr(bp ifexpr, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder); - - llvm::Value * codegen(bp expr, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder); - - llvm::Value * codegen_toplevel(bp expr); - - // ----- jit online execution ----- - - /** add IR code in current module to JIT, - * so that its available for execution - **/ - void machgen_current_module(); - - /** dump text description of module contents to console **/ - void dump_current_module(); - - /** report mangled symbol for @p x **/ - std::string_view mangle(const std::string & x) const; - - /** lookup symbol in jit-associated output library **/ - llvm::Expected lookup_symbol(const std::string & x); - - virtual void display(std::ostream & os) const; - virtual std::string display_string() const; - - private: - /** construct instance, adopting jit for compilation+execution **/ - explicit MachPipeline(std::unique_ptr jit); - - /** iniitialize native builder (i.e. for platform we're running on) **/ - static void init_once(); - - /** helper function. find all lambda expressions in AST @p expr **/ - std::vector> find_lambdas(bp expr) const; - - public: - /** codegen helper for a user-defined function. - * create stack slot on behalf of formal parameters. - * linked to (dynamic) callers for stack unwinding - **/ - llvm::AllocaInst * create_entry_frame_alloca(llvm::Function * llvm_fn, - llvm::StructType * frame_llvm_type); - -#ifdef OBSOLETE // see activation_record::create_entry_block_alloca() - /** codegen helper for a user-defined function (codegen_lambda()): - * create stack slot on behalf of some formal parameter to a function, - * so we can avoid SSA restriction on function body - * - * @p var_type. variable type - **/ - llvm::AllocaInst * create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type); -#endif - - private: - /** (re)create pipeline to turn expressions into llvm IR code **/ - void recreate_llvm_ir_pipeline(); - - private: - // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- - - /** just-in-time compiler -- construct machine code that can - * be invoked from this running process - **/ - std::unique_ptr jit_; - - // ----- this part adapted from kaleidoscope.cpp ----- - - /** everything below represents a pipeline - * that takes expressions, and turns them into llvm IR. - * - * llvm IR can be added to running JIT by calling - * jit_->addModule() - * Note that this makes the module itself unavailable to us - **/ - rp ir_pipeline_; - - /** owns + manages core "global" llvm data, - * including type- and constant- unique-ing tables. - * - * Not threadsafe, but ok to have multiple threads, - * each with its own LLVMContext - **/ - rp llvm_cx_; - - /** builder for intermediate-representation objects **/ - std::unique_ptr> llvm_toplevel_ir_builder_; - - /** a module (1:1 with library ?) being prepared by llvm. - * IR-level -- does not contain machine coode - * - * - function names are unique within a module. - **/ - std::unique_ptr llvm_module_; - - /** map global names to functions/variables **/ - rp global_env_; - - /** map variable names (formal parameters) to - * corresponding llvm IR. - * - * only supports one level atm (i.e. only top-level functions) - * - * All values live on the stack, so that we can evade single-assignment - * restrictions. - * - * rhs identifies logical stack location of a variable - **/ - std::stack env_stack_; /* <-> kaleidoscope NamedValues */ - }; /*MachPipeline*/ - - inline std::ostream & - operator<<(std::ostream & os, const MachPipeline & x) { - x.display(os); - return os; - } - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end MachPipeline.hpp **/ diff --git a/.xo-jit/include/xo/jit/activation_record.hpp b/.xo-jit/include/xo/jit/activation_record.hpp deleted file mode 100644 index ae834986..00000000 --- a/.xo-jit/include/xo/jit/activation_record.hpp +++ /dev/null @@ -1,230 +0,0 @@ -/** @file activation_record.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "LlvmContext.hpp" -#include "xo/expression/Lambda.hpp" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -# include -# include -#pragma GCC diagnostic pop -#include -//#include - -namespace xo { - namespace jit { - /** analagous to xo::scm::binding_path, - * but with locations renumbered to include only vars that belong to an explict runtime - * environment object; in other words we exclude vars with stack-only storage - **/ - struct runtime_binding_path { - public: - runtime_binding_path() = default; - runtime_binding_path(int i_rt_link, - int j_rt_slot) - : i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {} - - static runtime_binding_path stackonly() { - return runtime_binding_path(0, -1); - } - static runtime_binding_path local(int j_rt_slot) { - return runtime_binding_path(0, j_rt_slot); - } - - bool is_stackonly() const { return (i_rt_link_ == 0) && (j_rt_slot_ == -1); } - bool is_captured() const { return !is_stackonly(); } - - public: - /** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/ - int i_rt_link_ = -2; - /** >= 0: slot# within explicit runtime environment where this variable bound. - * (local vars only -- ignored for global vars) - * -1: stack-only parameter - **/ - int j_rt_slot_ = 0; - }; - - struct runtime_binding_detail { - /** Formal index position for this formal parameter. - * Index into @ref activation_record::binding_v_, - * also for @ref Lambda::fn_arg - **/ - int i_argno_ = -1; - - /** instructions for establishing stack address of this variable - * In practice will be either an AllocaInst (for non-captured variables), - * or result of IRBuilder<>::CreateInBoundsGEP (for captured variables). - **/ - llvm::Value * llvm_addr_ = nullptr; - - /** llvm type associated with stack-allocated variable. - * Determines (when combined with llvm::DataLayout) how much space - * will be required for this particular variable - **/ - llvm::Type * llvm_type_ = nullptr; - }; - - inline std::ostream & - operator<<(std::ostream & os, const runtime_binding_detail & x) { - os << ""; - - return os; - } - } - -#ifndef ppdetail_atomic - namespace print { - PPDETAIL_ATOMIC(xo::jit::runtime_binding_path); - PPDETAIL_ATOMIC(xo::jit::runtime_binding_detail); - } -#endif - - namespace jit { - /** - * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) - * - * 2. each function needs its own IR builder, to keep track of things like insert point - * - * 3. simple case first. - * if lm->needs_closure_flag() is false, then: - * - * a. still need a closure-shaped object, because when we invoke function, we may - * not know until runtime whether it relies on closure. - * For such function we will generate a closure with empty environment pointer. - * b. all formal parameters of lm - * are used only in the layer associated with that lambda's body; in particular - * they aren't free in any nested lambda - * c. conversely, the top layer of lm's body has no free variables. - * The only variables that *do* appear are lm's formal parameters. - * - * In this case, all of lm's formals will be allocated on the stack using regular - * allocInst, and we don't need a closure for lm. - * - * 4. complex case second - * If lm->needs_closure_flag() is true, then either: - * - * a. at least one formal parameter of lm appears free in some nested lambda. - * b. lambda's top layer itself contains one or more free variables. - * - * In either case we will create an explicit environment for lm, - * containing all the variables needed by some nested lambda - **/ - class activation_record { - public: - using Lambda = xo::scm::Lambda; - using TypeDescr = xo::reflect::TypeDescr; - - public: - activation_record(const rp & lm); - - const rp lambda() const { return lambda_; } - - /** retrieve @c llvm::Value* representing the primary stack location - * for formal parameter @p var_name - **/ - const runtime_binding_detail * lookup_var(const std::string & var_name) const; - - /** Remember allocation of a function variable on the stack - * - * @param var_name. formal parameter name - * @param binding. address + supporting details for - * primary (stack-allocated) storage for this variable - **/ - const runtime_binding_detail * alloc_var(const std::string & var_name, - const runtime_binding_detail & binding); - -#ifdef NOT_USING - llvm::AllocaInst * create_runtime_localenv_alloca(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & fn_ir_builder); -#endif - - runtime_binding_detail create_entry_block_alloca(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & fn_ir_builder, - int i_arg, - const std::string & var_name, - TypeDescr var_td); - - /** generate instructions that establish stacck location for a local-environment slot - * - * @param llvm_cx. handle for context -- manages storage for llvm::Types + related - * @param localenv_llvm_type. describes contents of local environment - * for a particular function. Same as @c localenv_alloca->getAllocatedType() - * @param localenv_alloca. stack location for local environment - * @param i_slot. 0-based slot number within local environment, - * for which address is required - * @param fn_ir_builder. insertion point for generated instructions - * that compute target slot address (will be at/near top of function, - * since we will copy captured function arguments to localenv, - * then use the localenv copy exclusively. - * @return value representing localenv slot address - **/ - llvm::Value * runtime_localenv_slot_addr(bp llvm_cx, - llvm::StructType * localenv_llvm_type, - llvm::AllocaInst * localenv_alloca, - int i_slot, - llvm::IRBuilder<> & fn_ir_builder); - - /** establish storage for formal parameters on behalf of a new-but-empty - * llvm function @p llvm_fn. Creates llvm IR instructions on function - * entry that - * 1. allocates stack space for function parameters. - * 2. stores incoming parameters in that stack space. - * - * Strategy: - * - for stackonly parameters, use individual @c llvm::AllocaInst instances - * - create custom @c llvm::StructType for captured parameters, also initially stack-allocated - **/ - bool bind_locals(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & ir_builder); - - private: - /** this activation record created on behalf of a call to @ref lambda_. - * @ref Variable::path_ specifies a logical path to a variable, - * but does not distinguish stack-native variables from variables in explicit - * runtime environment records. - * - **/ - rp lambda_; - - /** @c binding_v_[i] specifies how/where we mean to navigate to - * location for formal parameter number *i* of @ref lambda_. - **/ - std::vector binding_v_; - - /** if this function requires an explicit environment, - * gives stack location for that environment. - **/ - llvm::AllocaInst * localenv_alloca_ = nullptr; - - /** maps named slots in a stack frame to logical addresses. - * - * - For captured arguments: will refer to slot within stack-allocated local environment - * (an llvm::StructType, created by type2llvm::create_localenv_llvm_type()) - * - * - For non-captured arguments: will refer to stack-allocated argument copy - * - * In either case using copy-to-stack to evade directly confronting - * so we don't have to comply with llvm IR's SSA requirement. - **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ - }; /*activation_record*/ - - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end activation_record.hpp **/ diff --git a/.xo-jit/include/xo/jit/activation_record.new.hpp b/.xo-jit/include/xo/jit/activation_record.new.hpp deleted file mode 100644 index 7c70ba3f..00000000 --- a/.xo-jit/include/xo/jit/activation_record.new.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/** @file activation_record.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "LlvmContext.hpp" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -# include -# include -#pragma GCC diagnostic pop -#include -//#include - -namespace xo { - namespace jit { - /** scope for a stack frame associated with a user-defined function - * - * each function needs its own IR builder, to keep track of things like insert point - **/ - class activation_record { - public: - activation_record(llvm::Function * llvm_fn, - llvm::AllocaInst * frame) : frame_{frame} { - int i_arg = 0; - for (auto & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - name2ix_map_[arg_name] = 2 + i_arg; - } - } - - std::int32_t lookup_var(const std::string & var_name) const; - -#ifdef OBSOLETE - llvm::AllocaInst * lookup_var(const std::string & var_name) const; - - llvm::AllocaInst * alloc_var(const std::string & var_name, - llvm::AllocaInst * alloca); -#endif - - private: - /** stack frame for a user-defined function (lambda) **/ - llvm::AllocaInst * frame_ = nullptr; - - /** for each formal parameter, - * reports its position in stack frame. - * This is the position to use with getelementptr, - * i.e. +2 to skip first two slots, that are reserved - * for nextframe pointer (slot 0) + unwind pointer (slot 1) - **/ - std::map name2ix_map_; - -#ifdef OBSOLETE - /** maps named slots in a stack frame to logical addresses **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ -#endif - }; /*activation_record*/ - - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end activation_record.hpp **/ diff --git a/.xo-jit/include/xo/jit/activation_record.orig.hpp b/.xo-jit/include/xo/jit/activation_record.orig.hpp deleted file mode 100644 index c2aba2dd..00000000 --- a/.xo-jit/include/xo/jit/activation_record.orig.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/** @file activation_record.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "LlvmContext.hpp" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -# include -# include -#pragma GCC diagnostic pop -#include -//#include - -namespace xo { - namespace jit { - /** scope for a stack frame associated with a user-defined function - * - * each function needs its own IR builder, to keep track of things like insert point - **/ - class activation_record { - public: - activation_record() = default; - - llvm::AllocaInst * lookup_var(const std::string & var_name) const; - - llvm::AllocaInst * alloc_var(const std::string & var_name, - llvm::AllocaInst * alloca); - - private: - /** maps named slots in a stack frame to logical addresses **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ - }; /*activation_record*/ - - } /*namespace jit*/ -} /*namespace xo*/ - - -/** end activation_record.hpp **/ diff --git a/.xo-jit/include/xo/jit/intrinsics.hpp b/.xo-jit/include/xo/jit/intrinsics.hpp deleted file mode 100644 index a0528dc2..00000000 --- a/.xo-jit/include/xo/jit/intrinsics.hpp +++ /dev/null @@ -1,13 +0,0 @@ -/** @file intrinsics.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include - -extern "C" int32_t mul_i32(int32_t x, int32_t y); -extern "C" double mul_f64(double x, double y); - -/** end intrinsics.hpp **/ diff --git a/.xo-jit/include/xo/jit/type2llvm.hpp b/.xo-jit/include/xo/jit/type2llvm.hpp deleted file mode 100644 index d19ce492..00000000 --- a/.xo-jit/include/xo/jit/type2llvm.hpp +++ /dev/null @@ -1,228 +0,0 @@ -/** @file type2llvm.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "LlvmContext.hpp" -#include "xo/expression/Lambda.hpp" -#include "xo/reflect/TypeDescr.hpp" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#include -#pragma GCC diagnostic pop -//#include - -namespace xo { - namespace jit { - /** - **/ - struct type2llvm { - public: - using FunctionInterface = xo::scm::ProcedureExprInterface; - using Lambda = xo::scm::Lambda; - using TypeDescr = xo::reflect::TypeDescr; - - public: - /** establish suitable llvm representation for a c++ type (described by @p td) - * llvm types are unique'd, at least within @p llvm_cx - **/ - static llvm::Type * td_to_llvm_type(xo::bp llvm_cx, - TypeDescr td); - - /** establish llvm representation for a function type - * described by @p fn_td - * - * @param wrapper_flag If true, create function type for a wrapper - * to be associated with a closure. - * The wrapper accepts (and ignores) an envapi pointer as first argument. - * Necessary to (for example) support function pointers that may refer - * to either {primitive functions, functions-requiring-closures}, - * with choice deferred until runtime - **/ - static llvm::FunctionType * function_td_to_lvtype(xo::bp llvm_cx, - TypeDescr fn_td, - bool wrapper_flag = false); - - /** establish llvm representation for a function-pointer type - * described by @p fn_td - * - * @param wrapper_flag If true, create function type for a wrapper - * to be associated with a closure. - * The wrapper accepts (and ignores) an envapi pointer as first argument. - * Necessary to (for example) support function pointers that may refer - * to either {primitive functions, functions-requiring-closures}, - * with choice deferred until runtime - **/ - static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::bp llvm_cx, - TypeDescr fn_td, - bool wrapper_flag); - - /** establish llvm concrete representation for a closure. - * - * +-------+ - * [0] | o-------> fnptr T (*)(envptr, ...) - * +-------+ - * [1] | o-------\ - * +-------+ | - * | - * | - * v - * +-------+ - * parent_env [0] | o-------> _env_api* - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * - * @return struct type. typename will be @c c.foo for a function - * (primitive or lambda) with name @c foo - **/ - static llvm::StructType * - create_closureapi_lvtype(xo::bp llvm_cx, - xo::bp fn); - - /** establish llvm abstract representation for a closure: - * struct with - * - [0] function pointer - * - [1] runtime localenv pointer - * - * +-------+ - * | o---------> native function - * +-------+ - * | o---------> runtime localenv - * +-------+ (possibly nullptr) - * - * 1. for primitives, localenv will be null pointer - * 2. for lambdas L with L->requires_closure_flag() = false, - * localenv will also be null pointer - * 3. for lambdas with L->requires_closure_flag() = true, - * - * localenv will (for lambdas requiring closures) - * in practice be struct: - * - * ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * arg[i] [2+i] . ... . - * . ... . - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * Implementation here will just use generic pointer for runtime - * localenv. - **/ - static llvm::StructType * - function_td_to_closureapi_lvtype(xo::bp llvm_cx, - TypeDescr fn_td, - const std::string & hint_name); - - /** establish llvm concrete representation for a particular lambda's - * runtime local environment: - * - * ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * arg[i] [2+i] . ... . - * . ... . - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * arg[] comprises the subset of lambda arg names arg[j] for which - * lambda->is_captured(arg[j]) is true - * - * @return struct type. typename will be @c e.foo for lambda with name @c foo - **/ - static llvm::StructType * - create_localenv_llvm_type(xo::bp llvm_cx, - xo::bp lambda); - - /** establish llvm rep'n for a pointer to an abstract local environment: - * - * +-------+ - * | o-------------\ - * +-------+ | - * | - * | - * | - * v - * +-------+ - * parent_env [0] | o-------> _env_api* - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - **/ - static llvm::PointerType * - env_api_llvm_ptr_type(xo::bp llvm_cx); - - /** function type: - * @code - * env_api_* (env_api* env, int ctl); - * @endcode - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * returns function-pointer type - **/ - static llvm::PointerType * - require_localenv_unwind_llvm_fnptr_type(xo::bp llvm_cx, - llvm::PointerType * hint_envptr_llvm_type = nullptr); - - private: - - /** establish llvm representation for a struct type described by @p struct_td - **/ - static llvm::StructType * struct_td_to_llvm_type(xo::bp llvm_cx, - TypeDescr struct_td); - - /** establish llvm representation for a pointer type described by @p pointer_td **/ - static llvm::PointerType * pointer_td_to_llvm_type(xo::bp llvm_cx, - TypeDescr pointer_td); - - /** establish llvm abstract representation for a local environment: - * - * ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * Concrete implementation will probably occupy additional memory, - * to store captured lambda variables. - * - * @see type2llvm::function_td_to_llvm_closure_type - **/ - static llvm::StructType * - env_api_llvm_type(xo::bp llvm_cx); - - }; /*type2llvm*/ - } /*namespace jit*/ -} /*namespace xo*/ - -/** end type2llvm.hpp **/ diff --git a/.xo-jit/src/jit/CMakeLists.txt b/.xo-jit/src/jit/CMakeLists.txt deleted file mode 100644 index 77bd9a5e..00000000 --- a/.xo-jit/src/jit/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -# jit/CMakeLists.txt - -set(SELF_LIB xo_jit) -set(SELF_SRCS - LlvmContext.cpp - IrPipeline.cpp - MachPipeline.cpp - intrinsics.cpp - activation_record.cpp - type2llvm.cpp -) - -xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) -xo_dependency(${SELF_LIB} xo_expression) - -# llvm {16,17} api will not build without some retro work -find_package(LLVM 18.1 REQUIRED CONFIG) -message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") -message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") -message(STATUS "LLVM_DIR=${LLVM_DIR}") -message(STATUS "LLVM_DEFINITIONS=${LLVM_DEFINITIONS}") -message(STATUS "LLVM_INCLUDE_DIRS=[${LLVM_INCLUDE_DIRS}]") - -separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) - -message(STATUS "LLVM_DEFINITIONS_LIST=[${LLVM_DEFINITIONS_LIST}]") - -# LLVM library directory -execute_process( - COMMAND llvm-config --libdir - COMMAND tr -d '\n' - OUTPUT_VARIABLE LLVM_LIBRARY_DIR -) - -message(STATUS "LLVM_LIBRARY_DIR=[${LLVM_LIBRARY_DIR}]") - -# Find the libraries that correspond to the LLVM components -execute_process( - COMMAND llvm-config --libs all - COMMAND tr -d '\n' - OUTPUT_VARIABLE LLVM_LIBS -) - -message(STATUS "LLVM_LIBS=[${LLVM_LIBS}]") - -# SYSTEM: suppress warnings from LLVM headers (e.g. gcc14 false positive in SmallDenseMap) -target_include_directories(${SELF_LIB} SYSTEM PUBLIC ${LLVM_INCLUDE_DIRS}) -target_compile_definitions(${SELF_LIB} PUBLIC ${LLVM_DEFINITIONS_LIST}) -target_link_directories(${SELF_LIB} PUBLIC ${LLVM_LIBRARY_DIR}) -target_link_libraries(${SELF_LIB} PUBLIC ${LLVM_LIBS}) - -# end CMakeLists.txt diff --git a/.xo-jit/src/jit/IrPipeline.cpp b/.xo-jit/src/jit/IrPipeline.cpp deleted file mode 100644 index cefd9766..00000000 --- a/.xo-jit/src/jit/IrPipeline.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* @file IrPipeline.cpp */ - -#include "IrPipeline.hpp" - -namespace xo { - namespace jit { - IrPipeline::IrPipeline(rp llvm_cx) - { - using std::make_unique; - - this->llvm_cx_ = std::move(llvm_cx); - - this->llvm_fpmgr_ = make_unique(); - this->llvm_lamgr_ = std::make_unique(); - this->llvm_famgr_ = std::make_unique(); - this->llvm_cgamgr_ = std::make_unique(); - this->llvm_mamgr_ = std::make_unique(); - this->llvm_pic_ = std::make_unique(); - /* reference kept alive by @ref llvm_cx_ */ - this->llvm_si_ = std::make_unique(llvm_cx_->llvm_cx_ref(), - /*DebugLogging*/ true); - - this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); - - /** transform passes **/ - this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); - - /* NOTE: llvm 19 adds mem2reg transform here. - * speculating that PromotePass() does same/goodenough thing in llvm 18. - * This pays off, works first try! - */ - this->llvm_fpmgr_->addPass(llvm::PromotePass()); - - this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); - this->llvm_fpmgr_->addPass(llvm::GVNPass()); - this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); - - /** tracking for analysis passes that share info? **/ - llvm::PassBuilder llvm_pass_builder; - llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); - llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); - llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); - } /*ctor*/ - - void - IrPipeline::run_pipeline(llvm::Function & fn) - { - llvm_fpmgr_->run(fn, *llvm_famgr_); - } /*run_pipeline*/ - } /*namespace jit*/ -} /*namespace xo*/ - - - -/* end IrPipeline.cpp */ diff --git a/.xo-jit/src/jit/Jit.cpp b/.xo-jit/src/jit/Jit.cpp deleted file mode 100644 index 8f915406..00000000 --- a/.xo-jit/src/jit/Jit.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* @file Jit.cpp */ - -#include "Jit.hpp" - -namespace xo { - namespace jit { - } /*namespace jit*/ -} /*namespace xo*/ - -/* end Jit.cpp */ diff --git a/.xo-jit/src/jit/LlvmContext.cpp b/.xo-jit/src/jit/LlvmContext.cpp deleted file mode 100644 index 1e4ad007..00000000 --- a/.xo-jit/src/jit/LlvmContext.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* @file LlvmContext.cpp */ - -#include "LlvmContext.hpp" - -namespace xo { - namespace jit { - rp - LlvmContext::make() { - return new LlvmContext(); - } - - LlvmContext::LlvmContext() - : llvm_cx_{std::make_unique()} - {} - - } /*namespace jit*/ -} /*namespace xo*/ - - -/* end LlvmContext.cpp */ diff --git a/.xo-jit/src/jit/MachPipeline.cpp b/.xo-jit/src/jit/MachPipeline.cpp deleted file mode 100644 index 6c7fb152..00000000 --- a/.xo-jit/src/jit/MachPipeline.cpp +++ /dev/null @@ -1,1147 +0,0 @@ -/* @file MachPipeline.cpp */ - -#include "MachPipeline.hpp" -#include "activation_record.hpp" -#include "type2llvm.hpp" -#include "xo/expression/pretty_variable.hpp" -#include - -namespace xo { - using xo::scm::exprtype; - using xo::scm::Expression; - using xo::scm::ConstantInterface; - //using xo::scm::FunctionInterface; - using xo::scm::PrimitiveExprInterface; - using xo::scm::Lambda; - using xo::scm::Variable; - using xo::scm::Apply; - using xo::scm::IfExpr; - using xo::scm::GlobalSymtab; - using xo::scm::llvmintrinsic; - using xo::reflect::Reflect; - using xo::reflect::StructMember; - using xo::reflect::TypeDescr; - using xo::scope; - using llvm::orc::ExecutionSession; - using llvm::DataLayout; - using std::cerr; - using std::endl; - - namespace jit { - void - MachPipeline::init_once() { - static bool s_init_once = false; - - if (!s_init_once) { - s_init_once = true; - - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - } - } /*init_once*/ - - /* tracking KaleidoscopeJIT::Create() here.. - * - * Verified: - * + 'execution session' as per Kaleidoscope JIT, - * can instantiate from python - * + 'jit object layer' - * (realtime dynamic library object linking layer) - * + 'jit_compile_layer' - * + 'jit_our_dynamic_lib' - */ - llvm::Expected> - MachPipeline::make_aux() - { - MachPipeline::init_once(); - - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); - - return std::unique_ptr(new MachPipeline(std::move(jit) - )); - } /*make*/ - - rp - MachPipeline::make() { - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(make_aux()); - - return jit.release(); - } /*make*/ - - MachPipeline::MachPipeline(std::unique_ptr jit) - : jit_{std::move(jit)}, - global_env_{GlobalEnv::make_empty()} - { - this->recreate_llvm_ir_pipeline(); - } - - void - MachPipeline::recreate_llvm_ir_pipeline() - { - //llvm_cx_ = std::make_unique(); - llvm_cx_ = LlvmContext::make(); - llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); - - llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(this->jit_->data_layout()); - - if (!llvm_cx_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); - } - if (!llvm_toplevel_ir_builder_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); - } - if (!llvm_module_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); - } - - ir_pipeline_ = new IrPipeline(llvm_cx_); - } /*recreate_llvm_ir_pipeline*/ - - const DataLayout & - MachPipeline::data_layout() const { - return this->jit_->data_layout(); - } - - const ExecutionSession * - MachPipeline::xsession() const { - return this->jit_->xsession(); - } - - /** identifies target host/architecture for machine code. - * e.g. "x86_64-unknown-linux-gnu" - **/ - const std::string & - MachPipeline::target_triple() const { - // although this getter is defined, seems to be empty in practice - //return llvm_module_->getTargetTriple(); - - return this->jit_->target_triple(); - } - - std::vector - MachPipeline::get_function_name_v() { - std::vector retval; - for (const auto & fn_name : *llvm_module_) - retval.push_back(fn_name.getName().str()); - - return retval; - } /*get_function_names*/ - - void - MachPipeline::dump_execution_session() { - this->jit_->dump_execution_session(); - } - - llvm::Value * - MachPipeline::codegen_constant(bp expr) - { - TypeDescr td = expr->value_td(); - - if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } - - return nullptr; - } /*codegen_constant*/ - - llvm::Type * - MachPipeline::codegen_type(TypeDescr td) { - return type2llvm::td_to_llvm_type(llvm_cx_.borrow(), td); - } - - llvm::Function * - MachPipeline::codegen_primitive(bp expr) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag)); - - /** note: documentation (such as it is) for llvm::Function here: - * - * https://llvm.org/doxygenL/classllvm_1_1Function.html - **/ - - auto * fn = llvm_module_->getFunction(expr->name()); - - if (fn) { - /** function with this name already known to llvm module; - * use that definition - * - * TODO: verify that signatures match! - **/ - return fn; - } - - /** establish prototype for this function **/ - - TypeDescr fn_td = expr->valuetype(); - - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td); - - if (!llvm_fn_type) - return nullptr; - - fn = llvm::Function::Create(llvm_fn_type, - llvm::Function::ExternalLinkage, - expr->name(), - llvm_module_.get()); - -#ifdef NOT_USING - // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here - { - int i_arg = 0; - for (auto & arg : fn->args()) { - std::stringstream ss; - ss << "x_" << i_arg; - - arg.setName(ss.str()); - ++i_arg; - } - } -#endif - - if (expr->explicit_symbol_def()) { - static llvm::ExitOnError llvm_exit_on_err; - - auto name = expr->name(); - auto fn_addr = expr->function_address(); - - log && log(xtag("sym", name), - xtag("mangled_sym", this->jit_->mangle(name))); - - llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); - -#ifdef NOT_USING - if (!llvm_result) { - cerr << "MachPipeline::codegen_primitive" - << ": intern_symbol failed" - << xtag("name", expr->name()) - << xtag("addr", expr->function_address()) - << endl; - - return nullptr; - } -#endif - } else { - log && log("not requiring absolute address", xtag("sym", expr->name())); - } - -#ifdef OBSOLETE - log && log("returning llvm function"); -#endif - - return fn; - } /*codegen_primitive*/ - - llvm::Function * - MachPipeline::codegen_primitive_wrapper(bp expr, - llvm::IRBuilder<> & /*ir_builder*/) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("primitive-name", expr->name())); - - constexpr const char * c_prefix = "w."; - - /* unique name for wrapper. Note we don't allow period in schematica identifiers - * (though we could if we replace . with .. when lowering) - */ - std::string wrap_name = std::string(c_prefix) + expr->name(); - - /* original primitive */ - auto * native_lvfn = this->codegen_primitive(expr); - - /* wrapped primitive */ - auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); - - if (wrap_lvfn) { - /* wrapper already defined */ - return wrap_lvfn; - } - - TypeDescr fn_td = expr->valuetype(); - - llvm::FunctionType * native_lvtype - = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), - fn_td, - false /*!wrapper_flag*/); - - if (!native_lvtype) - return nullptr; - - llvm::FunctionType * wrapper_lvtype - = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), - fn_td, - true /*wrapper_flag (for closure)*/); - - wrap_lvfn = llvm::Function::Create(wrapper_lvtype, - llvm::Function::ExternalLinkage, - wrap_name, - llvm_module_.get()); - - /* at least we know the name of the 1st argument :) */ - auto ix = wrap_lvfn->args().begin(); - ix->setName(".env"); - - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "entry", wrap_lvfn); - - /* don't call SetInsertPoint() on incoming ir_builder argument. - * Want to avoid disturbing top-to-bottom flow - */ - llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); - tmp_ir_builder.SetInsertPoint(block); - - std::vector args; - - /* call to native_lvfn, - * forwarding all args of wrap_lvfn, except the first - */ - { - args.reserve(expr->n_arg()); - - int i_wrap_arg = 0; - for (auto & arg : wrap_lvfn->args()) { - if (i_wrap_arg > 0) - args.push_back(&arg); - - ++i_wrap_arg; - } - } - - /* {caller,callee} must agree on calling convention, - * so for primitives we need to assume c. - */ - llvm::CallInst * call = tmp_ir_builder.CreateCall(native_lvtype, - native_lvfn, - args, - "w.calltmp"); - if (call) { - call->setTailCall(true); - - /* does this work if call returns void? Is this needed with tail call? */ - tmp_ir_builder.CreateRet(call); - - llvm::verifyFunction(*wrap_lvfn); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - wrap_lvfn->print(ss); - - log(xtag("IR-before-opt", buf)); - } - - /* optimize! */ - ir_pipeline_->run_pipeline(*wrap_lvfn); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - wrap_lvfn->print(ss); - - log(xtag("IR-after-opt", buf)); - } - } else { - wrap_lvfn->eraseFromParent(); - wrap_lvfn = nullptr; - } - - return wrap_lvfn; - } /*codegen_primitive_wrapper*/ - - llvm::Value * - MachPipeline::codegen_primitive_closure(bp expr, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - llvm::StructType * closure_lvtype - = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); - - llvm::Function * pm_wrapper = codegen_primitive_wrapper(expr, ir_builder); - llvm::Value * env_0ptr = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx_)); - - llvm::Value * lv_closure = nullptr; - - lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), - pm_wrapper, {0}, "wrapfnptr" /*name*/); - lv_closure = ir_builder.CreateInsertValue(lv_closure, - env_0ptr, {1}, "nullenvptr" /*name*/); - - return lv_closure; - } /*codegen_primitive_closure*/ - - llvm::Value * - MachPipeline::codegen_apply(bp apply, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("apply", apply)); - - // see here: - // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api - - using std::cerr; - using std::endl; - - /* IR for closure in function position - * see: - * - MachPipeline::codegen_primitive_closure - * - MachPipeline::codegen_lambda_closure - * - type2llvm::create_closure_lvtype - * - * although this refers to a closure, llvm doesn't know that - */ - llvm::Value * llvm_closure = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - { - /* special treatement for primitive in apply position: - * allows substituting LLVM intrinsic - */ - if (apply->fn()->extype() == exprtype::primitive) { - auto pm = PrimitiveExprInterface::from(apply->fn()); - - if (pm) { - llvm_closure = this->codegen_primitive_closure(pm, ir_builder); - /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ - intrinsic = pm->intrinsic(); - } - } else { - llvm_closure = this->codegen(apply->fn(), envptr, ir_builder); - - /* we don't need any special checking here. - * already know (from xo-level checking) that pointer has the right type. - * - * Specifically, xo::scm::Apply::make() requires the expression in function position - * have suitable function type. - * - * Now: we have an llvm::Value (fn_value) representing the pointer. - * However it's not an llvm::Function instance, and we can't get one. - * - * (Older LLVM versions allowed getting the element type from a pointer, - * for some reasons that's deprecated at least in 18.1.5) - */ - } - } - - if (!llvm_closure) { - return nullptr; - } - - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); - - if (log) { - log("MachPipeline::codegen_apply: fn in apply pos..."); - llvm_closure->print(llvm::errs()); - log("...done"); - log("llvm type..."); - llvm_closure->getType()->dump(); - log("...done"); - } - - /* checks here will be redundant */ - -#ifdef OBSOLETE - llvm::StructType * closure_lvtype - = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, - ast_fn_td, - "" /*name - not required*/); -#endif - - llvm::Value * lv_fnptr = nullptr; - { -#ifdef MAYBE_VERBOSE - llvm::Value * i0_slot - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, 0 /*value*/)); - - llvm::Value * fnptr_slot - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, 0 /*value*/)); - - std::array index_v - = {{i0_slot, - fnptr_slot /*fnptr slot = closure[0]*/}}; - - llvm::Value * lv_fnptr_addr - = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, - llvm_closure, - index_v); - - llvm::Type * fnptr_lvtype - = type2llvm::function_td_to_llvm_fnptr_type(llvm_cx_, - apply->fn()->valuetype(), - true /*wrapper_flag*/); - - /* the thing we're going to call */ - lv_fnptr = ir_builder.CreateLoad(fnptr_lvtype, lv_fnptr_addr); -#endif - - std::array index_v = {{ 0 }}; - - //ir_builder.CreateExtractValue(Value *Agg, ArrayRef Idxs) - - lv_fnptr = ir_builder.CreateExtractValue(llvm_closure, - index_v, - "fnptr"); - } - - llvm::Value * lv_fnenvptr = nullptr; - { -#ifdef MAYBE_VERBOSE - llvm::Value * i0_slot - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, 0 /*value*/)); - - llvm::Value * envptr_slot - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, 1 /*value*/)); - - std::array index_v - = {{i0_slot, - envptr_slot /*envptr slot = closure[1]*/}}; - - llvm::Value * lv_fnenvptr_addr - = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, - llvm_closure, - index_v); - - llvm::Type * fnenvptr_lvtype - = type2llvm::env_api_llvm_ptr_type(llvm_cx_); - - lv_fnenvptr = ir_builder.CreateLoad(fnenvptr_lvtype, lv_fnenvptr_addr); -#endif - - std::array index_v = {{ 1 }}; - - lv_fnenvptr = ir_builder.CreateExtractValue(llvm_closure, - index_v, - "envptr"); - } - - std::vector args; - /* +1 for envptr */ - args.reserve(1 + apply->argv().size()); - - - /* we must take envptr from closure, - * and we need to do this using some version of getelementptr - */ - args.push_back(lv_fnenvptr); - - int i = 0; - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, envptr, ir_builder); - - if (log) { - /* TODO: print helper for llvm::Value* */ - std::string llvm_value_str; - - if (arg) { - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); - } else { - llvm_value_str = ""; - } - - log(xtag("i_arg", i), - xtag("arg", llvm_value_str)); - } - - args.push_back(arg); - ++i; - - if (!arg) { - cerr << "MachPipeline::codegen_apply: failed for i'th argument" - << xtag("i", i) - << endl; - - return nullptr; - } - } - - /* if we have an intrinsic hint, - * then instead of invoking a function, - * we use some native machine instruction instead. - * - * args[0] not used here, that holds envptr from faux closure - */ - switch(intrinsic) { - case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[1]); - case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[1], args[2]); - case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[1], args[2]); - case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[1], args[2]); - case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[1], args[2]); - case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[1], args[2]); - - case llvmintrinsic::i_eq: - return ir_builder.CreateICmpEQ(args[1], args[2]); - case llvmintrinsic::i_ne: - return ir_builder.CreateICmpNE(args[1], args[2]); - case llvmintrinsic::i_sgt: - return ir_builder.CreateICmpSGT(args[1], args[2]); - case llvmintrinsic::i_sge: - return ir_builder.CreateICmpSGE(args[1], args[2]); - case llvmintrinsic::i_slt: - return ir_builder.CreateICmpSLT(args[1], args[2]); - case llvmintrinsic::i_sle: - return ir_builder.CreateICmpSLE(args[1], args[2]); - - case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[1], args[2]); - case llvmintrinsic::fp_sub: - return ir_builder.CreateFSub(args[1], args[2]); - case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[1], args[2]); - case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[1], args[2]); - case llvmintrinsic::invalid: - case llvmintrinsic::fp_sqrt: - case llvmintrinsic::fp_pow: - case llvmintrinsic::fp_sin: - case llvmintrinsic::fp_cos: - case llvmintrinsic::fp_tan: - case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ - break; - } - - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_lvtype(this->llvm_cx_, - ast_fn_td, - true /*wrapper_flag*/); - - return ir_builder.CreateCall(llvm_fn_type, - lv_fnptr, - args, - "calltmp"); - - } /*codegen_apply*/ - - std::vector> - MachPipeline::find_lambdas(bp expr) const - { - std::vector> retval_v; - - expr->visit_preorder( - [&retval_v](bp x) - { - if (x->extype() == exprtype::lambda) { - retval_v.push_back(Lambda::from(x)); - } - }); - - return retval_v; - } /*find_lambdas*/ - - llvm::Function * - MachPipeline::codegen_lambda_decl(bp lambda) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - this->global_env_->require_global(lambda->name(), lambda); - - /* do we already know a function with this name? */ - auto * fn = llvm_module_->getFunction(lambda->name()); - - if (fn) { - return fn; - } - - /* establish prototype for this function */ - - /* wrapper_flag: llvm function type takes extra first argument, - * supplying environment pointer from surrounding closure. - * - * Note that this argument is not present in lambda, - * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] - */ - llvm::FunctionType * fn_lvtype - = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), - lambda->valuetype(), - true /*wrapper_flag*/); - - /* create (initially empty) function */ - fn = llvm::Function::Create(fn_lvtype, - llvm::Function::ExternalLinkage, - lambda->name(), - llvm_module_.get()); - - /* also adopt lambda's formal argument names */ - { - int i = 0; - for (auto & arg : fn->args()) { - if (i == 0) { - log && log("llvm inserted env param", - xtag("i", i)); - - arg.setName(".env"); - } else { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i-1))); - - arg.setName(lambda->argv().at(i-1)->name()); - } - - ++i; - } - } - - return fn; - } /*codegen_lambda_decl*/ - - llvm::Function * - MachPipeline::codegen_lambda_defn(bp lambda, - llvm::IRBuilder<> & /*ir_builder*/) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - global_env_->require_global(lambda->name(), lambda.get()); - - /* correct PROVIDED this is a toplevel lambda */ - lambda->attach_envs(this->global_env_); - - /* do we already know a function with this name? */ - auto * llvm_fn = llvm_module_->getFunction(lambda->name()); - - if (!llvm_fn) { - /** function with this name not declared? **/ - cerr << "MachPipeline::codegen_lambda: function f not declared" - << xtag("f", lambda->name()) - << endl; - - return nullptr; - } - - /* environment for this lambda's clsoure - * passed as extra 1st argument - */ - llvm::Value * envptr = llvm_fn->args().begin(); - - /* generate function body */ - - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "entry", - llvm_fn); - - /* since we need to explictly set builder's insert point, - * make a new builder instead of disturbing the top-to-bottom flow of the - * called ir_builder - */ - llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); - tmp_ir_builder.SetInsertPoint(block); - - /** Actual parameters will need their own activation record. - * Track its shape + setup/teardown here. - **/ - this->env_stack_.push(activation_record(lambda.get())); - - bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, tmp_ir_builder); - - if (!ok_flag) { - this->env_stack_.pop(); - return nullptr; - } - - llvm::Value * retval = this->codegen(lambda->body(), - envptr, - tmp_ir_builder); - - if (retval) { - /* completes the function.. */ - tmp_ir_builder.CreateRet(retval); - - /* validate! always validate! */ - llvm::verifyFunction(*llvm_fn); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-before-opt", buf)); - } - - /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-after-opt", buf)); - } - } else { - /* oops, something went wrong */ - llvm_fn->eraseFromParent(); - - llvm_fn = nullptr; - } - - this->env_stack_.pop(); - - log && log("after pop, env stack size Z", - xtag("Z", env_stack_.size()), - xtag("llvm_fn", (void*)llvm_fn)); - - return llvm_fn; - } /*codegen_lambda_defn*/ - - llvm::Value * - MachPipeline::codegen_lambda_closure(bp lambda, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder) - { - llvm::StructType * closure_lvtype - = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); - - llvm::Function * lvfn = codegen_lambda_defn(lambda, ir_builder); - - if (!lvfn) { - cerr << "MachPipeline::codegen_lambda_closure: codegen lambda failed" - << endl; - return nullptr; - } - - llvm::Value * lv_closure = nullptr; - { - lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), - lvfn, {0}); //, "lmfnptr" /*name*/); - lv_closure = ir_builder.CreateInsertValue(lv_closure, - envptr, {1}, "closure" /*name*/); - } - - return lv_closure; - } /*codegen_lambda_closure*/ - - llvm::Value * - MachPipeline::codegen_variable(bp var, - llvm::Value * /*envptr*/, - llvm::IRBuilder<> & ir_builder) - { - /* TODO: navigate envptr to handle non-local variables */ - - if (env_stack_.empty()) { - cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" - << xtag("x", var->name()) - << endl; - - return nullptr; - } - - activation_record & ar = env_stack_.top(); - - const runtime_binding_detail * binding = ar.lookup_var(var->name()); - - if (!binding) - return nullptr; - - /* code to load value from stack */ - return ir_builder.CreateLoad(binding->llvm_type_, - binding->llvm_addr_, - var->name().c_str()); - } /*codegen_variable*/ - - llvm::Value * - MachPipeline::codegen_ifexpr(bp expr, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder) - { - llvm::Value * test_ir = this->codegen(expr->test(), envptr, ir_builder); - - /** need test result in a variable **/ - llvm::Value * test_with_cmp_ir - = ir_builder.CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); - - llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); - - /* when_true_bb, when_false_bb, merge_bb: - * initially-empty basic-blocks for {when_true, when_false, merged} codegen - */ - - /* when_true branch inserted at (current) end of function */ - llvm::BasicBlock * when_true_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_true", - parent_fn); - llvm::BasicBlock * when_false_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_false"); - - llvm::BasicBlock * merge_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "merge"); - - /* IR to direct control flow to one of {when_true_bb, when_false_bb}, - * depending on result of test_with_cmp_ir - */ - ir_builder.CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); - - /* populate when_true_bb */ - llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); - tmp_ir_builder.SetInsertPoint(when_true_bb); - - llvm::Value * when_true_ir = this->codegen(expr->when_true(), - envptr, - tmp_ir_builder); - - if (!when_true_ir) - return nullptr; - - /* at end of when-true sequence, jump to merge suffix */ - tmp_ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = tmp_ir_builder.GetInsertBlock(); - - /* populate when_false_bb */ - parent_fn->insert(parent_fn->end(), when_false_bb); - tmp_ir_builder.SetInsertPoint(when_false_bb); - - llvm::Value * when_false_ir = this->codegen(expr->when_false(), - envptr, - tmp_ir_builder); - if (!when_false_ir) - return nullptr; - - /* at end of when-false sequence, jump to merge suffix */ - tmp_ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = tmp_ir_builder.GetInsertBlock(); - - /* merged suffix sequence */ - parent_fn->insert(parent_fn->end(), merge_bb); - tmp_ir_builder.SetInsertPoint(merge_bb); - - /** TODO: switch to getInt1Ty here **/ - llvm::PHINode * phi_node - = tmp_ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); - phi_node->addIncoming(when_true_ir, when_true_bb); - phi_node->addIncoming(when_false_ir, when_false_bb); - - return phi_node; - } /*codegen_ifexpr*/ - - llvm::Value * - MachPipeline::codegen(bp expr, - llvm::Value * envptr, - llvm::IRBuilder<> & ir_builder) - { - switch(expr->extype()) { - case exprtype::define: - case exprtype::assign: - case exprtype::sequence: - case exprtype::convert: - break; - case exprtype::constant: - return this->codegen_constant(ConstantInterface::from(expr)); - case exprtype::primitive: - return this->codegen_primitive_closure(PrimitiveExprInterface::from(expr), ir_builder); - case exprtype::apply: - return this->codegen_apply(Apply::from(expr), envptr, ir_builder); - case exprtype::lambda: - return this->codegen_lambda_closure(Lambda::from(expr), envptr, ir_builder); - //return this->codegen_lambda_decl(Lambda::from(expr)); - case exprtype::variable: - return this->codegen_variable(Variable::from(expr), envptr, ir_builder); - case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder); - case exprtype::invalid: - case exprtype::n_expr: - return nullptr; - break; - } - - cerr << "MachPipeline::codegen: error: no handler for expression of type T" - << xtag("T", expr->extype()) - << endl; - - return nullptr; - } /*codegen*/ - - llvm::Value * - MachPipeline::codegen_toplevel(bp expr) - { - /* - Pass 1. - * get set of lambdas. - * Generate decls for all. - * - * TODO: for lexical scoping (not implemented yet) - * will need traversal that maintains stack - * of ancestor lambdas, or at least their - * activation records. May want to generalize - * activation_record so we can track the set of variables - * before generating AllocaInst's. - * - * - Pass 2. - * Generate code for lambdas. - * - * - Pass 3. - * If toplevel expressions isn't a lambda - * (? won't make sense at present when called from python) - * generate code for it too - */ - - /* WIP. STRATEGY: - * - xo::scm::ClosureExpr (an expression that generates a closure) - * closure = {lambda, env} - * - * - pass 1: - * return list of closure expressions; - * codegen the lambda decls using lambda from each closure - * - pass 2: - * codegen closures: use env chain to resolve variables - */ - - /* Pass 1. */ - auto fn_v = this->find_lambdas(expr); - - for (auto lambda : fn_v) { - this->codegen_lambda_decl(lambda); - } - -#ifdef OBSOLETE /* don't do this anymore, obscures lexical context */ - - /* Pass 2 */ - for (auto lambda : fn_v) { - this->codegen_lambda_defn(lambda, - *(this->llvm_toplevel_ir_builder_.get())); - } - - /* Pass 3 */ - if (expr->extype() == exprtype::lambda) { - /* code already generated in pass 2; - * look it up - */ - - return llvm_module_->getFunction(Lambda::from(expr)->name()); - } else { - /* toplevel expression isn't a lambda, - * so code for it hasn't been generated. - * Do that now - */ - return this->codegen(expr, - *(this->llvm_toplevel_ir_builder_.get())); - } -#endif - - /* 1. using nullptr as runtime representation for global environment - * 2. may have to elaborate this later? not clear to me - */ - - llvm::Value * env_0ptr - = (llvm::ConstantPointerNull::get - (type2llvm::env_api_llvm_ptr_type(llvm_cx_))); - - return this->codegen(expr, - env_0ptr, - *(this->llvm_toplevel_ir_builder_.get())); - } /*codegen_toplevel*/ - - void - MachPipeline::dump_current_module() - { - /* dump module contents to console */ - - llvm_module_->dump(); - } - - void - MachPipeline::machgen_current_module() - { - static llvm::ExitOnError llvm_exit_on_err; - - auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); - - /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create - * - * Note that @ref ir_pipeline_ holds reference, which is invalidated here - */ - auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), - std::move(llvm_cx_->llvm_cx())); - - /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ - this->llvm_cx_ = nullptr; - - llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); - - this->recreate_llvm_ir_pipeline(); - } /*machgen_current_module*/ - - std::string_view - MachPipeline::mangle(const std::string & sym) const - { - return this->jit_->mangle(sym); - } /*mangle*/ - - llvm::Expected - MachPipeline::lookup_symbol(const std::string & sym) - { - /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym_expected = this->jit_->lookup(sym); - - if (llvm_sym_expected) { - auto llvm_addr = llvm_sym_expected.get().getAddress(); - - return llvm_addr; - } else { - return llvm_sym_expected.takeError(); - } - } /*lookup_symbol*/ - - void - MachPipeline::display(std::ostream & os) const { - os << ""; - } - - std::string - MachPipeline::display_string() const { - return tostr(*this); - } - } /*namespace jit*/ -} /*namespace xo*/ - -/* end MachPipeline.cpp */ diff --git a/.xo-jit/src/jit/MachPipeline.new.cpp b/.xo-jit/src/jit/MachPipeline.new.cpp deleted file mode 100644 index 7a03e4a0..00000000 --- a/.xo-jit/src/jit/MachPipeline.new.cpp +++ /dev/null @@ -1,1341 +0,0 @@ -/* @file MachPipeline.cpp */ - -#include "MachPipeline.hpp" -#include - -namespace xo { - using xo::scm::exprtype; - using xo::scm::Expression; - using xo::scm::ConstantInterface; - //using xo::scm::FunctionInterface; - using xo::scm::PrimitiveInterface; - using xo::scm::Lambda; - using xo::scm::Variable; - using xo::scm::Apply; - using xo::scm::IfExpr; - using xo::scm::llvmintrinsic; - using xo::reflect::Reflect; - using xo::reflect::StructMember; - using xo::reflect::TypeDescr; - using llvm::orc::ExecutionSession; - using llvm::DataLayout; - using std::cerr; - using std::endl; - - namespace jit { - void - MachPipeline::init_once() { - static bool s_init_once = false; - - if (!s_init_once) { - s_init_once = true; - - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - } - } /*init_once*/ - - /* tracking KaleidoscopeJIT::Create() here.. - * - * Verified: - * + 'execution session' as per Kaleidoscope JIT, - * can instantiate from python - * + 'jit object layer' - * (realtime dynamic library object linking layer) - * + 'jit_compile_layer' - * + 'jit_our_dynamic_lib' - */ - llvm::Expected> - MachPipeline::make_aux() - { - MachPipeline::init_once(); - - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); - - return std::unique_ptr(new MachPipeline(std::move(jit) - )); - } /*make*/ - - xo::ref::rp - MachPipeline::make() { - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(make_aux()); - - return jit.release(); - } /*make*/ - - MachPipeline::MachPipeline(std::unique_ptr jit) - : jit_{std::move(jit)} - { - this->recreate_llvm_ir_pipeline(); - } - - void - MachPipeline::recreate_llvm_ir_pipeline() - { - //llvm_cx_ = std::make_unique(); - llvm_cx_ = LlvmContext::make(); - llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); - - llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(this->jit_->data_layout()); - - if (!llvm_cx_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); - } - if (!llvm_toplevel_ir_builder_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); - } - if (!llvm_module_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); - } - - ir_pipeline_ = new IrPipeline(llvm_cx_); - } /*recreate_llvm_ir_pipeline*/ - - const DataLayout & - MachPipeline::data_layout() const { - return this->jit_->data_layout(); - } - - const ExecutionSession * - MachPipeline::xsession() const { - return this->jit_->xsession(); - } - - /** identifies target host/architecture for machine code. - * e.g. "x86_64-unknown-linux-gnu" - **/ - const std::string & - MachPipeline::target_triple() const { - // although this getter is defined, seems to be empty in practice - //return llvm_module_->getTargetTriple(); - - return this->jit_->target_triple(); - } - - std::vector - MachPipeline::get_function_name_v() { - std::vector retval; - for (const auto & fn_name : *llvm_module_) - retval.push_back(fn_name.getName().str()); - - return retval; - } /*get_function_names*/ - - void - MachPipeline::dump_execution_session() { - this->jit_->dump_execution_session(); - } - - llvm::Value * - MachPipeline::codegen_constant(ref::brw expr) - { - TypeDescr td = expr->value_td(); - - if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } - - return nullptr; - } /*codegen_constant*/ - - namespace { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - int n_fn_arg = fn_td->n_fn_arg(); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } - - llvm::PointerType * - function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - struct_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) - { - assert(pointer_td->is_pointer()); - - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* in this context, we're looking for a representation for a value, - * i.e. something that can be stored in a variable - */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } - } - - llvm::Type * - MachPipeline::codegen_type(TypeDescr td) { - return td_to_llvm_type(llvm_cx_.borrow(), td); - } - - llvm::Function * - MachPipeline::codegen_primitive(ref::brw expr) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - /** note: documentation (such as it is) for llvm::Function here: - * - * https://llvm.org/doxygenL/classllvm_1_1Function.html - **/ - - auto * fn = llvm_module_->getFunction(expr->name()); - - if (fn) { - /** function with this name already known to llvm module; - * use that definition - * - * TODO: verify that signatures match! - **/ - return fn; - } - - /** establish prototype for this function **/ - - TypeDescr fn_td = expr->valuetype(); - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); - - if (!llvm_fn_type) - return nullptr; - - fn = llvm::Function::Create(llvm_fn_type, - llvm::Function::ExternalLinkage, - expr->name(), - llvm_module_.get()); - -#ifdef NOT_USING - // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here - { - int i_arg = 0; - for (auto & arg : fn->args()) { - std::stringstream ss; - ss << "x_" << i_arg; - - arg.setName(ss.str()); - ++i_arg; - } - } -#endif - - if (expr->explicit_symbol_def()) { - static llvm::ExitOnError llvm_exit_on_err; - - auto name = expr->name(); - auto fn_addr = expr->function_address(); - - log && log(xtag("sym", name), - xtag("mangled_sym", this->jit_->mangle(name))); - - llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); - -#ifdef NOT_USING - if (!llvm_result) { - cerr << "MachPipeline::codegen_primitive" - << ": intern_symbol failed" - << xtag("name", expr->name()) - << xtag("addr", expr->function_address()) - << endl; - - return nullptr; - } -#endif - } else { - log && log("not requiring absolute address", xtag("sym", expr->name())); - } - -#ifdef OBSOLETE - log && log("returning llvm function"); -#endif - - return fn; - } /*codegen_primitive*/ - - llvm::Value * - MachPipeline::codegen_apply(ref::brw apply, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("apply", apply)); - - // see here: - // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api - - using std::cerr; - using std::endl; - - /* IR for value in function position. - * Although it will generate a function (or pointer-to-function), - * it need not have inherited type llvm::Function. - */ - llvm::Value * llvm_fnval = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); - { - /* special treatement for primitive in apply position: - * allows substituting LLVM intrinsic - */ - if (apply->fn()->extype() == exprtype::primitive) { - auto pm = PrimitiveInterface::from(apply->fn()); - - if (pm) { - llvm_fnval = this->codegen_primitive(pm); - /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ - intrinsic = pm->intrinsic(); - } - } else { - llvm_fnval = this->codegen(apply->fn(), ir_builder); - - /* we don't need any special checking here. - * already know (from xo-level checking) that pointer has the right type. - * - * Specifically, xo::scm::Apply::make() requires the expression in function position - * have suitable function type. - * - * Now: we have an llvm::Value (fn_value) representing the pointer. - * However it's not an llvm::Function instance, and we can't get one. - * - * (Older LLVM versions allowed getting the element type from a pointer, - * for some reasons that's deprecated at least in 18.1.5) - */ - } - } - - if (!llvm_fnval) { - return nullptr; - } - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif - - /* checks here will be redundant */ - -#ifdef REDUNDANT_TYPECHECK - if (apply->argv().size() != ast_fn_td->n_fn_arg()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - //<< xtag("f", ast_fn->name()) - << xtag("n1", ast_fn_td->n_fn_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" - << xtag("F", apply->fn()) - << xtag("I", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", ast_fn_td->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } -#endif - - std::vector args; - args.reserve(apply->argv().size()); - - int i = 0; - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); - - if (log) { - /* TODO: print helper for llvm::Value* */ - std::string llvm_value_str; - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); - - log(xtag("i_arg", i), - xtag("arg", llvm_value_str)); - } - - args.push_back(arg); - ++i; - } - - /* if we have an intrinsic hint, - * then instead of invoking a function, - * we use some native machine instruction instead. - */ - switch(intrinsic) { - case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); - case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); - case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); - case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); - case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); - case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); - case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); - case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); - case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); - case llvmintrinsic::invalid: - case llvmintrinsic::fp_sqrt: - case llvmintrinsic::fp_pow: - case llvmintrinsic::fp_sin: - case llvmintrinsic::fp_cos: - case llvmintrinsic::fp_tan: - case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ - break; - } - - /* At least as of 18.1.5, LLVM needs us to supply function type - * when making a function call. In particular it doesn't remember - * the function type with each function pointer - */ - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); - - return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, - args, - "calltmp"); - - } /*codegen_apply*/ - - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return nullptr; - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_block_alloca*/ - - - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, - llvm::StructType * frame_llvm_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn)); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - if (!frame_llvm_type) - return nullptr; - - if (log) { - std::string llvm_frame_type_str; - llvm::raw_string_ostream ss(llvm_frame_type_str); - frame_llvm_type->print(ss); - - log(xtag("frame_llvm_type", llvm_frame_type_str)); - } - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, - nullptr, - llvm_fn->getName()); - - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_frame_alloca*/ - - std::vector> - MachPipeline::find_lambdas(ref::brw expr) const - { - std::vector> retval_v; - - expr->visit_preorder( - [&retval_v](ref::brw x) - { - if (x->extype() == exprtype::lambda) { - retval_v.push_back(Lambda::from(x)); - } - }); - - return retval_v; - } /*find_lambdas*/ - - llvm::Function * - MachPipeline::codegen_lambda_decl(ref::brw lambda) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - global_env_[lambda->name()] = lambda.get(); - - /* do we already know a function with this name? */ - auto * fn = llvm_module_->getFunction(lambda->name()); - - if (fn) { - return fn; - } - - /* establish prototype for this function */ - -#ifdef OBSOLETE - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_retval()); - - std::vector arg_type_v(lambda->n_arg()); - - for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_arg(i)); - } -#endif - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); - - /* create (initially empty) function */ - fn = llvm::Function::Create(llvm_fn_type, - llvm::Function::ExternalLinkage, - lambda->name(), - llvm_module_.get()); - /* also capture argument names */ - { - int i = 0; - for (auto & arg : fn->args()) { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i))); - - arg.setName(lambda->argv().at(i)->name()); - ++i; - } - } - - return fn; - } /*codegen_lambda_decl*/ - - namespace { - /** A function type: - * - * _baseframe* (*) (_baseframe* - **/ - llvm::FunctionType * - require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, - llvm::PointerType * frameptr_llvm_type) - { - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(2); - - /* 1st arg is frame pointer */ - llvm_argtype_v.push_back(frameptr_llvm_type); - - /* 2nd arg is an i32. - * 0 -> unwind. - * 1 -> lift to heap (someday) - */ - llvm_argtype_v.push_back - (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); - - /* return value is frame pointer */ - llvm::Type * retval_llvm_type = frameptr_llvm_type; - - auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, - llvm_argtype_v, - false /*!varargs*/); - - return unwind_llvm_type; - } /*require_baseframe_unwind_llvm_type*/ - - /** Each lambda gets its own stack frame definition. - * However all the various frame representations share the same 'baseframe' - * prefix. - * - * _baseframe: - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) - * +-------+ - * - * This helper function generates an llvm::Type* for a baseframe. - * It only needs to be invoked once (per LlvmContext, I guess ..) - **/ - llvm::StructType * - require_baseframe_llvm_type(xo::ref::brw llvm_cx) - { - /* _baseframe: base type for a stack frame */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_baseframe"); - - /* _baseframe*: pointer to a stack frame */ - llvm::PointerType * frameptr_llvm_type - = llvm::PointerType::getUnqual(frame_llvm_type); - - /* unwind function = frame[1] */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - frameptr_llvm_type); - - /* _baseframe members */ - std::vector llvm_membertype_v; - { - llvm_membertype_v.reserve(2); - - /* frame[0] = pointer to next frame */ - llvm_membertype_v.push_back(frameptr_llvm_type); - /* frame[1] = unwind function */ - llvm_membertype_v.push_back(unwind_llvm_type); - } - - frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, - unwind_llvm_type /*frame[1]*/); - - return frame_llvm_type; - } /*require_baseframe_llvm_type*/ - - llvm::PointerType * - require_baseframe_ptr_llvm_type(xo::ref::brw llvm_cx) - { - llvm::StructType * baseframe_llvm_type - = require_baseframe_llvm_type(llvm_cx); - - return llvm::PointerType::getUnqual(baseframe_llvm_type); - } - - /** need a supporting type for stack frame - * - so we can handle variables with non-trivial dtors - * (e.g. smart pointers) - * - so we can implement nested lexical scoping - * - so we can walk stack for exception handling - * - eventually: so we can eventually implement trampoline - * - * frame representation: - * - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) - * +-------| - * arg[i] [2+i] | ... | - * +-------+ - * - * invoke frame.unwind_fn to dispose of a frame - * - ctl=0 dtor. deal with smart pointers etc. - * - ctl=1 copy. lift frame into heap for lambda capture - * - * every frame is a subtype of _baseframe (ofc llvm doesn't know this). - * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above - * - * editor bait: activation_record_to_llvm_type - **/ - llvm::StructType * - frame_to_llvm_type(xo::ref::brw llvm_cx, - ref::brw lambda, - llvm::Function * lambda_llvm_fn) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - /* frame type doesn't need a name */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - - /* _baseframe */ - llvm::StructType * baseframe_llvm_type - = require_baseframe_llvm_type(llvm_cx); - - /* _baseframe*: pointer to a generic stack frame */ - llvm::PointerType * baseframeptr_llvm_type - = llvm::PointerType::getUnqual(baseframe_llvm_type); - - /* _baseframe* (*)(_baseframe*, i32) */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - baseframeptr_llvm_type); - - /* llvm_argtype_v: - * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame - * - llvm_argtype_v[1] = llvm::Type* for unwind_fn - * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) - */ - std::vector llvm_argtype_v; - { - llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); - - /* frame pointer */ - llvm_argtype_v.push_back(baseframeptr_llvm_type); - /* unwind function */ - llvm_argtype_v.push_back(unwind_llvm_type); - - int i_arg = 0; - for (auto & arg : lambda_llvm_fn->args()) { - log && log(xtag("i_arg", i_arg), - xtag("param", std::string(arg.getName()))); - - llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, - lambda->fn_arg(i_arg))); - - ++i_arg; - } - } - - frame_llvm_type->setBody(llvm_argtype_v); - - return frame_llvm_type; - } /*frame_to_llvm_type*/ - } /*namespace*/ - - llvm::Function * - MachPipeline::codegen_lambda_defn(ref::brw lambda, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - global_env_[lambda->name()] = lambda.get(); - - /* do we already know a function with this name? */ - auto * llvm_fn = llvm_module_->getFunction(lambda->name()); - - if (!llvm_fn) { - /** function with this name not declared? **/ - cerr << "MachPipeline::codegen_lambda: function f not declared" - << xtag("f", lambda->name()) - << endl; - - return nullptr; - } - - /* generate function body -- code to execute later if/when function is called */ - - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - - ir_builder.SetInsertPoint(block); - - /* create stack frame */ - llvm::StructType * frame_llvm_type - = frame_to_llvm_type(llvm_cx_, - lambda, - llvm_fn); - - llvm::AllocaInst * frame_alloca - = create_entry_frame_alloca(llvm_fn, - frame_llvm_type); - - if (!frame_alloca) - return nullptr; - - /** Actual parameters will need their own activation record. - * Track its shape here. - * - * Local variables will be formal parameters to a nested lambda; - **/ - this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); - - { - log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); - - int i_slot = 0; - int n_slot = 2 + lambda->n_arg(); - - auto arg_ix = llvm_fn->arg_begin(); - - for (i_slot = 0; i_slot < n_slot; ++i_slot) { - // TODO: move the frame-slot computation into helper function - - /* argument i_slot-2 */ - llvm::Value * frame_slot_ptr = nullptr; - { - /* note: we have to create instructions here because - * the llvm IR is invariant w.r.t. data layout. - * But we can't compute byte offsets until later - * when data layout is revealed. - */ - - llvm::Value * i32_zero - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, 0)); - llvm::Value * i32_slot - = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, i_slot)); - - std::array index_v = { - {i32_zero /*deref frame pointer*/, - i32_slot /*field# relative to frame pointer*/}}; - - /* location in stack frame of arg #i */ - frame_slot_ptr - = ir_builder.CreateInBoundsGEP(frame_llvm_type, - frame_alloca, - index_v); - } - - if (i_slot == 0) { - /* frame pointer */ - assert(false); - } else if (i_slot == 1) { - /* unwind function */ - assert(false); - } else { - int i_arg = i_slot - 2; - - std::string arg_name = std::string(lambda->argv().at(i_arg)->name()); - - /* store param on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&(*arg_ix), - frame_slot_ptr /*destination*/); - - ++arg_ix; - } - } - } - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); - - if (retval) { - /* completes the function.. */ - ir_builder.CreateRet(retval); - - /* validate! always validate! */ - llvm::verifyFunction(*llvm_fn); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-before-opt", buf)); - } - - /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-after-opt", buf)); - } - } else { - /* oops, something went wrong */ - llvm_fn->eraseFromParent(); - - llvm_fn = nullptr; - } - - this->env_stack_.pop(); - - log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); - - return llvm_fn; - } /*codegen_lambda_defn*/ - - /* frame pointer */ - llvm::Value * - MachPipeline::codegen_global_frameptr(llvm::IRBuilder<> & ir_builder) - { - /* only want to jit this once */ - - /* though really: this would be once per thread */ - - llvm::Type * baseframe_ptr_llvm_type - = require_baseframe_ptr_llvm_type(llvm_cx_); - - /** nullptr - initial value for global frame pointer **/ - llvm::Constant * nullptr_llvm_value - = llvm::ConstantPointerNull::get(baseframe_ptr_llvm_type); - - llvm::Value * fp_llvm_value - = llvm::GlobalVariable(baseframe_ptr_llvm_type, - false /*!isConstant*/ - llvm::GlobalValues::LinkOnceOdrLinkage, - nullptr_llvm_value /*Initializer*/, - "frame_pointer"); - - } /*codegen_global_frameptr*/ - - llvm::Value * - MachPipeline::codegen_variable(ref::brw var, - llvm::IRBuilder<> & ir_builder) - { - if (env_stack_.empty()) { - cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" - << xtag("x", var->name()) - << endl; - - return nullptr; - } - - std::int32_t i_slot = env_stack_.top().lookup_var(var->name()); - //llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); - - if (!alloca) - return nullptr; - - /* code to load value from stack */ - return ir_builder.CreateLoad(alloca->getAllocatedType(), - alloca, - var->name().c_str()); - } /*codegen_variable*/ - - llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) - { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); - - /** need test result in a variable **/ - llvm::Value * test_with_cmp_ir - = ir_builder.CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); - - llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); - - /* when_true_bb, when_false_bb, merge_bb: - * initially-empty basic-blocks for {when_true, when_false, merged} codegen - */ - - /* when_true branch inserted at (current) end of function */ - llvm::BasicBlock * when_true_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_true", - parent_fn); - llvm::BasicBlock * when_false_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_false"); - - llvm::BasicBlock * merge_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "merge"); - - /* IR to direct control flow to one of {when_true_bb, when_false_bb}, - * depending on result of test_with_cmp_ir - */ - ir_builder.CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); - - /* populate when_true_bb */ - ir_builder.SetInsertPoint(when_true_bb); - - llvm::Value * when_true_ir = this->codegen(expr->when_true(), - ir_builder); - - if (!when_true_ir) - return nullptr; - - /* at end of when-true sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = ir_builder.GetInsertBlock(); - - /* populate when_false_bb */ - parent_fn->insert(parent_fn->end(), when_false_bb); - ir_builder.SetInsertPoint(when_false_bb); - - llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); - if (!when_false_ir) - return nullptr; - - /* at end of when-false sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = ir_builder.GetInsertBlock(); - - /* merged suffix sequence */ - parent_fn->insert(parent_fn->end(), merge_bb); - ir_builder.SetInsertPoint(merge_bb); - - /** TODO: switch to getInt1Ty here **/ - llvm::PHINode * phi_node - = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); - phi_node->addIncoming(when_true_ir, when_true_bb); - phi_node->addIncoming(when_false_ir, when_false_bb); - - return phi_node; - } /*codegen_ifexpr*/ - - llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) - { - switch(expr->extype()) { - case exprtype::constant: - return this->codegen_constant(ConstantInterface::from(expr)); - case exprtype::primitive: - return this->codegen_primitive(PrimitiveInterface::from(expr)); - case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); - case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); - case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); - case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); - case exprtype::invalid: - case exprtype::n_expr: - return nullptr; - break; - } - - cerr << "MachPipeline::codegen: error: no handler for expression of type T" - << xtag("T", expr->extype()) - << endl; - - return nullptr; - } /*codegen*/ - - llvm::Value * - MachPipeline::codegen_toplevel(ref::brw expr) - { - /* - Pass 1. - * get set of lambdas. - * Generate decls for all. - * - * TODO: for lexical scoping (not implemented yet) - * will need traversal that maintains stack - * of ancestor lambdas, or at least their - * activation records. May want to generalize - * activation_record so we can track the set of variables - * before generating AllocaInst's. - * - * - Pass 2. - * Generate code for lambdas. - * - * - Pass 3. - * If toplevel expressions isn't a lambda - * (? won't make sense at present when called from python) - * generate code for it too - */ - - /* Pass 1. */ - auto fn_v = this->find_lambdas(expr); - - for (auto lambda : fn_v) { - this->codegen_lambda_decl(lambda); - } - - /* Pass 2 */ - for (auto lambda : fn_v) { - this->codegen_lambda_defn(lambda, - *(this->llvm_toplevel_ir_builder_.get())); - } - - /* Pass 3 */ - if (expr->extype() == exprtype::lambda) { - /* code already generated in pass 2; - * look it up - */ - - return llvm_module_->getFunction(Lambda::from(expr)->name()); - } else { - /* toplevel expression isn't a lambda, - * so code for it hasn't been generated. - * Do that now - */ - return this->codegen(expr, - *(this->llvm_toplevel_ir_builder_.get())); - } - } /*codegen_toplevel*/ - - void - MachPipeline::dump_current_module() - { - /* dump module contents to console */ - - llvm_module_->dump(); - } - - void - MachPipeline::machgen_current_module() - { - static llvm::ExitOnError llvm_exit_on_err; - - auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); - - /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create - * - * Note that @ref ir_pipeline_ holds reference, which is invalidated here - */ - auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), - std::move(llvm_cx_->llvm_cx())); - - /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ - this->llvm_cx_ = nullptr; - - llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); - - this->recreate_llvm_ir_pipeline(); - } /*machgen_current_module*/ - - std::string_view - MachPipeline::mangle(const std::string & sym) const - { - return this->jit_->mangle(sym); - } /*mangle*/ - - llvm::Expected - MachPipeline::lookup_symbol(const std::string & sym) - { - /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym_expected = this->jit_->lookup(sym); - - if (llvm_sym_expected) { - auto llvm_addr = llvm_sym_expected.get().getAddress(); - - return llvm_addr; - } else { - return llvm_sym_expected.takeError(); - } - } /*lookup_symbol*/ - - void - MachPipeline::display(std::ostream & os) const { - os << ""; - } - - std::string - MachPipeline::display_string() const { - return tostr(*this); - } - } /*namespace jit*/ -} /*namespace xo*/ - -/* end MachPipeline.cpp */ diff --git a/.xo-jit/src/jit/MachPipeline.orig.cpp b/.xo-jit/src/jit/MachPipeline.orig.cpp deleted file mode 100644 index 786ca581..00000000 --- a/.xo-jit/src/jit/MachPipeline.orig.cpp +++ /dev/null @@ -1,1064 +0,0 @@ -/* @file MachPipeline.cpp */ - -#include "MachPipeline.hpp" -#include - -namespace xo { - using xo::scm::exprtype; - using xo::scm::Expression; - using xo::scm::ConstantInterface; - //using xo::scm::FunctionInterface; - using xo::scm::PrimitiveInterface; - using xo::scm::Lambda; - using xo::scm::Variable; - using xo::scm::Apply; - using xo::scm::IfExpr; - using xo::scm::llvmintrinsic; - using xo::reflect::Reflect; - using xo::reflect::StructMember; - using xo::reflect::TypeDescr; - using llvm::orc::ExecutionSession; - using llvm::DataLayout; - using std::cerr; - using std::endl; - - namespace jit { - void - MachPipeline::init_once() { - static bool s_init_once = false; - - if (!s_init_once) { - s_init_once = true; - - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - } - } /*init_once*/ - - /* tracking KaleidoscopeJIT::Create() here.. - * - * Verified: - * + 'execution session' as per Kaleidoscope JIT, - * can instantiate from python - * + 'jit object layer' - * (realtime dynamic library object linking layer) - * + 'jit_compile_layer' - * + 'jit_our_dynamic_lib' - */ - llvm::Expected> - MachPipeline::make_aux() - { - MachPipeline::init_once(); - - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); - - return std::unique_ptr(new MachPipeline(std::move(jit) - )); - } /*make*/ - - xo::ref::rp - MachPipeline::make() { - static llvm::ExitOnError llvm_exit_on_err; - - std::unique_ptr jit = llvm_exit_on_err(make_aux()); - - return jit.release(); - } /*make*/ - - MachPipeline::MachPipeline(std::unique_ptr jit) - : jit_{std::move(jit)} - { - this->recreate_llvm_ir_pipeline(); - } - - void - MachPipeline::recreate_llvm_ir_pipeline() - { - //llvm_cx_ = std::make_unique(); - llvm_cx_ = LlvmContext::make(); - llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); - - llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(this->jit_->data_layout()); - - if (!llvm_cx_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); - } - if (!llvm_toplevel_ir_builder_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); - } - if (!llvm_module_.get()) { - throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); - } - - ir_pipeline_ = new IrPipeline(llvm_cx_); - } /*recreate_llvm_ir_pipeline*/ - - const DataLayout & - MachPipeline::data_layout() const { - return this->jit_->data_layout(); - } - - const ExecutionSession * - MachPipeline::xsession() const { - return this->jit_->xsession(); - } - - /** identifies target host/architecture for machine code. - * e.g. "x86_64-unknown-linux-gnu" - **/ - const std::string & - MachPipeline::target_triple() const { - // although this getter is defined, seems to be empty in practice - //return llvm_module_->getTargetTriple(); - - return this->jit_->target_triple(); - } - - std::vector - MachPipeline::get_function_name_v() { - std::vector retval; - for (const auto & fn_name : *llvm_module_) - retval.push_back(fn_name.getName().str()); - - return retval; - } /*get_function_names*/ - - void - MachPipeline::dump_execution_session() { - this->jit_->dump_execution_session(); - } - - llvm::Value * - MachPipeline::codegen_constant(ref::brw expr) - { - TypeDescr td = expr->value_td(); - - if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } else if (Reflect::is_native(td)) { - return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APSInt(*(expr->value_tp().recover_native()))); - } - - return nullptr; - } /*codegen_constant*/ - - namespace { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - int n_fn_arg = fn_td->n_fn_arg(); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } - - llvm::PointerType * - function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - struct_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) - { - assert(pointer_td->is_pointer()); - - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* in this context, we're looking for a representation for a value, - * i.e. something that can be stored in a variable - */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } - } - - llvm::Type * - MachPipeline::codegen_type(TypeDescr td) { - return td_to_llvm_type(llvm_cx_.borrow(), td); - } - - llvm::Function * - MachPipeline::codegen_primitive(ref::brw expr) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - /** note: documentation (such as it is) for llvm::Function here: - * - * https://llvm.org/doxygenL/classllvm_1_1Function.html - **/ - - auto * fn = llvm_module_->getFunction(expr->name()); - - if (fn) { - /** function with this name already known to llvm module; - * use that definition - * - * TODO: verify that signatures match! - **/ - return fn; - } - - /** establish prototype for this function **/ - - TypeDescr fn_td = expr->valuetype(); - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); - - if (!llvm_fn_type) - return nullptr; - - fn = llvm::Function::Create(llvm_fn_type, - llvm::Function::ExternalLinkage, - expr->name(), - llvm_module_.get()); - -#ifdef NOT_USING - // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here - { - int i_arg = 0; - for (auto & arg : fn->args()) { - std::stringstream ss; - ss << "x_" << i_arg; - - arg.setName(ss.str()); - ++i_arg; - } - } -#endif - - if (expr->explicit_symbol_def()) { - static llvm::ExitOnError llvm_exit_on_err; - - auto name = expr->name(); - auto fn_addr = expr->function_address(); - - log && log(xtag("sym", name), - xtag("mangled_sym", this->jit_->mangle(name))); - - llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); - -#ifdef NOT_USING - if (!llvm_result) { - cerr << "MachPipeline::codegen_primitive" - << ": intern_symbol failed" - << xtag("name", expr->name()) - << xtag("addr", expr->function_address()) - << endl; - - return nullptr; - } -#endif - } else { - log && log("not requiring absolute address", xtag("sym", expr->name())); - } - -#ifdef OBSOLETE - log && log("returning llvm function"); -#endif - - return fn; - } /*codegen_primitive*/ - - llvm::Value * - MachPipeline::codegen_apply(ref::brw apply, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("apply", apply)); - - // see here: - // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api - - using std::cerr; - using std::endl; - - /* IR for value in function position. - * Although it will generate a function (or pointer-to-function), - * it need not have inherited type llvm::Function. - */ - llvm::Value * llvm_fnval = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); - { - /* special treatement for primitive in apply position: - * allows substituting LLVM intrinsic - */ - if (apply->fn()->extype() == exprtype::primitive) { - auto pm = PrimitiveInterface::from(apply->fn()); - - if (pm) { - llvm_fnval = this->codegen_primitive(pm); - /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ - intrinsic = pm->intrinsic(); - } - } else { - llvm_fnval = this->codegen(apply->fn(), ir_builder); - - /* we don't need any special checking here. - * already know (from xo-level checking) that pointer has the right type. - * - * Specifically, xo::scm::Apply::make() requires the expression in function position - * have suitable function type. - * - * Now: we have an llvm::Value (fn_value) representing the pointer. - * However it's not an llvm::Function instance, and we can't get one. - * - * (Older LLVM versions allowed getting the element type from a pointer, - * for some reasons that's deprecated at least in 18.1.5) - */ - } - } - - if (!llvm_fnval) { - return nullptr; - } - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif - - /* checks here will be redundant */ - -#ifdef REDUNDANT_TYPECHECK - if (apply->argv().size() != ast_fn_td->n_fn_arg()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - //<< xtag("f", ast_fn->name()) - << xtag("n1", ast_fn_td->n_fn_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" - << xtag("F", apply->fn()) - << xtag("I", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", ast_fn_td->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } -#endif - - std::vector args; - args.reserve(apply->argv().size()); - - int i = 0; - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); - - if (log) { - /* TODO: print helper for llvm::Value* */ - std::string llvm_value_str; - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); - - log(xtag("i_arg", i), - xtag("arg", llvm_value_str)); - } - - args.push_back(arg); - ++i; - } - - /* if we have an intrinsic hint, - * then instead of invoking a function, - * we use some native machine instruction instead. - */ - switch(intrinsic) { - case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); - case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); - case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); - case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); - case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); - case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); - case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); - case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); - case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); - case llvmintrinsic::invalid: - case llvmintrinsic::fp_sqrt: - case llvmintrinsic::fp_pow: - case llvmintrinsic::fp_sin: - case llvmintrinsic::fp_cos: - case llvmintrinsic::fp_tan: - case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ - break; - } - - /* At least as of 18.1.5, LLVM needs us to supply function type - * when making a function call. In particular it doesn't remember - * the function type with each function pointer - */ - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); - - return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, - args, - "calltmp"); - - } /*codegen_apply*/ - - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return nullptr; - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_block_alloca*/ - - - std::vector> - MachPipeline::find_lambdas(ref::brw expr) const - { - std::vector> retval_v; - - expr->visit_preorder( - [&retval_v](ref::brw x) - { - if (x->extype() == exprtype::lambda) { - retval_v.push_back(Lambda::from(x)); - } - }); - - return retval_v; - } /*find_lambdas*/ - - llvm::Function * - MachPipeline::codegen_lambda_decl(ref::brw lambda) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - global_env_[lambda->name()] = lambda.get(); - - /* do we already know a function with this name? */ - auto * fn = llvm_module_->getFunction(lambda->name()); - - if (fn) { - return fn; - } - - /* establish prototype for this function */ - -#ifdef OBSOLETE - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_retval()); - - std::vector arg_type_v(lambda->n_arg()); - - for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_arg(i)); - } -#endif - - llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); - - /* create (initially empty) function */ - fn = llvm::Function::Create(llvm_fn_type, - llvm::Function::ExternalLinkage, - lambda->name(), - llvm_module_.get()); - /* also capture argument names */ - { - int i = 0; - for (auto & arg : fn->args()) { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i))); - - arg.setName(lambda->argv().at(i)->name()); - ++i; - } - } - - return fn; - } /*codegen_lambda_decl*/ - - llvm::Function * - MachPipeline::codegen_lambda_defn(ref::brw lambda, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda->name())); - - global_env_[lambda->name()] = lambda.get(); - - /* do we already know a function with this name? */ - auto * llvm_fn = llvm_module_->getFunction(lambda->name()); - - if (!llvm_fn) { - /** function with this name not declared? **/ - cerr << "MachPipeline::codegen_lambda: function f not declared" - << xtag("f", lambda->name()) - << endl; - - return nullptr; - } - - - /* generate function body */ - - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - - ir_builder.SetInsertPoint(block); - - /** Actual parameters will need their own activation record. - * Track its shape here. - **/ - this->env_stack_.push(activation_record()); - - { - log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); - - int i = 0; - for (auto & arg : llvm_fn->args()) { - log && log("nested environment", - xtag("i", i), - xtag("param", std::string(arg.getName()))); - - std::string arg_name = std::string(arg.getName()); - - /* stack location for arg[i] */ - llvm::AllocaInst * alloca - = create_entry_block_alloca(llvm_fn, - arg_name, - lambda->fn_arg(i)); - - if (!alloca) { - this->env_stack_.pop(); - return nullptr; - } - - /* store on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&arg, alloca); - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - env_stack_.top().alloc_var(arg_name, alloca); - ++i; - } - } - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); - - if (retval) { - /* completes the function.. */ - ir_builder.CreateRet(retval); - - /* validate! always validate! */ - llvm::verifyFunction(*llvm_fn); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-before-opt", buf)); - } - - /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); - - if (log) { - std::string buf; - llvm::raw_string_ostream ss(buf); - llvm_fn->print(ss); - - log(xtag("IR-after-opt", buf)); - } - } else { - /* oops, something went wrong */ - llvm_fn->eraseFromParent(); - - llvm_fn = nullptr; - } - - this->env_stack_.pop(); - - log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); - - return llvm_fn; - } /*codegen_lambda_defn*/ - - llvm::Value * - MachPipeline::codegen_variable(ref::brw var, - llvm::IRBuilder<> & ir_builder) - { - if (env_stack_.empty()) { - cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" - << xtag("x", var->name()) - << endl; - - return nullptr; - } - - llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); - - if (!alloca) - return nullptr; - - /* code to load value from stack */ - return ir_builder.CreateLoad(alloca->getAllocatedType(), - alloca, - var->name().c_str()); - } /*codegen_variable*/ - - llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) - { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); - - /** need test result in a variable **/ - llvm::Value * test_with_cmp_ir - = ir_builder.CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); - - llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); - - /* when_true_bb, when_false_bb, merge_bb: - * initially-empty basic-blocks for {when_true, when_false, merged} codegen - */ - - /* when_true branch inserted at (current) end of function */ - llvm::BasicBlock * when_true_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_true", - parent_fn); - llvm::BasicBlock * when_false_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "when_false"); - - llvm::BasicBlock * merge_bb - = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), - "merge"); - - /* IR to direct control flow to one of {when_true_bb, when_false_bb}, - * depending on result of test_with_cmp_ir - */ - ir_builder.CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); - - /* populate when_true_bb */ - ir_builder.SetInsertPoint(when_true_bb); - - llvm::Value * when_true_ir = this->codegen(expr->when_true(), - ir_builder); - - if (!when_true_ir) - return nullptr; - - /* at end of when-true sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = ir_builder.GetInsertBlock(); - - /* populate when_false_bb */ - parent_fn->insert(parent_fn->end(), when_false_bb); - ir_builder.SetInsertPoint(when_false_bb); - - llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); - if (!when_false_ir) - return nullptr; - - /* at end of when-false sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); - /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = ir_builder.GetInsertBlock(); - - /* merged suffix sequence */ - parent_fn->insert(parent_fn->end(), merge_bb); - ir_builder.SetInsertPoint(merge_bb); - - /** TODO: switch to getInt1Ty here **/ - llvm::PHINode * phi_node - = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); - phi_node->addIncoming(when_true_ir, when_true_bb); - phi_node->addIncoming(when_false_ir, when_false_bb); - - return phi_node; - } /*codegen_ifexpr*/ - - llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) - { - switch(expr->extype()) { - case exprtype::constant: - return this->codegen_constant(ConstantInterface::from(expr)); - case exprtype::primitive: - return this->codegen_primitive(PrimitiveInterface::from(expr)); - case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); - case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); - case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); - case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); - case exprtype::invalid: - case exprtype::n_expr: - return nullptr; - break; - } - - cerr << "MachPipeline::codegen: error: no handler for expression of type T" - << xtag("T", expr->extype()) - << endl; - - return nullptr; - } /*codegen*/ - - llvm::Value * - MachPipeline::codegen_toplevel(ref::brw expr) - { - /* - Pass 1. - * get set of lambdas. - * Generate decls for all. - * - * TODO: for lexical scoping (not implemented yet) - * will need traversal that maintains stack - * of ancestor lambdas, or at least their - * activation records. May want to generalize - * activation_record so we can track the set of variables - * before generating AllocaInst's. - * - * - Pass 2. - * Generate code for lambdas. - * - * - Pass 3. - * If toplevel expressions isn't a lambda - * (? won't make sense at present when called from python) - * generate code for it too - */ - - /* Pass 1. */ - auto fn_v = this->find_lambdas(expr); - - for (auto lambda : fn_v) { - this->codegen_lambda_decl(lambda); - } - - /* Pass 2 */ - for (auto lambda : fn_v) { - this->codegen_lambda_defn(lambda, - *(this->llvm_toplevel_ir_builder_.get())); - } - - /* Pass 3 */ - if (expr->extype() == exprtype::lambda) { - /* code already generated in pass 2; - * look it up - */ - - return llvm_module_->getFunction(Lambda::from(expr)->name()); - } else { - /* toplevel expression isn't a lambda, - * so code for it hasn't been generated. - * Do that now - */ - return this->codegen(expr, - *(this->llvm_toplevel_ir_builder_.get())); - } - } /*codegen_toplevel*/ - - void - MachPipeline::dump_current_module() - { - /* dump module contents to console */ - - llvm_module_->dump(); - } - - void - MachPipeline::machgen_current_module() - { - static llvm::ExitOnError llvm_exit_on_err; - - auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); - - /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create - * - * Note that @ref ir_pipeline_ holds reference, which is invalidated here - */ - auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), - std::move(llvm_cx_->llvm_cx())); - - /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ - this->llvm_cx_ = nullptr; - - llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); - - this->recreate_llvm_ir_pipeline(); - } /*machgen_current_module*/ - - std::string_view - MachPipeline::mangle(const std::string & sym) const - { - return this->jit_->mangle(sym); - } /*mangle*/ - - llvm::Expected - MachPipeline::lookup_symbol(const std::string & sym) - { - /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym_expected = this->jit_->lookup(sym); - - if (llvm_sym_expected) { - auto llvm_addr = llvm_sym_expected.get().getAddress(); - - return llvm_addr; - } else { - return llvm_sym_expected.takeError(); - } - } /*lookup_symbol*/ - - void - MachPipeline::display(std::ostream & os) const { - os << ""; - } - - std::string - MachPipeline::display_string() const { - return tostr(*this); - } - } /*namespace jit*/ -} /*namespace xo*/ - -/* end MachPipeline.cpp */ diff --git a/.xo-jit/src/jit/activation_record.cpp b/.xo-jit/src/jit/activation_record.cpp deleted file mode 100644 index 32b711bc..00000000 --- a/.xo-jit/src/jit/activation_record.cpp +++ /dev/null @@ -1,435 +0,0 @@ -/* @file activation_record.cpp */ - -#include "activation_record.hpp" -#include "type2llvm.hpp" -#include "xo/indentlog/print/tag.hpp" -#include - -namespace xo { - namespace jit { - using std::cerr; - using std::endl; - - activation_record::activation_record(const rp & lm) - : lambda_{lm}, - binding_v_(lm->n_arg()) - { - /* populate binding_v_ */ - int n_arg = lm->n_arg(); - binding_v_.resize(n_arg); - - /* next slot# to use in explicit activation record */ - int rt_env_slot = 0; - - for (int i_arg = 0; i_arg < n_arg; ++i_arg) { - if (lm->is_captured(lm->i_argname(i_arg))) { - /* local param #i_arg needs a slot in explicit activation record */ - binding_v_[i_arg] = runtime_binding_path::local(rt_env_slot); - ++rt_env_slot; - } else { - binding_v_[i_arg] = runtime_binding_path::stackonly(); - } - } - } /*ctor*/ - - const runtime_binding_detail * - activation_record::lookup_var(const std::string & x) const - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - auto ix = frame_.find(x); - - if (ix == frame_.end()) { - cerr << "activation_record::lookup_var: no binding for variable x" - << xtag("x", x) - << endl; - cerr << "frame:"; - for (const auto & ix : frame_) - cerr << xtag("var", ix.first) << xtag("->", ix.second) << endl; - - return nullptr; - } - - return &(ix->second); - } /*lookup_var*/ - - const runtime_binding_detail * - activation_record::alloc_var(const std::string & x, - const runtime_binding_detail & binding) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - log && log(xtag("var", x), - xtag("binding", binding)); - - if (frame_.find(x) != frame_.end()) { - cerr << "activation_record::alloc_var: variable x already present in frame" - << xtag("x", x) - << endl; - return nullptr; - } - - frame_[x] = binding; - - return &(frame_[x]); - } /*alloc_var*/ - - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - runtime_binding_detail - activation_record::create_entry_block_alloca(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & fn_ir_builder, - int i_arg, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - log && log(xtag("llvm_fn", (void*)llvm_fn), - xtag("i_arg", i_arg), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return runtime_binding_detail{}; /*sentinel*/ - - llvm::AllocaInst * stackaddr = fn_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - - log && log(xtag("alloca", (void*)stackaddr), - xtag("align", stackaddr->getAlign().value()) - //xtag("size", retval->getAllocationSize(data_layout).value()) - ); - - return {i_arg, stackaddr, llvm_var_type}; - } /*create_entry_block_alloca*/ - -#ifdef NOT_USING - llvm::AllocaInst * - activation_record::create_runtime_localenv_alloca(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & ir_builder) - - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn)); - - llvm::StructType * localenv_llvm_type - = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); - - if (!localenv_llvm_type) - return nullptr; - - llvm::AllocaInst * retval = ir_builder.CreateAlloca(localenv_llvm_type, - nullptr /*ArraySize*/, - "_localenv"); - - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()) - //xtag("size", retval->getAllocationSize(data_layout).value()) - ); - - return retval; - } /*create_runtime_localenv_alloca*/ -#endif - - llvm::Value * - activation_record::runtime_localenv_slot_addr(bp llvm_cx, - llvm::StructType * localenv_llvm_type, - llvm::AllocaInst * localenv_alloca, - int i_slot, -#ifdef NOT_HERE - llvm::Value * llvm_slot_value, -#endif - llvm::IRBuilder<> & tmp_ir_builder) - { - llvm::Value * i0_slot - = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, 0)); - - llvm::Value * i32_slot - = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), - llvm::APInt(32 /*bits*/, - i_slot /*value*/)); - std::array index_v = { - {i0_slot, i32_slot /*environment slot #0*/}}; - - llvm::Value * llvm_localenv_slot_ptr - = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, - localenv_alloca, - index_v); - - return llvm_localenv_slot_ptr; - -#ifdef NOT_HERE - tmp_ir_builder.CreateStore(llvm_value, //llvm_0ptr, - llvm_parent_env_ptr); -#endif - } /*runtime_localenv_slot_addr*/ - - bool - activation_record::bind_locals(bp llvm_cx, - //const llvm::DataLayout & data_layout, - llvm::Function * llvm_fn, - llvm::IRBuilder<> & ir_builder) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("lambda-name", lambda_->name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - /* 1st pass: handle stackonly variables - * - * We presume this must come first, - * for subsequent mem2reg optimization pass to consider - */ - { - int i_arg = 0; - for (auto & arg : llvm_fn->args()) { - if (i_arg == 0) { - /* 1st argument is injected environment pointer. - * we don't need that to be on the stack, - * since not modifiable and not user-referencable. - */ - } else { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("stackonly(i)", binding_v_[i_arg-1].is_stackonly())); - - if (binding_v_[i_arg-1].is_stackonly()) { - /* stack location for arg[i] */ - runtime_binding_detail binding - = create_entry_block_alloca(llvm_cx, - //data_layout, - llvm_fn, - tmp_ir_builder, - i_arg, - arg_name, - lambda_->fn_arg(i_arg-1)); - - if (!binding.llvm_addr_) - return false; - - /* store on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&arg, binding.llvm_addr_); - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - this->alloc_var(arg_name, binding); - } - } - - ++i_arg; - } - } - - /* REMINDER: all functions need to follow the closure pattern, - * to accomodate cases where we don't know until runtime - * what kind of function we are invoking. - * - * This means: - * - always represent function in IR by a closure-shaped object - * - * +-------+ - * | o---------> native function - * +-------+ - * | o---------> runtime localenv - * +-------+ (possibly nullptr) - * - * We hope to optimize away the closures in cases where we know - * their contents at compile time - * - */ - - /* 2nd pass: handle captured formal parameters */ - if (lambda_->needs_closure_flag()) { - llvm::StructType * localenv_llvm_type - = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); -#ifdef NOT_USING - llvm::PointerType * envapiptr_llvm_type - = type2llvm::env_api_llvm_ptr_type(llvm_cx); -#endif - - if (!localenv_llvm_type) - return false; - - /* - * runtime localenv: ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * arg[i] [2+i] . ... . - * . ... . - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * arg[] comprises the subset of lambda arg names arg[j] for which - * lambda->is_captured(arg[j]) is true - */ - llvm::AllocaInst * localenv_alloca - = tmp_ir_builder.CreateAlloca(localenv_llvm_type, - nullptr /*ArraySize*/, - "_localenv"); - - if (!localenv_alloca) - return false; - - /* remember environemnt location. - * Will need this if need to copy-to-stack - */ - this->localenv_alloca_ = localenv_alloca; - - int i_localenv_slot = 0; - - /* store localenv->parent_env */ - { - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - //llvm_0ptr, - tmp_ir_builder); - - if (!slot_addr) - return false; - - ++i_localenv_slot; - - /* null pointer for now */ - /* TODO: get parent environment (from runtime closure created for this function) */ - llvm::Value * llvm_0ptr - = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx)); - - tmp_ir_builder.CreateStore(llvm_0ptr, - slot_addr); - } - - /* store localenv->unwind_fn */ - { - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - //llvm_0ptr, - tmp_ir_builder); - - if (!slot_addr) - return false; - - ++i_localenv_slot; - - /* null function pointer for now */ - /* TODO: construct unwind function */ - llvm::Value * llvm_0ptr - = (llvm::ConstantPointerNull::get - (type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx))); - - tmp_ir_builder.CreateStore(llvm_0ptr, - slot_addr); - } - - int i_arg = 0; - - for (llvm::Argument & arg : llvm_fn->args()) { - if (i_arg == 0) { - /* to remove all doubt, ignore first arg here. - * it's non-captureable environment pointer - */ - } else { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("captured(i)", binding_v_[i_arg-1].is_captured())); - - if (binding_v_[i_arg-1].is_captured()) { - // do something with runtime-local-env for this llvm_fn - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - - TypeDescr arg_td = lambda_->fn_arg(i_arg-1); - - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - tmp_ir_builder); - - if (!slot_addr) - return false; - - ++i_localenv_slot; - - tmp_ir_builder.CreateStore(&arg, slot_addr); - - runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; - - this->alloc_var(arg_name, binding); - } - } - - ++i_arg; - } - } - - return true; - } /*bind_locals*/ - } /*namespace jit*/ -} /*namespace xo*/ - - -/* end activation_record.cpp */ diff --git a/.xo-jit/src/jit/activation_record.new.cpp b/.xo-jit/src/jit/activation_record.new.cpp deleted file mode 100644 index cc1aaae3..00000000 --- a/.xo-jit/src/jit/activation_record.new.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* @file activation_record.cpp */ - -#include "activation_record.hpp" -#include "xo/indentlog/print/tag.hpp" -#include - -namespace xo { - namespace jit { - using std::cerr; - using std::endl; - - int32_t - activation_record::lookup_var(const std::string & x) const { - - auto ix = name2ix_map_.find(x); - - if (ix == name2ix_map_.end()) { - cerr << "activation_record::lookup_var: no binding for variable x" - << xtag("x", x) - << endl; - return -1; - } - - return ix->second; - } /*lookup_var*/ - -#ifdef OBSOLETE - llvm::AllocaInst * - activation_record::alloc_var(const std::string & x, - llvm::AllocaInst * alloca) - { - if (frame_.find(x) != frame_.end()) { - cerr << "activation_record::alloc_var: variable x already present in frame" - << xtag("x", x) - << endl; - return nullptr; - } - - frame_[x] = alloca; - return alloca; - } /*alloc_var*/ -#endif - } /*namespace jit*/ -} /*namespace xo*/ - - -/* end activation_record.cpp */ diff --git a/.xo-jit/src/jit/activation_record.orig.cpp b/.xo-jit/src/jit/activation_record.orig.cpp deleted file mode 100644 index c7f40362..00000000 --- a/.xo-jit/src/jit/activation_record.orig.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* @file activation_record.cpp */ - -#include "activation_record.hpp" -#include "xo/indentlog/print/tag.hpp" -#include - -namespace xo { - namespace jit { - using std::cerr; - using std::endl; - - llvm::AllocaInst * - activation_record::lookup_var(const std::string & x) const { - - auto ix = frame_.find(x); - - if (ix == frame_.end()) { - cerr << "activation_record::lookup_var: no binding for variable x" - << xtag("x", x) - << endl; - return nullptr; - } - - return ix->second; - } /*lookup_var*/ - - llvm::AllocaInst * - activation_record::alloc_var(const std::string & x, - llvm::AllocaInst * alloca) - { - if (frame_.find(x) != frame_.end()) { - cerr << "activation_record::alloc_var: variable x already present in frame" - << xtag("x", x) - << endl; - return nullptr; - } - - frame_[x] = alloca; - return alloca; - } /*alloc_var*/ - } /*namespace jit*/ -} /*namespace xo*/ - - -/* end activation_record.cpp */ diff --git a/.xo-jit/src/jit/intrinsics.cpp b/.xo-jit/src/jit/intrinsics.cpp deleted file mode 100644 index 2abdf7d8..00000000 --- a/.xo-jit/src/jit/intrinsics.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* @file intrinsics.cpp */ - -#include "intrinsics.hpp" - -/* FIXME: don't know how to mangle symbols yet, - * so putting functions invoked from jit into global namespace - */ -extern "C" -int32_t -mul_i32(int32_t x, int32_t y) { - return x * y; -} - -extern "C" -double -mul_f64(double x, double y) { - return x * y; -} - -/* end intrinsics.cpp */ diff --git a/.xo-jit/src/jit/type2llvm.cpp b/.xo-jit/src/jit/type2llvm.cpp deleted file mode 100644 index 6b2a9161..00000000 --- a/.xo-jit/src/jit/type2llvm.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/* @file type2llvm.cpp */ - -#include "type2llvm.hpp" -#include "xo/reflect/Reflect.hpp" -//#include "xo/reflect/struct/StructMember.hpp" - -namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TypeDescr; - using xo::reflect::StructMember; - using std::cerr; - using std::endl; - - namespace jit { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - type2llvm::td_to_llvm_type(xo::bp llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* in this context, we're looking for a representation for a value, - * i.e. something that can be stored in a variable - */ - //return function_td_to_llvm_fnptr_type(llvm_cx, td); - return function_td_to_closureapi_lvtype(llvm_cx, td, ""); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } /*td_to_llvm_type*/ - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - type2llvm::function_td_to_lvtype(xo::bp llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) - { - constexpr bool c_debug_flag = false; - - scope log(XO_DEBUG(c_debug_flag)); - - int n_ast_fn_arg = fn_td->n_fn_arg(); - - if (log) { - log(xtag("fn_td", fn_td->short_name())); - log(xtag("n_ast_fn_arg", n_ast_fn_arg)); - } - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_ast_fn_arg + (wrapper_flag ? 1 : 0)); - - if (wrapper_flag) - llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx)); - - /** check function args are all known **/ - for (int i = 0; i < n_ast_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - if (log) { - log(xtag("arg_td", arg_td->short_name())); - log(xtag("llvm_argtype", "...")); - llvm_argtype->dump(); - log("...done"); - } - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td); - - if (log) { - log(xtag("retval_td", retval_td->short_name())); - log(xtag("llvm_retval", "...")); - llvm_retval->dump(); - log("...done"); - } - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } /*function_td_to_llvm_type*/ - - llvm::PointerType * - type2llvm::function_td_to_llvm_fnptr_type(xo::bp llvm_cx, - TypeDescr /*fn_td*/, - bool /*wrapper_flag*/) - { -#ifdef OBSOLETE - auto * llvm_fn_type = function_td_to_lvtype(llvm_cx, fn_td, wrapper_flag); -#endif - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); -#ifdef OBSOLETE - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); -#endif - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - type2llvm::struct_td_to_llvm_type(xo::bp llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(type2llvm::td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - type2llvm::pointer_td_to_llvm_type(xo::bp llvm_cx - , TypeDescr pointer_td - ) - { - (void)pointer_td; - - assert(pointer_td->is_pointer()); - -#ifdef OBSOLETE - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = type2llvm::td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); -#endif - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::PointerType * - type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::bp llvm_cx, - llvm::PointerType * /*envptr_llvm_type*/) - { -#ifdef OBSOLETE - if (!envptr_llvm_type) - envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(2); - - /* 1st arg is _env_api pointer */ - llvm_argtype_v.push_back(envptr_llvm_type); - - /* 2nd arg is i32 */ - llvm_argtype_v.push_back(llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); - - /* return value is _env_api pointer */ - llvm::Type * retval_llvm_type = envptr_llvm_type; - - /* _env_api* (_env_api*, i32) */ - auto * unwind_llvm_type - = llvm::FunctionType::get(retval_llvm_type, - llvm_argtype_v, - false /*!varargs*/); - - /* _env_api* (*)(_env_api*, i32) */ - auto * unwind_llvm_fnptr_type - = llvm::PointerType::getUnqual(unwind_llvm_type); -#endif - auto * unwind_llvm_fnptr_type - = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); - - return unwind_llvm_fnptr_type; - } /*require_localenv_unwind_llvm_fnptr_type*/ - - llvm::StructType * - type2llvm::env_api_llvm_type(xo::bp llvm_cx) - { - /* _env_api: base type for a local environment */ - llvm::StructType * env_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_env_api"); - -#ifdef OBSOLETE - /* _env_api[0]: pointer to a local environment */ - llvm::PointerType * envptr_llvm_type - = llvm::PointerType::getUnqual(env_llvm_type); -#endif - llvm::PointerType * envptr_llvm_type - = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); - - /* _env_api[1]: unwwind/copy function */ - llvm::PointerType * unwind_llvm_fnptr_type - = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, - envptr_llvm_type); - - /* now supply _env_api members */ - env_llvm_type->setBody(envptr_llvm_type /*_env_api[0]*/, - unwind_llvm_fnptr_type /*_env_api[1]*/); - - return env_llvm_type; - } /*env_api_llvm_type*/ - - llvm::PointerType * - type2llvm::env_api_llvm_ptr_type(xo::bp llvm_cx) - { -#ifdef OBSOLETE - llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); - return llvm::PointerType::getUnqual(env_llvm_type); -#endif - return llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); - } /*env_api_llvm_ptr_type*/ - - llvm::StructType * - type2llvm::create_closureapi_lvtype(xo::bp llvm_cx, - xo::bp fn) - { - constexpr const char * c_prefix = "c."; - - /* e.g. "c.foo" */ - std::string closure_name = std::string(c_prefix) + fn->name(); - - return function_td_to_closureapi_lvtype(llvm_cx, - fn->valuetype(), - closure_name); - } /*create_closureapi_lvtype*/ - - llvm::StructType * - type2llvm::function_td_to_closureapi_lvtype(xo::bp llvm_cx, - TypeDescr fn_td, - const std::string & hint_name) - { - constexpr bool c_debug_flag = false; - - scope log(XO_DEBUG(c_debug_flag)); - - /* would be precisely correct to use create_localenv_llvm_type() - * here. However judged not sufficiently helpful. - * Would still - * need environment cast whenever closure in apply position is - * not known at compile time. - */ - llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, - fn_td, - true /*wrapper_flag*/); - if (log) { - log(xtag("fn_lvtype", "...")); - fn_lvtype->dump(); - log("...done"); - } - - llvm::PointerType * envptr_lvtype = env_api_llvm_ptr_type(llvm_cx); - - if (log) { - log(xtag("env_lvtype", "...")); - envptr_lvtype->dump(); - log("...done"); - } - - std::vector member_lvtype_v = { fn_lvtype, envptr_lvtype }; - - llvm::StructType * closure_lvtype - = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); - - //closure_lvtype->setBody(member_lvtype_v); - - if (!hint_name.empty()) - closure_lvtype->setName(llvm::StringRef(hint_name)); - - if (log) { - log(xtag("closure_lvtype", "...")); - closure_lvtype->dump(); - log("...done"); - } - - return closure_lvtype; - } /*function_td_to_closureapi_lvtype*/ - - llvm::StructType * - type2llvm::create_localenv_llvm_type(xo::bp llvm_cx, - xo::bp lambda) - { - constexpr const char * c_prefix = "e."; - - llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); - llvm::PointerType * unwind_llvm_fnptr_type - = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type); - - std::vector member_llvm_type_v; - member_llvm_type_v.push_back(parentenvptr_llvm_type); - member_llvm_type_v.push_back(unwind_llvm_fnptr_type); - - for (const auto & var : lambda->argv()) { - if (lambda->is_captured(var->name())) { - /* var is captured -> needs a slot in the localenv_llvm_type belonging to this lambda */ - - member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, - var->valuetype())); - } - } - - /* e.g. "e.foo" */ - std::string env_name = std::string(c_prefix) + lambda->name(); - - llvm::StructType * localenv_lvtype - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - - localenv_lvtype->setName(env_name); - localenv_lvtype->setBody(member_llvm_type_v, false /*!is_packed*/); - - return localenv_lvtype; - } /*create_localenv_llvm_type*/ - - - } /*namespace jit*/ -} /*namespace xo*/ - -/* end type2llvm.cpp */ diff --git a/.xo-jit/utest/CMakeLists.txt b/.xo-jit/utest/CMakeLists.txt deleted file mode 100644 index b0193a74..00000000 --- a/.xo-jit/utest/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -# xo-jit/utest/CMakeLists.txt - -set(SELF_EXE utest.jit) -set(SELF_SRCS - jit_utest_main.cpp - MachPipeline.test.cpp -) - -if (ENABLE_TESTING) - xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) - xo_self_dependency(${SELF_EXE} xo_jit) - xo_dependency(${SELF_EXE} xo_ratio) - xo_headeronly_dependency(${SELF_EXE} xo_reflectutil) - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -endif() - -# end CMakeLists.txt diff --git a/.xo-jit/utest/MachPipeline.test.cpp b/.xo-jit/utest/MachPipeline.test.cpp deleted file mode 100644 index 1466710b..00000000 --- a/.xo-jit/utest/MachPipeline.test.cpp +++ /dev/null @@ -1,392 +0,0 @@ -/* @file MachPipeline.test.cpp */ - -#include "xo/jit/MachPipeline.hpp" -#include "xo/expression/PrimitiveExpr.hpp" -#include "xo/ratio/ratio.hpp" -#include "xo/ratio/ratio_reflect.hpp" -#include "xo/reflect/reflect_struct.hpp" -#include "xo/indentlog/scope.hpp" -#include -#include - -namespace xo { - using xo::jit::MachPipeline; - using xo::scm::make_apply; - using xo::scm::make_var; - using xo::scm::make_primitive; - using xo::scm::llvmintrinsic; - using xo::scm::Expression; - using xo::scm::Lambda; - using xo::scm::exprtype; - using xo::reflect::Reflect; - using xo::reflect::reflect_struct; - using xo::ref::brw; - using std::cerr; - using std::endl; - - namespace ut { - double (*sqrt_double)(double) = &std::sqrt; - - /* abstract syntax tree for a function: - * def root4(x :: double) { sqrt(sqrt(x)); } - */ - rp - root4_ast() { - auto sqrtx = make_primitive("sqrt", - sqrt_double, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(sqrtx, {x_var}); - auto call2 = make_apply(sqrtx, {call1}); - - auto fn_ast = make_lambda("root4", - {x_var}, - call2, - nullptr /*parent_env*/); - - return fn_ast; - } - - /* abstract syntax tree for a function: - * def twice(f :: double->double, x :: double) { f(f(x)); } - */ - rp - root_2x_ast() { - auto root = make_primitive("sqrt", - sqrt_double, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - - /* def twice(f :: double->double, x :: double) { f(f(x)) } */ - auto f_var = make_var("f", Reflect::require()); - auto x_var = make_var("x", Reflect::require()); - auto call1 = make_apply(f_var, {x_var}); /* (f x) */ - auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ - - /* def twice(f :: double->double, x :: double) { f(f(x)); } */ - auto twice = make_lambda("twice", - {f_var, x_var}, - call2, - nullptr /*parent_env*/); - - auto x2_var = make_var("x2", Reflect::require()); - auto call3 = make_apply(twice, {root, x2_var}); - - /* def root4(x2 :: double) { - * def twice(f :: double->double, x :: double) { f(f(x)); }}; - * twice(sqrt, x2) - * } - */ - auto fn_ast = make_lambda("root_2x", - {x2_var}, - call3, - nullptr /*parent_env*/); - - return fn_ast; - } - - struct TestCase { - rp (*make_ast_)(); - /* each pair is (input, output) for function double->double */ - std::vector> call_v_; - }; - - static std::vector - s_testcase_v = { - {&root4_ast, - {std::make_pair(1.0, 1.0), - std::make_pair(16.0, 2.0), - std::make_pair(81.0, 3.0)}}, - {&root_2x_ast, - {std::make_pair(1.0, 1.0), - std::make_pair(16.0, 2.0), - std::make_pair(81.0, 3.0)}} - }; - - /** testcase root_ast tests: - * - nested function call - * - * testcase root_2x_ast relies on: - * - lambda in function position - * - argument with function type - **/ - TEST_CASE("machpipeline.fptr", "[llvm][llvm_fnptr]") { - constexpr bool c_debug_flag = true; - - // can get bits from /dev/random by uncommenting the 2nd line below - //uint64_t seed = xxx; - //rng::Seed seed; - - //auto rng = xo::rng::xoshiro256ss(seed); - - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.fptr")); - //log && log("(A)", xtag("foo", foo)); - - - for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { - /** can't share jit across examples, - * until we fix treatment of primitives: - * now that we build a wrapper for each primitive, - * need some bookkeeping to avoid trying to build - * the same wrapper twice. - **/ - auto jit = MachPipeline::make(); - - TestCase const & testcase = s_testcase_v[i_tc]; - - INFO(tostr(xtag("i_tc", i_tc))); - - auto ast = (*testcase.make_ast_)(); - - REQUIRE(ast.get()); - - log && log(xtag("ast", ast)); - - REQUIRE(ast->extype() == exprtype::lambda); - - brw fn_ast = Lambda::from(ast); - - llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); - - /* TODO: printer for llvm::Value* */ - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "code generation failed" - << xtag("fn_ast", fn_ast) - << endl; - } - - REQUIRE(llvm_ircode); - - jit->machgen_current_module(); - - /** lookup compiled function pointer in jit **/ - auto llvm_addr = jit->lookup_symbol(fn_ast->name()); - - if (!llvm_addr) { - cerr << "ex2: lookup: symbol not found" - << xtag("symbol", fn_ast->name()) - << endl; - } else { - cerr << "ex2: lookup: symbol found" - << xtag("llvm_addr", llvm_addr.get().getValue()) - << xtag("symbol", fn_ast->name()) - << endl; - } - - auto fn_ptr = llvm_addr.get().toPtr(); - - REQUIRE(fn_ptr); - - for (std::size_t j_call = 0, n_call = testcase.call_v_.size(); j_call < n_call; ++j_call) { - double input = testcase.call_v_[j_call].first; - double expected = testcase.call_v_[j_call].second; - - INFO(tostr(xtag("j_call", j_call), xtag("input", input), xtag("expected", expected))); - - auto actual = (*fn_ptr)(input); - - REQUIRE(actual == expected); - } - } - } /*TEST_CASE(machpipeline.fptr)*/ - - TEST_CASE("machpipeline.wrap", "[llvm][llvm_closure]") { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipelin.wrap")); - - auto jit = MachPipeline::make(); - - auto root = make_primitive("sqrt", - sqrt_double, - false /*!explicit_symbol_def*/, - llvmintrinsic::fp_sqrt); - - llvm::Value * llvm_ircode - = jit->codegen_primitive_wrapper(root, *(jit->llvm_current_ir_builder())); - - /* TODO: printer for llvm::Value* */ - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "llvm_ircode for primitive wrapper:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "code generation failed" - << xtag("root", root) - << endl; - } - - REQUIRE(llvm_ircode); - - std::string wrapper_name = std::string("w.") + root->name(); - - jit->machgen_current_module(); - - auto llvm_addr = jit->lookup_symbol(wrapper_name); - - bool llvm_addr_flag = static_cast(llvm_addr); - - if (!llvm_addr_flag) { - cerr << "ex2: lookup: symbol not found" - << xtag("symbol", wrapper_name) - << endl; - } else { - cerr << "ex2: lookup: symbol found" - << xtag("llvm_addr", llvm_addr.get().getValue()) - << xtag("symbol", wrapper_name) - << endl; - } - - REQUIRE(llvm_addr_flag); - - auto fn_ptr = llvm_addr.get().toPtr(); - - REQUIRE(fn_ptr); - - auto actual = (*fn_ptr)(nullptr, 4.0); - - REQUIRE(actual == 2.0); - } - - rp - make_ratio() { - auto make_ratio_impl = make_primitive("make_ratio_impl", - xo::ratio::make_ratio, - true /*explicit_symbol_def*/, - llvmintrinsic::invalid); - REQUIRE(make_ratio_impl.get()); - REQUIRE(make_ratio_impl->explicit_symbol_def()); - - /* jit-prepared library: - * 1. *uses* make_ratio_impl - * 2. *provides* make_ratio (can do jit->lookup_symbol("make_ratio")) - */ - auto n_var = make_var("n", Reflect::require()); - auto d_var = make_var("d", Reflect::require()); - auto call1 = make_apply(make_ratio_impl, {n_var, d_var}); /*make_ratio(n,d)*/ - - auto make_ratio = make_lambda("make_ratio", - {n_var, d_var}, - call1, - nullptr /*parent_env*/); - - return make_ratio; - } - - TEST_CASE("machpipeline.struct", "[llvm][llvm_struct]") { - constexpr bool c_debug_flag = true; - - // can get bits from /dev/random by uncommenting the 2nd line below - //uint64_t seed = xxx; - //rng::Seed seed; - - //auto rng = xo::rng::xoshiro256ss(seed); - - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.struct")); - //log && log("(A)", xtag("foo", foo)); - - auto jit = MachPipeline::make(); - - /* let's reflect xo::ratio::ratio */ - - using ratio_type = xo::ratio::ratio; - - auto struct_td = reflect_struct(); - - REQUIRE(struct_td); - - // ----- build AST ----- - - auto fn_ast = make_ratio(); - - // ----- convert AST -> llvm IR datastructure ----- - - llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); - - /* TODO: printer for llvm::Value* */ - if (llvm_ircode) { - /* note: llvm:errs() is 'raw stderr stream' */ - cerr << "llvm_ircode:" << endl; - llvm_ircode->print(llvm::errs()); - cerr << endl; - } else { - cerr << "code generation failed" - << xtag("fn_ast", fn_ast) - << endl; - } - - REQUIRE(llvm_ircode); - - // ----- inspect alignment ----- - - llvm::StructType * struct_llvm_type - = static_cast(jit->codegen_type(struct_td)); - - auto struct_layout = jit->data_layout().getStructLayout(struct_llvm_type); - - log && log(xtag("struct-size", struct_layout->getSizeInBytes()), - xtag("struct-alignment", struct_layout->getAlignment().value())); - for (int i = 0, n = struct_llvm_type->getNumElements(); i < n; ++i) { - llvm::TypeSize llvm_tz = struct_layout->getElementOffset(i); - auto offset = reinterpret_cast(struct_td->struct_member(i).get_member_tp(nullptr).address()); - - log && log(xtag("i", i), - xtag("name(c++)", struct_td->struct_member(i).member_name()), - xtag("type(c++)", struct_td->struct_member(i).get_member_td()->short_name()), - xtag("offset(c++)", offset), - xtag("offset(llvm)", llvm_tz.getKnownMinValue())); - - REQUIRE(offset == llvm_tz.getKnownMinValue()); - } - - // ----- generate JIT machine code ----- - - jit->machgen_current_module(); - - log && log("execution session after codegen:"); - //log && log(jit->xsession()); // segfaults - jit->dump_execution_session(); - - // ----- verify: lookup symbol - - /** lookup compiled function pointer in jit **/ - auto llvm_addr = jit->lookup_symbol(fn_ast->name()); - - log && log("execution session after lookup attempt:"); - jit->dump_execution_session(); - - if (!llvm_addr) { - cerr << "ex2: lookup: symbol not found" - << xtag("symbol", fn_ast->name()) - << endl; - } else { - cerr << "ex2: lookup: symbol found" - << xtag("llvm_addr", llvm_addr.get().getValue()) - << xtag("symbol", fn_ast->name()) - << endl; - } - - auto fn_ptr = llvm_addr.get().toPtr(); - - REQUIRE(fn_ptr); - - // ---- invoke compiled function ----- - - auto value = (*fn_ptr)(2, 3); - - log && log(xtag("value.num", value.num()), - xtag("value.den", value.den())); - - } /*TEST_CASE(machpipeline.struct)*/ - } /*namespace ut*/ -} /*namespace xo*/ - - -/* end MachPipeline.test.cpp */ diff --git a/.xo-jit/utest/jit_utest_main.cpp b/.xo-jit/utest/jit_utest_main.cpp deleted file mode 100644 index 1bea47b1..00000000 --- a/.xo-jit/utest/jit_utest_main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -/* @file jit_utest_main.cpp */ - -#define CATCH_CONFIG_MAIN -#include - -/* end jit_utest_main.cpp */