Add 'xo-jit/' from commit '855887df71'
git-subtree-dir: xo-jit git-subtree-mainline:35555df976git-subtree-split:855887df71
This commit is contained in:
commit
757dfed99c
49 changed files with 7305 additions and 0 deletions
8
xo-jit/.gitignore
vendored
Normal file
8
xo-jit/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 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
|
||||||
34
xo-jit/CMakeLists.txt
Normal file
34
xo-jit/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# reminder: must come last: docs targets depend on all the other library/utest targets
|
||||||
|
add_subdirectory(docs)
|
||||||
|
|
||||||
|
# end CMakeLists.txt
|
||||||
72
xo-jit/HOWTO
Normal file
72
xo-jit/HOWTO
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
* 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 <cmath>
|
||||||
|
|
||||||
|
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
|
||||||
29
xo-jit/LICENSE
Normal file
29
xo-jit/LICENSE
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, 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.
|
||||||
62
xo-jit/README.md
Normal file
62
xo-jit/README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# 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
|
||||||
|
```
|
||||||
35
xo-jit/cmake/xo-bootstrap-macros.cmake
Executable file
35
xo-jit/cmake/xo-bootstrap-macros.cmake
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# 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 (NOT XO_SUBMODULE_BUILD)
|
||||||
|
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()
|
||||||
6
xo-jit/cmake/xo_jitConfig.cmake.in
Normal file
6
xo-jit/cmake/xo_jitConfig.cmake.in
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
include(CMakeFindDependencyMacro)
|
||||||
|
find_dependency(xo_expression)
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||||
|
check_required_components("@PROJECT_NAME@")
|
||||||
7
xo-jit/docs/CMakeLists.txt
Normal file
7
xo-jit/docs/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# xo-jit/docs/CMakeLists.txt
|
||||||
|
|
||||||
|
xo_doxygen_collect_deps()
|
||||||
|
xo_docdir_doxygen_config()
|
||||||
|
xo_docdir_sphinx_config(
|
||||||
|
index.rst glossary.rst
|
||||||
|
)
|
||||||
70
xo-jit/docs/README
Normal file
70
xo-jit/docs/README
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
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}
|
||||||
39
xo-jit/docs/conf.py
Normal file
39
xo-jit/docs/conf.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# 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'
|
||||||
67
xo-jit/docs/glossary.rst
Normal file
67
xo-jit/docs/glossary.rst
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
.. _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::
|
||||||
21
xo-jit/docs/index.rst
Normal file
21
xo-jit/docs/index.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
.. 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
|
||||||
4
xo-jit/example/CMakeLists.txt
Normal file
4
xo-jit/example/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
add_subdirectory(ex1)
|
||||||
|
add_subdirectory(ex2_jit)
|
||||||
|
add_subdirectory(ex3_fptr)
|
||||||
|
add_subdirectory(ex_kaleidoscope4)
|
||||||
12
xo-jit/example/ex1/CMakeLists.txt
Normal file
12
xo-jit/example/ex1/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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
|
||||||
173
xo-jit/example/ex1/ex1.cpp
Normal file
173
xo-jit/example/ex1/ex1.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
/** @file ex1.cpp **/
|
||||||
|
|
||||||
|
#include "xo/jit/MachPipeline.hpp"
|
||||||
|
#include "xo/expression/Constant.hpp"
|
||||||
|
#include "xo/expression/Primitive.hpp"
|
||||||
|
#include "xo/expression/Apply.hpp"
|
||||||
|
#include "xo/expression/Lambda.hpp"
|
||||||
|
#include "xo/expression/Variable.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
//double foo(double x) { return x; }
|
||||||
|
|
||||||
|
int
|
||||||
|
main() {
|
||||||
|
using xo::scope;
|
||||||
|
using xo::jit::MachPipeline;
|
||||||
|
using xo::ast::make_constant;
|
||||||
|
using xo::ast::make_primitive;
|
||||||
|
using xo::ast::llvmintrinsic;
|
||||||
|
using xo::ast::make_apply;
|
||||||
|
using xo::ast::make_var;
|
||||||
|
using xo::ast::make_lambda;
|
||||||
|
using xo::reflect::Reflect;
|
||||||
|
using xo::xtag;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
//using xo::ast::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<decltype(&foo)>);
|
||||||
|
|
||||||
|
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", &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", &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",
|
||||||
|
::sin,
|
||||||
|
false /*!explicit_symbol_def*/,
|
||||||
|
llvmintrinsic::fp_sin);
|
||||||
|
auto cos = make_primitive("cos",
|
||||||
|
::cos,
|
||||||
|
false /*!explicit_symbol_def*/,
|
||||||
|
llvmintrinsic::fp_cos);
|
||||||
|
|
||||||
|
auto x_var = make_var("x", Reflect::require<double>());
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 **/
|
||||||
12
xo-jit/example/ex2_jit/CMakeLists.txt
Normal file
12
xo-jit/example/ex2_jit/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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
|
||||||
170
xo-jit/example/ex2_jit/ex2_jit.cpp
Normal file
170
xo-jit/example/ex2_jit/ex2_jit.cpp
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
/** @file ex2_jit.cpp **/
|
||||||
|
|
||||||
|
#include "xo/jit/MachPipeline.hpp"
|
||||||
|
#include "xo/jit/activation_record.hpp"
|
||||||
|
#include "xo/expression/Constant.hpp"
|
||||||
|
#include "xo/expression/Primitive.hpp"
|
||||||
|
#include "xo/expression/Apply.hpp"
|
||||||
|
#include "xo/expression/Lambda.hpp"
|
||||||
|
#include "xo/expression/Variable.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
//double foo(double x) { return x; }
|
||||||
|
|
||||||
|
int
|
||||||
|
main() {
|
||||||
|
using xo::scope;
|
||||||
|
using xo::jit::MachPipeline;
|
||||||
|
using xo::jit::activation_record;
|
||||||
|
using xo::ast::make_constant;
|
||||||
|
using xo::ast::make_primitive;
|
||||||
|
using xo::ast::llvmintrinsic;
|
||||||
|
using xo::ast::make_apply;
|
||||||
|
using xo::ast::make_var;
|
||||||
|
using xo::ast::make_lambda;
|
||||||
|
using xo::reflect::Reflect;
|
||||||
|
using xo::xtag;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
//using xo::ast::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<decltype(&foo)>);
|
||||||
|
|
||||||
|
scope log(XO_DEBUG(true));
|
||||||
|
|
||||||
|
/* try spelling everything out */
|
||||||
|
|
||||||
|
{
|
||||||
|
auto sqrt = make_primitive("sqrt",
|
||||||
|
::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<double (*)(double)>());
|
||||||
|
auto x_var = make_var("x", Reflect::require<double>());
|
||||||
|
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);
|
||||||
|
#elif CHOICE == 1
|
||||||
|
#define FUNCTION_SYMBOL "root4"
|
||||||
|
/* def root4(x : double) { sqrt(sqrt(x)) } */
|
||||||
|
|
||||||
|
auto x_var = make_var("x", Reflect::require<double>());
|
||||||
|
auto call1 = make_apply(sqrt, {x_var});
|
||||||
|
auto call2 = make_apply(sqrt, {call1});
|
||||||
|
|
||||||
|
auto lambda = make_lambda("root4",
|
||||||
|
{x_var},
|
||||||
|
call2);
|
||||||
|
#elif CHOICE == 2
|
||||||
|
#define FUNCTION_SYMBOL "twice"
|
||||||
|
auto root = make_primitive("sqrt",
|
||||||
|
::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<int (*)(int)>());
|
||||||
|
auto x_var = make_var("x", Reflect::require<int>());
|
||||||
|
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);
|
||||||
|
#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 **/
|
||||||
12
xo-jit/example/ex3_fptr/CMakeLists.txt
Normal file
12
xo-jit/example/ex3_fptr/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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
|
||||||
45
xo-jit/example/ex3_fptr/ex3_fptr.cpp
Normal file
45
xo-jit/example/ex3_fptr/ex3_fptr.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#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<llvm::Type *> putArgs;
|
||||||
|
putArgs.push_back(builder.getInt8Ty()->getPointerTo());
|
||||||
|
llvm::ArrayRef<llvm::Type *> 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
|
||||||
|
}
|
||||||
6
xo-jit/example/ex_cpp/README
Normal file
6
xo-jit/example/ex_cpp/README
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
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
|
||||||
40
xo-jit/example/ex_cpp/ex_cpp.cpp
Normal file
40
xo-jit/example/ex_cpp/ex_cpp.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
108
xo-jit/example/ex_cpp/ex_cpp.ll
Normal file
108
xo-jit/example/ex_cpp/ex_cpp.ll
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
; 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"}
|
||||||
58
xo-jit/example/ex_cpp/tmp.ll
Normal file
58
xo-jit/example/ex_cpp/tmp.ll
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
12
xo-jit/example/ex_kaleidoscope4/CMakeLists.txt
Normal file
12
xo-jit/example/ex_kaleidoscope4/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# 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
|
||||||
16
xo-jit/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp
Normal file
16
xo-jit/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/** ex_kaleidoscop4.cpp **/
|
||||||
|
|
||||||
|
#include "xo/jit/Jit.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int
|
||||||
|
main() {
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
//auto jit = xo::jit::KaleidoscopeJIT::Create();
|
||||||
|
|
||||||
|
//cerr << "created kaleidoscope jit successfully" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** end ex_kaleidoscope4.cpp **/
|
||||||
83
xo-jit/include/xo/jit/IrPipeline.hpp
Normal file
83
xo-jit/include/xo/jit/IrPipeline.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/** @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"
|
||||||
|
# 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 <cstdint>
|
||||||
|
|
||||||
|
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<LlvmContext> llvm_cx);
|
||||||
|
|
||||||
|
void run_pipeline(llvm::Function & fn);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ----- transforms (also adapted from kaleidescope.cpp) ------
|
||||||
|
|
||||||
|
/** keepalive for contained llvm::LLVMContext **/
|
||||||
|
rp<LlvmContext> llvm_cx_;
|
||||||
|
|
||||||
|
/** manages all the passes+analaysis (?) **/
|
||||||
|
std::unique_ptr<llvm::FunctionPassManager> llvm_fpmgr_;
|
||||||
|
/** loop analysis (?) **/
|
||||||
|
std::unique_ptr<llvm::LoopAnalysisManager> llvm_lamgr_;
|
||||||
|
/** function-level analysis (?) **/
|
||||||
|
std::unique_ptr<llvm::FunctionAnalysisManager> llvm_famgr_;
|
||||||
|
/** cgscc (?) analysis **/
|
||||||
|
std::unique_ptr<llvm::CGSCCAnalysisManager> llvm_cgamgr_;
|
||||||
|
/** module analsyis (?) **/
|
||||||
|
std::unique_ptr<llvm::ModuleAnalysisManager> llvm_mamgr_;
|
||||||
|
/** pass instrumentation **/
|
||||||
|
std::unique_ptr<llvm::PassInstrumentationCallbacks> llvm_pic_;
|
||||||
|
/** standard instrumentation **/
|
||||||
|
std::unique_ptr<llvm::StandardInstrumentations> llvm_si_;
|
||||||
|
}; /*IrPipeline*/
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/** end IrPipeline.hpp **/
|
||||||
171
xo-jit/include/xo/jit/Jit.hpp
Normal file
171
xo-jit/include/xo/jit/Jit.hpp
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
/** @file Jit.hpp **/
|
||||||
|
|
||||||
|
/** Adapted from LLVM KaleidoscopeJIT.h **/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
# 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 <memory>
|
||||||
|
|
||||||
|
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<ExecutionSession> 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<ExecutionSession> 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<SectionMemoryManager>(); }),
|
||||||
|
compile_layer_(*this->xsession_, object_layer_,
|
||||||
|
std::make_unique<ConcurrentIRCompiler>(std::move(jtmb))),
|
||||||
|
dest_dynamic_lib_(this->xsession_->createBareJITDylib("<main>"))
|
||||||
|
{
|
||||||
|
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<std::unique_ptr<Jit>> Create() {
|
||||||
|
auto EPC = SelfExecutorProcessControl::Create();
|
||||||
|
if (!EPC)
|
||||||
|
return EPC.takeError();
|
||||||
|
|
||||||
|
auto xsession = std::make_unique<ExecutionSession>(std::move(*EPC));
|
||||||
|
|
||||||
|
JITTargetMachineBuilder jtmb
|
||||||
|
(xsession->getExecutorProcessControl().getTargetTriple());
|
||||||
|
|
||||||
|
auto data_layout = jtmb.getDefaultDataLayoutForTarget();
|
||||||
|
if (!data_layout)
|
||||||
|
return data_layout.takeError();
|
||||||
|
|
||||||
|
return std::make_unique<Jit>(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 <typename T>
|
||||||
|
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<ExecutorSymbolDef> 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 **/
|
||||||
45
xo-jit/include/xo/jit/LlvmContext.hpp
Normal file
45
xo-jit/include/xo/jit/LlvmContext.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/** @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 <cstdint>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
/** @class LlvmContext
|
||||||
|
* @brief Keepalive for a llvm::LLVMContext instance.
|
||||||
|
*
|
||||||
|
* For example IrPipeline holds an rp<LlvmContext>
|
||||||
|
* to help ensure validity of embedded llvm::LLVMContext reference
|
||||||
|
**/
|
||||||
|
class LlvmContext : public ref::Refcount {
|
||||||
|
public:
|
||||||
|
static rp<LlvmContext> make();
|
||||||
|
|
||||||
|
llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; }
|
||||||
|
std::unique_ptr<llvm::LLVMContext> & 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::LLVMContext> llvm_cx_;
|
||||||
|
}; /*LlvmContext*/
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/** end LlvmContext.hpp **/
|
||||||
281
xo-jit/include/xo/jit/MachPipeline.hpp
Normal file
281
xo-jit/include/xo/jit/MachPipeline.hpp
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
/** @file MachPipeline.hpp
|
||||||
|
*
|
||||||
|
* Author: Roland Conybeare
|
||||||
|
**/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
#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/PrimitiveInterface.hpp"
|
||||||
|
#include "xo/expression/Apply.hpp"
|
||||||
|
#include "xo/expression/Lambda.hpp"
|
||||||
|
#include "xo/expression/Variable.hpp"
|
||||||
|
#include "xo/expression/IfExpr.hpp"
|
||||||
|
#include "xo/expression/GlobalEnv.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 <llvm/ExecutionEngine/Orc/Core.h>
|
||||||
|
|
||||||
|
|
||||||
|
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::ast::Expression;
|
||||||
|
using Lambda = xo::ast::Lambda;
|
||||||
|
using GlobalEnv = xo::ast::GlobalEnv;
|
||||||
|
using TypeDescr = xo::reflect::TypeDescr;
|
||||||
|
using ExecutionSession = llvm::orc::ExecutionSession;
|
||||||
|
using DataLayout = llvm::DataLayout;
|
||||||
|
//using ConstantInterface = xo::ast::ConstantInterface;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* tracking KaleidoscopeJIT::Create() here.. */
|
||||||
|
static llvm::Expected<std::unique_ptr<MachPipeline>> make_aux();
|
||||||
|
static rp<MachPipeline> make();
|
||||||
|
|
||||||
|
// ----- access -----
|
||||||
|
|
||||||
|
llvm::Module * current_module() { return llvm_module_.get(); }
|
||||||
|
ref::brw<LlvmContext> 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<std::string> 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<T>
|
||||||
|
* - string
|
||||||
|
* - map<T1,T2>
|
||||||
|
* - 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(ref::brw<xo::ast::ConstantInterface> expr);
|
||||||
|
llvm::Function * codegen_primitive(ref::brw<xo::ast::PrimitiveInterface> 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(ref::brw<xo::ast::PrimitiveInterface> 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(ref::brw<xo::ast::PrimitiveInterface> expr,
|
||||||
|
llvm::IRBuilder<> & ir_builder);
|
||||||
|
|
||||||
|
llvm::Value * codegen_apply(ref::brw<xo::ast::Apply> 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(ref::brw<xo::ast::Lambda> expr);
|
||||||
|
llvm::Function * codegen_lambda_defn(ref::brw<xo::ast::Lambda> 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(ref::brw<xo::ast::Lambda> lambda,
|
||||||
|
llvm::Value * envptr,
|
||||||
|
llvm::IRBuilder<> & ir_builder);
|
||||||
|
llvm::Value * codegen_variable(ref::brw<xo::ast::Variable> var,
|
||||||
|
llvm::Value * envptr,
|
||||||
|
llvm::IRBuilder<> & ir_builder);
|
||||||
|
llvm::Value * codegen_ifexpr(ref::brw<xo::ast::IfExpr> ifexpr,
|
||||||
|
llvm::Value * envptr,
|
||||||
|
llvm::IRBuilder<> & ir_builder);
|
||||||
|
|
||||||
|
llvm::Value * codegen(ref::brw<Expression> expr,
|
||||||
|
llvm::Value * envptr,
|
||||||
|
llvm::IRBuilder<> & ir_builder);
|
||||||
|
|
||||||
|
llvm::Value * codegen_toplevel(ref::brw<Expression> 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<llvm::orc::ExecutorAddr> 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> 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<ref::brw<Lambda>> find_lambdas(ref::brw<Expression> 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> 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<IrPipeline> 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<LlvmContext> llvm_cx_;
|
||||||
|
|
||||||
|
/** builder for intermediate-representation objects **/
|
||||||
|
std::unique_ptr<llvm::IRBuilder<>> 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> llvm_module_;
|
||||||
|
|
||||||
|
/** map global names to functions/variables **/
|
||||||
|
rp<GlobalEnv> 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<activation_record> 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 **/
|
||||||
221
xo-jit/include/xo/jit/activation_record.hpp
Normal file
221
xo-jit/include/xo/jit/activation_record.hpp
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
/** @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 <llvm/IR/IRBuilder.h>
|
||||||
|
# include <llvm/IR/Instructions.h>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#include <map>
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
/** analagous to xo::ast::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 << "<runtime_binding_detail"
|
||||||
|
<< xtag("i_argno", x.i_argno_)
|
||||||
|
<< xtag("llvm_addr", (void*)x.llvm_addr_)
|
||||||
|
<< xtag("llvm_type", (void*)x.llvm_type_)
|
||||||
|
<< ">";
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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::ast::Lambda;
|
||||||
|
using TypeDescr = xo::reflect::TypeDescr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
activation_record(const rp<Lambda> & lm);
|
||||||
|
|
||||||
|
const rp<Lambda> 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(ref::brw<LlvmContext> llvm_cx,
|
||||||
|
//const llvm::DataLayout & data_layout,
|
||||||
|
llvm::Function * llvm_fn,
|
||||||
|
llvm::IRBuilder<> & fn_ir_builder);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
runtime_binding_detail create_entry_block_alloca(ref::brw<LlvmContext> 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(ref::brw<LlvmContext> 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(ref::brw<LlvmContext> 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> lambda_;
|
||||||
|
|
||||||
|
/** @c binding_v_[i] specifies how/where we mean to navigate to
|
||||||
|
* location for formal parameter number *i* of @ref lambda_.
|
||||||
|
**/
|
||||||
|
std::vector<runtime_binding_path> 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<std::string, runtime_binding_detail> frame_; /* <-> kaleidoscope NamedValues */
|
||||||
|
}; /*activation_record*/
|
||||||
|
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/** end activation_record.hpp **/
|
||||||
66
xo-jit/include/xo/jit/activation_record.new.hpp
Normal file
66
xo-jit/include/xo/jit/activation_record.new.hpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/** @file activation_record.hpp
|
||||||
|
*
|
||||||
|
* Author: Roland Conybeare
|
||||||
|
**/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LlvmContext.hpp"
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
# include <llvm/IR/IRBuilder.h>
|
||||||
|
# include <llvm/IR/Instructions.h>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#include <map>
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
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<std::string, std::int32_t> name2ix_map_;
|
||||||
|
|
||||||
|
#ifdef OBSOLETE
|
||||||
|
/** maps named slots in a stack frame to logical addresses **/
|
||||||
|
std::map<std::string, llvm::AllocaInst*> frame_; /* <-> kaleidoscope NamedValues */
|
||||||
|
#endif
|
||||||
|
}; /*activation_record*/
|
||||||
|
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/** end activation_record.hpp **/
|
||||||
41
xo-jit/include/xo/jit/activation_record.orig.hpp
Normal file
41
xo-jit/include/xo/jit/activation_record.orig.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/** @file activation_record.hpp
|
||||||
|
*
|
||||||
|
* Author: Roland Conybeare
|
||||||
|
**/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LlvmContext.hpp"
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
# include <llvm/IR/IRBuilder.h>
|
||||||
|
# include <llvm/IR/Instructions.h>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#include <map>
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
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<std::string, llvm::AllocaInst*> frame_; /* <-> kaleidoscope NamedValues */
|
||||||
|
}; /*activation_record*/
|
||||||
|
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/** end activation_record.hpp **/
|
||||||
13
xo-jit/include/xo/jit/intrinsics.hpp
Normal file
13
xo-jit/include/xo/jit/intrinsics.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/** @file intrinsics.hpp
|
||||||
|
*
|
||||||
|
* Author: Roland Conybeare
|
||||||
|
**/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
extern "C" int32_t mul_i32(int32_t x, int32_t y);
|
||||||
|
extern "C" double mul_f64(double x, double y);
|
||||||
|
|
||||||
|
/** end intrinsics.hpp **/
|
||||||
228
xo-jit/include/xo/jit/type2llvm.hpp
Normal file
228
xo-jit/include/xo/jit/type2llvm.hpp
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
/** @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 <llvm/IR/DerivedTypes.h>
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
//#include <cstdint>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
/**
|
||||||
|
**/
|
||||||
|
struct type2llvm {
|
||||||
|
public:
|
||||||
|
using FunctionInterface = xo::ast::FunctionInterface;
|
||||||
|
using Lambda = xo::ast::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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> llvm_cx,
|
||||||
|
xo::ref::brw<FunctionInterface> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> llvm_cx,
|
||||||
|
xo::ref::brw<Lambda> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> llvm_cx);
|
||||||
|
|
||||||
|
}; /*type2llvm*/
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/** end type2llvm.hpp **/
|
||||||
50
xo-jit/src/jit/CMakeLists.txt
Normal file
50
xo-jit/src/jit/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
find_package(LLVM 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}]")
|
||||||
|
|
||||||
|
target_include_directories(${SELF_LIB} 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
|
||||||
55
xo-jit/src/jit/IrPipeline.cpp
Normal file
55
xo-jit/src/jit/IrPipeline.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* @file IrPipeline.cpp */
|
||||||
|
|
||||||
|
#include "IrPipeline.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
IrPipeline::IrPipeline(rp<LlvmContext> llvm_cx)
|
||||||
|
{
|
||||||
|
using std::make_unique;
|
||||||
|
|
||||||
|
this->llvm_cx_ = std::move(llvm_cx);
|
||||||
|
|
||||||
|
this->llvm_fpmgr_ = make_unique<llvm::FunctionPassManager>();
|
||||||
|
this->llvm_lamgr_ = std::make_unique<llvm::LoopAnalysisManager>();
|
||||||
|
this->llvm_famgr_ = std::make_unique<llvm::FunctionAnalysisManager>();
|
||||||
|
this->llvm_cgamgr_ = std::make_unique<llvm::CGSCCAnalysisManager>();
|
||||||
|
this->llvm_mamgr_ = std::make_unique<llvm::ModuleAnalysisManager>();
|
||||||
|
this->llvm_pic_ = std::make_unique<llvm::PassInstrumentationCallbacks>();
|
||||||
|
/* reference kept alive by @ref llvm_cx_ */
|
||||||
|
this->llvm_si_ = std::make_unique<llvm::StandardInstrumentations>(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 */
|
||||||
10
xo-jit/src/jit/Jit.cpp
Normal file
10
xo-jit/src/jit/Jit.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* @file Jit.cpp */
|
||||||
|
|
||||||
|
#include "Jit.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Jit.cpp */
|
||||||
20
xo-jit/src/jit/LlvmContext.cpp
Normal file
20
xo-jit/src/jit/LlvmContext.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* @file LlvmContext.cpp */
|
||||||
|
|
||||||
|
#include "LlvmContext.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
rp<LlvmContext>
|
||||||
|
LlvmContext::make() {
|
||||||
|
return new LlvmContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
LlvmContext::LlvmContext()
|
||||||
|
: llvm_cx_{std::make_unique<llvm::LLVMContext>()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
} /*namespace jit*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
|
||||||
|
/* end LlvmContext.cpp */
|
||||||
1132
xo-jit/src/jit/MachPipeline.cpp
Normal file
1132
xo-jit/src/jit/MachPipeline.cpp
Normal file
File diff suppressed because it is too large
Load diff
1341
xo-jit/src/jit/MachPipeline.new.cpp
Normal file
1341
xo-jit/src/jit/MachPipeline.new.cpp
Normal file
File diff suppressed because it is too large
Load diff
1064
xo-jit/src/jit/MachPipeline.orig.cpp
Normal file
1064
xo-jit/src/jit/MachPipeline.orig.cpp
Normal file
File diff suppressed because it is too large
Load diff
435
xo-jit/src/jit/activation_record.cpp
Normal file
435
xo-jit/src/jit/activation_record.cpp
Normal file
|
|
@ -0,0 +1,435 @@
|
||||||
|
/* @file activation_record.cpp */
|
||||||
|
|
||||||
|
#include "activation_record.hpp"
|
||||||
|
#include "type2llvm.hpp"
|
||||||
|
#include "xo/indentlog/print/tag.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace jit {
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
activation_record::activation_record(const rp<Lambda> & 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(ref::brw<LlvmContext> 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(ref::brw<LlvmContext> 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(ref::brw<LlvmContext> 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<llvm::Value*, 2> 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(ref::brw<LlvmContext> 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 */
|
||||||
47
xo-jit/src/jit/activation_record.new.cpp
Normal file
47
xo-jit/src/jit/activation_record.new.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* @file activation_record.cpp */
|
||||||
|
|
||||||
|
#include "activation_record.hpp"
|
||||||
|
#include "xo/indentlog/print/tag.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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 */
|
||||||
45
xo-jit/src/jit/activation_record.orig.cpp
Normal file
45
xo-jit/src/jit/activation_record.orig.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* @file activation_record.cpp */
|
||||||
|
|
||||||
|
#include "activation_record.hpp"
|
||||||
|
#include "xo/indentlog/print/tag.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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 */
|
||||||
20
xo-jit/src/jit/intrinsics.cpp
Normal file
20
xo-jit/src/jit/intrinsics.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* @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 */
|
||||||
409
xo-jit/src/jit/type2llvm.cpp
Normal file
409
xo-jit/src/jit/type2llvm.cpp
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
/* @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::ref::brw<LlvmContext> 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<bool>(td)) {
|
||||||
|
return llvm::Type::getInt1Ty(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<char>(td)) {
|
||||||
|
return llvm::Type::getInt8Ty(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<short>(td)) {
|
||||||
|
return llvm::Type::getInt16Ty(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<int>(td)) {
|
||||||
|
return llvm::Type::getInt32Ty(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<long>(td)) {
|
||||||
|
return llvm::Type::getInt64Ty(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<float>(td)) {
|
||||||
|
return llvm::Type::getFloatTy(llvm_cx_ref);
|
||||||
|
} else if (Reflect::is_native<double>(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::ref::brw<LlvmContext> 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::Type *> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::Type *> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::Type *> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> 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::ref::brw<LlvmContext> llvm_cx,
|
||||||
|
xo::ref::brw<FunctionInterface> 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::ref::brw<LlvmContext> 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<llvm::Type *> 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::ref::brw<LlvmContext> llvm_cx,
|
||||||
|
xo::ref::brw<Lambda> 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<llvm::Type *> 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 */
|
||||||
17
xo-jit/utest/CMakeLists.txt
Normal file
17
xo-jit/utest/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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_dependency(${SELF_EXE} xo_reflectutil)
|
||||||
|
xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# end CMakeLists.txt
|
||||||
387
xo-jit/utest/MachPipeline.test.cpp
Normal file
387
xo-jit/utest/MachPipeline.test.cpp
Normal file
|
|
@ -0,0 +1,387 @@
|
||||||
|
/* @file MachPipeline.test.cpp */
|
||||||
|
|
||||||
|
#include "xo/jit/MachPipeline.hpp"
|
||||||
|
#include "xo/expression/Primitive.hpp"
|
||||||
|
#include "xo/ratio/ratio.hpp"
|
||||||
|
#include "xo/ratio/ratio_reflect.hpp"
|
||||||
|
#include "xo/reflect/reflect_struct.hpp"
|
||||||
|
#include "xo/indentlog/scope.hpp"
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
using xo::jit::MachPipeline;
|
||||||
|
using xo::ast::make_apply;
|
||||||
|
using xo::ast::make_var;
|
||||||
|
using xo::ast::make_primitive;
|
||||||
|
using xo::ast::llvmintrinsic;
|
||||||
|
using xo::ast::Expression;
|
||||||
|
using xo::ast::Lambda;
|
||||||
|
using xo::ast::exprtype;
|
||||||
|
using xo::reflect::Reflect;
|
||||||
|
using xo::reflect::reflect_struct;
|
||||||
|
using xo::ref::rp;
|
||||||
|
using xo::ref::brw;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
namespace ut {
|
||||||
|
|
||||||
|
/* abstract syntax tree for a function:
|
||||||
|
* def root4(x :: double) { sqrt(sqrt(x)); }
|
||||||
|
*/
|
||||||
|
rp<Expression>
|
||||||
|
root4_ast() {
|
||||||
|
auto sqrt = make_primitive("sqrt",
|
||||||
|
::sqrt,
|
||||||
|
false /*!explicit_symbol_def*/,
|
||||||
|
llvmintrinsic::fp_sqrt);
|
||||||
|
auto x_var = make_var("x", Reflect::require<double>());
|
||||||
|
auto call1 = make_apply(sqrt, {x_var});
|
||||||
|
auto call2 = make_apply(sqrt, {call1});
|
||||||
|
|
||||||
|
auto fn_ast = make_lambda("root4",
|
||||||
|
{x_var},
|
||||||
|
call2);
|
||||||
|
|
||||||
|
return fn_ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* abstract syntax tree for a function:
|
||||||
|
* def twice(f :: double->double, x :: double) { f(f(x)); }
|
||||||
|
*/
|
||||||
|
rp<Expression>
|
||||||
|
root_2x_ast() {
|
||||||
|
auto root = make_primitive("sqrt",
|
||||||
|
::sqrt,
|
||||||
|
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<double (*)(double) noexcept>());
|
||||||
|
auto x_var = make_var("x", Reflect::require<double>());
|
||||||
|
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);
|
||||||
|
|
||||||
|
auto x2_var = make_var("x2", Reflect::require<double>());
|
||||||
|
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);
|
||||||
|
|
||||||
|
return fn_ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
rp<Expression> (*make_ast_)();
|
||||||
|
/* each pair is (input, output) for function double->double */
|
||||||
|
std::vector<std::pair<double,double>> call_v_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<TestCase>
|
||||||
|
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<xoshio256ss> 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<Lambda> 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<double(*)(double)>();
|
||||||
|
|
||||||
|
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,
|
||||||
|
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<bool>(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<double(*)(void*, double)>();
|
||||||
|
|
||||||
|
REQUIRE(fn_ptr);
|
||||||
|
|
||||||
|
auto actual = (*fn_ptr)(nullptr, 4.0);
|
||||||
|
|
||||||
|
REQUIRE(actual == 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
rp<Lambda>
|
||||||
|
make_ratio() {
|
||||||
|
auto make_ratio_impl = make_primitive("make_ratio_impl",
|
||||||
|
xo::ratio::make_ratio<int, int>,
|
||||||
|
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<int>());
|
||||||
|
auto d_var = make_var("d", Reflect::require<int>());
|
||||||
|
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);
|
||||||
|
|
||||||
|
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<xoshio256ss> 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<int> */
|
||||||
|
|
||||||
|
using ratio_type = xo::ratio::ratio<int>;
|
||||||
|
|
||||||
|
auto struct_td = reflect_struct<ratio_type>();
|
||||||
|
|
||||||
|
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<llvm::StructType *>(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<uint64_t>(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<ratio_type(*)(int,int)>();
|
||||||
|
|
||||||
|
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 */
|
||||||
6
xo-jit/utest/jit_utest_main.cpp
Normal file
6
xo-jit/utest/jit_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* @file jit_utest_main.cpp */
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
|
/* end jit_utest_main.cpp */
|
||||||
Loading…
Add table
Add a link
Reference in a new issue