Add 'xo-jit/' from commit '855887df71'

git-subtree-dir: xo-jit
git-subtree-mainline: 35555df976
git-subtree-split: 855887df71
This commit is contained in:
Roland Conybeare 2025-05-11 01:54:47 -05:00
commit 757dfed99c
49 changed files with 7305 additions and 0 deletions

8
xo-jit/.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
```

View 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()

View 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@")

View 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
View 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
View 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
View 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
View 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

View file

@ -0,0 +1,4 @@
add_subdirectory(ex1)
add_subdirectory(ex2_jit)
add_subdirectory(ex3_fptr)
add_subdirectory(ex_kaleidoscope4)

View 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
View 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 **/

View 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

View 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 **/

View 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

View 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
}

View 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

View 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;
}

View 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"}

View 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
}

View 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

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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 **/

View 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

View 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
View file

@ -0,0 +1,10 @@
/* @file Jit.cpp */
#include "Jit.hpp"
namespace xo {
namespace jit {
} /*namespace jit*/
} /*namespace xo*/
/* end Jit.cpp */

View 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 */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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

View 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 */

View file

@ -0,0 +1,6 @@
/* @file jit_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
/* end jit_utest_main.cpp */